一、中断机制
在IO处理中有2种思路,一种就是轮训(polling)机制,一种是中断(interrupt)机制,前置是一种同步的通信机制,不是计算机中IO采用的机制,我们重点来说明中断机制。
CPU停下当前的工作任务,去处理其他事情,处理完后回来继续执行刚才的任务,这一过程便是中断。中断分为外部中断和内部中断。
外部中断
- 可屏蔽中断:通过INTR线向CPU请求的中断,主要来自外部设备如硬盘,打印机,网卡等。此类中断并不会影响系统运行,可随时处理,甚至不处理,所以名为可屏蔽中断。
- 不可屏蔽中断:通过NMI线向CPU请求的中断,如电源掉电,硬件线路故障等。这里不可屏蔽的意思不是不可以屏蔽,不建议屏蔽,而是问题太大,屏蔽不了,不能屏蔽的意思。
内部中断
- 陷阱:是一种有意的,预先安排的异常事件,一般是在编写程序时故意设下的陷阱指令,而后执行到陷阱指令后,CPU将会调用特定程序进行相应的处理,处理结束后返回到陷阱指令的下一条指令。如系统调用,程序调试功能等。
- 故障:故障是在引起故障的指令被执行,但还没有执行结束时,CPU检测到的一类的意外事件。出错时交由故障处理程序处理,如果能处理修正这个错误,就将控制返回到引起故障的指令即CPU重新执这条指令。如果不能处理就报错。常见的故障为缺页。
- 终止:执行指令的过程中发生了致命错误,不可修复,程序无法继续运行,只能终止,通常会是一些硬件的错误。终止处理程序不会将控制返回给原程序,而是直接终止原程序。
二、DMA机制
在现代计算机中,进行磁盘IO或者网络IO的时候,CPU是不会全程参与的,CPU只会下发数据传输的指令,比如是读还是写,设备类型还有内存地址等发送给设备的设备控制器,后续会由设备和内存直接进行数据传输,这样就能节省CPU昂贵的开销,这就是DMA机制。
DMA的主要成就是将耗时较久的数据IO时间直接交由内存和设备来做,不太需要CPU的参与,让CPU去做更有价值的事情。
没有DMA之前:
- 用户进程调用read()请求到CPU;
- CPU向IO设备发送请求;
- IO设备将数据准备到内存缓存区后向CPU发送中断请求;
- CPU接受到中断请求后,就将数据拷贝到page cache中,这个过程一直持续到数据传输完成;
- 数据传输完成后,read()调用返回数据;
- 整个过程中,CPU一直阻塞在IO设备拷贝数据到page cache的这个过程;
DMA解决CPU数据拷贝阻塞的问题:
- 用户进程调用read()请求到CPU;
- CPU向DMA控制器发起IO请求,再由DMA向IO设备发起IO请求;
- IO设备数据准备好了,是向DMA发送数据,一直等数据都传到DMA了,由DMA向CPU发送中断请求;
- DMA控制器将数据一致性发送给CPU,在这个过程中CPU不需要一直阻塞;
总结:DMA相当于在CPU和IO设备间加入了一个中间层,之前他们直接交互,现在DMA等数据传输好了再通知CPU。
三、零拷贝(zero copy)
传统的数据拷贝过程中,数据需要从内核缓冲区复制到用户空间缓冲区,然后再从用户空间缓冲区复制到内核缓冲区,这个过程会耗费大量的CPU时间和内存带宽,降低系统的性能和吞吐量。
为了解决这个问题,零拷贝技术应运而生。零拷贝技术是指在数据传输过程中,尽量避免将数据从一块内存拷贝到另一块内存,从而减少了CPU的开销和内存带宽的消耗,提高了系统的性能。
未应用零拷贝技术之前:
- 磁盘数据通过dma拷贝到内核态的缓存区;
- 内核空间缓存区的数据通过cpu拷贝到用户空间的缓存区,其中涉及到2次的用户态内核态上下文切换;
- 用户空间缓存区的数据通过cpu拷贝到用户空间的socket缓存区,其中涉及到2次的用户态内核态上下文切换;
- socket缓存区的数据通过dma拷贝到网卡发送出去;
传统的IO拷贝技术,会有4次数据拷贝,2次系统调用,4次上下文切换,但是由于内核态到用户态的数据拷贝不涉及数据的运算,所以理论上不需要拷贝到用户态;
目前有2中常见的零拷贝技术:(1)mmap+write;(2)sendfile;
1.mmap技术
- 调用mmap方法,直接将内核缓存区的数据进行数据共享;
- 将共享缓存区的数据通过cpu拷贝到socket缓存区;
- 内核态的socket缓存区的数据直接通过dma拷贝到网卡;
通过mmap技术,解决用户态无法访问内核态数据的的问题,能够直接在内核态进行数据的拷贝,这样就减少了在用户态进行CPU拷贝的过程;
2.sendfile技术
对于mmap技术,还是会有2次系统调用,造成4次上下文切换。
现在引入sendfile调用,直接是将缓存区的描述符传递给socket缓存的描述符,直接替换read和write调用,这样只需要一次系统调用,2次上下文中断;
四、IO多路复用技术
IO多路复用技术是指这样一个过程:
- 获取IO操作的文件描述符,将其放到文件描述符集合中保存,IO操作包括网络IO或者磁盘IO等;
- 系统调用一个函数,这个函数替操作系统监控文件描述符集合中事件,比如read()或者write()事件;
- 当有事件监听到,就通知工作线程来执行;
过程中描述的函数在Linux中select、poll、epoll实现。
1.select
将所有连接的socket文件描述符放置到文件描述符集合中,通过select函数将其复制到内核态空间,检查其网络事件,该检查是通过轮训的方式,并且该描述符集合是顺序表保存。所以保存的文件描述符有限。
select是一种轮训机制,会去轮训文件状态描述符,缺点是轮训对象有限,64位的Linux系统也只能是2048个;将已经连接的socket文件描述符放置到数组中,再将该数组拷贝到内核空间中做检查,看哪些socket是在read还是write状态,然后对其进行处理。
2.poll
poll看起来和select实现的机制差不多,只是没有了socket连接数量的限制,这是由于他是用链表来保存socket文件描述符;
3.epoll
epoll是基于OS的事件通知机制,socket事件发生了会将事件通知到工作线程处理;
总结:select和poll的实现都是监听对象轮训文件描述符集合中事件,select中文件描述符集合有限,poll中文件描述符集合理论上上无限,但其工作线程其实是阻塞的,就是一直在等待他们将事件报送过来在进行处理。
epoll是基于事件机制来实现,其实现是在对每个文件描述符后都记录其对应的工作线程信息,但监听到事件就能直接通知工作线程来工作。