I/O复用--select函数源码剖析
来源:互联网 发布:安全风险评估矩阵 编辑:程序博客网 时间:2024/06/06 14:08
一、select进入内核态之后就调用sys_select()
//n表示调用时的参数表中一共有多少个位图,即需要监听的文件描述符最大值,一般为最大值加1//fd_set表示已打开文件的位图,位图的每一位都代表着当前进程的一个已打开文件,根据其结构定义得知,select最多可以监听1024个文件描述符//tvp表示睡眠等待的最长时间,如果为0则表示立即返回,如果为NULL则表示阻塞等待,直到所监听的事件就绪asmlinkage long sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp){ //其中分别保存了3种位图的要求和结果 fd_set_bits fds; char *bits; long timeout; int ret, size; timeout = MAX_SCHEDULE_TIMEOUT; if (tvp) { time_t sec, usec; //将所需数据数据从用户空间拷贝到内核空间 if ((ret = verify_area(VERIFY_READ, tvp, sizeof(*tvp))) || (ret = __get_user(sec, &tvp->tv_sec)) || (ret = __get_user(usec, &tvp->tv_usec))) goto out_nofds; ret = -EINVAL; if (sec < 0 || usec < 0) goto out_nofds; if ((unsigned long) sec < MAX_SELECT_SECONDS) { timeout = ROUND_UP(usec, 1000000/HZ); timeout += sec * (unsigned long) HZ; } } ret = -EINVAL; if (n < 0) goto out_nofds; //判断文件描述符数量有没有超过最大值 if (n > current->files->max_fdset) n = current->files->max_fdset; /* * We need 6 bitmaps (in/out/ex for both incoming and outgoing), * since we used fdset we need to allocate memory in units of * long-words. */ ret = -ENOMEM; size = FDS_BYTES(n); //一共分配6个位图 bits = select_bits_alloc(size); //为要求和结果,一共6个位图初始化 if (!bits) goto out_nofds; fds.in = (unsigned long *) bits; fds.out = (unsigned long *) (bits + size); fds.ex = (unsigned long *) (bits + 2*size); fds.res_in = (unsigned long *) (bits + 3*size); fds.res_out = (unsigned long *) (bits + 4*size); fds.res_ex = (unsigned long *) (bits + 5*size); //将3个要求位图从用户空间复制到内核空间中的fds的要求位图 if ((ret = get_fd_set(n, inp, fds.in)) || (ret = get_fd_set(n, outp, fds.out)) || (ret = get_fd_set(n, exp, fds.ex))) goto out; //将内核空间的fds的结果位图初始化为0 zero_fd_set(n, fds.res_in); zero_fd_set(n, fds.res_out); zero_fd_set(n, fds.res_ex); // ret记录就绪事件的总数 ret = do_select(n, &fds, &timeout); if (tvp && !(current->personality & STICKY_TIMEOUTS)) { time_t sec = 0, usec = 0; if (timeout) { sec = timeout / HZ; usec = timeout % HZ; usec *= (1000000/HZ); } put_user(sec, &tvp->tv_sec); put_user(usec, &tvp->tv_usec); } if (ret < 0) goto out; if (!ret) { ret = -ERESTARTNOHAND; if (signal_pending(current)) goto out; ret = 0; } //将3个结果位图的内容复制到用户空间中 set_fd_set(n, inp, fds.res_in); set_fd_set(n, outp, fds.res_out); set_fd_set(n, exp, fds.res_ex);out: select_bits_free(bits, size); //释放要求和结果位图的6个位图的空间out_nofds: return ret; //返回就绪事件的总数}
static void *select_bits_alloc(int size){ return kmalloc(6 * size, GFP_KERNEL); //通过kmalloc分配6个位图}static void select_bits_free(void *bits, int size){ kfree(bits);}
struct timeval { time_t tv_sec; /* 秒数 seconds */ suseconds_t tv_usec; /* 微秒数 microseconds */};
//fd_set类型的定义#undef __NFDBITS//__NFDBITS的值为32#define __NFDBITS (8 * sizeof(unsigned long))#undef __FD_SETSIZE#define __FD_SETSIZE 1024#undef __FDSET_LONGS//__FDSET_LONGS的值为32#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)#undef __FDELT#define __FDELT(d) ((d) / __NFDBITS)#undef __FDMASK#define __FDMASK(d) (1UL << ((d) % __NFDBITS))//因此fd_set实际上是一个具有32个元素的unsigned long类型的数组typedef struct { unsigned long fds_bits [__FDSET_LONGS];} __kernel_fd_set;typedef __kernel_fd_set fd_set;
sys_select做了以下几件事情:
1、参数检查,对n进行判断,n的最大值就是当前进程所能打开的最大文件数量,一般情况下为1024。
2、用一个结构体保存用户传进来的参数,fd_set_bits结构,定义如下:
typedef struct { unsigned long *in, *out, *ex; unsigned long *res_in, *res_out, *res_ex; } fd_set_bits;
in,out,ex分别保存用户注册的感兴趣事件,而res_in,res_out,res_ex,分别保存这个文件描述符上的用户感兴趣的事情中已发生事件,当返回时会把res_in,res_out,res_ex中的值赋给in,out,ex,所以这里就有从用户空间到内核空间,内核空间到用户空间的拷贝问题,每次调用select时都会发生大量的重复来回拷贝问题,造成效率上的问题。
二、fd_set_bits保存了用户感兴趣的事件之后就要监听多个文件描述符了。把fd_set_bits当作参数调用do_select()函数做具体的文件描述符上的监听工作。
int do_select(int n, fd_set_bits *fds, long *timeout){ //poll_table类型结构在下面有解释说明 poll_table table, *wait; int retval, i, off; long __timeout = *timeout; read_lock(¤t->files->file_lock); //计算所监听的文件描述符在位图中的最大的序号是多少,高于这个序号的文件描述符都与本次操作无关 //max_select_fd定义在上面 retval = max_select_fd(n, fds); read_unlock(¤t->files->file_lock); if (retval < 0) return retval; n = retval; //当一个进程要进入睡眠,而想要某个设备的驱动程序在设备的状态发生变化时将其唤醒,就要准备一个wait_queue_t数据结构,并将这个数据结构挂入目标设备的某个等待队列中。而wait_queue_t就封装在poll_table类型结构中 //初始化poll_table类型结构变量,将table成员置为NULL,error成员置为0 poll_initwait(&table); wait = &table; if (!__timeout) wait = NULL; retval = 0; //进入for循环,直到所监听的事件就绪,或指定的睡眠等待时间到期,或者当前进程收到了信号时才会结束 for (;;) { set_current_state(TASK_INTERRUPTIBLE); //将当前进程的状态置为可中断阻塞,即当前进程将会进入浅睡眠状态 //内层for循环中,对所要监视的文件描述符对应的位图进行一次扫描 for (i = 0 ; i < n; i++) { unsigned long bit = BIT(i); unsigned long mask; struct file *file; off = i / __NFDBITS; //将文件描述符的个数i转化为位图中所对应的元素下标off if (!(bit & BITS(fds, off))) //如果三种位图的某一位为1,就对相应的文件描述符做一次询问 continue; file = fget(i); mask = POLLNVAL; //文件的具体询问方式和其类型有关,即是通过file_operations数据结构中的函数指针poll进行的。 if (file) { mask = DEFAULT_POLLMASK; if (file->f_op && file->f_op->poll) mask = file->f_op->poll(file, wait); fput(file); } //retval记录一共有几个事件就绪 //将询问的输入结果汇集到fds所指的fd_set_bits变量中 if ((mask & POLLIN_SET) && ISSET(bit, __IN(fds,off))) { SET(bit, __RES_IN(fds,off)); retval++; //记录一共有多少个事件就绪 wait = NULL; } //将询问的输出结果汇集到fds所指的fd_set_bits变量中 if ((mask & POLLOUT_SET) && ISSET(bit, __OUT(fds,off))) { SET(bit, __RES_OUT(fds,off)); retval++; //记录一共有多少个事件就绪 wait = NULL; } //将询问的异常结果汇集到fds所指的fd_set_bits变量中 if ((mask & POLLEX_SET) && ISSET(bit, __EX(fds,off))) { SET(bit, __RES_EX(fds,off)); retval++; //记录一共有多少个事件就绪 wait = NULL; } } wait = NULL; //对所有的文件描述符进行询问后,检查是否有事件就绪、睡眠等待时间超时、接收到了信号,如果有条件满足,就不会再进入睡眠状态,直接结束大循环 //统计好就绪事件后,此时retval不为0,从break跳出,结束大循环 if (retval || !__timeout || signal_pending(current)) break; //检查是否出错,如果出错,也不会进入睡眠状态,直接结束大循环 if(table.error) { retval = table.error; break; } //进入睡眠状态,被唤醒后再进行一次扫描询问 //除第一次以外,以后都是在进程被唤醒时才执行一遍循环,从本质上讲是一种do-while循环 __timeout = schedule_timeout(__timeout); } current->state = TASK_RUNNING; //设置当前进程的状态为运行态 //将所有进程对应的wait_queue_t结构从各个等待队列中删除 //poll_freewait定义在下面 poll_freewait(&table); /* * Up-to-date the caller timeout. */ *timeout = __timeout; return retval; //返回就绪事件的总数}
static int max_select_fd(unsigned long n, fd_set_bits *fds){ unsigned long *open_fds; unsigned long set; int max; /* handle last in-complete long-word first */ set = ~(~0UL << (n & (__NFDBITS-1))); n /= __NFDBITS; //将所监听的文件描述符的个数转化为位图的元素下标 open_fds = current->files->open_fds->fds_bits+n; max = 0; //记录最大的序号 if (set) { set &= BITS(fds, n); if (set) { if (!(set & ~*open_fds)) goto get_max; return -EBADF; } } while (n) { open_fds--; n--; set = BITS(fds, n); //判断在位图下标位n的元素中是否有要监听的文件描述符 if (!set) //如果位图中下标为n的元素中没有要监听的文件描述符,就继续寻找 continue; if (set & ~*open_fds) return -EBADF; if (max) //这里的最大序号可以理解为一个标志位 continue;get_max: do { max++; set >>= 1; } while (set); max += n * __NFDBITS; //再将位图中的元素序号转化为文件描述符对应的个数 } return max;}
do_select函数做了以下几件事情:
1、每次取一个long字节长度的fd过来,然后分别把他们的读写异常事件做逻辑或运算,如果不为0就说明此fd里面是用户感兴趣的事件对应的文件描述符,于是就调用该文件的poll方法,如果通过poll方法获取到的掩码为0就说明这个文件描述符上没有事件发生,直接执行下次循环,如果发生了,但是还不知道是读事件或写事件或异常事件,于是把1每次左移一位和这个fd做逻辑与运算,如果不为0就说明这个位对应的fd有感兴趣的事件发生,然后就拿这个分别去与用户传进来的in,out,ex做逻辑与运算就知道了,并且做相应的计数++。
2、如果第一次对这个文件描述符调用poll方法的话,调用时传递的参数wait就不为空,就需要将此进程挂入该文件的等待队列中,在file中有该文件的等待队列的头,此时就会调用刚刚在第一步的过程中在poll之前初始化一个poll_table的结构体。poll_table的定义:
typedef struct poll_table_struct { int error; struct poll_table_page * table;} poll_table;
三、在select、poll中被初始化为__pollwait(),当需要将该进程加入该文件的等待队列时就要调用这个函数。
void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p){ struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt); struct poll_table_page *table = p->table; if (!table || POLL_TABLE_FULL(table)) { struct poll_table_page *new_table; new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL); if (!new_table) { p->error = -ENOMEM; __set_current_state(TASK_RUNNING); return; } new_table->entry = new_table->entries; new_table->next = table; p->table = new_table; table = new_table; } /* Add a new entry */ { struct poll_table_entry * entry = table->entry; table->entry = entry+1; get_file(filp); entry->filp = filp; entry->wait_address = wait_address; init_waitqueue_entry(&entry->wait, current); add_wait_queue(wait_address,&entry->wait); }}
这个函数首先通过p根据其在poll_wqueue中的偏移量找到poll_wqueue结构体,然后通过这个结构体中的poll_table_page分配一个poll_tale_entry,而加入该文件的等待队列就是通过这个结构体实现的(由于监听的fd可能会很很多,不可能全部在堆栈上分配,因此就采用动态分配的方式,每次分配一页的大小,用单链表串起来,而一页当中有很多的poll_table_entry项)。
poll_table_entry定义如下:
struct poll_table_entry { struct file * filp; wait_queue_t wait; wait_queue_head_t * wait_address; }; /*wait_address中保存该文件的等待队列的头,wait就是一个等待队列的节点,包含进程的task_struct,唤醒的回调函数(如果该文件有事件到达就会调用该函数唤醒正在等待的进程,重新扫描一遍fd,如果计数不为0就从select中返回,并且还会释放poll_wqueue等数据结构)*///poll_wqueue定义如下:struct poll_wqueues{ poll_table pt; struct poll_table_page * table; int error; }; struct poll_table_page { struct poll_table_page * next; //entry总是指向entries中第一个空闲的poll_table_entry结构,根据需要动态的分配entries中的表项 struct poll_table_entry * entry; struct poll_table_entry entries[0]; };
- I/O复用--select函数源码剖析
- I/O复用:select()函数
- I/O复用select函数
- I/O复用:Select和Poll函数
- tcp,select函数支持I/O复用
- I/O复用 select和poll函数
- I/O复用:select和poll函数
- I/O复用------select
- I/O复用-select
- select函数:I/O多路复用
- I/O多路复用- select函数
- Linux I/O复用 —— epoll部分源码剖析
- 第6章:I/O复用:select和poll函数
- [UNP笔记]I/O复用,select和poll函数
- 第六章:I/O复用:select和poll函数
- 第六章:I/O复用2:select和poll函数
- 6 I/O复用:select and poll函数
- I/O复用:select和poll函数(一)
- opencv(c++)文件输入和输出使用XML和YAML文件
- 设计模式_中介者模式(25)
- 2017模拟赛 密室(多维最短路)
- base.css
- 6.1
- I/O复用--select函数源码剖析
- mysql表行列互换
- 我就是想发一下我的小站 哈哈哈
- 重磅来袭
- 动态规划问题数字三角形的(递归程序)
- Android开发-数据存储方式一
- HAProxy常用配置介绍,ACL详解
- css知识点
- codeforces C. Solution for Cube