目录
背景
什么是IO?
什么是高级IO?
OS如何得知外设当中有数据可读取?
OS如何处理从网卡中读取到的数据包?
五种IO模型
概念区分
消息通信机制
同步通信 与 同步与互斥
即input/output,在冯若依曼体系中,将数据从输入设备拷贝到内存中就叫做input,从内存拷贝到输出设备就叫output,但是数据未必是就绪的,需要等待数据就绪才能拷贝,所以IO=等待数据就绪+拷贝,基于不同的等待和拷贝策略,就产生了不同IO方式,那什么是高效的IO?显然等待时间越少IO效率越高。
在Linux下高级I/O(Advanced IO)指的是一组函数,这些函数提供了比标准I/O更高级别的输入输出操作。高级I/O具有更好的性能和更多的灵活性,之所以高级,体现在:
提供更好的性能:在处理大量数据时,高级I/O明显优于标准I/O,因为高级I/O以异步和非阻塞方式进行I/O操作。
更精细的控制:高级I/O可以进行更细致的I/O控制,例如读写过程中的超时控制以及对文件的锁定等。
更好的可移植性:与标准I/O函数库不同,高级I/O函数库可移植性较强,因为其API是Unix操作系统遵循POSIX标准化的结果之一。
更好的可移植性?
高级IO通常是基于操作系统提供的低级IO接口实现的,但其在使用时不需要考虑底层细节,而是更关注于提供方便、易用的API,并隐藏了与底层IO接口相关的差异。因此,高级IO的可移植性更强,能够在不同的操作系统上使用相同的API进行IO操作而不需要修改代码,从而提高了代码的可维护性和复用性。另外,高级IO还可以比底层IO提供更丰富的选项和功能,例如缓存、编码转换、流控制等。
注意:高级 IO 并不一定意味着高效的 IO 方式。虽然高级 IO 接口通常具有更高层次的抽象和更方便的 API,但它们可能在底层使用的仍然是相同的系统调用。因此,在某些情况下,与低级 IO 相比,高级 IO 的性能可能会更低。
当外设(例如键盘或鼠标)有数据可读取时,它们会触发一个中断信号,通知操作系统有数据可用。当CPU收到某个中断信号时就会自动停止正在运行的程序,根据不同中断信号在中断向量表对应不同的中断处理程序,去处理中断处理程序,处理完毕后再返回原被暂停的程序继续运行。
一旦中断处理程序检测到有可读数据,它会向操作系统的输入缓冲区添加该数据。然后,应用程序可以通过系统调用从缓冲区读取数据。
需要注意的是,不同的外设使用不同的协议来通知操作系统有数据可用。因此,操作系统必须了解各种协议以正确处理输入。
中断向量表
中断向量表是一个特殊的数据表,用于存储中断处理程序的入口地址。当计算机系统出现中断事件时,例如硬件错误、输入/输出请求等,CPU需要停止正在执行的任务,并跳转到对应的中断处理程序去执行。而中断向量表就记录了每种类型中断所对应的处理程序入口地址,这样 CPU 就可以根据中断类型查找对应的入口地址,在接下来的处理中将控制权转移给相关的中断处理程序。
答案是:自底向上。具体而言:当网络接口收到一个数据包时,它会将该数据包传输到与之关联的缓冲区中。接着,操作系统内核会从缓冲区读取数据包,并在协议栈中进行处理。操作系统内核会根据数据包中的目标 MAC 地址来确定它是否是本地主机的数据包。如果是,则传输到上层协议进行进一步处理;否则,将其转发到合适的下一个路由器或主机。在协议栈中,数据包经历了多个协议处理层,包括物理层,数据链路层,网络层和传输层,在每个层次上进行相应的处理,例如解封装、路由选择、重组/分段并报告错误等等。最后,数据包会被传输到应用层进行处理。
阻塞式IO
套接字的默认方式,内核将数据准备好之前,系统调用会一直等待
比如调用recvfrom等待数据就绪,进程或线程会阻塞,当数据就绪后将数据从内核拷贝到用户空间,函数才返回
图示:
在recvfrom等待数据就绪期间,在用户看来该进程或线程阻塞住了,本质就是操作系统将该进程或线程的状态设置为非R状态,然后将其放入等待队列中,当数据就绪后操作系统再将其从等待队列中唤醒。
代码示例:
#include
#include
#include
#include
using namespace std;
int main()
{
char buffer[1024];
while(1)
{
ssize_t s=read(0,buffer,sizeof buffer-1);
if(s<0)
{
cerr<<"read error:"<
输出:
非阻塞式IO
非阻塞式IO就是内核数据没有就绪,系统调用仍直接返回,将错误码设置为EWOULDBLOCK,进程或线程不会阻塞等待。
图示:
轮询:后续的检测动作由用户发起,需要程序员以非阻塞循环的方式反复尝试读取文件描述符,这个过程称为轮询。
打开文件的时候默认都是阻塞方式,要以非阻塞方式打开就要在open函数打开文件的时候携带O_NONBLOCK或O_NODELAY。倘若是已经打开的文件,要将其设置为非阻塞就可以使用fcntl函数。
fcntl函数原型:
函数参数:fd,cmd(需要进行的操作)
fd:打开的文件描述符
cmd:进行的操作
可变参数:根据不同的cmd,后面的参数不同
调用失败的时候函数返回-1,错误码会被设置
使用方法(比如将文件描述符设置为非阻塞):
先调用一次获取文件描述符对应的状态(位图),传入F_GETFL,再次调用将文件状态标记上添加(或上)非阻塞标记O_NONBLOCK,此时传入的cmd值为F_SETFL。
代码示例:
#include
#include
#include
#include
using namespace std;
void setNonblock(int fd)
{
int fl=fcntl(fd,F_GETFL);
if(fl<0)
{
cerr<<"fcntl error:"<
输出:
信号驱动式IO
信号驱动式IO:当内核数据准备好,使用SIGIO信号通知应用程序
图示:
用signal或sigaction将信号处理方法自定义为IO操作
信号的产生是异步的,但是属于同步IO,因为进程或线程需要参与IO过程
IO多路复用(多路转接)
思想:一次等待多个文件描述符就绪,将等待的时间重叠
recvfrom也有等的能力,但这些接口一次只能等待一个文件描述符上的数据或空间就绪,IO效率低,系统为我们提供了三组接口(select,poll,epoll),可以将所有等的工作交给它们,一次等待多个文件描述符,将等待的时间重叠,从而提高效率。
如图:
异步IO
异步IO就是由内核在数据拷贝完成的时候,再通知应用程序。
在内核完成数据拷贝时才通知用户,也就是说用户既不等待也不进行拷贝,没有参与IO,所以被称为异步IO,用户要做的只有发起IO,等待和拷贝都交给了操作系统,用户得知结果即可。
注意:调用异步IO接口的时候会立马返回,用户需要传递缓冲区和回调函数。
同步通信
发出调用,没得到结果前不返回,调用返回就得到返回值,调用者主动等待这个结果
非阻塞IO没有得到结果也会返回,但后续需要轮询,理解为未返回
异步通信
调用发出就返回,没有返回结果,当异步过程调用发出,调用者不会立即得到结果,被调用者通过状态,通知调用者或回调函数处理这个结果
两个同步是不同的意义:
同步通信:指的是没有结果之前调用不返回,同步IO指的是进程/线程与OS之间的关系,谈论的是进程/线程是否需要主动参与IO过程。
进程/线程同步:指的是在保证数据安全的情况下让进程、线程按某种特定的顺序访问临界资源。