文件系统
Linux 存储系统的 I/O 软件分层,分为三个层次,分别是文件系统层、通用块层、设备层。
- 文件系统层,包括虚拟文件系统和其他文件系统的具体实现,它向上为应用程序统一提供了标准的文件访问接口,向下会通过通用块层来存储和管理磁盘数据。
- 通用块层,包括块设备的 I/O 队列和 I/O 调度器,它会对文件系统的 I/O 请求进行排队,再通过 I/O 调度器,选择一个 I/O 发给下一层的设备层。
-设备层,包括硬件设备、设备控制器和驱动程序,负责最终物理设备的 I/O 操作。
文件I/O
常见的IO分为三类:
缓冲与非缓冲 I/O
直接与非直接 I/O
阻塞与非阻塞 I/O VS 同步与异步 I/O
阻塞 I/O、非阻塞 I/O,还是基于非阻塞 I/O 的多路复用都是同步调用。因为它们在 read 调用时,内核将数据从内核空间拷贝到应用程序空间,过程都是需要等待的,也就是说这个过程是同步的,如果内核实现的拷贝效率不高,read 调用就会在这个同步过程中等待比较长的时间。
1.缓冲与非缓冲IO
根据是否利用标准库缓冲:
- 缓冲IO:利用标准库缓存实现文件的加速范围跟,标准库则再通过系统调用访问文件。
- 非缓冲:直接通过系统调用访问文件,不经过标准库缓存
比如linux中输入密码经常看不见,是因为直到遇见换行指令,才进行IO,之前的内容都是先被标准库暂时缓存起来,这样做可以减少系统调用次数。
2.直接与非直接IO
Linux 内核为了减少磁盘 I/O 次数,在系统调用后,会把用户数据拷贝到内核中缓存起来,这个内核缓存空间也就是「页缓存」,只有当缓存满足某些条件的时候,才发起磁盘 I/O 的请求。
根据是否利用操作系统缓存:
- 直接IO,不会发送内核缓存与用户程序之间的数据复制,而是直接经过文件系统访问磁盘。
- 非直接IO,读操作时,数据从内核缓存拷贝到用户程序,进行写操作时,数据从用户程序拷贝给内核缓存,再由内核决定什么时候写入数据到磁盘。
触发内核缓存写磁盘的操作
- 调用write函数的最后 ,发现内核缓存数据太多,内核会把数据写到磁盘上。
- 用户主动sync,内核缓存写到磁盘上。
- 内存紧张,无法再分配页面
- 内核缓存数据超过某个时间。
3.阻塞与非阻塞IO 与 同步与异步IO
阻塞IO:
执行read,线程先被阻塞,一直到内核数据准备好,并把数据从内核拷贝到应用程序的缓冲区中,拷贝完成read返回。
阻塞得等1.内核准备好数据 2.数据从内核态拷贝到用户态 两个过程
非阻塞IO
read请求在数据尚未准备好就立即返回,此时应用程序不断轮询,直到数据准备好,内核将数据拷贝到应用程序缓冲区,read获取到结果。
即read发起系统调用一直轮询返回,直到数据准备好,而后从内核拷贝到应用进程。
最后一次read,获取数据是一个同步的过程,需要等待,同步指内核态数据拷贝到用户态程序缓存区的过程。
访问管道或者socket ,设置O_NONBLOCK,就表示非阻塞。
IO多路复用——使用一个进程来维护多个 Socket
socket:IP+端口;
为了解决非阻塞IO傻乎乎的轮询,通过IO事件分发select /poll,等内核数据准备好,再以事件通知应用程序操作。
这个做法大大改善了应用进程对 CPU 的利用率,在没有被通知的情况下,应用进程可以使用 CPU 做其他的事情。
- select阻塞系统调用
- 数据直到准备好,从内核拷贝到应用进程
- 通知数据可读
- read调用系统调用
- 拷贝完成,返回结果
多进程模型:
如果服务器要支持多个客户端,其中比较传统的方式,就是使用多进程模型,也就是为每个客户端分配一个进程来处理请求。
服务器的主进程负责监听客户的连接,一旦与客户端连接完成,accept() 函数就会返回一个「已连接 Socket」,这时就通过 fork() 函数创建一个子进程,实际