第 3 章 - 文件 IO

  1. 特殊 fd
    1. 0:STDIN
    2. 1: STDOUT
    3. 2: STDERR
  2. 延迟写(delayed write):向文件中写入数据时,内核通常将数据复制到缓冲区重,然后排入队列,最后写入磁盘

文件操作的函数

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

文件共享

  1. 内核使用 3 种数据结构表示打开文件,即文件描述符表、文件表、v-node
    1. 文件描述符表:每个进程一张,每个 fd 占一项,与 fd 关联的是 close_on_exec 和指向文件表项的指针
    2. 打开文件表,每个表项包括文件状态标志(flags)、当前文件偏移量、指向改文件 v-node 表项的指针
    3. v-node:包含了文件类型和对该文件进行各种操作的指针,以及 i-node

原子操作

  1. 没有使用 O_APPEND 模式打开时,lseek 到文件末尾,然后再 write 没有原子性保证,如果多进程写同一个文件,则会相互覆盖,正确做法是使用 O_APPEND 模式打开
  2. preadpwrite 保证原子性读写
    1. 调用 pread 是无法中断其定位和操作
    2. 不更新当前文件偏移量