入职作业总结(2)I/O复用

16 篇文章 0 订阅
2 篇文章 0 订阅

参考文献:《Unix网络编程》

一个输入操作通常包含两个阶段:

  1. 等待数据准备好。
  2. 内核向进程复制数据。

Unix下有5种可用的I/O模型:

  • 阻塞式I/O;
  • 非阻塞式I/O;
  • I/O复用;
  • 信号驱动式I/O;
  • 异步I/O(POSIX的aio_系列函数);

以下例子中,我们把recvfrom函数视为系统调用(以区分进程和内核)。不论它如何实现(在源自Berkeley的内核上是作为系统调用,在System V内核上是作为调用系统调用getmsg的函数),一般都会从进程空间运行切换到内核空间运行,再切换回来。


阻塞式I/O

阻塞式I/O
如图,进程调用recvfrom,该系统调用知道数据报到达且被复制到应用进程的缓冲区中,或者发生错误(如:被信号中断)才返回。


非阻塞式I/O

把一个套接字设置成非阻塞式,是在通知内核:当所请求的I/O操作非得把本金诚投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误
非阻塞式I/O
前三次调用recvfrom时没有数据可返回,因此内核转而返回一个EWOULDBLOCK错误。直到第四次调用时数据准备完成,被复制到进程缓冲区,则recvfrom成功返回。


I/O复用模型

IO复用
我们可以调用selectpoll,阻塞在多个系统调用的某一个上,而不是阻塞在真正的具体一个I/O系统调用上


信号驱动式I/O

信号驱动式I/O
信号驱动式I/O模型是要求内核在描述符就绪时发送SIGIO信号通知使用者
具体来说,需要先开启套接字的信号驱动式IO功能,通过sigaction安装一个对应的信号处理函数。在本例中,我们可以考虑在信号处理函数中调用recvfrom读取数据,也可以通知主循环来发起读取动作。


异步I/O模型

异步IO
我们调用aio_read函数,给内核传递描述符、缓冲区指针、缓冲区大小与文件偏移,并告诉内核当整个操作完成时通知我们。也就是说,我们的进程不会被阻塞,直到数据已被复制到进程缓冲区
与之相对的是,信号驱动式IO,是由内核通知我们何时可以启动一个IO操作,而异步IO模型由内核通知我们IO操作何时完成


各种IO模型对比

POSIX定义同步/异步如下:

  • 同步I/O操作**导致请求进程阻塞,直到I/O操作完成;
  • 异步I/O操作不导致请求进程阻塞。

IO模型对比
根据定义,前4种模型,都在真正的I/O操作(recvfrom)时阻塞进程,只有第五种与POSIX定义的异步I/O匹配。


select函数

select函数声明如下:

int select(int maxfdp1, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
先从最后一个参数timeval结构说起
struct timeval {
    long tv_sec;     // seconds
    long tv_usec;    // microseconds
};

该参数有三种可能:

  1. 设置为空指针,即仅在有一个描述符准备好I/O时才返回,不然永远等待下去。
  2. 可能是等待一段固定时间,在有一个描述符准备好I/O时返回,但是不超过由该参数所指向的timeval 结构中指定的秒数和微秒数。
  3. 不等待,检查描述符后立即返回,这称为轮询(polling),为此,该参数必须指向一个timeval结构,而且其中的字段都必须是0。

前2种的往往有可能进程等待期间被被捕获的信号中断,并从信号处理函数返回。不同系统对被中断的select的是否自动重启不同,因此为了可移植性考虑,如果我们在捕获信号,那么必须做好select返回EINTR错误的准备。

如果该timeval结构的值超过1亿秒(3年多,虽然不太可能被使用),有些系统的select函数将以EINVAL错误失败返回。

中间三个参数readsetwritesetexceptset按字面意思就可以知道,分别表示要监视的可读、写、或异常的描述符。
如何给这三个参数的每一个指定一个或多个描述字值是一个设计上的问题。select使用描述符集,通常是一个整数数组,其中每个整数中的每一位对应一个描述字。举例来说,假设使用32位整数,那么该数组的第一个元素对应描述字0-31,第二个元素对应描述字32-63,以此类推。具体实现隐藏在fd_set的类型以及以下四个宏中:

void FD_ZERO(fd_set *fdset);    /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset);   /*turn on the bit for fd in fdset*/
void FD_CLR(int fd, fd_set *fdset);   /*turn off the bit for fd in fdset*/
int FD_ISSET(int fd, fd_set *fdset);   /*test if the bit for fd is turned on in fdset*/

对于其中不感兴趣的条件,可以将相应描述符集指针设置为空指针。如果三个指针都为空,则变成一个类似sleep的计数器。

我们讨论的每个描述符占用整数数组中的一位的策略仅仅是select可能的实现方式之一,但却是很常见的办法。后续可以看到poll函数使用另一个办法,一个可变长度的结构数组,每个结构代表一个描述符。

第一个参数maxfdp1指定待测试的描述符个数,它的值是待测试的最大描述符加1。这里有两个值得注意的点:

  1. 这里前半句“指定待测试的描述符个数”稍有不精确,maxfdp1是指描述符编号的最大值加1。例如我们需要观察1,4,5三个描述符,则maxfdp1是6(这是因为描述符从0开始计数,一共需要至少6bit来表示0-5号的描述符)。定义在<sys/select.h>中的FD_SETSIZE常数是fd_set中的描述符总数,通常是1024——不过很少有程序用到这么多描述符。maxfdp1参数迫使我们计算出所关心的最大的描述符并告知内核
  2. 在上一条中有所提及,maxfdp1是指描述符编号的最大值加1

由指针传入的readsetwritesetexceptset都是值-结果参数。传入函数以指定我们关心的描述符,返回后可以通过FD_ISSET来测试集合,如果该描述符未就绪,则被清为0,否则保持为1。也因此,每次重新调用select函数时,都得重新设置描述符集的各个位
select函数的返回值表示已就绪的描述符总个数。如果在任何描述符就绪前计时器耗尽,则返回0。另外返回-1表示出错(如被信号中断)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值