1. 简介:
本文将基于内核2.6.32版本,了解select运作的原理。
2. 数据结构:
select基于位图的,因此对于三种事件(读写超时)的位图集合都是使用unsigned long的数组来表示。
__kernel_fd_set就是fd_set,其内在就是一个数组,每个unsigned long在不同机器上表示长度不同,总大小1024,写死在内核,因此如果修改select监听的总fd数需要重新编译内核。
这里出现的几个宏比较常用到,__NFDBITS表示一个unsigned long能表示多少个位,__FD_SETSIZE表示监听的fd数量,__FD_SET_LONGS表示unsigned long数组的大小。对应FD的事件置位只要将相应的位图置位即可。
select中监听的fd应该都是fd号小于1024的,位图的使用都是直接使用fd号当做位图的下标进行处理的,如果fd号大于1024可能会导致异常。
#undef __NFDBITS
#define __NFDBITS (8 * sizeof(unsigned long))
#undef __FD_SETSIZE
#define __FD_SETSIZE 1024
#undef __FDSET_LONGS
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)
#undef __FDELT
#define __FDELT(d) ((d) / __NFDBITS)
#undef __FDMASK
#define __FDMASK(d) (1UL << ((d) % __NFDBITS))
typedef struct {
unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;
3. select 实现
函数系统调用的入口在fs/select.c中,系统调用处主要是对超时时间的处理,从用户空间拷贝到内核空间,并检查其合法性。主要流程在core_sys_select。函数poll_select_copy_remaining主要对结果数据进行处理,把数据从内核空间拷贝回用户空间。
SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
fd_set __user *, exp, struct timeval __user *, tvp)
{
struct timespec end_time, *to = NULL;
struct timeval tv;
int ret;
if (tvp) {
if (copy_from_user(&tv, tvp, sizeof(tv)))
return -EFAULT;
to = &end_time;
/* 检查超时的合法性。 */
if (poll_select_set_timeout(to,
tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
return -EINVAL;
}
/* 实际处理函数。 */
ret = core_sys_select(n, inp, outp, exp, to);
/* 数据后处理。 */
ret = poll_select_copy_remaining(&end_time, tvp,