深入了解文件IO
1.Linux下文件如何管理
1.1静态文件
文件在没有被打开的情况下一般都是存放在磁盘中的,譬如电脑硬盘、移动硬盘、U 盘等外部存储设
备,文件存放在磁盘文件系统中,并且以一种固定的形式进行存放,我们把他们称为静态文件.
磁盘存储的最小单位: 扇区(sector),每个扇区存储512字节(相当于0.5KB)
块 (block)= 8个扇区 , 4KB
1.2 inode
磁盘进行分区时分为两个区域:数据区和inode区
数据区存放数据例如"hello world"
inode区存放inode table,里面有很多inode节点,每个文件对应一个inode
打开一个文件,系统内部会分三步走:
- 系统找到文件名对应的inode编号
- 通过inode编号从inode table中找到对应的inode结构体
- 根据inode结构体中记录的信息,确定文件数据所在 block,并读出数据
1.3 PCB(process control block)进程控制块
定义: Linux系统中,内核为每个进程设置一个专门的数据结构用于管理该进程,譬如用于记录进程的状态信息、运行特征等。
目的: 为了方便管理进程 ,提升效率
2.返回错误错误编号errno
2.1 errno变量
在 Linux 系统下对常见的错误做了一个编号,每一个编号都代表着每一种不同的错误类型,当函数执行发生错误的时候,操作系统会将这个错误所对应的编号赋值给 errno 变量,每一个进程(程序)都维护了自己的 errno 变量,它是程序中的全局变量,该变量用于存储就近发生的函数执行错误编号,也就意味着下一次的错误码会覆盖上一次的错误码
本质是int类型的变量,下一次错误码会覆盖上一次的错误码,库文件<errno.h>
2.2 strerror()函数
库文件:<string.h>
该函数可以将对应的 errno 转换成适合我们查看的字符串信息
2.3 perror()函数
库文件:<stdio.h>
3. 空洞文件
应用场景:在多线程下对文件的写操作
3.1 再论lseek()函数
空洞文件测试
读取文件空洞部分
4.O_APPEND 和 O_TRUNC 标志
open函数flags参数学习了O_RDONO,O_WRONLY,O_CREAT,O_EXCL等,再学习两个O_APPEND 和 O_TRUNC
4.1 O_TRUNC
如果使用了这个标志,调用 open 函数打开文件的时候会将文件原本的内容全部丢弃,文件大小变为 0;
4.1 O_APPEND
如果 open 函数携带了 O_APPEND 标志,调用 open 函数打开文件,当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾,从文件末尾开始写入数据,也就是意味着每次写入数据都是从文件末尾开始.
5.同一文件被多次打开
5.1 文件描述符与文件的对应关系
n对1
使用任何一个fd都可对同一文件进行读写操作(文件共享)
5.2 读写位置偏移量是相对的
每个文件描述符都有各自的文件表,所以偏移量是各自维护自己。
5.3 使用多个文件描述符对同一文件进行写操作
加不加O_APPEND结果是有区别的
![]()
- 不加O_APPEND,多个线程操作同一文件时,指针互不干扰,会出现覆盖的问题
- 加O_APPEND,每次使用write都会使偏移指针指向末尾
6.复制文件描述符
6.1 dup函数
函数原型
oldfd:旧文件描述符
返回值:成功 newfd 失败 -1
6.2 dup2函数
函数原型
7.文件共享
7.1 什么是文件共享
同一个文件(譬如磁盘上的同一个文件,对应同一个 inode)被多个独立的读写体同时进行 IO 操作
7.2 常见的文件共享方式
- 同一进程的多个线程间共享
① 同一进程 使用多个open函数打开同一个文件
② 同一进程使用dup或dup2函数复制文件
- 多个不同的进程间实现共享
不同进程使用open函数打开同一文件
7.3 文件共享存在竞争冒险问题
7.4 原子操作
有多步操作组成的一个操作,原子操作要么一步也不执行,一旦执行,必须要执行完所有
步骤,不可能只执行所有步骤中的一个子集。
7.4.1 O_APPEND标志
文件添加flag->O_APPEND后,每次执行write函数,都会自动将偏移指针指向末尾,达到解决竞争冒险问题
7.4.2 pread函数 和 pwrite函数
函数原型
虽然 pread(或 pwrite)函数相当于 lseek 与 pread(或 pwrite)函数的集合,但还是有下列区别:
- 调用 pread 函数时,无法中断其定位和读操作(也就是原子操作);
- 不更新文件表中的当前位置偏移量。
使用pread后偏移指针仍是0
7.4.3 O_EXCL标志
场景:两个进程都要创建文件,如果进程A创建文件时被进程B插入,那么文件被B创建,A进而也会创建一个文件,就会出现问题
解决办法就是使用O_CREAT| O_EXCL 防止多个相同文件被创建
8. 截断文件
8.1 truncate 和 ftruncate函数
这两个函数的区别在于:ftruncate()使用文件描述符 fd 来指定目标文件,而 truncate()则直接使用文件路径 path 来指定目标文件,其功能一样。
定义:如果文件目前的大小大于参数 length 所指定的大小,则多余的数据将被丢失,类似于多余的部分被“砍”掉了;如果文件目前的大小小于参数 length 所指定的大小,则将其进行扩展,对扩展部分进行读取将得到空字节"\0"。
截断不会改变文件偏移量,需要注意截断后调整偏移量(偏移量和文件大小无关系)
8.2 fcntl 和 ioctl (系统调用)
* fcntl()
> 可以对fd做一系列的控制操作,是一个多功能的文件描述符管理工具
> 例如:复制文件描述符、获取或设置文件描述符、获取状态标志
可变参函数,fd:文件描述符 cmd:操作命令
⚫ 复制文件描述符(cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC);
⚫ 获取/设置文件描述符标志(cmd=F_GETFD 或 cmd=F_SETFD);
⚫ 获取/设置文件状态标志(cmd=F_GETFL 或 cmd=F_SETFL);
⚫ 获取/设置异步 IO 所有权(cmd=F_GETOWN 或 cmd=F_SETOWN);
⚫ 获取/设置记录锁(cmd=F_GETLK 或 cmd=F_SETLK);
返回值与cmd有关,失败返回-1
示例
- 复制
使用F_DUPFD时需要第三个参数,0表示fd2文件描述符需要大于等于0
- 得到或修改文件状态标志
-
ioctl()
用于操作特殊文件或硬件外设, ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。
偏移量问题
write read 会改变偏移量
多个进程或一个进程多个open同一文件,偏移量互不影响
dup 共用一个偏移量
截断不影响偏移量
lseek调整偏移量
pread pwrite不影响偏移指针
偏移量对于文件大小的影响