哈喽,我是老吴,俺又来分享文章啦!
浑浑噩噩到了 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> 为通知信号安装处理函数。
通过 sigaction() 来完成:
int sigaction(int signum, const struct sigaction *act, struct sigaction *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 近乎无用。
太多情况都会产生信号,而我们又无法得知事件类型,因此这里就不再列举其产生信号的情况。 四、最简单的示例 信号处理函数:
static volatile sig_atomic_t gotsigio = 0;static void handler(int sig){ gotsigio = 1;} 主程序:
int main(int argc, char *argv[]){ int flags, j, cnt; struct termios origtermios; char ch; struct sigaction sa; int done; /* establish handler */ sigemptyset(&sa.sa_mask); sa.sa_flags = sa_restart; sa.sa_handler = handler; if (sigaction(sigio, &sa, null) == -1) { perror(sigaction()); exit(1); } /* set owner process */ if (fcntl(stdin_fileno, f_setown, getpid()) == -1) { perror(fcntl() / f_setown); exit(1); } /* enable i/o possible signaling and make i/o nonblocking */ 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; /* read all available input until error (probably eagain) or eof */ while (read(stdin_fileno, &ch, 1) > 0 && !done) { printf(cnt=%d; read %c, cnt, ch); done = ch == '#'; } } } exit(0);} 运行效果:
./build/sigio acnt=0; read acnt=0; read abccnt=4; read acnt=4; read bcnt=4; read ccnt=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 是一个更好的选择。
六、相关参考 unix 网络编程卷1
6.2 i/o模型 25 信号驱动式i/o linux-unix 系统编程手册
63 其他备选的i/o模型 linux 高性能服务器编程
8.3 i/o 模型 linux 多线程服务端编程_使用muduo c++网络库
景旺电子科技成果评审会在江西召开
海泰方圆当选《2022网信自主创新调研报告》商用密码和安全浏览器双组长单位
如何使用3DIC Compiler实现芯片堆叠设计
Maxim全新高度集成的数字脉冲发生器
什么是接地电阻?如何测量防雷接地电阻?
Linux的5种IO模型
换芯片怎么选型,怎么选择合适的替换型号方案及选型参考
iPhone12mini即将来袭,你准备好了吗
如何用最少的设备达到最佳的无线覆盖效果?
基于ADI技术的哮喘管理设备
浅谈ldc1000的单片机测试数据程序
玻璃颗粒耐水性试验仪产品介绍
钛酸锂技术有望引领储能产业发展 被业界称为最具应用前景的储能电池之一
微软上线 AI商学院,专注于战略、文化和责任
关于电老虎详解,真的是内存越大耗电越快吗?
联想ZUK手机即将回归 迎来手机行业的新开端
简述晶闸管的结构和原理
上海乐高探索中心乐玩卡在飞猪独家发售
研究人员新设计材料可回收废热作为能源
三巨头操控DRAM芯片价格!发改委将要出手!