Linux 文件IO
简单的实例
|
文件描述符
- 调用 open 函数会有一个返回值,譬如示例代码 2.1.1 中的 fd1 和 fd2,这是一个 int 类型的数据,在 open 函数执行成功的情况下,会返回一个非负整数,该返回值就是一个文件描述符(file descriptor),这说明文 件描述符是一个非负整数;对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引。
- 当调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返回一个文件描述符,用于指 代被打开的文件,所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件,譬如示例代码 2.1.1 中,当调用 read/write 函数进行文件读写时,会将文件描述符传送给 read/write 函数
- 所以对于一个进程来说,文件描述符是一种有限资源,文件描述符是从 0 开始分配的,譬如说进程中第 一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3……以此类推,所以由此可知,文件描述符数字最大值为 1023(0~1023)。每一个被打开的文件在同一个进程中都有 一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描 述符将可以再次分配给其它打开的文件、与对应的文件绑定起来。
一切皆文件
- Linux 系统下,一切皆文件,也包括各种硬件设备,使用 open 函数打开任何文件成功情况下便会 返回对应的文件描述符 fd。每一个硬件设备都会对应于 Linux 系统下的某一个文件,把这类文件称为设备文 件。所以设备文件对应的其实是某一硬件设备,应用程序通过对设备文件进行读写等操作、来使用、操控硬 件设备,譬如 LCD 显示器、串口、音频、键盘等。
- 标准输入一般对应的是键盘,可以理解为 0 便是打开键盘对应的设备文件时所得到的文件描述符;标 准输出一般指的是 LCD 显示器,可以理解为 1 便是打开 LCD 设备对应的设备文件时所得到的文件描述符; 而标准错误一般指的也是 LCD 显示器。
打开文件
|
pathname:字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信 息,譬如:”./src_file”(当前目录下的 src_file 文件)、”/home/dengtao/hello.c”等;如果 pathname 是一个符号 链接,会对其进行解引用。
flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使 用宏定义进行描述,都是常量,open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一 个标志,也可以通过位或运算(|)将多个标志进行组合。
| 标志 | 用途 | 说明 |
|---|---|---|
| O_WRONLY | 只写 | |
| O_RDONLY | 只读 | |
| O_RDWR | 可读可写 | 这三个是文件访问权限标志,传入的 flags 参数中必须要包含其中一种标 志,而且只能包含一种 |
| O_CREAT | 如果地址指向的文件不存在就创建文件 | 使用此标志时,调用 open 函数需要 传入第 3 个参数 mode,参数 mode 用 于指定新建文件的访问权限,稍后将 对此进行说明。 open 函数的第 3 个参数只有在使用 了 O_CREAT 或 O_TMPFILE 标志 时才有效。 |
| O_DIRECTORY | 如果地址指向的是目录,返回调用失败 | |
| O_EXCL | 此标志一般结合 O_CREAT 标志一起使用, 用于专门创建文件。 在 flags 参数同时使用到了 O_CREAT 和 O_EXCL 标志的情况下,如果 pathname 参数 指向的文件已经存在,则 open 函数返回错 误。 | 可以用于测试一个文件是否存在,如 果不存在则创建此文件,如果存在则 返回错误,这使得测试和创建两者成 为一个原子操作;关于原子操作,在 后面的内容当中将会对此进行说明。 |
| O_NOFOLLOW | 如果 pathname 参数指向的是一个符号链接, 将不对其进行解引用,直接返回错误。 | 不加此标志情况下,如果 pathname 参数是一个符号链接,会对其进行解引用,加了之后会对符号链接直接返回错误。 |
- flag可以通过
|标志添加大于一个,比如
open("./src_file", O_RDONLY) //单独使用某一个标志 |
文件权限(rwx)
前面的博客中已经详细介绍过了,略
写入文件
- 调用 write 函数可向打开的文件写入数据,其函数原型如下所示
|
fd:文件描述符。关于文件描述符,前面已经给大家进行了简单地讲解,这里不再重述!我们需要将进 行写操作的文件所对应的文件描述符传递给 write 函数。
buf:指定写入数据对应的缓冲区。
count:指定写入的字节数。
返回值:如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。
针对写入文件的开始地址
- 读写操作都是从文件的当前位置偏移量处开始,当然当前位置偏移量可以通过 lseek 系统 调用进行设置,关于此函数后面再讲;默认情况下当前位置偏移量一般是 0,也就是指向了文件起始位置, 当调用 read、write 函数读写操作完成之后,当前位置偏移量也会向后移动对应字节数
读文件
- 调用 read 函数可从打开的文件中读取数据
|
- fd:文件描述符。与 write 函数的 fd 参数意义相同。
- buf:指定用于存储读取数据的缓冲区。
- count:指定需要读取的字节数。
- 返回值:如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节 数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少 于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成 功只能返回 30;而下一次再调用 read 读,它将返回 0(文件末尾)。
关闭文件
- close
|
fd:文件描述符,需要关闭的文件所对应的文件描述符。
返回值:如果成功返回 0,如果失败则返回-1。
在 Linux 系统中,当一个进程终止时,内核会自动关闭它打开 的所有文件,也就是说在我们的程序中打开了文件,如果程序终止退出时没有关闭打开的文件,那么内核会 自动将程序中打开的文件关闭。很多程序都利用了这一功能而不显式地用 close 关闭打开的文件。
更改偏移量位置
- lseek
|
fd:文件描述符。
offset:偏移量,以字节为单位。
whence:用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
- SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
- SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为 负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
- SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负, 如果是正数表示往后偏移、如果是负数则表示往前偏移。
成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生 错误将返回-1。可以用此函数获取文件此时的偏移量
Linux文件系统简单讲述
磁盘空间包括两个部分,一个是真正存储文件的区域,另一个是存储文件inode的区域(inode见前面的博客)

windows的快速格式化不会真正删除存储问文件内容的区域,只是删除了inode表的区域
打开文件的过程
系统找到这个文件名所对应的 inode 编号;
通过 inode 编号从 inode table 中找到对应的 inode 结构体;
根据 inode 结构体中记录的信息,确定文件数据所在的 block,并读出数据。
文件打开的时候内核会申请一段内存(一段缓冲区),并且将静态文件的数 据内容从磁盘这些存储设备中读取到内存中进行管理、缓存(也把内存中的这份文件数据叫做动态文件、内 核缓冲区)。打开文件后,以后对这个文件的读写操作,都是针对内存中这一份动态文件进行相关的操作。
因为磁盘、硬盘、U 盘等存储设备基本都是 Flash 块设备,因为块设备硬件本身有读写限制等特征,块 设备是以一块一块为单位进行读写的(一个块包含多个扇区,而一个扇区包含多个字节),一个字节的改动 也需要将该字节所在的 block 全部读取出来进行修改,修改完成之后再写入块设备中,所以导致对块设备的 读写操作非常不灵活;而内存可以按字节为单位来操作,而且可以随机操作任意地址数据,非常地很灵活, 所以对于操作系统来说,会先将磁盘中的静态文件读取到内存中进行缓存,读写操作都是针对这份动态文 件,而不是直接去操作磁盘中的静态文件,不但操作不灵活,效率也会下降很多,因为内存的读写速率远比 磁盘读写快得多。
在 Linux 系统中,内核会为每个进程(关于进程的概念,这是后面的内容,我们可以简单地理解为一个 运行的程序就是一个进程,运行了多个程序那就是存在多个进程)设置一个专门的数据结构用于管理该进 程,譬如用于记录进程的状态信息、运行特征等,我们把这个称为进程控制块(Process control block,缩写 PCB)。
- PCB 数据结构体中有一个指针指向了文件描述符表(File descriptors),文件描述符表中的每一个元素索引到对应的文件表(File table),文件表也是一个数据结构体,其中记录了很多文件相关的信息,譬如文 件状态标志、引用计数、当前文件的读写偏移量以及 i-node 指针(指向该文件对应的 inode)等,进程打开 的所有文件对应的文件描述符都记录在文件描述符表中,每一个文件描述符都会指向一个对应的文件表

程序出错
略,详见原子教程pdf
程序退出
基本方法
- return
- return 0表示程序正常结束
- return -1表示程序异常退出
Linux 下的其他方法
进程正常退出除了可以使用 return 之外,还可以使用
exit()、_exit()以及_Exit()_exit()调用
_exit()函数会 清除其使用的内存空间,并销毁其在内核中的各种数据结构,关闭进程的所有文件描述符,并结束进程、将 控制权交给操作系统。
|
其中的status含义与上面return的相同,0代表正常,其他数值代表异常
_Exit()和_exit()等价,不再介绍
|
exit()是一个标准 C 库函数,而_exit()和_Exit()是系统调用。
执行 exit()会执行一些清理工作,最后调用_exit()函数。
|
- 不管你用哪一种都可以结束进程,但还是推荐大家使用 exit()
空洞文件
什么是空洞文件(hole file)?在上一章内容中,笔者给大家介绍了 lseek()系统调用,使用 lseek 可以修 改文件的当前读写位置偏移量,此函数不但可以改变位置偏移量,并且还允许文件偏移量超出文件长度,这 是什么意思呢?譬如有一个 test_file,该文件的大小是 4K(也就是 4096 个字节),如果通过 lseek 系统调 用将该文件的读写偏移量移动到偏移文件头部 6000 个字节处,大家想一想会怎样?如果笔者没有提前告诉 大家,大家觉得不能这样操作,但事实上 lseek 函数确实可以这样操作。
接下来使用 write()函数对文件进行写入操作,也就是说此时将是从偏移文件头部 6000 个字节处开始写 入数据,也就意味着 4096~6000 字节之间出现了一个空洞,因为这部分空间并没有写入任何数据,所以形 成了空洞,这部分区域就被称为文件空洞,那么相应的该文件也被称为空洞文件。
文件空洞部分实际上并不会占用任何物理空间,直到在某个时刻对空洞部分进行写入数据时才会为它 分配对应的空间,但是**空洞文件形成时,逻辑上该文件的大小是包含了空洞部分的大小的**,这点需要注意。
空洞文件对多线程共同操作文件是及其有用的,有时候我们创建 一个很大的文件,如果单个线程从头开始依次构建该文件需要很长的时间,有一种思路就是将文件分为多 段,然后使用多线程来操作,每个线程负责其中一段数据的写入
测试
|
上面的代码从文件的4k位置开始,一次写入1k,写入4次。
下面查看文件的大小

利用
ls察看文件大小的时候,虽然文件只有4k有数据,但是文件大小查出来逻辑大小,也就是8k。但是使用du查看的时候,只有文件占用的实际存储的大小,也就是4k。
O_APPEND 和 O_TRUNC 标志
在上一章给大家讲解 open 函数的时候介绍了一些 open 函数的 flags 标志,譬如 O_RDONLY、 O_WRONLY、O_CREAT、O_EXCL 等,本小节再给大家介绍两个标志,分别是 O_APPEND 和 O_TRUNC, 接下来对这两个标志分别进行介绍。
O_TRUNC 标志
- O_TRUNC 这个标志的作用非常简单,如果使用了这个标志,调用 open 函数打开文件的时候会将文件 原本的内容全部丢弃,文件大小变为 0;这里我们直接测试即可!测试代码如下所示:
|
O_APPEND 标志
- 如果 open 函数携带了 O_APPEND 标志,调用 open 函数打开文件, 当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾,从文件末 尾开始写入数据,也就是意味着每次写入数据都是从文件末尾开始。
|

通过控制台可知,读出的内容确实是最后四个字节为0x55
O_APPEND 标志并不会影响读文件,当读取文件时,O_APPEND 标志并不会影响读位置偏移量,即使使用了 O_APPEND 标志,读文件位置偏移量默认情况下依然是文件头,关于这个问题大家可以自己进行测试,编程是一个实践 性很强的工作,有什么不能理解的问题,可以自己编写程序进行测试。
使用 lseek 函数来改变 write()时的写位置偏移量,其实这种做法并不会成功,这就是笔 者给大家提的第二个细节,使用了 O_APPEND 标志,即使是通过 lseek 函数也是无法修改写文件时对应的 位置偏移量(注意笔者这里说的是写文件,并不包括读),写入数据依然是从文件末尾开始,lseek 并不会 该变写位置偏移量
测试
|
此处函数不对文件偏移位置进修修改的前提下,在文件写入4次
, 0xXX的字符,观察效果
可以看出,在文件最后多出了四个字符串,可见每次写入的时候,文件的偏移量都自动移动到了文件的最后,即使文件在这个过程中并没有被保存。