1. 原理
2.1 select系统调用
从select
的系统调用开始:
-
select
系统调用,最终的核心逻辑是在do_select
函数中处理的,参考fs/select.c
文件; -
do_select
函数中,有几个关键的操作:-
初始化
poll_wqueues
结构,包括几个关键函数指针的初始化,用于驱动中进行回调处理; -
循环遍历监测的文件描述符,并且调用
f_op->poll()
函数,如果有监测条件满足,则会跳出循环; -
在监测的文件描述符都不满足条件时,
poll_schedule_timeout
让当前进程进行睡眠,超时唤醒,或者被所属的等待队列唤醒;
-
-
do_select
函数的循环退出条件有三个:-
检测的文件描述符满足条件;
-
超时;
-
有信号要处理;
-
-
在设备驱动程序中实现的
poll()
函数,会在do_select()
中被调用,而驱动中的poll()
函数,需要调用poll_wait()
函数,poll_wait
函数本身很简单,就是去回调函数p->_qproc()
,这个回调函数正是poll_initwait()
函数中初始化的__pollwait()
;
所以,来看看__pollwait()
函数喽。
2.2 __pollwait
-
驱动中的
poll_wait
函数回调__pollwait
,这个函数完成的工作是向struct poll_wqueue
结构中添加一条poll_table_entry
; -
poll_table_entry
中包含了等待队列的相关数据结构; -
对等待队列的相关数据结构进行初始化,包括设置等待队列唤醒时的回调函数指针,设置成
pollwake
; -
将任务添加到驱动程序中的等待队列中,最终驱动可以通过
wake_up_interruptile
等接口来唤醒处理;
这一顿操作,其实就是驱动向select
维护的struct poll_wqueue
中注册,并将调用select
的任务添加到驱动的等待队列中,以便在合适的时机进行唤醒。所以,本质上来说,这是基于等待队列的机制来实现的。
是不是还有点抽象,来看看数据结构的组织关系吧。
2.3 数据结构关系
-
调用
select
系统调用的进程/线程,会维护一个struct poll_wqueues
结构,其中两个关键字段:-
pll_table
:该结构体中的函数指针_qproc
指向__pollwait
函数; -
struct poll_table_entry[]
:存放不同设备的poll_table_entry
,这些条目的增加是在驱动调用poll_wait->__pollwait()
时进行初始化并完成添加的;
-
资源注册监听poll() -> poll_wait(struct file *, wait_queue_t *, poll_table *pt) -> pt->qproc(struct file *, wait_queue_t *, poll_table *)
资源就绪通知callback_function(wait_queue_t *, unsigned mode, int sync, void *key)
select/poll主要数据结构
一个select()/poll()调用对应一个struct poll_wqueues
一个监听事件对应一个struct poll_table_entry
2.4 驱动编写启示
如果驱动中要支持select
的接口调用,那么需要做哪些事情呢?如果理解了上文中的内容,你会毫不犹豫的大声说出以下几条:
-
定义一个等待队列头
wait_queue_head_t
,用于收留等待队列任务; -
struct file_operations
结构体中的poll
函数需要实现,比如xxx_poll()
; -
xxx_poll()
函数中,当然不要忘了poll_wait
函数的调用了,此外,该函数的返回值mask
需要注意是在条件满足时对应的值,比如EPOLLIN/EPOLL/EPOLLERR
等,这个返回值是在do_select()
函数中会去判断处理的; -
条件满足的时候,
wake_up_interruptible
唤醒任务,当然也可以使用wake_up
,区别是:wake_up_interruptible
只能唤醒处于TASK_INTERRUPTIBLE
状态的任务,而wake_up
能唤醒处于TASK_INTERRUPTIBLE
和TASK_UNINTERRUPTIBLE
状态的任务;
2.5 select/poll
的差异
-
select
与poll
本质上基本类似,其中select
是由BSD UNIX
引入,poll
由SystemV
引入; -
select
与poll
需要轮询文件描述符集合,并在用户态和内核态之间进行拷贝,在文件描述符很多的情况下开销会比较大,select
默认支持的文件描述符数量是1024; -
Linux提供了
epoll
机制,改进了select
与poll
在效率与资源上的缺点,未深入了解;
3. 示例代码
3.1 内核驱动
示例代码中的逻辑:
-
驱动维护一个count值,当count值大于0时,表明条件满足,poll返回正常的mask值;
-
poll函数每执行一次,count值就减去一次;
-
count的值可以由用户通过
ioctl
来进行设置;
#include <linux/init.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <asm/ioctl.h>
#define POLL_DEV_NAME "poll"
#define POLL_MAGIC 'P'
#define POLL_SET_COUNT (_IOW(POLL_MAGIC, 0, unsigned int))
struct poll_dev {
struct cdev cdev;
struct class *class;
struct device *device;
wait_queue_head_t wq_head;
struct mutex poll_mutex;
unsigned int count;
dev_t devno;
};
struct poll_dev *g_poll_dev = NULL;
static int poll_open(struct inode *inode, struct file *filp)
{
filp->private_data = g_poll_dev;
return 0;
}
static int poll_close(struct inode *inode, struct file *filp)
{
return 0;
}
static unsigned int poll_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct poll_dev *dev = filp->private_data;
mutex_lock(&dev->poll_mutex);
poll_wait(filp, &dev->wq_head, wait);
if (dev->count > 0) {
mask |= POLLIN | POLLRDNORM;
/* decrease each time */
dev->count--;
}
mutex_unlock(&dev->poll_mutex);
return mask;
}
static long poll_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct poll_dev *dev = filp->private_data;
unsigned int cnt;
switch (cmd) {
case POLL_SET_COUNT:
mutex_lock(&dev->poll_mutex);
if (copy_from_user(&cnt, (void __user *)arg, _IOC_SIZE(cmd))) {
pr_err("copy_from_user fail:%d\n", __LINE__);
return -EFAULT;
}
if (dev->count == 0) {
wake_up_interruptible(&dev->wq_head);
}
/* update count */
dev->count += cnt;
mutex_unlock(&dev->poll_mutex);
break;
default:
return -EINVAL;
}
return 0;
}
static struct file_operations poll_fops = {
.owner = THIS_MODULE,
.open = poll_open,
.release = poll_close,
.poll = poll_poll,
.unlocked_ioctl = poll_ioctl,
.compat_ioctl = poll_ioctl,
};
static int __init poll_init(void)
{
int ret;
if (g_poll_dev == NULL) {
g_poll_dev = (struct poll_dev *)kzalloc(sizeof(struct poll_dev), GFP_KERNEL);
if (g_poll_dev == NULL) {
pr_err("struct poll_dev allocate fail\n");
return -1;
}
}
/* allocate device number */
ret = alloc_chrdev_region(&g_poll_dev->devno, 0, 1, POLL_DEV_NAME);
if (ret < 0) {
pr_err("alloc_chrdev_region fail:%d\n", ret);
goto alloc_chrdev_err;
}
/* set char-device */
cdev_init(&g_poll_dev->cdev, &poll_fops);
g_poll_dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&g_poll_dev->cdev, g_poll_dev->devno, 1);
if (ret < 0) {
pr_err("cdev_add fail:%d\n", ret);
goto cdev_add_err;
}
/* create device */
g_poll_dev->class = class_create(THIS_MODULE, POLL_DEV_NAME);
if (IS_ERR(g_poll_dev->class)) {
pr_err("class_create fail\n");
goto class_create_err;
}
g_poll_dev->device = device_create(g_poll_dev->class, NULL,
g_poll_dev->devno, NULL, POLL_DEV_NAME);
if (IS_ERR(g_poll_dev->device)) {
pr_err("device_create fail\n");
goto device_create_err;
}
mutex_init(&g_poll_dev->poll_mutex);
init_waitqueue_head(&g_poll_dev->wq_head);
return 0;
device_create_err:
class_destroy(g_poll_dev->class);
class_create_err:
cdev_del(&g_poll_dev->cdev);
cdev_add_err:
unregister_chrdev_region(g_poll_dev->devno, 1);
alloc_chrdev_err:
kfree(g_poll_dev);
g_poll_dev = NULL;
return -1;
}
static void __exit poll_exit(void)
{
cdev_del(&g_poll_dev->cdev);
device_destroy(g_poll_dev->class, g_poll_dev->devno);
unregister_chrdev_region(g_poll_dev->devno, 1);
class_destroy(g_poll_dev->class);
kfree(g_poll_dev);
g_poll_dev = NULL;
}
module_init(poll_init);
module_exit(poll_exit);
MODULE_DESCRIPTION("select/poll test");
MODULE_AUTHOR("LoyenWang");
MODULE_LICENSE("GPL");
3.2 测试代码
测试代码逻辑:
-
创建一个设值线程,用于每隔2秒来设置一次count值;
-
主线程调用
select
函数监听,当设值线程设置了count值后,select便会返回;
1. Common
poll机制是所有多路转接的共性;调用控制块(struct poll_wqueues)与监听事件项(struct poll_table_entry)是select()与poll()的共性;事件描述集(fdset)与事件描述符(struct pollfd)是select()与poll()的特性。
/*
* Structures and helpers for sys_poll/sys_poll
*/
struct poll_wqueues {
poll_table pt;
struct poll_table_page *table;
/*
* 每次select()都会初始化一个poll_wqueues结构与这个
* 调用相对应。结构中的polling_task字段指向调用进程
* (也就是current进程)的task_struct
*
* 资源等待队列节点wait_queue_t中的private字段指向
* 对应的poll_wqueues对象。因此资源就绪时,通过获得
* poll_wqueues对象,然后访问其中的polling_task字段
* 能够得到调用进程的PCB,然后对其进行唤醒...
*/
struct task_struct *polling_task;
/* 已触发标记 */
int triggered;
int error;
int inline_index;
/* poll_table_entry结构缓存数组 */
struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};
typedef struct poll_table_struct {
poll_queue_proc qproc;
unsigned long key;
} poll_table;
struct poll_table_page {
struct poll_table_page * next;
/* 指向下一可用的poll_table_entry结构 */
struct poll_table_entry * entry;
struct poll_table_entry entries[0];
};
/* 真正被挂载到资源等待队列中的结构 */
struct poll_table_entry {
/* 链接到资源文件 */
struct file *filp;
/* 存储监听的事件 */
unsigned long key;
/*
* 挂载到资源等待队列的节点,其中包含了唤醒回调函数
*
* 节点的private成员关联poll_table_entry所属的poll_wqueues,
*/
wait_queue_t wait;
/* 指向资源等待队列队列头 */
wait_queue_head_t *wait_address;
};
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
pwq->polling_task = current;
pwq->triggered = 0;
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->qproc = qproc;
pt->key = ~0UL; /* all events enabled */
}
/* Add a new entry */
/**
* __pollwait - 将poll_table_entry挂载到资源文件的监听队列
* @file: 被监听的资源文件
* @wait_address: 被监听的资源文件的等待队列头
* @p: 在poll_initwait()中设置的poll_tbale
*/
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
/* 获取poll_wqueues */
struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
/* 从poll_wqueues中取得一个poll_table_entry结构 */
struct poll_table_entry *entry = poll_get_entry(pwq);
if (!entry)
return;
/* 增加资源文件引用计数 */
get_file(filp);
/* 关联资源文件 */
entry->filp = filp;
/* 保存资源文件监听队列队列头 */
entry->wait_address = wait_address;
/* 设置想要监听事件 */
entry->key = p->key;
/*
* 初始化一个等待队列节点,其中唤醒函数设置为pollwake
*
* 重点!!!:
* 唤醒函数为pollwake
*/
init_waitqueue_func_entry(&entry->wait, pollwake);
/*
* 来看一下为什么等待队列节点的private要这样设计:
* 1. 实际linux内核设计:
* 每个wait_queue_t的private字段指向同一个poll_wqueues,然后
* 共用的poll_wqueues中保存了指向调用进程PCB的指针,这样总共
* 需要n + 1个指针...
* 2. 假想设计:
* 每个wait_queue_t的private字段指向调用进程PCB,对应的事件
* 结构poll_table_entry中每个都保存了指向同一个poll_wqueues
* 的指针,这样总共需要n + n个指针...
*/
entry->wait.private = pwq;
/* 将poll_table_entry挂载到资源文件的监听队列中 */
add_wait_queue(wait_address, &entry->wait);
}
static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p)
{
struct poll_table_page *table = p->table;
/* 缓存数组够用就从缓存数组中分配... */
if (p->inline_index < N_INLINE_POLL_ENTRIES)
return p->inline_entries + p->inline_index++;
/* 动态分配的内存为空或者已用完... */
if (!table || POLL_TABLE_FULL(table)) {
struct poll_table_page *new_table;
/* 分配一页的内存给poll_table_page使用 */
new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
if (!new_table) {
p->error = -ENOMEM;
return NULL;
}
new_table->entry = new_table->entries;
new_table->next = table;
p->table = new_table;
table = new_table;
}
return table->entry++;
}
void poll_freewait(struct poll_wqueues *pwq)
{
struct poll_table_page * p = pwq->table;
int i;
/* 对缓存数组中的poll_table_entry进行卸载 */
for (i = 0; i < pwq->inline_index; i++)
free_poll_entry(pwq->inline_entries + i);
/* 对动态内存中的poll_table_entry进行卸载 */
while (p) {
struct poll_table_entry * entry;
struct poll_table_page *old;
entry = p->entry;
do {
/* 一个poll_table_page对象中至少分配了一个
* poll_table_entry,所以entry--是安全的 */
entry--;
free_poll_entry(entry);
} while (entry > p->entries);
old = p;
p = p->next;
free_page((unsigned long) old);
}
}
static void free_poll_entry(struct poll_table_entry *entry)
{
remove_wait_queue(entry->wait_address, &entry->wait);
fput(entry->filp);
}
/**
* pollwake - 唤醒回调函数,这个函数验证资源当前状态中是否有我们所关心的
* 事件,如果没有,就忽略这次唤醒;如果有,就转调用__pollwake...
* @wait: poll_table_entry.wait
* @mode:
* @key: 携带资源当前状态
*/
static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
struct poll_table_entry *entry;
/* 通过poll_wqueues中的wait获取poll_table_entry */
entry = container_of(wait, struct poll_table_entry, wait);
/* 如果资源的已就绪的状态中没有我们所关心的events的话,直接忽略返回 */
if (key && !((unsigned long)key & entry->key))
return 0;
/* 有我们所关心的events,那就转调用__pollwake去处理吧... */
return __pollwake(wait, mode, sync, key);
}
/* 资源就绪时真正调用的唤醒回调函数 */
static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
/* 见__pollwait()中关于等待队列节点private字段的注释 */
struct poll_wqueues *pwq = wait->private;
/* 构造一个有效的等待队列节点,private字段指向调用进程的PCB */
DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);
/*
* Although this function is called under waitqueue lock, LOCK
* doesn't imply write barrier and the users expect write
* barrier semantics on wakeup functions. The following
* smp_wmb() is equivalent to smp_wmb() in try_to_wake_up()
* and is paired with set_mb() in poll_schedule_timeout.
*/
smp_wmb();
/* 设置已触发状态标志 */
pwq->triggered = 1;
/*
* Perform the default wake up operation using a dummy
* waitqueue.
*
* TODO: This is hacky but there currently is no interface to
* pass in @sync. @sync is scheduled to be removed and once
* that happens, wake_up_process() can be used directly.
*/
/* 唤醒select()的调用进程 */
return default_wake_function(&dummy_wait, mode, sync, key);
}
select
调用链:
sys_select() -> core_sys_select() -> do_select() -> 1. poll_initwait()
-> 2. f_op->poll() [ -> poll_wait() -> __pollwait() -> poll_get_entry() ]
-> 3. [ block -> goto 2 ]
-> 4. poll_freewait() -> free_poll_entry()
pollwake() -> __pollwake()
typedef __kernel_fd_set fd_set;
#define __NFDBITS (8 * sizeof(unsigned long))
#define __FD_SETSIZE 1024
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)
/* fd_set中能够表示的最大文件描述符为__FD_SETSIZE - 1 */
typedef struct {
unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;
/*
* Scaleable version of the fd_set.
*/
typedef struct {
unsigned long *in, *out, *ex;
unsigned long *res_in, *res_out, *res_ex;
} fd_set_bits;
/*
* How many longwords for "nr" bits?
*/
/* 一个long包含的bit数 */
#define FDS_BITPERLONG (8*sizeof(long))
/* nr个bit需要多少long才能装下 */
#define FDS_LONGS(nr) (((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG)
/* nr个bit需要多大的long数组才能装下 */
#define FDS_BYTES(nr) (FDS_LONGS(nr)*sizeof(long))
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;
/*
* 1. 永久等待 tvp == NULL
* 2. 根本不等待 tvp->tv_sec == 0 && tvp->tc_nsec == 0
* 3. 等待指定时间 tvp->tv_sec != 0 || tvp->tc_nsec != 0
*/
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;
}
/*
* 处理完超时时间之后:
* 1. 永久等待 to == NULL
* 2. 根本不等待 to->ts_sec == 0 && to->ts_nsec == 0
* 3. 等待指定时间 to指向的timespec结构表示绝对超时时间
*/
/* 主线(至此,我们只进行了超时时间处理) */
ret = core_sys_select(n, inp, outp, exp, to);
/* 将剩余超时时间通过tvp指向的timeval结构返回给用户空间 */
ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
return ret;
}
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 */
/*
* 我们需要将用户空间传进来的inset、outset、exset拷贝到内核空间,并且
* 需要等容量的空间来存储结果集,之后会将结果集的内容写回到用户空间。
* 我们先在栈上分配一块缓冲区,用于缓存输入集以及结果集,如果缓存的
* 空间大小不够,那么再使用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 */
/* rdlock加锁:保护struct files的访问 */
rcu_read_lock();
fdt = files_fdtable(current->files);
max_fds = fdt->max_fds;
rcu_read_unlock();
/*
* 根据当前文件描述符表能表示的最大文件描述符对输入参数n进行修正
*
* fdtable.maxfd的意义:
* 在expand_fdtable() -> alloc_fdtable()中有以下代码:
* fdt = kmalloc(sizeof(struct fdtable), GFP_KERNEL);
* fdt->max_fds = nr;
* data = alloc_fdmem(nr * sizeof(struct file *));
* fdt->fd = (struct file **)data;
*/
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.
*/
/*
* n个bits至少需要size个long才能装下(之后我们使用long表示bits段)
* 为了存储输入集与结果集,我们需要6*size个long的存储空间
* 如果我们在栈上分配的那个缓冲区够用,那么就用它;而如果空间
* 容纳不下的话,那么我们只好kmalloc()动态分配内存了...
*/
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;
}
/*
* 将数组划分为6份...
* inset、outset、exset
* res_inset、res_outset、res_exset
*/
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;
/* 从用户空间将输入集拷贝到内核空间 */
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);
/* 主线(至此,我们只进行了描述符集处理) */
ret = do_select(n, &fds, end_time);
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;
}
/**
* do_select - select()核心函数
* @n: select()的第一个参数
* @fd_set_bits: core_sys_select()处理的描述符集
* @end_time: 绝对超时时间
*/
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
ktime_t expire, *to = NULL;
struct poll_wqueues table;
poll_table *wait;
/* timed_out指示是否已经超时,超时1,未超时0 */
int retval, i, timed_out = 0;
unsigned long slack = 0;
/* 根据输入集以及当前文件描述符打开情况对n修正 */
rcu_read_lock();
retval = max_select_fd(n, fds);
rcu_read_unlock();
if (retval < 0)
return retval;
n = retval;
/* ------------------------------------------------------------- */
/* ---------------------------- main --------------------------- */
/* ------------------------------------------------------------- */
/*
* 注意:
* poll_table被封装在了poll_wqueues结构体中,以便之后向资源
* 注册监听的时候,能够用poll_table得到对应的poll_wqueues
*
* 初始化poll_wqueues
* 1. 初始化poll_wqueues中的poll_table:
* * 设置监听注册函数为__pollwait
* * 设置想要监听的事件为所有事件(没必要,之后会修改)
* 2. 设置polling_task指向当前进程PCB
*
* 重点:资源注册函数为__pollwait
*/
poll_initwait(&table);
wait = &table.pt;
/* 根本不等待 */
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
/* 注意:wait设为NULL了!!! */
wait = NULL;
/* 还没开始就已经超时,这样就实现了根本不等待... */
timed_out = 1;
}
/* 重新估算相对超时时间... */
if (end_time && !timed_out)
slack = 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;
/* 通过i遍历[0, n)范围内的文件描述符 */
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;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
/* 跳过我们不关心的bits段 */
if (all_bits == 0) {
i += __NFDBITS;
continue;
}
/* 遍历bits段中的每一个bit */
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
int fput_needed;
/* 超出了关心的文件描述符范围[0, n),那么跳出... */
if (i >= n)
break;
/* 跳过我们不关心的bit */
if (!(bit & all_bits))
continue;
/* 通过current->files->fdt->fd[i]获得当前进程中描述符i
* 对应文件的struct file...同时,增加struct file的引用
* 计数,防止在获取struct file之后,它被异步删除... */
file = fget_light(i, &fput_needed);
/* 因为没有rdlock加锁,因此当前进程中描述符i对应的文件可能已经
* 被异步关闭。这就是为什么需要判断file是否为空的原因...
*/
if (file) {
f_op = file->f_op;
/* 注意:
* mask = POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM; */
mask = DEFAULT_POLLMASK;
/*
* 如果这个文件支持poll(),那么我们就向这个文件
* 注册监听函数;如果不支持,那么我们就忽略掉这
* 个文件描述符...
*
* 还有三种特殊情况:
* 1. 我们将超时时间设置为根本不等待
* 2. 已经发生了就绪事件
* 3. 已经注册了监听函数
* 这三种情况下,wait都为NULL,此时poll()只是
* 简单地返回资源文件当前状态...
*/
if (f_op && f_op->poll) {
/* 设置poll_table中想要监听的事件 */
wait_key_set(wait, in, out, bit);
/* 对文件注册监听函数,并返回资源的当前状态 */
mask = (*f_op->poll)(file, wait);
}
fput_light(file, fput_needed);
/*
* 如果文件不支持poll()的话,那么mask总为DEFAULT_POLLMASK,
* 表示当前资源对于任何事件都就绪...
* 对于不支持poll()的文件,主要是ext2/ext3/ext4等块设备上
* 的文件,这些文件总是对任意事件就绪的,读写操作不会引起
* 阻塞,有数据就返回数据,没有数据就返回0表示end-of-file
*/
/* events验证,其中retval表示就绪的资源数 */
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
/* 发生了就绪事件后就不再对资源进行注册了...
* 因为我们遍历完这次就可以返回了! */
wait = NULL;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
wait = NULL;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
wait = NULL;
}
}
} // end of for (j = 0; j < __NFDBITS; ++j)
/* 写出结果,注意写出的目的地是传进来的fd_set_bits */
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
/* 提供一个调度时机,给资源就绪更多的准备时间... */
cond_resched();
} // end of for (i = 0; i < n; )
/* 将wait设为NULL,表示我们不希望再进行监听注册... */
wait = NULL;
/*
* 1. 有事件发生了(retval)
* 2. 超时了(timed_out)
* 3. 发生了中断(signal_pending(current))
* 任意事件发生了都跳出死循环...
*/
/*
* 感觉这里存在问题:
* e.g.
* 假设我们监听文件描述符集#1#2#3#4,poll(#1)与poll(#2)时
* 向资源文件注册监听函数并返回0 events,poll(#3)时向资源
* 文件注册监听函数并返回可读events,然后wait被置NULL,
* poll(#4)时由于wait为NULL,所以只是返回#4的当前文件状态。
* 遍历描述符集完毕,retval != 0,此时跳出循环,返回用户
* 空间#3与#4的文件状态。但是在处理后续文件描述符的时候,
* #1和#2可能异步就绪,但是我们没有返回它们...
*/
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;
}
/* 能够到达这一步就说明没有发生就绪、中断以及超时 */
/*
* 判断poll_wqueues是否已触发,如果还没有触发,那就设置
* 当前运行状态为可中断阻塞并进行睡眠,等待被唤醒...
* 被唤醒之后重新进行迭代,获取资源就绪情况...
* 在向资源注册监听与判断poll_wqueues是否已触发这段时间
* 内,可能资源异步就绪了,如果没有触发标志,那么可能就
* 会丢失资源就绪这个事件,可能导致select()永久沉睡...
* 这就是为什么需要poll_wqueues.triggered字段的原因...
*/
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
to, slack))
timed_out = 1;
} // end of for ( ; ; )
/*
* 1. 卸载安装到资源监听队列上的poll_table_entry
* 2. 释放poll_wqueues占用的资源
*/
poll_freewait(&table);
return retval;
}
#define FDS_IN(fds, n) (fds->in + n)
#define FDS_OUT(fds, n) (fds->out + n)
#define FDS_EX(fds, n) (fds->ex + n)
#define BITS(fds, n) (*FDS_IN(fds, n)|*FDS_OUT(fds, n)|*FDS_EX(fds, n))
static int max_select_fd(unsigned long n, fd_set_bits *fds)
{
unsigned long *open_fds;
unsigned long set;
int max;
struct fdtable *fdt;
/* handle last in-complete long-word first */
/*
* set = ~(1111....11111111 << (n % __NFDBITS))
* e.g.
* n = 16, set = (unsigned long)0000....00000000B
* n = 18, set = (unsigned long)0000....00000011B
* n = 23, set = (unsigned long)0000....01111111B
*/
set = ~(~0UL << (n & (__NFDBITS-1)));
n /= __NFDBITS;
fdt = files_fdtable(current->files);
open_fds = fdt->open_fds->fds_bits+n;
max = 0;
/* 处理最后一个不完整的bits段 */
if (set) {
set &= BITS(fds, n);
if (set) {
/* 有效性验证:
* 输入集中不能含有未打开的文件描述符 */
if (!(set & ~*open_fds))
goto get_max;
return -EBADF;
}
}
/* 循环处理完整的bits段 */
while (n) {
open_fds--;
n--;
set = BITS(fds, n);
if (!set)
continue;
/* 有效性验证:
* 输入集中不能含有未打开的文件描述符 */
if (set & ~*open_fds)
return -EBADF;
/* 判断max是否被设置而continue,而不是根据max是否被设置
* 而break,是想对fd_set中设置的所有文件描述符都进行有效
* 性验证 */
if (max)
continue;
get_max:
/* max会是__NFDBITS的整数倍 */
do {
max++;
set >>= 1;
} while (set);
max += n * __NFDBITS;
}
return max;
}
#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
#define POLLEX_SET (POLLPRI)
static inline void wait_key_set(poll_table *wait, unsigned long in,
unsigned long out, unsigned long bit)
{
if (wait) {
wait->key = POLLEX_SET;
if (in & bit)
wait->key |= POLLIN_SET;
if (out & bit)
wait->key |= POLLOUT_SET;
}
}
poll
调用链:
sys_poll() -> do_sys_poll() -> poll_initwait()
-> do_poll() -> 1. do_pollfd() -> f_op->poll() [ -> poll_wait() -> __pollwait() -> poll_get_entry() ]
-> 2. [ block -> goto 1 ]
-> poll_freewait() -> free_poll_entry()
pollwake() -> __pollwake()
struct pollfd {
/* 想要监听的文件描述符 */
int fd;
/* 关心的events */
short events;
/* 就绪的events */
short revents;
};
struct poll_list {
struct poll_list *next;
int len;
struct pollfd entries[0];
};
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;
poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
}
/* 主线(至此,我们只进行了超时时间处理) */
ret = do_sys_poll(ufds, nfds, to);
/* ... */
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;
ret = -ERESTART_RESTARTBLOCK;
}
return ret;
}
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;
/* Allocate small arguments on the stack to save memory and be
faster - use long to make sure the buffer is aligned properly
on 64 bit archs to avoid unaligned access */
/* poll_list缓存数组 */
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;
/* 将用户空间中的pollfd拷贝到内核空间 */
len = min_t(unsigned int, nfds, N_STACK_PPS);
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
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_initwait(&table);
/* 主线(至此,我们只进行了描述符集处理) */
fdcount = do_poll(nfds, head, &table, end_time);
poll_freewait(&table);
/* 将结果写给用户空间 */
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;
for (j = 0; j < walk->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;
/* Optimise the no-wait case */
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
pt = NULL;
timed_out = 1;
}
if (end_time && !timed_out)
slack = estimate_accuracy(end_time);
for (;;) {
struct poll_list *walk;
/* 遍历poll_list */
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end;
/* 遍历pollfd */
pfd = walk->entries;
pfd_end = pfd + walk->len;
for (; pfd != pfd_end; pfd++) {
/*
* Fish for events. If we found one, record it
* and kill the poll_table, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
/*
* 对每一个想要监听的pollfd调用do_pollfd()
* 返回非0表示监听的事件已发生...
*/
if (do_pollfd(pfd, pt)) {
count++;
pt = NULL;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table to them on the next loop iteration.
*/
pt = NULL;
if (!count) {
count = wait->error;
if (signal_pending(current))
count = -EINTR;
}
if (count || timed_out)
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(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
}
return count;
}
/*
* Fish for pollable events on the pollfd->fd file descriptor. We're only
* interested in events matching the pollfd->events mask, and the result
* matching that mask is both recorded in pollfd->revents and returned. The
* pwait poll_table will be used by the fd-provided poll handler for waiting,
* if non-NULL.
*/
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;
file = fget_light(fd, &fput_needed);
mask = POLLNVAL;
if (file != NULL) {
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll) {
if (pwait)
/* poll()总是设置events中的POLLERR与POLLHUP */
pwait->key = pollfd->events |
POLLERR | POLLHUP;
mask = file->f_op->poll(file, pwait);
}
/* Mask out unneeded events. */
mask &= pollfd->events | POLLERR | POLLHUP;
fput_light(file, fput_needed);
}
}
pollfd->revents = mask;
return mask;
}
关于惊群:select()惊群与poll()惊群目前无法等到解决,因为它无法像accept()那样使得每次只有一个调用者能挂载到资源的监听队列上...虽然epoll()已经较好的解决了惊群,但只限于同一个epoll实例的ET模式。
- 本节继续在带你玩转中断方式按键驱动程序(详解)里改进,添加poll机制.
- 那么我们为什么还需要poll机制呢。之前的测试程序是这样:
while (1)
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
- 在没有poll机制的情况下,大部分时间程序都处在read中休眠的那个位置。如果我们不想让程序停在这个位置,而是希望当有按键按下时,我们再去read,因此我们编写poll函数,测试程序调用poll函数根据返回值,来决定是否执行read函数。
- poll机制作用:相当于定时器,设置一定时间使进程等待资源,如果时间到了中断还处于睡眠状态(等待队列),poll机制就会唤醒中断,获取一次资源
1.poll机制内核框架
- 如下图所示,在用户层上,使用poll或select函数时,和open、read那些函数一样,也要进入内核sys_poll函数里,接下来我们分析sys_poll函数来了解poll机制(位于/fs/select.c)
【 文章福利】小编推荐自己的Linux内核技术交流群:【 891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值 699的内核资料包(含视频教程、电子书、实战项目及代码)
学习直通车:
内核资料直通车:
1.1 sys_poll代码如下:
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs)
{
if (timeout_msecs > 0) //参数timeout>0
{
timeout_jiffies = msecs_to_jiffies(timeout_msecs); //通过频率来计算timeout时间需要多少计数值
}
else
{
timeout_jiffies = timeout_msecs; //如果timeout时间为0,直接赋值
}
return do_sys_poll(ufds, nfds, &timeout_jiffies); //调用do_sys_poll。
}
1.2 然后进入do_sys_poll(位于fs/select.c):
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
... ...
/*初始化一个poll_wqueues变量table*/
poll_initwait(&table);
... ...
fdcount = do_poll(nfds, head, &table, timeout);
... ...
}
1.3进入poll_initwait函数,发现主要实现以下一句,后面会分析这里:
table ->pt-> qproc=__pollwait; //__pollwait将在驱动的poll函数里的poll_wait函数用到
1.4然后进入do_poll函数, (位于fs/select.c):
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, s64 *timeout)
{
……
for (;;)
{
……
set_current_state(TASK_INTERRUPTIBLE); //设置为等待队列状态
......
for (; pfd != pfd_end; pfd++) { //for循环运行多个poll机制
/*将pfd和pt参数代入我们驱动程序里注册的poll函数*/
if (do_pollfd(pfd, pt)) //若返回非0,count++,后面并退出
{ count++;
pt = NULL; } }
……
/*count非0(.poll函数返回非0),timeout超时计数到0,有信号在等待*/
if (count || !*timeout || signal_pending(current))
break;
……
/*进入休眠状态,只有当timeout超时计数到0,或者被中断唤醒才退出,*/
__timeout = schedule_timeout(__timeout);
……
}
__set_current_state(TASK_RUNNING); //开始运行
return count;
}
1.4.1上面do_pollfd函数到底是怎么将pfd和pt参数代入的?代码如下(位于fs/select.c):
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
……
if (file->f_op && file->f_op->poll)
mask = file->f_op->poll(file, pwait);
……
return mask;
}
- 上面file->f_op 就是我们驱动里的file_oprations结构体,如下图所示:
- 所以do_pollfd(pfd, pt)就执行了我们驱动程序里的.poll(pfd, pt)函数(第2小节开始分析.poll函数)
1.4.2当poll进入休眠状态后,又是谁来唤醒它?这就要分析我们的驱动程序.poll函数(第2小节开始分析.poll函数)
2.写驱动程序.poll函数,并分析.poll函数:
- 在上一节驱动程序里添加以下代码:
#include <linux/poll.h> //添加头文件
/* .poll驱动函数: third_poll */
static unsigned int third_poll(struct file *fp, poll_table * wait) //fp:文件 wait:
{
unsigned int mask =0;
poll_wait(fp, &button_wait, wait);
if(even_press) //中断事件标志, 1:退出休眠状态 0:进入休眠状态
mask |= POLLIN | POLLRDNORM ;
return mask; //当超时,就返给应用层为0 ,被唤醒了就返回POLLIN | POLLRDNORM ;
}
static struct file_operations third_drv_fops={
.owner = THIS_MODULE,
.open = third_drv_open,
.read = third_drv_read,
.release=third_drv_class,
.poll = third_poll, //创建.poll函数
};
2.1 在我们1.4小节do_poll函数有一段以下代码:
if (do_pollfd(pfd, pt)) //若返回非0,count++,后面并退出
{
count++;
pt = NULL;
}
- 且在1.4.1分析出: do_pollfd(pfd, pt)就是指向的驱动程序third_poll()函数,
- 所以当我们有按键按下时, 驱动函数third_poll()就会返回mask非0值,然后在内核函数do_poll里的count就++,poll机制并退出睡眠.
2.2分析在内核中poll机制如何被驱动里的中断唤醒的
- 在驱动函数third_poll()里有以下一句:
poll_wait(fp, &button_wait, wait);
- 如上图所示,代入参数,poll_wait()就是执行了: p->qproc(filp, button_wait, p);
- 刚好对应了我们1.3小节的:
table ->pt-> qproc=__pollwait;
- 所以poll_wait()函数就是调用了: __pollwait(filp, button_wait, p);
- 然后我们来分析__pollwait函数,pollwait的代码如下:
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,poll_table *p)
{
... ...
//把current进程挂载到&entry->wait下
init_waitqueue_entry(&entry->wait, current);
//再&entry->wait把添加到到button_wait中断下
add_wait_queue(wait_address, &entry->wait);
}
- 它是将poll进程添加到了button_wait中断队列里,这样,一有按键按下时,在中断服务函数里就会唤醒button_wait中断,同样也会唤醒poll机制,使poll机制重新进程休眠计数
2.3 驱动程序.poll函数返回值介绍
当中断休眠状态时,返回mask为0
当运行时返回:mask |= POLLIN | POLLRDNORM
其中参数意义如下:
- 所以POLLIN | POLLRDNORM:普通数据可读|优先级带数据可读
- mask就返回到应用层poll函数,
3.改进测试程序third_poll_text.c(添加poll函数)
- 在linux中可以通过man poll 来查看poll函数如何使用
- poll函数原型如下(#include <poll.h>):
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数介绍:
1) *fds:是一个poll描述符结构体数组(可以处理多个poll),结构体pollfd如下:
struct pollfd {
int fd; /* file descriptor 文件描述符*/
short events; /* requested events 请求的事件*/
short revents; /* returned events 返回的事件(函数返回值)*/
};
- 其中events和revents值参数如下:
2) nfds:表示多少个poll,如果1个,就填入1
3) timeout:定时多少ms
返回值介绍:
- 返回值为0:表示超时或者fd文件描述符无法打开
- 返回值为 -1:表示错误
- 返回值为>0时 :就是以下几个常量
- 最终改进的测试代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <poll.h> //添加poll头文件
/*useg: thirdtext */
int main(int argc,char **argv)
{
int fd,ret;
unsigned int val=0;
struct pollfd fds; //定义poll文件描述结构体
fd=open("/dev/buttons",O_RDWR);
if(fd<0)
{printf("can't open!!!\n");
return -1;}
fds.fd=fd;
fds.events= POLLIN; //请求类型是 普通或优先级带数据可读
while(1)
{
ret=poll(&fds,1,5000) ; //一个poll, 定时5000ms,进入休眠状态
if(ret==0) //超时
{
printf("time out \r\n");
}
else if(ret>0) //poll机制被唤醒,表示有数据可读
{
read(fd,&val,1); //读取一个值
printf("key_val=0X%x\r\n",val);
}
}
return 0;
}