- 特殊 fd
0
:STDIN1
: STDOUT2
: STDERR
- 延迟写(delayed write):向文件中写入数据时,内核通常将数据复制到缓冲区重,然后排入队列,最后写入磁盘
文件操作的函数
open
打开或创建文件,int open(const char *path, int oflag, ... /* mode_t mode */)
path
:要打开或创建的文件的名称olfag
:打开文件选项- 必选:只读、只写、读写、只执行、只搜索(目录),只能指定一个
- 可选:追加、不存在时创建、如果不是目录则报错、如果文件已存在并尝试创建时报错、如果是符号链接则报错、非阻塞、Truncate(截断文件,长度置为 0)、DSYNC、RSYNC
- DSYNC:write 需要等物理 IO 完成,如果该写操作不影响读取刚写入的数据,则不需要等待文件属性被更新
- RSYNC:是每一个以文件描述符作为参数进行的 read 操作等待,知道所有对文件同意部分挂起的写操作都完成
openat
与open
类似,但是允许使用相对路径打开目录中的文件,而不是只能打开当前目录的文件create
创建文件,等价于open(path, O_WRONLY | O_CREAT O_TRUNC, mode)
close
关闭一个打开的文件,关闭文件时会释放改进程加载改文件上的所有记录锁,进程终止时,内核自动关闭它所有的打开文件off_t lseek(int fd, off_t offset, int whence)
移动文件的读写位置whence
参数选项SEEK_SET
:偏移量设置为距文件开始处 offset 个字节, 0 + offsetSEEK_CUR
:current_pos + offset,offset 可正可负SEEK_END
:file_length + offset,offset 可正可负
- 并非所有文件都可以设置偏移量,如 pipe、FIFO、socket 都不可以
- lseek 只是将当前文件偏移量记录在内核中(文件表项) ,并不引起任何 IO 操作,该偏移量应用于下一个读写操作
- lseek 可以用于构建文件空洞,读是返回 0,不占磁盘块
- lseek 到文件末尾时,当前偏移量设置为 i-node 中的文件长度
read
从打开的文件中读数据,如果成果则返回读到的字节数,如果已到达文件末尾,则返回 0ssize_t write(int fd, const void *buf, size_t nbytes)
向打开的文件中写数据,返回写成功的字节数,通常与 nbytes 相同,否则出错,- 写文件后会导致当前文件的偏移量增加,增加量为写入 byte 数,如果偏移量超过了 i-node 中的文件长度,则更新 i-node 中的当前文件长度
- 如果使用
O_APPEND
模式打开的问题,每次写之前会把文件偏移量设置为 i-node 中的文件长度
- read ahead:当操作系统检测到正在进行顺序读取时,OS 尝试读入比用户进程要求更多的数据,并假想用户进程很快就会读这些数据
dup
复制一个现有的 fd,即增肌一个文件指针,指向已经存在的文件表项,fcntl(fd, F_DUPFD, 0)
可以达到同样的效果,可用dup2(int fd, int fd2)
指定复制出的 fd 的值,如果 fd2 已打开则先关闭它
!sync
/fsync
/fdatasync
:将内存中的数据刷到磁盘sync
:只将修改过的 buffer 写入队列就返回,不等待实际的磁盘写操作结束fsync
:只对 fd 指定的一个文件起左右,并且等写磁盘操作结束才返回fdatasync
:类似 fsync 但是只影响数据部分,而fsync
还会同步更新文件的属性
int fcntl(int fd, int cmd, ... /*int arg*/)
,由cmd
参数控制具体操作- 支持的操作
- 复制已有 fd
- 获取/设置 fd 标志,当前只定义了
FD_CLOEXEC
- 获取/设置文件状态标志
- 获取/设置记录锁
- 修改 fd 标志和文件状态标志时,必须先获得现在的值,修改后设置新值,否则会关闭之前设置的标志位
fcntl
只需要 fd 作为参数,而不需要文件名,对于管道能场景很有用
- 支持的操作
- 打开 ‘/dev/fd’ 下的文件,等效于
dup
文件共享
- 内核使用 3 种数据结构表示打开文件,即文件描述符表、文件表、v-node
- 文件描述符表:每个进程一张,每个 fd 占一项,与 fd 关联的是 close_on_exec 和指向文件表项的指针
- 打开文件表,每个表项包括文件状态标志(flags)、当前文件偏移量、指向改文件 v-node 表项的指针
- v-node:包含了文件类型和对该文件进行各种操作的指针,以及 i-node
原子操作
- 没有使用
O_APPEND
模式打开时,lseek
到文件末尾,然后再write
没有原子性保证,如果多进程写同一个文件,则会相互覆盖,正确做法是使用O_APPEND
模式打开 pread
和pwrite
保证原子性读写- 调用 pread 是无法中断其定位和操作
- 不更新当前文件偏移量