一、系统调用select,把原来当前进程的单睡眠等待状态变成了现在的多睡眠等待状态。具体请看代码,select在内核中的实现为sys_select,代码如下:
asmlinkage long
sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)//inp,outp,exp是关于已打开文件的位图,tvp表明准备睡眠等待的最长时间,0表示无限期的睡眠等待,这些指针都指向了用户空间,详细解释请看下面
{
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)))//从用户空间拷贝timeval结构tvp到内核空间的变量
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
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);
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);
if ((ret = get_fd_set(n, inp, fds.in)) || //把3个"要求"位图从用户空间复制过来,复制到fds中
(ret = get_fd_set(n, outp, fds.out)) ||
(ret = get_fd_set(n, exp, fds.ex)))
goto out;
zero_fd_set(n, fds.res_in);//清零fds.res_in,也就是清零输出
zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex);
ret = do_select(n, &fds, &timeout);//操作的主体
if (tvp && !(current->personality & STICKY_TIMEOUTS)) {
time_t sec = 0, usec = 0;
if (timeout) {
sec = timeout / HZ;//timeout转换成sec,usec
usec = timeout % HZ;
usec *= (1000000/HZ);
}
put_user(sec, &tvp->tv_sec);//把sec,uesc,也就是剩余的时间返回给用户空间的timeval结构tvp
put_user(usec, &tvp->tv_usec);
}
if (ret < 0)
goto out;
if (!ret) {
ret = -ERESTARTNOHAND;
if (signal_pending(current))
goto out;
ret = 0;
}
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);
out_nofds:
return ret;
}
其中fd_set,是关于已打开文件的位图,位图中的每一个位都代表着当前进程的一个已打开文件。
typedef __kernel_fd_set fd_set;
typedef struct {
unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;
inp,outp,exp都是fd_set结构,都是关于文件的位图。inp所指的位图表示当前进程在睡眠中要等待来自哪一些已打开文件的输入,也就是要读取输入;
返回时则表明对哪些已打开文件中已经有了输入,可以读了。类似地,outp表示当前进程在睡眠中药等待对哪一些已打开文件的写操作;
返回时则表明对哪一些已打开文件的写操作已可立即进行。至于exp,则用来监视在哪一些通道中发生了异常。参数n表示调用时的参数表中有几个位图。
get_fd_set,把3个"要求"位图从用户空间复制过来,复制到fds中,代码如下:
static inline
int get_fd_set(unsigned long nr, void *ufdset, unsigned long *fdset)
{
nr = FDS_BYTES(nr);
if (ufdset) {
int error;
error = verify_area(VERIFY_WRITE, ufdset, nr);
if (!error && __copy_from_user(fdset, ufdset, nr))
error = -EFAULT;
return error;
}
memset(fdset, 0, nr);
return 0;
}
最重要的函数,就是do_select,代码如下:
int do_select(int n, fd_set_bits *fds, long *timeout)//fds是个指针,指向一个fd_set_bits结构,结构中就是6个工作位图,其中前3个为"要求"位图
{
poll_table table, *wait;
int retval, i, off;
long __timeout = *timeout;
read_lock(¤t->files->file_lock);
retval = max_select_fd(n, fds);//根据这3个位图计算出本次操作所涉及最大的已打开文件号是什么
read_unlock(¤t->files->file_lock);
if (retval < 0)
return retval;
n = retval;
poll_initwait(&table);//初始化poll_table结构table
wait = &table;//刚刚初始化的table的指针赋值给wait
if (!__timeout)
wait = NULL;
retval = 0;
for (;;) {//第一次循环
set_current_state(TASK_INTERRUPTIBLE);//设置当前进程为TASK_INTERRUPTIBLE
for (i = 0 ; i < n; i++) {
unsigned long bit = BIT(i);
unsigned long mask;
struct file *file;
off = i / __NFDBITS;
if (!(bit & BITS(fds, off)))//如果三个位图之一中的某一位为1,就对应的已打开文件作一次询问,#define BITS(fds, n) (*__IN(fds, n)|*__OUT(fds, n)|*__EX(fds, n))
continue;
file = fget(i);//获取文件
mask = POLLNVAL;
if (file) {
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll)//