您的位置:首页 > 其它

select 实现分析 –1 【整理】

2013-05-09 18:55 288 查看
select源码结构图:











源码分析:

#undef __NFDBITS

#define __NFDBITS (8 * sizeof(unsigned long))

#undef __FD_SETSIZE

#define __FD_SETSIZE 1024

#undef __FDSET_LONGS

#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)

typedef struct {

unsigned longfds_bits [__FDSET_LONGS]; //1024个bit。

} __kernel_fd_set;

//由上可以看到可以支持1024个描述符

//系统调用(内核态)

//参数为 maxfd, r_fds, w_fds, e_fds, timeout。

asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp)

{

s64 timeout = -1;

struct timeval tv;

int ret;

//将超时时间换成jiffies

if (tvp) {

if (copy_from_user(&tv, tvp, sizeof(tv))) //将用户态参数拷贝到内核态

return -EFAULT;

if (tv.tv_sec < 0 || tv.tv_usec < 0)

return -EINVAL;

/* Cast to u64 to make GCC stop complaining */

if ((u64)tv.tv_sec >= (u64)MAX_INT64_SECONDS)

timeout = -1; /* infinite */

else {

timeout = ROUND_UP(tv.tv_usec, USEC_PER_SEC/HZ);

timeout += tv.tv_sec * HZ;

}

}

// (***) 调用 core_sys_select

ret = core_sys_select(n, inp, outp, exp, &timeout);

//将剩余时间拷贝回用户空间进程

if (tvp) {

struct timeval rtv;

if (current->personality & STICKY_TIMEOUTS) //判断当前环境是否支持修改超时时间(不确定)

goto sticky;

rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));

rtv.tv_sec = timeout;

if (timeval_compare(&rtv, &tv) >= 0)

rtv = tv;

if (copy_to_user(tvp, &rtv, sizeof(rtv))) {

sticky:

/*

* 如果应用程序将timeval值放在只读存储中,

* 我们不希望在成功完成select后引发错误(修改timeval)

* 但是,因为没修改timeval,所以我们不能重启这个系统调用。

*/

if (ret == -ERESTARTNOHAND)

ret = -EINTR;

}

}

return ret;

}

//主要的工作在这个函数中完成

staticint core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s64 *timeout)

{

fd_set_bits fds;

/* fd_set_bits 结构如下:

typedef struct {

unsigned long *in, *out, *ex;

unsigned long *res_in, *res_out, *res_ex;

} fd_set_bits;

这个结构体中定义的全是指针,这些指针都是用来指向描述符集合的。

*/

void *bits;

int ret, max_fds;

unsigned int size;

struct fdtable *fdt;

/* Allocate small arguments on the stack to save memory and be faster 先尝试使用栈(因为栈省内存且快速)*/

long stack_fds[SELECT_STACK_ALLOC/sizeof(long)]; // SELECT_STACK_ALLOC=256

ret = -EINVAL;

if (n < 0)

goto out_nofds;

/* max_fds can increase, so grab it once to avoid race */

rcu_read_lock(); //rcu锁

fdt = files_fdtable(current->files); //读取文件描述符表

/* struct fdtable 结构如下:

struct fdtable {

unsigned int max_fds;

struct file **fd;

...

};

*/

max_fds = fdt->max_fds; //从files结构中获取最大值(当前进程能够处理的最大文件数目)

rcu_read_unlock();

if (n > max_fds)// 如果传入的n大于当前进程最大的文件描述符,给予修正

n = max_fds;

/* 我们需要使用6倍于最大描述符的描述符个数,

* 分别是in/out/exception(参见fd_set_bits结构体),

* 并且每份有一个输入和一个输出(用于结果返回) */

size = FDS_BYTES(n);// 以一个文件描述符占一bit来计算,传递进来的这些fd_set需要用掉多少个字

bits = stack_fds;

if (size > sizeof(stack_fds) / 6) { // 除以6,因为每个文件描述符需要6个bitmaps上的位。

//栈不能满足,先前的尝试失败,只能使用kmalloc方式

/* Not enough space in on-stack array; must use kmalloc */

ret = -ENOMEM;

bits = kmalloc(6 * size, GFP_KERNEL);

if (!bits)

goto out_nofds;

}

//设置fds

fds.in = bits;

fds.out = bits + size;

fds.ex = bits + 2*size;

fds.res_in = bits + 3*size;

fds.res_out = bits + 4*size;

fds.res_ex = bits + 5*size;

// get_fd_set仅仅调用copy_from_user从用户空间拷贝了fd_set

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;

// 对这些存放返回状态的字段清0

zero_fd_set(n, fds.res_in);

zero_fd_set(n, fds.res_out);

zero_fd_set(n, fds.res_ex);

//执行do_select,完成监控功能

ret = do_select(n, &fds, timeout);

if (ret < 0) //有错误

goto out;

if (!ret) {// 超时返回,无设备就绪

ret = -ERESTARTNOHAND;

if (signal_pending(current))

goto out;

ret = 0;

}

if (set_fd_set(n, inp, fds.res_in) ||

set_fd_set(n, outp, fds.res_out) ||

set_fd_set(n, exp, fds.res_ex))

ret = -EFAULT;

out:

if (bits != stack_fds)

kfree(bits);

out_nofds:

return ret;

}

#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)

#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)

#define POLLEX_SET (POLLPRI)

int do_select(int n, fd_set_bits *fds, s64 *timeout)

{

struct poll_wqueues table;

/*

struct poll_wqueues {

poll_table pt;

struct poll_table_page *table;

struct task_struct *polling_task; //保存当前调用select的用户进程struct task_struct结构体

int triggered; // 当前用户进程被唤醒后置成1,以免该进程接着进睡眠

int error; // 错误码

int inline_index; // 数组inline_entries的引用下标

struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];

};

*/

poll_table *wait;

int retval, i;

rcu_read_lock();

//根据已经设置好的fd位图检查用户打开的fd, 要求对应fd必须打开, 并且返回最大的fd。

retval = max_select_fd(n, fds);

rcu_read_unlock();

if (retval < 0)

return retval;

n = retval;

/* 一些重要的初始化:

poll_wqueues.poll_table.qproc函数指针初始化,

该函数是驱动程序中poll函数(fop->poll)实现中必须要调用的poll_wait()中使用的函数。 */

poll_initwait(&table);

wait = &table.pt;

if (!*timeout)

wait = NULL; // 用户设置了超时时间为0

retval = 0;

for (;;) {

unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;

long __timeout;

set_current_state(TASK_INTERRUPTIBLE);

inp = fds->in; outp = fds->out; exp = fds->ex;

rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;

// 所有n个fd的循环

for (i = 0; i < n; ++rinp, ++routp, ++rexp) {

unsigned long in, out, ex, all_bits, bit = 1, mask, j;

unsigned long res_in = 0, res_out = 0, res_ex = 0;

const struct file_operations *f_op = NULL;

struct file *file = NULL;

// 先取出当前循环周期中的32(设long占32位)个文件描述符对应的bitmaps

in = *inp++; out = *outp++; ex = *exp++;

all_bits = in | out | ex;// 组合一下,有的fd可能只监测读,或者写,或者err,或者同时都监测

if (all_bits == 0) {

i += __NFDBITS; //如果这个字没有待查找的描述符, 跳过这个长字(32位,__NFDBITS=32),取下一个32个fd的循环中

continue;

}

// 本次32个fd的循环中有需要监测的状态存在

for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {

int fput_needed;

if (i >= n)

break;

if (!(bit & all_bits)) // bit每次循环后左移一位的作用在这里,用来跳过没有状态监测的fd

continue;

file = fget_light(i, &fput_needed);//得到file结构指针,并增加引用计数字段f_count

if (file) {// 如果file存在(这个文件描述符对应的文件确实打开了)

f_op = file->f_op;

mask = DEFAULT_POLLMASK;

if (f_op && f_op->poll) //这个文件对应的驱动程序提供了poll函数(fop->poll)。

mask = (*f_op->poll)(file, retval ? NULL : wait);//调用驱动程序中的poll函数。

/* 调用驱动程序中的poll函数,以evdev驱动中的evdev_poll()为例

* 该函数会调用函数poll_wait(file, &evdev->wait, wait),

* 继续调用__pollwait()回调来分配一个poll_table_entry结构体,

* 该结构体有一个内嵌的等待队列项,

* 设置好wake时调用的回调函数后将其添加到驱动程序中的等待队列头中。 */

fput_light(file, fput_needed); // 释放file结构指针,实际就是减小他的一个引用计数字段f_count。

//记录结果。poll函数返回的mask是设备的状态掩码。

if ((mask & POLLIN_SET) && (in & bit)) {

res_in |= bit; //如果是这个描述符可读, 将这个位置位

retval++; //返回描述符个数加1

}

if ((mask & POLLOUT_SET) && (out & bit)) {

res_out |= bit;

retval++;

}

if ((mask & POLLEX_SET) && (ex & bit)) {

res_ex |= bit;

retval++;

}

}

/*

* cond_resched()将判断是否有进程需要抢占当前进程,

* 如果是将立即发生调度,这只是为了增加强占点。

* (给其他紧急进程一个机会去执行,增加了实时性)

* 在支持抢占式调度的内核中(定义了CONFIG_PREEMPT),

* cond_resched是空操作。

*/

cond_resched();

}

//返回结果

if (res_in)

*rinp = res_in;

if (res_out)

*routp = res_out;

if (res_ex)

*rexp = res_ex;

}

wait = NULL;

if (retval || !*timeout || signal_pending(current)) // signal_pending(current)检查当前进程是否有信号要处理

break;

if(table.error) {

retval = table.error;

break;

}

if (*timeout < 0) {

/* Wait indefinitely 无限期等待*/

__timeout = MAX_SCHEDULE_TIMEOUT;

} elseif (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) {

/* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in a loop */

__timeout = MAX_SCHEDULE_TIMEOUT - 1;

*timeout -= __timeout;

} else {

__timeout = *timeout;

*timeout = 0;

}

/* schedule_timeout 用来让出CPU;

* 在指定的时间用完以后或者其它事件到达并唤醒进程(比如接收了一个信号量)时,

* 该进程才可以继续运行 */

__timeout = schedule_timeout(__timeout);

if (*timeout >= 0)

*timeout += __timeout;

}

__set_current_state(TASK_RUNNING);

poll_freewait(&table);

return retval;

}

参考链接: /article/2889984.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: