select
特点:监听少量fd时,效率还可以,大量fd时,性能急剧下降。
部分源码浅析
select 是系统调用,在\kernel\fs\select.c 有实现
原型SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp, fd_set __user *, exp, struct timeval __user *, tvp)
性能下降的原因
在select方法,inp outp exp进出都需要位图,对应fds.in fds.out fds.ex fds.res_in fds.res_out fds.res_ex。
当select根据它所监听的fd的多少决定使用栈内存和堆内存:
core_sys_select(n, inp, outp, exp, to);
里面在栈上分配一个数组 long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
SELECT_STACK_ALLOC 是 256
大小相当于是8(bit) * sizeof(long) * SELECT_STACK_ALLOC/sizeof(long) = 2048(bit),
供fds.in使用(这样看来,它监听的fd最多为2048-1个?)
因为需要6个位图,而传进来的fd_set,也就是seletc监控的fd,当fd的大小,
大于stack_fds的1/6时,其它5个位图(fds.out fds.ex fds.res_in fds.res_out fds.res_ex) ,
就开始申请使用堆内存,加上之后循环遍历,导致性能会急剧下降
poll
与 select 的情况相似,而且传进去的参数较少
函数原型 SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
int, timeout_msecs)
核心是用poll_list链表保存所有正在等待事件的进程和它们对应的文件描述符。
每个节点包含了一个指向进程的指针和一个指向文件描述符的指针。
当内核检测到某个文件描述符有事件发生时,就会遍历这个链表,找到相关的进程,并通知它们。
代码遍历fd的过程比select精简了不少,但是依旧不理想(最坏算法复杂度O(n),而且监听fd数量也是有限制的,源码如下:
if (nfds > rlimit(RLIMIT_NOFILE))
return -EINVAL;
len = min_t(unsigned int, nfds, N_STACK_PPS);
由 select pool引发的思考:
是否每一次都需要把IO全部copy进内核 ?
每一次检测,是否需要循环遍历 ?
====> epool模式
epoll
当有大量(成千上万)fd需要监听,这个时候,最好的选择就是使用epoll了,因为监听fd的底层数据结构是红黑树,算法复杂度O(log n)。
在有大量fd需要监听时,查找节点的效率得到极大的提高,相对于 select 和 poll就优势明显。
3个重要的方法
epool_create(n); //n>0即可,最早的版本是要规定大小,现在的版本内部的数据机构是链表。
epoll_ctl 设置要监听的fd(包括server_id、client_id),根据传进去的参数可以增加,也可以删除监听的fd。
epoll_wait,每隔多长时间检测一下epoll里面的消息。
。。。更新中。。。