Poll源码剖析
Posted ukernel
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Poll源码剖析相关的知识,希望对你有一定的参考价值。
Poll
函数接口
#include<poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
struct pollfd{
int fd;
short events; //注册的事件
short revents; //实际发生的事件
};
基本操作
- 注意事项
fds
是描述符数组,nfds
是监听描述符的个数,timeout
是以毫秒为单位的超时值- 返回事件发生的描述符的总数。
- 使用结束后,用户对刚刚传入的描述符数组进行轮询测试,看看那个pollfd上的事件已经改变了。
- POLLHUP和POLLHUP不管有没有设置都是默认监听的。
- 事件类型
- POLLIN:数据可读
- POLLRDNORM:普通数据可读
- POLLRDBAND:优先数据可读(Linux不支持)
- POLLPRI:高级有限数据可读(带外数据)
- POLLOUT:数据可写
- POLLWRNORM:普通数据可写
- POLLWRBAND:优先数据可写
- POLLRDHUP:TCP连接被对方关闭,或者对方关闭了写操作
- POLLERR:错误,要使用getsocketopt进行清除
- POLLHUP:挂起,比如该描述符上的写端被关闭,该端的描述符将收到该事件
- POLLNVAL:文件描述符没有打开
- 错误标志:
- EBADF:一个结构体或者多个结构体中存在文件描述符
- EFAULT:fds指向的地址超出了进程地址空间
- EINTR:在请求事件发生时收到一个信号,可以重新发起调用
- EINVAL:nfds参数超出了进程的软限制
ENOMEM:可用内存不足,无法完成请求
性能分析
- 时间角度:
- 使用了两层for循环:其一是管理时间的循环,可以在在有事件发生或者定时器到期的条件下退出循环
- 在循环之前会将进程的状态设置为TASK_INTERRUPTIBLE:进程会等待定时事件的发生,之后退出时正常
- 空间角度:
- 从用户的地址空间中将
struct pollfd
逐个拷贝进内核空间,构造成链表,然后进行轮训。有一个高级技术,使用链表混合使用堆与栈的空间。不过逐个将struct pollfd
拷贝进内核地址空间,可能有点效率影响。 内核为每一个关注的事件都会存在一个
struct pollfd
,与同一个fd存储在一个链表中,也就是说,链表的总长度= 文件描述符个数 × 关注的事件个数
。将发生的事件从内核空间拷贝出来的时候也是逐个与拷贝。源码分析
- 从用户的地址空间中将
基础数据结构:
struct poll_list { struct poll_list *next; int len; struct pollfd entries[0]; };
通用函数:
#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / sizeof(struct pollfd)) #define min_t(type,x,y) ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; }) #define max_t(type,x,y) ({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
Order 0:
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs)
:接受用户空间传入的指针,经过拷贝后在内核空间进行操作asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs) { s64 timeout_jiffies; //获得毫秒级的准确度 int ret; if (timeout_msecs > 0) { //根据传入的参数对数据进行处理 #if HZ > 1000 /* We can only overflow if HZ > 1000 */ if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ) timeout_jiffies = -1; else #endif timeout_jiffies = msecs_to_jiffies(timeout_msecs); } else { /* Infinite (< 0) or no (0) timeout */ timeout_jiffies = timeout_msecs; } ret = do_sys_poll(ufds, nfds, &timeout_jiffies); //做实际的操作 if (ret == -EINTR) { //如果被中断(也就是现实的告诉用户可以重复的进行调用),进行善后处理 struct restart_block *restart_block; restart_block = ¤t_thread_info()->restart_block; restart_block->fn = do_restart_poll; restart_block->arg0 = (unsigned long)ufds; restart_block->arg1 = nfds; restart_block->arg2 = timeout_jiffies & 0xFFFFFFFF; restart_block->arg3 = (u64)timeout_jiffies >> 32; ret = -ERESTART_RESTARTBLOCK; } return ret; //返回有事件发生的描述符的个数 }
Order 1:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout) { struct poll_wqueues table; //获得poll等待队列的操作 int err = -EFAULT, fdcount, len, size; //在还没有将用户空间的数据拷贝进来,将标志设置为默认出错 long stack_pps[POLL_STACK_ALLOC/sizeof(long)]; //在栈上分配一点空间,POLL_STACK_ALLOC = 256 struct poll_list *const head = (struct poll_list *)stack_pps; //使用链表将描述pollfd的数据结构 struct poll_list *walk = head; unsigned long todo = nfds; if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur) //如果注册的描述符大小超过当前的进程的软资源限制,直接返回错误标志 return -EINVAL; len = min_t(unsigned int, nfds, N_STACK_PPS); //获取用户传入struct pollfd的个数 for (;;) { //nb,挨个处理pollfd,混合栈空间和堆空间。。被秀了一脸。。 walk->next = NULL; walk->len = len; if (!len) //如果用户传入的len有0个,退出循环 break; if (copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len)) //一次从user空间中拷贝一个struct pollfd goto out_fds; todo -= walk->len; if (!todo) //如果拷贝完成了,进行处理 break; len = min(todo, POLLFD_PER_PAGE); //牛逼,竟然把栈空间和堆空间混合起来用了 size = sizeof(struct poll_list) + sizeof(struct pollfd) * len; walk = walk->next = kmalloc(size, GFP_KERNEL); if (!walk) { err = -ENOMEM; goto out_fds; } } poll_initwait(&table); //初始化等待链表 fdcount = do_poll(nfds, head, &table, timeout); //获得发生事件的描述符个数 poll_freewait(&table); //释放等待链表 for (walk = head; walk; walk = walk->next) { //轮训所有描述符的链表 struct pollfd *fds = walk->entries; //获得fds数组 int j; for (j = 0; j < walk->len; j++, ufds++) //每个描述符上观察的事件拷贝到用户空间,逐个字节拷贝 if (__put_user(fds[j].revents, &ufds->revents)) goto out_fds; } err = fdcount; out_fds: walk = head->next; //对之前分配的堆空间进行处理 while (walk) { struct poll_list *pos = walk; walk = walk->next; kfree(pos); } return err; }
Order 2:
static int do_poll(unsigned int nfds, struct poll_list *list,struct poll_wqueues *wait, s64 *timeout)
:在链表组成的pollfd上进行操作static int do_poll(unsigned int nfds, struct poll_list *list,struct poll_wqueues *wait, s64 *timeout) { int count = 0; poll_table* pt = &wait->pt; if (!(*timeout)) //如果时间为0,也就是说只轮训一遍不管有没有事件发生都直接返回 pt = NULL; for (;;) { struct poll_list *walk; long __timeout; set_current_state(TASK_INTERRUPTIBLE); //设置当前进程的状态 for (walk = list; walk != NULL; walk = walk->next) { //循环pollfd所在的链表 struct pollfd * pfd, * pfd_end; pfd = walk->entries; pfd_end = pfd + walk->len; //获得pollfd的描述 for (; pfd != pfd_end; pfd++) { if (do_pollfd(pfd, pt)) { //调用文件系统例程,对pfd进行处理 count++; pt = NULL; } } } pt = NULL; if (!count) { //如果没有事件发生 count = wait->error; if (signal_pending(current)) count = -EINTR; //将设置为可以重复调用的状态 } if (count || !*timeout) //如果有事件发生或者事件用尽,打破循环,直接返回 break; if (*timeout < 0) { //和select一样对设置时间的处理 __timeout = MAX_SCHEDULE_TIMEOUT; } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) __timeout = MAX_SCHEDULE_TIMEOUT - 1; *timeout -= __timeout; } else { __timeout = *timeout; *timeout = 0; } __timeout = schedule_timeout(__timeout); if (*timeout >= 0) *timeout += __timeout; } __set_current_state(TASK_RUNNING); //将进程设置为正常的状态 return count; //返回准备就绪的文件描述符的个数 }
Order 3:
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
:调用文件系统例程对pollfd进行处理static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait) { unsigned int mask; int fd; mask = 0; fd = pollfd->fd; if (fd >= 0) { int fput_needed; struct file * file; file = fget_light(fd, &fput_needed); mask = POLLNVAL; if (file != NULL) { mask = DEFAULT_POLLMASK; if (file->f_op && file->f_op->poll) mask = file->f_op->poll(file, pwait); //文件系统例程 mask &= pollfd->events | POLLERR | POLLHUP; fput_light(file, fput_needed); } } pollfd->revents = mask; return mask; }
以上是关于Poll源码剖析的主要内容,如果未能解决你的问题,请参考以下文章