poll机制源码深入剖析
文章目录
poll调用方法
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
各参数含义:pollfd为我们关心的fd数组,nfds是数组大小,timeout是等待的时间,如果是-1表示一直等待,如果是0表示立即返回。
返回值:
- 大于0:表示数组fds中有socket描述符的状态发生变化,或可以读取、或可以写入、或出错。并且返回的值表示这些状态有变化的文件描述符的总数量;此时可以对fds数组进行遍历,以寻找那些revents不空的文件描述符,然后判断这个里面有哪些事件以读取数据。
- 等于0:表示没有socket描述符有状态变化,并且调用超时。
- 小于0:此时表示有错误发生,此时全局变量errno保存错误码。
struct pollfd结构如下:【在源码文件poll.h文件中】
struct pollfd {
int fd; //文件描述符
short events; //关心事件
short revents; //活跃事件
};
poll实现原理
poll是采用了一个单向链表来存储我们需要关心的pollfd结构,其中采用__put_user和copy_from_uer完成pollfd结构体数组的复制。从用户复制了数组以后将其构成链表,然后对链表中的每个fd都调用其对应的驱动程序中file_operation的poll方法,该poll方法会调用poll_wait将进程或者线程放到该文件描述符的等待队列上(一个文件描述符有一个等待队列)。此时没有真正睡眠,一直等到每个监听的文件描述符都添加完了以后,才会进入睡眠,也就是说每个fd的等待队列上都会有一个当前的进程,任何一个fd有数据,都会唤醒当前进程。如果有数据到来,就会唤醒进程。将事件复制到用户区。
poll源码实现
应用程序调用poll函数
当app应用程序调用poll函数,会陷入系统调用,从而进入sys_poll
//ufds:待检测的fd数组集合,nfds:待检测的fd数组大小,timeout超时时间
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
int, timeout_msecs)
{
struct timespec end_time, *to = NULL;
int ret;
//有一些计算时间的代码,得到超时的时间节点放到to里面
ret = do_sys_poll(ufds, nfds, to);
//有一些错误处理,暂时先忽略了
if (ret == -EINTR) {
}
return ret;
}
真正工作的是 do_sys_poll(ufds, nfds, to);
重要的数据结构:
该结构是保存pollfds的链表,这个链表是按块分配的,其中next就是将各个链表块连接起来的。
struct poll_list {
struct poll_list *next;
int len;
struct pollfd entries[0];
};
struct poll_wqueues {
poll_table pt;
struct poll_table_page * table;
int error;
int inline_index;
struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
typedef struct poll_table_struct {
poll_queue_proc qproc;
} poll_table;
do_sys_poll复制用户区数据构建监听结构链表–>调用do_poll–>数据返回
//ufds:待检测的fd数组集合,nfds:待检测的fd数组大小,endtime超时的时间节点
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec *end_time)
{
struct poll_wqueues table;
int err = -EFAULT, fdcount, len, size;
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
//为了加快处理速度和提高系统性能,这里优先使用已经定好的一个栈空间,其大小为POLL_STACK_ALLOC,在我系统上,其值为256,大小为256个字节的栈空间转换为struct poll_list结构,以存储需要被检测的文件描述符
struct poll_list *const head = (struct poll_list *)stack_pps;
//walk存储了需要检测的文件描述符
struct poll_list *walk = head;
unsigned long todo = nfds;
if (nfds > rlimit(RLIMIT_NOFILE))
return -EINVAL;
//计算能存储多少个pollfd结构
len = min_t(unsigned int, nfds, N_STACK_PPS);
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
//把用户空间的fds复制到walk链表