Ch 3 文件IO
文件操作
open 和 opent 参数:要打开或创建的文件名 这两个返回的文件描述符一定是最小的未用描述符数值,关闭的文件描述符就是未用的。
opent 的参数 fd 指出了相对路径名在文件系统中的开始地址
create 其实就是 open 一个新文件 close 关闭文件的时候还会释放该进程在该文件上的所有记录锁
lseek 可以显式地为打开的文件设置偏移量,有的可以设置有的不行,比如管道,FIFO,套接字。不引起任何 IO 操作 偏移量可以大于文件的当前长度,中间的部分为空洞,不分配磁盘块
read, write 都会将数组暂存在 buf 中。 buf size 的确定:大于磁盘的块长度 多数文件系统都有预读技术,顺序读取的时候,系统会事先读出更多的数据放入页缓存中,因此我们在多次度量IO性能的时候使用多个副本,防止命中缓存
文件共享
进程表项 | 描述符表 - 文件表项 - inode 节点
文件表项:文件状态标志,当前文件偏移量,i 节点指针(lseek作用的地方,write 会对偏移量增加写入的字节数) 使得每个进程有自己对该文件的偏移量
原子操作
两个进程同时往偏移量相同的地方开始写会有覆盖的问题 先调用 lseek 然后调用 read 或 write 并不是原子操作而且可能被中断!
pread 和 pwrite 可以保证原子IO操作并且在这个过程中不会被中断
复制文件描述符
dup 可以使传入的描述符和返回的新描述符都指向同一个文件表项 新描述符同样满足最小未用的分配原则 dup2 可以直接指定新的描述符
sync, fsync, fdatasync
sync 只将修改过的块缓冲区排入写队列,然后就返回,并不等待写操作结束。 update 守护进程会周期性调用 sync 保证定期冲洗块缓冲区
fsync 会等待特定的文件描述符写操作结束后才返回 fdatasync 只关心数据更新,不关心文件的属性更新
fcntl
可改变已经打开的文件的属性
ioctl
对特别的设备进行 IO 操作
open /dev/fd/n 这个路径,当 n 是打开的时候,会复制描述符 n
缓冲IO
读和写会经过内核维护的内存页面进行缓冲,每个页面可以映射到磁盘的多个块,根据块的大小,每个块可以映射到磁盘硬件的一个或多个扇区。根据内核的策略将脏页懒刷入硬盘中,从而减少 IO 次数,提高性能。
直接IO
读和写都不经过内核的页缓存 page cache,打开文件的时候使用 O_DIRECT 标志可以在读写的时候防止额外的拷贝。
普通应用程序使用直接IO会导致性能的下降,但是能够让使用者完全掌握IO操作从而通过某种方式提高性能,数据库就是这样一种使用直接 IO 但是大大提高 IO 性能的程序。例如它们会使用直接 IO 来编写 WAL 组件,确保数据变更记录能够先落盘
块对齐
如果使用直接IO,需要注意的是写入和读取的数据必须是块大小的整数倍,也就是 512 B 的整数倍
mmap
内存映射使得进程能够在内存中访问文件的所有位置,进程的虚拟页会直接映射到内核的页缓存中,意味着不会有 buffer io 从用户空间拷贝数据到内核空间的开销,当中不会有拷贝的 syscall 存在。读的时候除非触发 page fault,否则不会执行IO随机读操作。
私有模式和共享模式
私有模式类似于缓冲IO的无拷贝版 共享模式可以让多个进程共享同一个 mmap