哈喽,我是老吴,俺又来分享文章啦!
浑浑噩噩到了 30 岁,距离开滴滴还有 5 年的时间。
还有机会全身而退吗?
哈哈!
30 而立,今年会是值得拼搏的一年,干它!
以下是正文:
一、Linux 的 5 种 IO 模型
二、如何使用信号驱动式 I/O?
三、内核何时会发送 "IO 就绪" 信号?
四、最简单的示例
五、扩展知识
一、Linux 的 5 种 IO 模型
阻塞式 I/O:
系统调用可能因为无法立即完成而被操作系统挂起,直到等待的事件发生为止。
点击查看大图
非阻塞式 I/O (O_NONBLOCK):
系统调用则总是立即返回,而不管事件是否已经发生。
点击查看大图
I/O 复用 (select、poll、epoll):
通过 I/O 复用函数向内核注册一组事件,内核通过 I/O 复用函数把其中就绪的事件通知给应用程序。
点击查看大图
信号驱动式 I/O (SIGIO):
为一个目标文件描述符指定宿主进程,当文件描述符上有事件发生时,SIGIO 的信号处理函数将被触发,然后便可对目标文件描述符执行 I/O 操作。
点击查看大图
异步 I/O (POSIX 的 aio_ 系列函数):
异步 I/O 的读写操作总是立即返回,而不论 I/O 是否是阻塞的,真正的读写操作由内核接管。
点击查看大图
思考一下,什么时候应该选择何种 I/O 模型?为何要这么选择?
下面重点关注信号驱动式 I/O 这一模型,其他模型可查阅文末参考书籍。
二、如何使用信号驱动式 I/O?
一般通过如下 6 个步骤来使用信号驱动式 I/O 模型。
1> 为通知信号安装处理函数。
intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);
默认情况下,这个通知信号为 SIGIO。
2> 为文件描述符的设置属主。
通过 fcntl() 的 F_SETOWN 操作来完成:
fcntl(fd,F_SETOWN,pid)
属主是当文件描述符上可执行 I/O 时,会接收到通知信号的进程或进程组。
pid 为正整数时,代表了进程 ID 号。
pid 为负整数时,它的绝对值就代表了进程组 ID 号。
3> 使能非阻塞 I/O。
通过 fcntl() 的 F_SETFL 操作来完成:
flags=fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|O_NONBLOCK);
4> 使能信号驱动 I/O。
通过 fcntl() 的 F_SETFL 操作来完成:
flags=fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|O_ASYNC);
5> 进程等待 "IO 就绪" 信号的到来。
当 I/O 操作就绪时,内核会给进程发送一个信号,然后调用在第 1 步中安装好的信号处理函数。
6> 进程尽可能多地执行 I/O 操作。
循环执行 I/O 系统调用直到失败为止,此时错误码为 EAGAIN 或 EWOULDBLOCK。
原因:
信号驱动 I/O 提供的是边缘触发通知,即只有当 I/O 事件发生时我们才会收到通知,
且当文件描述符收到 I/O 事件通知时,并不知道要处理多少 I/O 数据。
三、内核何时会发送 "IO 就绪" 信号?
对于不同类型的文件描述符,情况不一样。
1> 终端
- 对于终端,当有新的输入时会会产生信号。
2> 管道和 FIFO
对于读端,下列情况会产生信号:
- 数据写入到管道中;
- 管道的写端关闭;
对于写端,下列情况会产生信号:
- 对管道的读操作增加了管道中的空余空间大小。
- 管道的读端关闭;
3> 套接字
对于 UDP 套接字,下列情况会产生信号:
- 数据报到达套接字;
- 套接字上发生异步错误;
对于 TCP 套接字,信号驱动式 I/O 近乎无用。
- 太多情况都会产生信号,而我们又无法得知事件类型,因此这里就不再列举其产生信号的情况。
四、最简单的示例
信号处理函数:
staticvolatilesig_atomic_tgotSigio=0;
staticvoidhandler(intsig)
{
gotSigio=1;
}
主程序:
intmain(intargc,char*argv[])
{
intflags,j,cnt;
structtermiosorigTermios;
charch;
structsigactionsa;
intdone;
/*Establishhandler*/
sigemptyset(&sa.sa_mask);
sa.sa_flags=SA_RESTART;
sa.sa_handler=handler;
if(sigaction(SIGIO,&sa,NULL)==-1){
perror("sigaction()
");
exit(1);
}
/*Setownerprocess*/
if(fcntl(STDIN_FILENO,F_SETOWN,getpid())==-1){
perror("fcntl()/F_SETOWN
");
exit(1);
}
/*Enable"I/Opossible"signalingandmakeI/Ononblocking*/
flags=fcntl(STDIN_FILENO,F_GETFL);
if(fcntl(STDIN_FILENO,F_SETFL,flags|O_ASYNC|O_NONBLOCK)==-1){
perror("fcntl()/F_SETFL
");
exit(1);
}
for(done=0,cnt=0;!done;cnt++){
sleep(1);
if(gotSigio){
gotSigio=0;
/*Readallavailableinputuntilerror(probablyEAGAIN)
orEOF*/
while(read(STDIN_FILENO,&ch,1)>0&&!done){
printf("cnt=%d;read%c
",cnt,ch);
done=ch=='#';
}
}
}
exit(0);
}
运行效果:
./build/sigio
a
cnt=0;reada
cnt=0;read
abc
cnt=4;reada
cnt=4;readb
cnt=4;readc
cnt=4;read
#
cnt=7;read#
该程序会先使能信号驱动 IO,然后循环执行计数操作。
当有 IO 就绪信号到来时,会去终端读取数据并打印出来,然后继续执行计数操作。
五、扩展知识
I/O 多路复用 、信号驱动 I/O 以及 epoll 机制可用于监视多个文件描述符。
它们并不实际执行 I/O 操作,当某个文件描述符处于就绪态,仍需采用传统的 I/O 系统调用来完成 I/O 操作。
相比 I/O 多路复用,当监视大量的文件描述符时信号驱动 I/O 有着显著的性能优势,原因是内核能够帮进程记录了正在监视的文件描述符列表。
信号驱动 I/O 的缺点:
-
信号的处理流程较为复杂;
-
无法指定需要监控的事件类型。
Linux 特有的 epoll 是一个更好的选择。
六、相关参考
- 6.2 I/O模型
- 25 信号驱动式I/O
Linux-UNIX 系统编程手册
- 63 其他备选的I/O模型
Linux 高性能服务器编程
- 8.3 I/O 模型
Linux 多线程服务端编程_使用muduo C++网络库
- 7.4.1 muduo的IO模型
审核编辑 :李倩
-
Linux
+关注
关注
87文章
11216浏览量
208761 -
信号处理
+关注
关注
48文章
996浏览量
103168 -
函数
+关注
关注
3文章
4290浏览量
62346
原文标题:思考技术,也思考人生
文章出处:【微信号:zhuyandz,微信公众号:FPGA之家】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论