磁盘IO
详情参见 磁盘IO:缓存IO与直接IO
缓存I/O
缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都是缓存I/O
在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区,然后从内核空间缓冲区复制到应用程序的地址空间
读操作:操作系统检查内核的缓冲区有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回;否则从磁盘中读取,然后缓存在操作系统的缓存中
写操作:将数据从用户空间复制到内核空间的缓存中。这时对用户程序来说写操作就已经完成,至于什么时候再写到磁盘中由操作系统决定,除非显示地调用了sync同步命令
直接IO
直接IO就是应用程序直接访问磁盘数据,而不经过内核缓冲区
文件读写
虚拟文件系统(VFS)
一个操作系统可以支持多种底层不同的文件系统(比如NTFS, FAT, ext3, ext4),为了给内核和用户进程提供统一的文件系统视图,Linux在用户进程和底层文件系统之间加入了一个抽象层,即虚拟文件系统(Virtual File System, VFS),进程所有的文件操作都通过VFS,由VFS来适配各种底层不同的文件系统,完成实际的文件操作
系统主要模块
详情参考从内核文件系统看文件读写过程
模块 | 说明 |
---|---|
super_block | 超级块,用于保存一个文件系统的所有元数据 |
dir_entry | 目录项模块,管理路径的的文件和目录项 |
inode | 指向一个具体的文件,是文件的唯一标识 |
open_files | 打开的文件列表模块,包含所有内核已经打开的文件 |
file_operations | 一系列文件操作函数指针的集合 |
address_space | 记录了文件在页缓存中已经缓存了的物理页 |
内核数据结构
文件节点在unix中是
vnode
,linux中是inode
进程文件表
每个进程都有一张进程文件表,其中的每一项都指向了某一个打开的文件
子进程会共享父进程的进程文件表
打开文件表
内核为所有打开文件维持一张文件表,其中的每一项都指向了真正的文件
打开文件表是系统级别的,和进程无关
文件节点表
文件节点表记录了文件的相关信息,包括所有者,长度,所在设备等信息
两个打开文件表项可以指向同一个文件,使得文件被进程共享
操作的原子性
- 创建文件的操作是原子性的
- read()/write()函数的调用是原子性
- lseek()可以设置偏移量,是原子性的
并发数据冲突
对于write()
操作,我们可以通过open
文件时指定O_APPEND
选项来解决
多进程单线程,每个进程打开一个fd
这种情况下数据冲突的本质是存在多个fd
- 每个fd指向不同的打开文件表项
- 也就是拥有多个不同的文件偏移量
- 可能会导致写入文件的数据相互覆盖
单进程多线程,单个进程打开一个fd
这种情况下线程间共用同一个fd
- 只有一个文件表项,也就是只有一个文件偏移量
- 如果不需要调用
lseek
,那么并没有数据冲突的危险
单进程多线程,每个线程打开一个fd
这种情况下线程间各自打开一个fd
- 每个fd指向不同的打开文件表项
- 也就是拥有多个不同的文件偏移量
- 可能会导致写入文件的数据相互覆盖
参考
磁盘IO:缓存IO与直接IO
Linux 内核详解以及内核缓冲区技术
从内核文件系统看文件读写过程
linux下多进程/多线程文件操作详解
Linux中的文件描述符与打开文件之间的关系
Linux系统环境下关于多进程并发写同一个文件的讨论