在网络编程中,经常用到selec系统调用来判断套接字上是否存在数据可读,或者能否向一个套接字写入数据。其原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
Note: select,poll和epoll监听的fd一定是需要实现poll函数,例如ext3,xfs这些文件系统都没有实现poll函数,所以这些文件系统中被打开的文件是不能使用select,poll和epoll函数。
想要彻底理解清楚select的原理,首先需要清楚一些数据结构
#define __FD_SETSIZE 1024
typedef __kernel_fd_set fd_set;
//fd_set其实就是一个unsigned long数组,默认数组长度是1024/8*8=16,每一个bit位都代表一个fd是否被监听
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;
Linux为了让用户方便的操作fd_set,提供了一系列的macro
FD_CLR( s, *set) //从set中删除句柄s;
FD_ISSET( s, *set) //检查句柄s是否存在与set中;
FD_SET( s, *set ) //把句柄s添加到set中;
FD_ZERO( *set ) //把set队列初始为空.
#define __FD_SET(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 6] |= (1<<((fd) & 63))) //设置对应的bit
#define __FD_CLR(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 6] &= ~(1<<((fd) & 63))) //清除对应的bit
#define __FD_ISSET(fd, fdsetp) ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 6] & (1<<((fd) & 63))) != 0) //判断对应的bit是否为1
#define __FD_ZERO(fdsetp) (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp)))) //重置bitmap
我们先举个比较简单的demo,根据这个demo来分析select这个系统调用
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
fd_set rd;
struct timeval tv;
int err;
FD_ZERO(&rd);
//设置标准输入的fd到fd_set
FD_SET(0,&rd);
tv.tv_sec = 5;
tv.tv_usec = 0;
err = select(1,&rd,NULL,NULL,&tv);
if(err == 0) //超时
{
printf("select time out!\n");
}
else if(err == -1) //失败
{
printf("fail to select!\n");
}
else //成功
{
printf("data is available!\n");
}
return 0;
}
select系统调用的源码在select.c中
sys_select
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, 1, ret);
return ret;
}
core_sys_select
int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timespec *end_time)
{
fd_set_bits fds;
void *bits;
int ret, max_fds;
unsigned int size;
struct fdtable *fdt;
/* Allocate small arguments on the stack to save memory and be faster */
/*预先分配固定长度的数组,这个数组最后用来存in_set,out_set,ex_set,res_in_set,res_out_set,res_ex_set。
但是如果预分配的内存不足以存放,会重新使用kmalloc来分配*/
long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
ret = -EINVAL;
if (n < 0)
goto out_nofds;
/* max_fds can increase, so grab it once to avoid race */
rcu_read_lock();
fdt = files_fdtable(current->files);
//获取当前进程打开文件的最大文件描述符
max_fds = fdt->max_fds;
rcu_read_unlock();
if (n > max_fds)
n = max_fds;
/*
* 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.
*/
size = FDS_BYTES(n);
bits = stack_fds;
/*如果6个位图的大小没有超过预分配的,就使用预分配的数组,否则重新分配内存空间*/
if (size > sizeof(stack_fds) / 6) {
/* Not enough space in on-stack array; must use kmalloc */
ret = -ENOMEM;
bits = kmalloc(6 * size, GFP_KERNEL);
if (!bits)
goto out_nofds;
}
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;
/*将用户态传递过来的三个fd_set赋值给fds.in,fds.out和fds.ex*/
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;
/*将三个response fd_set置为0,等待后面操作进行相关置位*/
zero_fd_set(n, fds.res_in);
zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex);
//核心函数
ret = do_select(n, &fds, end_time);
if (ret < 0)
goto out;
if (!ret) {
ret = -ERESTARTNOHAND;
if (signal_pending(current))
goto out;
ret = 0;
}
/*将response fd_set返回给用户态,从这里可以知道,在select()返回后,
只有对应fd有读事件或写事件发生的时候,相关的bit位才会被置1,
其他bit位都是0,所以每次select返回后,都要在用户态程序中重新设置fd_set,即监听哪些fd*/
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;
}
do_select
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
ktime_t expire, *to = NULL;
/*poll_wqueues table这个变量在下面的三个for循环,
每次调用poll的时候,都会从table->inline_entries中获取一个entry,
来记录当前等待队列的对头和wait,并将wait挂在等待队列上*/
struct poll_wqueues table;
poll_table *wait;
int retval, i, timed_out = 0;
unsigned long slack = 0;
rcu_read_lock();
/*返回设置的fd中最大值+1,同时会检查设置的fd对应的文件是否有打开,
因为在用户态设置的fd并不一定是打开的,如果检查到有设置的fd对应的文件
没有打开,那么select就会直接返回错误,同时这里返回max(fd)+1,
有人会说n就是最大的fd+1么,但是用户态的用户不一定传下来的就是max(fd)+1*/
retval = max_select_fd(n, fds);
rcu_read_unlock();
if (retval < 0)
return retval;
n = retval;
/*这里就是设置poll_wait调用的回调函数,其实还是有必要看下
poll_wqueues结构体的*/
poll_initwait(&table);
/*wait会传递poll(),在poll函数中,调用poll_wait(),
poll_wait()主要就是执行上面设置的回调函数*/
wait = &table.pt;
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
wait->_qproc = NULL;
timed_out = 1;
}
if (end_time && !timed_out)
slack = select_estimate_accuracy(end_time);
retval = 0;
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
inp = fds->in; outp = fds->out; exp = fds->ex;
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
/*这层循环,每次拿出来一个unsigned long,前面介绍过,最多也就
16个unsigned long*/
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;
in = *inp++; out = *outp++; ex = *exp++;
/*如果这次循环取出来unsigned long,都没有在in,out,ex中
设置bit位,那么直接跳过,取下一个unsigned long*/
all_bits = in | out | ex;
if (all_bits == 0) {
i += BITS_PER_LONG;
continue;
}
/*这层循环是把每一个unsigned long的每一位取出来,然后查看是否有监听,如果有监听,就调用对应文件的poll()方法*/
for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
struct fd f;
if (i >= n)
break;
/*检查当前的这一bit是否有文件监听,如果没有则跳过*/
if (!(bit & all_bits))
continue;
f = fdget(i);
if (f.file) {
const struct file_operations *f_op;
f_op = f.file->f_op;
mask = DEFAULT_POLLMASK;
if (f_op && f_op->poll) {
/*由于in,out,ex只要有一个fd_set对应的bit位置为1,就会执行poll(),但是我们不知道这个fd到底需要监听什么类型的事件,
这里就是利用wait_key_set来设置监听什么类型的事件*/
wait_key_set(wait, in, out, bit);
/*执行对应文件的poll()*/
mask = (*f_op->poll)(f.file, wait);
}
fdput(f);
/*如果执行的poll函数返回有对应的事件的mask,就会将其在res bitmap中置位,同时将retval计数器累加*/
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
/*当检查到一个fd对应有事件发生,就把回调函数kill掉,因为没有必要再
设置了,在循环完,就会直接退出,select并不会睡眠了,也减少了后边
free的开销*/
wait->_qproc = NULL;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
wait->_qproc = NULL;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
wait->_qproc = NULL;
}
}
}
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
/*每次执行完一个unsigned long的轮询,就调用这个函数,让更高优先级的进程得到运行的机会*/
cond_resched();
}
/*这里把回调函数置为null,因为如果不置null,在下次执行poll的时候,
依然会执行_poll_wait,把waiter挂在到等待队列上,那样就会出现同样
一个fd,挂载2次waiter到同一个等待队列上*/
wait->_qproc = NULL;
/*如果retval不为0说明已经产生了对应的事件,或者超时了,或者有信号需要处理,则会退出当前的循环*/
if (retval || timed_out || signal_pending(current))
break;
if (table.error) {
retval = table.error;
break;
}
/*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
if (end_time && !to) {
expire = timespec_to_ktime(*end_time);
to = &expire;
}
/*睡眠在这里*/
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
to, slack))
timed_out = 1;
}
/*这个函数也很重要,在最后返回的时候,需要将挂载在等待队列里waiter给移除掉*/
poll_freewait(&table);
return retval;
}
接下来,我会拿一个比较简单的字符设备poll函数来举例,说明poll函数调用的过程。
/*mem设备描述结构体*/
struct mem_dev
{
char *data;
unsigned long size;
wait_queue_head_t inq;
};
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
/*从用户空间写入数据*/
if (copy_from_user(dev->data + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
have_data = true; /* 有新的数据可读 */
/* 唤醒读进程 */
wake_up(&(dev->inq));
return ret;
}
unsigned int mem_poll(struct file *filp, poll_table *wait)
{
struct mem_dev *dev = filp->private_data;
unsigned int mask = 0;
/*执行对应的wait->_qproc*/
poll_wait(filp, &dev->inq, wait);
/*判断当前是否有数据可读,如果有,返回对应的mask*/
if (have_data) mask |= POLLIN | POLLRDNORM; /* readable */
return mask;
}
poll_wait
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
/*判断回调函数是否存在以及等待队列是否存在,如果存在执行回调函数,对应select的回调函数是
__pollwait*/
if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p);
}
__pollwait
struct poll_table_entry {
struct file *filp;
unsigned long key;
wait_queue_t wait;
wait_queue_head_t *wait_address;
};
/*
* Structures and helpers for select/poll syscall
*/
struct poll_wqueues {
poll_table pt;
struct poll_table_page *table;
struct task_struct *polling_task;
int triggered;
int error;
int inline_index;
struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
/*获取一个poll_table_entry,如果inline_entries已经使用完,则会另外分配空间*/
struct poll_table_entry *entry = poll_get_entry(pwq);
if (!entry)
return;
entry->filp = get_file(filp);
entry->wait_address = wait_address;
entry->key = p->_key;
/*设置唤醒回调函数*/
init_waitqueue_func_entry(&entry->wait, pollwake);
entry->wait.private = pwq;
/*将wait挂在等待队列上*/
add_wait_queue(wait_address, &entry->wait);
}
所以在使用select的时候,如果没有指定timeout的话,在执行do_select函数的时候,会主动poll所有监听的fd,如果在第一次poll的时候,没有任何事件发生,那么用户态进程会睡眠,在有新的事件到来的时候,一般在write函数中使用wake_up()来唤醒等待队列上所有的waiter,即执行waiter的唤醒函数(pollwake),睡眠的进程会被唤醒,然后接着执行for循环,接着主动调用poll函数,来查看是否有对应的事件产生。所以可以看出来,select函数是一直在轮询,即使对应fd上产生了事件,将进程环境后,依然要再主动调用poll函数,来查看产生了什么事件。
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
void __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags, void *key)
{
wait_queue_t *curr, *next;
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
/*在__pollwait中设置了func为pollwake*/
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
struct poll_table_entry *entry;
entry = container_of(wait, struct poll_table_entry, wait);
if (key && !((unsigned long)key & entry->key))
return 0;
/*唤醒用户态进程*/
return __pollwake(wait, mode, sync, key);
}