I/O复用技术是:把我们关注的描述符组成一个描述符表(通常不止一个描述符),调用I/O复用函数(select/poll/epoll),当描述符表中有可进行非阻塞I/O操作的描述符时,复用函数返回;否则阻塞复用函数,直到描述符表中有可进行非阻塞I/O操作的描述符出现时,才唤醒进程继续执行复用函数;当复用函数正常返回时,就可以知道哪些描述符可进行非阻塞I/O操作。
I/O复用的描述符通常包括:终端/伪终端,pipes,socket等
I/O复用函数主要过程:
1.遍历描述符表,判断该描述符表中是否有描述符可进行非阻塞I/O操作(读、写、异常等);
2.如果描述符表中有描述符可进行非阻塞I/O操作,I/O复用函数通知用户进程这些描述符;
3.如果描述符表中没有描述符可进行非阻塞I/O操作,那么I/O复用函数被阻塞,并将进程添加到描述符表中所有描述符的poll等待队列中
4.当有描述符可进行非阻塞I/O操作时,内核唤醒该描述符poll等待队列中的阻塞进程;进程唤醒后继续执行I/O复用函数,I/O复用函数将进程从描述符表中所有描述符的poll等待队列中移除;然后重新遍历描述符表
I.poll
poll是I/O复用函数之一,其原型为:
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
39 /* Data structure describing a polling request. */
40 struct pollfd
41 {
42 int fd; /* File descriptor to poll. */
43 short int events; /* Types of events poller cares about. */
44 short int revents; /* Types of events that actually occurred. */
45 };
输入参数:
fds:描述符及事件组成的pollfd数组
nfds:pollfd数组大小
timeout:超时时间,单位为毫秒
输出参数:
1、pollfd.events中发生的事件pollfd.revents
返回值:
>0:pollfd.events事件发生的文件描述符的个数
0:超时,所有文件描述中pollfd.events事件都未发生
-1:错误返回
poll与select功能近似,差异主要是:
1.poll的事件更为精确,select的事件只能为POLLIN_SET,POLLOUT_SET,POLLEX_SET,而这三个事件均是由poll事件组成的集合;
2.poll的描述符表没有大小限制;select描述符表最大不能超过FD_SET_SIZE(fd_set类型的比特位数)
3.poll的描述符及事件不用每次调用poll都重新赋值(输入为pollfd.events,输出为pollfd.revents),但是select每次调用都必须重新赋值(输入输出均为readfds,writefds,errorfds);
II.数据结构
i.poll_list
694 struct poll_list {
695 struct poll_list *next;
696 int len;
697 struct pollfd entries[0];
698 };
808 #define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \
809 sizeof(struct pollfd))
poll_list主要用于将pollfd数据存储在内核空间中,内存取自内核堆栈和slab;只有内核堆栈预分配给pollfd的内存使用完(pollfd数据大于N_STACK_PPS)后才会从slab中获取额外的内存
ii.poll_list之间关系图
iii.poll_wqueues
33 typedef struct poll_table_struct {
34 poll_queue_proc qproc;
35 unsigned long key;
36 } poll_table;
50 struct poll_table_entry {
51 struct file *filp;
52 unsigned long key;
53 wait_queue_t wait;
54 wait_queue_head_t *wait_address;
55 };
56
57 /*
58 * Structures and helpers for sys_poll/sys_poll
59 */
60 struct poll_wqueues {
61 poll_table pt;
62 struct poll_table_page *table;
63 struct task_struct *polling_task;
64 int triggered;
65 int error;
66 int inline_index;
67 struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
68 };
poll_table:对每个文件进行poll操作时,判断是否能够非阻塞的进行key值(poll事件组成)标识的I/O操作;如果不能,调用回调函数qproc将进程添加到文件的poll等待队列中
poll_table_entry:用于阻塞进程并将进程添加到文件的poll等待队列中,一个文件对应一个poll_table_entry
poll_wqueues:用于在select/poll时,如果需要阻塞进程,将进程添加到描述符表标识的所有文件的poll等待队列中,以便任意一个文件可进行非阻塞I/O操作时唤醒进程
iv.进程、打开文件、poll等待队列之间关系图
III.复用函数阻塞/唤醒
i.poll_wqueues的初始化
44 static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
45 {
46 pt->qproc = qproc;
47 pt->key = ~0UL; /* all events enabled */
48 }
116 void poll_initwait(struct poll_wqueues *pwq)
117 {
118 init_poll_funcptr(&pwq->pt, __pollwait);
119 pwq->polling_task = current;
120 pwq->triggered = 0;
121 pwq->error = 0;
122 pwq->table = NULL;
123 pwq->inline_index = 0;
124 }
1.将阻塞回调函数设置成__pollwait
2.将阻塞进程设置成当前进程
ii.文件poll阻塞
1.poll阻塞
当对单个文件执行poll操作时,如果文件不能非阻塞的进行key标识的I/O操作,会将当前进程添加到该文件的poll等待队列中
tcp阻塞f_op->poll:socket_file_ops->sock_poll->inet_stream_ops->tcp_poll->sock_poll_wait->poll_wait
pipe阻塞f_op->poll