我们的计算机是通过中断程序来做到的,8259,中断组件来实现的,等到外来设备来的时候,就会像CPU里面直接发送消息(数据不会发送,只是通过控制信号可以直接通知CPU)
CPU通过中断向量表,通过其中的方法进行对应的操作,而当网卡有数据,就是通知CPU去将数据从网卡里面 搬运到内存里面
首先我们先介绍一下中断
中断:所谓的中断就是指CPU 在正常运行程序的过程中由于内部/外部的事件的触发或因为程序预先的安排,引起CPU暂时的中断当前正在运行的程序,而转而去执行内部/外部事件,或者程序预先安排的事件的服务子程序,待中断服务子程序执行完毕之后,CPU 在返回被暂时中断的地方,(CPU接收到了信号暂时离开执行其他事情,完了之后再回来执行原本要执行的事情)
中断向量:中断服务程序的入口地址
中断向量表:把系统中所有的中断类型码及其对应的中断向量按照一定规律存放在一个区域内,这个区域就叫做中断向量表
底层网卡有数据到达时候,此时硬中断通知操作系统,操作系统生成软中断对数据进行拷贝工作
通常引入中断都是外设,系统当中数据准备就绪,需要拷贝到内存里面,需要硬件层面上的中断来完成
waitpid并没有使用中断
:本身就是一个软件,父子进程有直接的关系,子进程在退出的时候,可以根据PCB 找到父进程
谈及高级IO为何高效,来讨论一下为何read和write低效
在之前的网络套接字代码编写的时候,我们可能使用的是read和write进行操作,使用这个还是的时候,我们在写入或者读取的时候进程都是阻塞的,此时这个就称为IO,尤其是在套接字的场景当中读取数据时候,不知道会被阻塞多久,一个进程没读完就不能进行其他的操作,我们因为只等待一个文件描述符,就在那里一直等待,所以就非常的低效率
如:read,write,recv,send,recvfrom,sendto,fopen,fread cin,cout,scanf,printf
因为调用了select poll,epoll进行等待的时候就可以等待批量的文件描述符,等待的事件重叠了,一次可以处理多个事情,所以就高效了
现在我们将钓鱼简化,过程可以分为等和钓两个步骤
钓鱼大佬们是怎么提高效率调到更多的鱼呢?
看看下面5个人的做法,谁最有可能调到最多的鱼儿
张三:阻塞式的钓鱼
李四:一边玩手机,时不时看看鱼鳔
王五:鱼竿带一个铃铛,忙自己的事情,等到铃铛响的时候再去钓鱼
赵六:用一大堆鱼竿,在岸边进行轮询检测,只要有一个鱼咬住钩就行了
田七:派人去钓鱼,调到了鱼就给田七打电话,钓鱼的桶就相当于一个缓冲区
答案是:赵六
低效IO:在等待过程特别长的通信过程中,让等的时间占比特别高,此时IO大部分的时间都是在等待,所以效率就会很低,
高效IO:在一次IO中让等待的时间占比小,recv读不是立马有数据就去读取的,还要等数据超过低水位线,或者对端给我发送PSH字段才会通知上层将数据从内核拷贝数据到用户
我们的做法就是让一个中介,这个中介去执行等待,服务器只对其负责,当有客户端发起连接时,先告诉中介,中介将消息告诉服务器,服务器再执行操作,所以就不需要服务器去做无意义的等待,只要让这个中介去等待就行了
如何使用信号驱动IO
操作系统收到数据的时候,会给进程发送SIGIO,默认处理动作是忽略,我们可以注册SIGIO 的处理方法,当底层好了给我们发送信号,我们再一次调用read接收就可以了,简单IO用的多,复杂IO用的少,因为它是普通信号,只会记录一个信号,信号可能会丢失
同步IO和异步IO 的本质就是在数据拷贝的过程中,异步IO不关心数据的拷贝,只提供一个缓冲区,让OS在合适的时候拷贝到缓冲区里面,即拷贝的过程都是由OS来完成的,数据就绪的通知方案是由信号所决定的
类比:
家里来了客人,母亲在做菜,此时我是负责端菜的,我可以以同步IO的方式进行端菜,但时对于客人来说,不需要帮忙,对于客人来说就是异步的
总结
:同步IO需要用户在调用recv/read将数据拷贝到缓冲区,但是异步IO需要用户提前告知缓冲区,OS会选择合适的时机进行拷贝数据
因为协议栈是自底向上收的,所以操作系统先收到,硬件,驱动,操作系统,用户,这个问题虽然简单,但是很关键,通常是我们硬件设备网卡检测到有数据的时候,然后中断拷贝到系统的缓冲区里面
如select,select的时候阻塞等待了多个文件描述符就绪,只要有一个准备好了,就会调用recv将数据读取上来,recv一次只能等待一个,因为它的参数只有一个,但是select后能保证recv不会被阻塞住了,
select只负责等待的环节,后续的recv就不会被阻塞了,因为数据已经就绪了,
但是细节还有很多,比如数据就绪我能够疯狂进行读取吗,或者我能读取一部分就不读了吗,下一次它还会送来给我读取吗
fcntl函数主要是用来操作文件描述符的
一个文件描述符,默认都是阻塞IO,fcntl可以让文件描述符为非阻塞的
但是骑士除了fcntl的方式,还有好几种方法,如open的时候,可以设置第二个参数为O_NONBLOCK,可以让打开的文件描述符就是非阻塞的,或者调用recv等接口的时候,设置flags 为O_NONBLOCK
#include
#include
int fcntl(int fd, int cmd, ... /* arg */ );
传入的cmd值不同,后面追加的参数也不一样,fcntl函数有5个功能
复制一个现有的描述符(cmd=F_DUPFD).
获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
我们此处只是用第三种功能 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞.
接口测试:
测试代码:默认read读取为非阻塞,每次往标准输入里面读取一个数据,默认缓冲区为空,便会卡住让我们输入数据
非阻塞等待
#include
using namespace std;
#include
#include
#include
void SetNonBlock(int fd)
{
//获取之前文件的状态
int fl=fcntl(fd,F_GETFD);
if(fl<0)
perror("fcntl");
//把文件描述符设置为非阻塞,设计标记
fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}
int main()
{
//观察标准输入阻塞和非阻塞状态读取数据
char ch;
SetNonBlock(0);//给0设置为非阻塞
while (1)
{
sleep(1);
ssize_t s = read(0, &ch, 1);
if (s > 0)
{
printf("%c\n", ch); //读取成功
}
else if(s<0&&(errno==EAGAIN||errno==EWOULDBLOCK))
{
//非阻塞读取,底层的数据没有就位
cout<<"continue"<<endl;
}
else if(errno==EINTR&&s<0)//读取被信号中断了
{
continue;
}
else
{
// cout << ch << endl;
cout<<s<<endl;
}
cout << "............." << endl;
}
return 0;
}
//当我们不输入的时候,就会卡住等待我们进行输入
//设置为非阻塞,当缓冲区里面没有数据的时候,read直接返回失败,ssize_t 是一个有符号整数,-1代表底层数据没有就绪,
//读取数据不算错误,而是一种通知,并且会设置errno为EAGAIN,(try again)表示底层数据没有准备好,下次再来的话,EWOULDBLOCK也同样的效果
//如果错误码是EINT表示阻塞等待时候被信号给阻塞掉了