参考:点击打开链接
公共结构
struct file {
const struct file_operations *f_op;
spinlock_t f_lock;
// 文件内部实现细节
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
// 其他细节....
};
// 文件操作
struct file_operations {
// 文件提供给poll/select/epoll
// 获取文件当前状态, 以及就绪通知接口函数
unsigned int (*poll) (struct file *, struct poll_table_struct *);
// 其他方法read/write 等... ...
};
// 通常的file.f_ops.poll 方法的实现
unsigned int file_f_op_poll (struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
wait_queue_head_t * wait_queue;
//1. 根据事件掩码wait->key_和文件实现filep->private_data 取得事件掩码对应的一个或多个wait queue head
some_code();
// 2. 调用poll_wait 向获得的wait queue head 添加节点
poll_wait(filp, wait_queue, wait);
// 3. 取得当前就绪状态保存到mask
some_code();
return mask;
}
// select/poll/epoll 向文件注册就绪后回调节点的接口结构
typedef struct poll_table_struct {
// 向wait_queue_head 添加回调节点(wait_queue_t)的接口函数
poll_queue_proc _qproc;
// 关注的事件掩码, 文件的实现利用此掩码将等待队列传递给_qproc
unsigned long _key;
} poll_table;
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
// 通用的poll_wait 函数, 文件的f_ops->poll 通常会调用此函数
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address) {
// 调用_qproc 在wait_address 上添加节点和回调函数
// 调用 poll_table_struct 上的函数指针向wait_address添加节点, 并设置节点的func
// (如果是select或poll 则是 __pollwait, 如果是 epoll 则是 ep_ptable_queue_proc),
p->_qproc(filp, wait_address, p);
}
}
// wait_queue 头节点
typedef struct __wait_queue_head wait_queue_head_t;
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
// wait_queue 节点 等待队列中存放的是在执行设备操作时不能获得资源而挂起的进程
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
unsigned int flags; /*指明等待的进程是互斥进程还是非互斥进程*/
#define WQ_FLAG_EXCLUSIVE 0x01
void *private; //通常指向当前任务控制块
wait_queue_func_t func;//唤醒阻塞任务的函数 ,决定了唤醒的方式
struct list_head task_list; // 阻塞任务链表
};
typedef struct __wait_queue_head wait_queue_head_t
flag:指明该等待的进程是互斥还是非互斥,为0时非互斥,为1时互斥;
WQ_FLAG_EXCLUSIVE :从变量可以看出,此宏代表进程是互斥的;
private:void型指针变量功能强大,你可以赋给它你需要的结构体指针。一般赋值为task_struct类型的指针,也就是说指向一个进程;
func:函数指针,指向该等待队列项的唤醒函数;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
3.在等待队列上睡眠
如何实现进程的阻塞?大致过程就是将当前进程的状态设置成睡眠状态,然后将这个进程加入到等待队列即可。在linux内核中有一组函数接口来实现这个功能。
4.唤醒函数
唤醒函数会唤醒以x为头结点的等待队列中的等待队列项所对应的进程。与睡眠函数类似,内核中也有一组函数可以对阻塞的进程进行唤醒。通常哪段代码促使等待条件达成,它就负责随后调用wake_up()函数。
// 当文件的状态发生改变时, 文件会调用此函数,此函数通过调用wait_queue_t.func通知poll的调用者
// 其中key是文件当前的事件掩码
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;
// 遍历并调用func 唤醒, 通常func会唤醒调用poll的线程
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) { //执行默认的唤醒函数,将指定的进程curr以mode方式唤醒。成功唤醒返回1;否则,返回0;
break;
}
}
}
关键结构体
下面是poll/select共用的结构体及其相关功能:
poll_wqueues 是 select/poll 对poll_table接口的具体化实现,其中的table, inline_index和inline_entries都是为了管理内存。
poll_table_entry 与一个文件相关联,用于管理插入到文件的wait_queue节点。
//每一个调用select()系统调用的应用进程都会存在一个struct poll_wqueues结构体
,用来统一辅佐实现这个进程中所有待监测的fd的轮询工作,后面所有的工作和都这个结构体有关,所以它非常重要。
struct poll_wqueues {
poll_table pt;
struct poll_table_page *table;// 如果inline_entries 空间不足, 从poll_table_page 中分配
struct task_struct *polling_task; //保存当前调用select或poll的用户进程struct task_struct结构体
int triggered; // 已触发标记
int error; // 错误码
int inline_index; // 下一个要分配的inline_entrie 索引
struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};
//实际上结构体poll_wqueues内嵌的poll_table_entry数组inline_entries[] 的大小是有限的,
如果空间不够用,后续会动态申请物理内存页以链表的形式挂载poll_wqueues.table上统一管理。接下来的两个结构体就和这项内容密切相关:
// 帮助管理select/poll 申请的内存
struct poll_table_page { // 申请的物理页都会将起始地址强制转换成该结构体指针
struct poll_table_page *next; // 指向下一个申请的物理页
struct poll_table_entry *entry; // 指向entries[]中首个待分配(空的) poll_table_entry地址
struct poll_table_entry entries[0]; // 该page页后面剩余的空间都是待分配的poll_table_entry结构体
};
//对每一个fd调用fop->poll() => poll_wait() => __pollwait()都会先从poll_wqueues.inline_entries[]
//中分配一个poll_table_entry结构体,直到该数组用完才会分配物理页挂在链表指针poll_wqueues.table上然后才会分配一个
//poll_table_entry结构体(poll_get_entry函数)。
//poll_table_entry具体用处:函数__pollwait声明如下:
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p);
该函数调用时需要3个参数,第一个是特定fd对应的file结构体指针,第二个就是特定fd对应的硬件驱动程序中的等待队列头指针,
第3个是调用select()的应用进程中poll_wqueues结构体的poll_table项(该进程监测的所有fd调用fop->poll函数都用这
一个poll_table结构体)。
struct poll_table_entry {
struct file *filp; // 指向特定fd对应的file结构体;
unsigned long key; // 等待特定fd对应硬件设备的事件掩码,如POLLIN、 POLLOUT、POLLERR;
wait_queue_t wait; // 代表调用select()的应用进程,等待在fd对应设备的特定事件 (读或者写)的等待队列头上,的等待队列项;
wait_queue_head_t *wait_address; // //内部有一个指针指向一个进程
wait_queue_head_t wait_address;//等待队列头部(等待队列有多个wait_queue_t组成,通过双链表连接)
};
![](http://www.embeddedlinux.org.cn/uploads/allimg/140511/1047250.jpg)
总结几点:
1. 特定的硬件设备驱动程序的事件等待队列头是有限个数的,通常是有读事件和写事件的等待队列头;
2. 而一个调用了select()的应用进程只存在一个poll_wqueues结构体;
3. 该应用程序可以有多个fd在进行同时监测其各自的事件发生,但该应用进程中每一个fd有多少个poll_table_entry存在,那就取决于fd对应的驱动程序中有几个事件等待队列头了,也就是说,通常驱动程序的poll函数中需要对每一个事件的等待队列头调用poll_wait()函数。比如,如果有读写两个等待队列头,那么就在这个应用进程中存在两个poll_table_entry结构体,在这两个事件的等待队列头中分别将两个等待队列项加入;
4. 如果有多个应用进程使用select()方式同时在访问同一个硬件设备,此时硬件驱动程序中加入等待队列头中的等待队列项对每一个应用程序来说都是相同数量的(一个事件等待队列头一个,数量取决于事件等待队列头的个数)。
poll和select的实现基本上是一致的,只是传递参数有所不同,他们的基本流程如下:
1. 复制用户数据到内核空间
2. 估计超时时间
3. 遍历每个文件并调用f_op->poll 取得文件当前就绪状态, 如果前面遍历的文件都没有就绪,向文件插入wait_queue节点
4. 遍历完成后检查状态:
a). 如果已经有就绪的文件转到5;
b). 如果有信号产生,重启poll或select(转到 1或3);
c). 否则挂起进程等待超时或唤醒,超时或被唤醒后再次遍历所有文件取得每个文件的就绪状态
5. 将所有文件的就绪状态复制到用户空间
6. 清理申请的资源
公共函数
下面是poll/select公用的一些函数,这些函数实现了poll和select的核心功能。
poll_initwait 用于初始化poll_wqueues,
__pollwait 实现了向文件中添加回调节点的逻辑,
pollwake 当文件状态发生改变时,由文件调用,用来唤醒线程,
poll_get_entry,free_poll_entry,poll_freewait用来申请释放poll_table_entry 占用的内存,并负责释放文件上的wait_queue节点。
// poll_wqueues 的初始化:
// 初始化 poll_wqueues , __pollwait会在文件就绪时被调用
void poll_initwait(struct poll_wqueues *pwq)
{
// 初始化poll_table, 相当于调用基类的构造函数
init_poll_funcptr(&pwq->pt, __pollwait);
/*
* static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
* {
* pt->_qproc = qproc;
* pt->_key = ~0UL;
* }
*/
pwq->polling_task = current;
pwq->triggered = 0;
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
// wait_queue设置函数
// poll/select 向文件wait_queue中添加节点的方法
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);
struct poll_table_entry *entry = poll_get_entry(pwq);
if (!entry) {
return;
}
get_file(filp); //put_file() in free_poll_entry()
entry->filp = filp;
entry->wait_address = wait_address; // 等待队列头
entry->key = p->key;
// 设置回调为 pollwake
init_waitqueue_func_entry(&entry->wait, pollwake);
entry->wait.private = pwq;
// 添加到等待队列
add_wait_queue(wait_address, &entry->wait);
}
// 在等待队列(wait_queue_t)上回调函数(func)
// 文件就绪后被调用,唤醒调用进程,其中key是文件提供的当前状态掩码
static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
struct poll_table_entry *entry;
// 取得文件对应的poll_table_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);
}
static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
struct poll_wqueues *pwq = wait->private;
// 将调用进程 pwq->polling_task 关联到 dummy_wait
DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);
smp_wmb();
pwq->triggered = 1;// 标记为已触发
// 唤醒调用进程
return default_wake_function(&dummy_wait, mode, sync, key);
}
// 默认的唤醒函数,poll/select 设置的回调函数会调用此函数唤醒
// 直接唤醒等待队列上的线程,即将线程移到运行队列(rq)
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
void *key)
{
// 这个函数比较复杂, 这里就不具体分析了
return try_to_wake_up(curr->private, mode, wake_flags);
}
/* The fd_set member is required to be an array of longs. */
typedef long int __fd_mask;
/* Some versions of <linux/posix_types.h> define this macros. */
#undef __NFDBITS
/* It's easier to assume 8-bit bytes than to get CHAR_BIT. */
#define __NFDBITS (8 * (int) sizeof (__fd_mask)) //32
#define __FD_ELT(d) ((d) / __NFDBITS)
#define __FD_MASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS))
/* fd_set for select and pselect. */
typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];// __FD_SETSIZE=1024 __NFDBITS=32
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;
/*
sys_select(fs/select.c)
处理了超时值(如果有),将struct timeval转换成了时钟周期数,调用core_sys_select,然后检查剩余时间,处理时间
*/
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;
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; /* 无限等待*/
else {
timeout = DIV_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)/*模拟bug的一个机制,不详细描述*/
goto sticky;
/*rtv中是剩余的时间*/
rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));
rtv.tv_sec = timeout;
if (timeval_compare(&rtv, &tv) >= 0)/*如果core_sys_select超时返回,更新时间*/
rtv = tv;
/*拷贝更新后的时间到用户空间*/
if (copy_to_user(tvp, &rtv, sizeof(rtv))) {
sticky:
/*
* If an application puts its timeval in read-only
* memory, we don't want the Linux-specific update to
* the timeval to cause a fault after the select has
* completed successfully. However, because we're not
* updating the timeval, we can't restart the system
* call.
*/
if (ret == -ERESTARTNOHAND)/*ERESTARTNOHAND表明,被中断的系统调用*/
ret = -EINTR;
}
}
return ret;
}
/*core_sys_select
为do_select准备好了位图,然后调用do_select,将返回的结果集,返回到用户空间
*/
static int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, s64 *timeout)
{
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 */
/*SELECT_STACK_ALLOC 定义为256*/
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)/*修正用户传入的第一个参数:fd_set中文件描述符的最大值*/
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.
*/
/*
如果stack_fds数组的大小不能容纳下所有的fd_set,就用kmalloc重新分配一个大数组。
然后将位图平均分成份,并初始化fds结构
*/
size = FDS_BYTES(n);
bits = stack_fds;
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;
/*get_fd_set仅仅调用copy_from_user从用户空间拷贝了fd_set*/
if ((ret = get_fd_set(n, inp, fds.in)) || //(1)select慢的第一个原因,fdset每次都要从用户空间拷贝到内核空间
(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);
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;
/*do_select返回,是一种异常状态*/
if (!ret) {
/*记得上面的sys_select不?将ERESTARTNOHAND转换成了EINTR并返回。EINTR表明系统调用被中断*/
ret = -ERESTARTNOHAND;
if (signal_pending(current))/*当当前进程有信号要处理时,signal_pending返回真,这符合了EINTR的语义*/
goto out;
ret = 0;
}
/*把结果集,拷贝回用户空间*/
if (set_fd_set(n, inp, fds.res_in) ||(1)然后又从内核空间拷贝到用户空间
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);/*对应上面的kmalloc*/
out_nofds:
return ret;
}
/*do_select
真正的select在此,遍历了所有的fd,调用对应的xxx_poll函数
*/
int do_select(int n, fd_set_bits *fds, s64 *timeout)
{
struct poll_wqueues table;
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;
/*将当前进程放入自已的等待队列table, 并将该等待队列加入到该测试表wait*/
//函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。
//void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p)
//这个函数首先通过p根据其在poll_wqueue中的偏移量找到poll_wqueue结构体,然后通过这个结构
//体中的poll_table_page分配一个poll_table_entry,而加入该文件的等待队列就是通过这个结构体实现的
//这个里面它wait_address中保存该文件的等待队列的头,wait就是一个等待队列的节点,包含进程
//的task_struct,唤醒的回调函数(这里使用默认的回调函数),如果该文件的有事件到达就会调用该函数,
//唤醒正在等待的进程,重新扫描一遍fd,如果计数不为零就从select中返回了,并且还会释放这里的poll_wqueue等数据结构,
poll_initwait(&table);poll_initwait
wait = &table.pt;
if (!*timeout)
wait = NULL;
retval = 0;
for (;;) {/*死循环*/(2)slect慢的原因,每次调用都要轮询
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;
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {/*遍历所有fd*/
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;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
if (all_bits == 0) {
/*
__NFDBITS定义为(8 * sizeof(unsigned long)),即long的位数。
因为一个long代表了__NFDBITS位,所以跳到下一个位图i要增加__NFDBITS
*/
i += __NFDBITS;
continue;
}
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
int fput_needed;
if (i >= n)
break;
/*测试每一位*/
if (!(bit & all_bits))
continue;
/*得到file结构指针,并增加引用计数字段f_count*/
file = fget_light(i, &fput_needed);
if (file) {
f_op = file->f_op;
mask = DEFAULT_POLLMASK;
/*对于socket描述符,f_op->poll对应的函数是sock_poll
注意第三个参数是等待队列,在poll成功后会将本进程唤醒执行*/
//调用该文件的poll方法(每个文件的类型都对应有相应的文件操作,这个操作
//在file->f_op中的poll方法被赋予不同的值,例如管道文件的poll方法就被
//赋值为pipe_poll())
if (f_op && f_op->poll)
mask = (*f_op->poll)(file, retval ? NULL : wait);
/*释放file结构指针,实际就是减小他的一个引用计数字段f_count*/
fput_light(file, fput_needed);
/*根据poll的结果设置状态,要返回select出来的fd数目,所以retval++。
注意:retval是in out ex三个集合的总和*/
//如果通过poll方法获取到的掩码为零就说明这个文件描述上没有事件发生,直接执行下次的循环,
//如果发生了,但是还不知道是读或者写或者异常中的哪一些,于是就拿1每次左移动以为和这个long长
//度的fd分别做逻辑与运算,如果不为零就说明这个位对应的fd有感兴趣的事件发生,然后就拿这个l
//分别去和用户传进来的in,out,ex做逻辑与运算就知道了,并且做相应的计数++
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
}
}
/*
注意前面的set_current_state(TASK_INTERRUPTIBLE);
因为已经进入TASK_INTERRUPTIBLE状态,所以cond_resched回调度其他进程来运行,
这里的目的纯粹是为了增加一个抢占点。被抢占后,由等待队列机制唤醒。
在支持抢占式调度的内核中(定义了CONFIG_PREEMPT),cond_resched是空操作
*/
cond_resched();
}
/*根据poll的结果写回到输出位图里*/
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前面说过了*/
break;
if(table.error) {
retval = table.error;
break;
}
if (*timeout < 0) {
/*无限等待*/
__timeout = MAX_SCHEDULE_TIMEOUT;
} else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) {
/* 时间超过MAX_SCHEDULE_TIMEOUT,即schedule_timeout允许的最大值,用一个循环来不断减少超时值*/
__timeout = MAX_SCHEDULE_TIMEOUT - 1;
*timeout -= __timeout;
} else {
/*等待一段时间*/
__timeout = *timeout;
*timeout = 0;
}
/*TASK_INTERRUPTIBLE状态下,调用schedule_timeout的进程会在收到信号后重新得到调度的机会,
即schedule_timeout返回,并返回剩余的时钟周期数
*/
__timeout = schedule_timeout(__timeout);//当前进程睡眠timeout个jiffies, 在指定的时间到期后(timeout了)将进程唤醒,然后又继续轮询
if (*timeout >= 0)
*timeout += __timeout;
}
/*设置为运行状态*/
__set_current_state(TASK_RUNNING);
/*清理等待队列*/
poll_freewait(&table);
return retval;
}
static unsigned int sock_poll(struct file *file, poll_table *wait)
{
struct socket *sock;
/*约定socket的file->private_data字段放着对应的socket结构指针*/
sock = file->private_data;
/*对应了三个协议的函数tcp_poll,udp_poll,datagram_poll,其中udp_poll几乎直接调用了datagram_poll
累了,先休息一下,这三个函数以后分析*/
return sock->ops->poll(file, sock, wait);
}
poll
// poll 使用的结构体
struct pollfd {
int fd; // 描述符
short events; // 关注的事件掩码
short revents; // 返回的事件掩码
};
// long sys_poll(struct pollfd *ufds, unsigned int nfds, long timeout_msecs)
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
long, timeout_msecs)
{
struct timespec end_time, *to = NULL;
int ret;
if (timeout_msecs >= 0) {
to = &end_time;
// 将相对超时时间msec 转化为绝对时间
poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
}
// do sys poll
ret = do_sys_poll(ufds, nfds, to);
// do_sys_poll 被信号中断, 重新调用, 对使用者来说 poll 是不会被信号中断的.
if (ret == -EINTR) {
struct restart_block *restart_block;
restart_block = ¤t_thread_info()->restart_block;
restart_block->fn = do_restart_poll; // 设置重启的函数
restart_block->poll.ufds = ufds;
restart_block->poll.nfds = nfds;
if (timeout_msecs >= 0) {
restart_block->poll.tv_sec = end_time.tv_sec;
restart_block->poll.tv_nsec = end_time.tv_nsec;
restart_block->poll.has_timeout = 1;
} else {
restart_block->poll.has_timeout = 0;
}
// ERESTART_RESTARTBLOCK 不会返回给用户进程,
// 而是会被系统捕获, 然后调用 do_restart_poll,
ret = -ERESTART_RESTARTBLOCK;
}
return ret;
}
这个函数还是比较容易理解,包括三个部分的工作:
- 函数调用超时阻塞时间转换,根据内核的软时钟设置频率将超时时间设置为jiffies标准时间。
- 调用do_sys_poll,这里完成主要的工作。
- 如果当前进程有待处理的信号,则先处理信号,这是根据do_sys_poll返回来决定的,事实上在这个调用中会检查当前的进程是否有未处理信号,如果有,就会返回EINTR以处理信号,然后返回-ERESTART_RESTARTBLOCK,这会导致重新调用。
do_sys_poll函数
struct poll_list {
struct poll_list *next;
int len;
struct pollfd entries[0];
};
上面可以看到该结构的entries为一个数组,结构为struct pollfd,这个有点眼熟,没错,它就是存储poll调用中需要被检测的socket描述符。那么前面分配的栈空间能存储多少个struct pollfd呢?这计算如下:
len = min_t(unsigned int, nfds, N_STACK_PPS);
#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \
sizeof(struct pollfd))
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec *end_time)
{
struct poll_wqueues table;
int err = -EFAULT, fdcount, len, size;
/* 首先使用栈上的空间,节约内存,加速访问 */
//为了加快处理速度和提高系统性能,这里优先使用已经定好的一个栈空间,其大小为POLL_STACK_ALLOC,
//其值为256,大小为256个字节的栈空间转换为struct poll_list结构,以存储需要被检测的socket描述符
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
unsigned long todo = nfds;
if (nfds > rlimit(RLIMIT_NOFILE)) {
// 文件描述符数量超过当前进程限制
return -EINVAL;
}
// 复制用户空间数据到内核
len = min_t(unsigned int, nfds, N_STACK_PPS); //栈大小和nfds的最小值
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len) {
break;
}
// 复制到当前的 entries
if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len)) {
goto out_fds;
}
todo -= walk->len;
if (!todo) { //拷贝完了
break;
}
// 栈上空间不足,在堆上申请剩余部分
len = min(todo, POLLFD_PER_PAGE);
size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
walk = walk->next = kmalloc(size, GFP_KERNEL);
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}
//设置poll_table结构中的qproc函数指针为__pollwait函数,就是pwq->pt->qproc=__pollwait。
//这个函数是一个回调函数,基本上这种机制的实现,就是依靠回调函数了。
poll_initwait(&table);
// poll
fdcount = do_poll(nfds, head, &table, end_time);
// 从文件wait queue 中移除对应的节点, 释放entry.
poll_freewait(&table);
// 复制结果到用户空间
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;
for (j = 0; j < len; j++, ufds++)
if (__put_user(fds[j].revents, &ufds->revents)) {
goto out_fds;
}
}
err = fdcount;
out_fds:
// 释放申请的内存
walk = head->next;
while (walk) {
struct poll_list *pos = walk;
walk = walk->next;
kfree(pos);
}
return err;
}
// 真正的处理函数
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, struct timespec *end_time)
{
poll_table* pt = &wait->pt;
ktime_t expire, *to = NULL;
int timed_out = 0, count = 0;
unsigned long slack = 0;
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
// 已经超时,直接遍历所有文件描述符, 然后返回
pt = NULL;
timed_out = 1;
}
if (end_time && !timed_out) {
// 估计进程等待时间,纳秒
slack = select_estimate_accuracy(end_time);
}
// 遍历文件,为每个文件的等待队列添加唤醒函数(pollwake)
for (;;) {
struct poll_list *walk;
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end;
pfd = walk->entries;
pfd_end = pfd + walk->len;
for (; pfd != pfd_end; pfd++) {
// do_pollfd 会向文件对应的wait queue 中添加节点
// 和回调函数(如果 pt 不为空)
// 并检查当前文件状态并设置返回的掩码
if (do_pollfd(pfd, pt)) {
// 该文件已经准备好了.
// 不需要向后面文件的wait queue 中添加唤醒函数了.
count++;
pt = NULL;
}
}
}
// 下次循环的时候不需要向文件的wait queue 中添加节点,
// 因为前面的循环已经把该添加的都添加了
pt = NULL;
// 第一次遍历没有发现ready的文件
if (!count) {
count = wait->error;
// 有信号产生
if (signal_pending(current)) {
count = -EINTR;
}
}
// 有ready的文件或已经超时
if (count || timed_out) {
break;
}
// 转换为内核时间
if (end_time && !to) {
expire = timespec_to_ktime(*end_time);
to = &expire;
}
// 等待事件就绪, 如果有事件发生或超时,就再循
// 环一遍,取得事件状态掩码并计数,
// 注意此次循环中, 文件 wait queue 中的节点依然存在
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) {
timed_out = 1;
}
}
return count;
}
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
unsigned int mask;
int fd;
mask = 0;
fd = pollfd->fd;
if (fd >= 0) {
int fput_needed;
struct file * file;
// 取得fd对应的文件结构体
file = fget_light(fd, &fput_needed);
mask = POLLNVAL;
if (file != NULL) {
// 如果没有 f_op 或 f_op->poll 则认为文件始终处于就绪状态.
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll) {
if (pwait) {
// 设置关注的事件掩码
pwait->key = pollfd->events | POLLERR | POLLHUP;
}
// 注册回调函数,并返回当前就绪状态,就绪后会调用pollwake
mask = file->f_op->poll(file, pwait);
}
mask &= pollfd->events | POLLERR | POLLHUP; // 移除不需要的状态掩码
fput_light(file, fput_needed);// 释放文件
}
}
pollfd->revents = mask; // 更新事件状态
return mask;
}
static long do_restart_poll(struct restart_block *restart_block)
{
struct pollfd __user *ufds = restart_block->poll.ufds;
int nfds = restart_block->poll.nfds;
struct timespec *to = NULL, end_time;
int ret;
if (restart_block->poll.has_timeout) {
// 获取先前的超时时间
end_time.tv_sec = restart_block->poll.tv_sec;
end_time.tv_nsec = restart_block->poll.tv_nsec;
to = &end_time;
}
ret = do_sys_poll(ufds, nfds, to); // 重新调用 do_sys_poll
if (ret == -EINTR) {
// 又被信号中断了, 再次重启
restart_block->fn = do_restart_poll;
ret = -ERESTART_RESTARTBLOCK;
}
return ret;
}