目录
内核版本:linux-3.10.61
一、简介
字符设备驱动f的poll机制是通过file_oprations的poll成员实现的,对应的在应用层函数调用是poll()或select(),poll()和select()是由不同标准定义的但其是由同一个系统调用实现的,但在内核空间对应的都是sys_poll()函数。 想要了解系统调用的具体过程可以看我之前的文章:ARM linux 系统调用过程 。
二、sys_poll解析
poll()调用到了内核的sys_poll(),让我们从sys_poll()作为入口分析一下IO多路复用的底层实现机制,从根上了解这其中的过程。
long sys_poll(struct pollfd __user *ufds, unsigned int nfds,
int timeout);{
return do_sys_poll(ufds, nfds, &timeout_jiffies);
}
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec *end_time)
{
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
for (;;) {
/* 把fd集从用户空间拷贝到内核空间 */
if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len))
}
/*
* 初始化table, ]table.pt->_qproc = __pollwait();
* __pollwait()最终会给驱动的poll调用到
* __pollwait()用于把等待队列加到table中
*/
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, end_time); /* do_poll()会调用驱动的poll函数 */
poll_freewait(&table); /* free table 的内存 */
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;
/* 把驱动的poll函数的返回值拷贝到用户空间 */
for (j = 0; j < walk->len; j++, ufds++)
if (__put_user(fds[j].revents, &ufds->revents))
goto out_fds;
}
}
对do_sys_poll()功能做一个总结:
1、从用户空间把fd集拷贝到内核。
2、table.pt->_qproc = __pollwait();
3、调用do_poll()
4、把驱动的poll函数的返回值拷贝到用户空间
为了方便理解do_poll()的实现原理,先来分析一驱动代码的poll做了什么。我在内核找了一份驱动函数的poll函数,来试着分析看看。
struct sonypi_compat_s {
struct fasync_struct *fifo_async;
struct kfifo fifo;
spinlock_t fifo_lock;
wait_queue_head_t fifo_proc_list; /* a. 一个等待队列 */
atomic_t open_count;
};
static unsigned int sonypi_misc_poll(struct file *file, poll_table *wait)
{
poll_wait(file, &sonypi_compat.fifo_proc_list, wait); /* 看看这函数做了啥 */
if (kfifo_len(&sonypi_compat.fifo)) /* b. 判断可读或可写条件成立返回 mask: POLLIN | POLLRDNORM */
return POLLIN | POLLRDNORM;
return 0; /* 判断可读或可写条件不成立返回0 */
}
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address) /* c. 如果p->_qproc为NULL(不超时),函数结束,否则调用p->_qproc() */
p->_qproc(filp, wait_address, p); /* d. 在do_sys_poll()分析中已知p->_qproc为函数__pollwait() */
}
驱动代码的poll函数功能:把一个等待队通过__pollwait()加入到table中,然后返回一个mask。
现在我们倒回去分析do_poll()函数
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, struct timespec *end_time){
/* 设置为不超时的动作 */
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
pt->_qproc = NULL; /* 和驱动代码分析的 第c点 就对应了,不超时就不添加等待队列*/
timed_out = 1; /* 会导致下面的外层for循环在第一次循环后就结束, do_poll()返回*/
}
for (;;) {
for (walk = list; walk != NULL; walk = walk->next) {
pfd = walk->entries;
/* 为每个文件描述符调用驱动函数的poll */
for (; pfd != pfd_end; pfd++){
if (do_pollfd(pfd, pt)) { /* 这里调用驱动的poll */
/* 符合读或写的条件,驱动poll返回非0的mask */
count++;
pt->_qproc = NULL; /* __pollwait()只调用一次 */
}
}
}
pt->_qproc = NULL; /* __pollwait()只调用一次 */
if (count || timed_out) /* 超时或者驱动poll返回非0,break */
break;
/* 使驱动poll函数中加入的等待队列休眠,等待被唤醒 */
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
/*
* 在可读或可写条件成立时,驱动会调用wake_up()函数唤醒等待队列,
* 进入下一次循环再次调用驱动的poll,poll返回非0的mask,在前面的驱动举例中就是POLLIN | POLLRDNORM
* 然后for循环结束,do_poll()返回到do_sys_poll(), do_sys_poll()把结果拷贝到用户空间
* 应用层poll()函数返回
*/
}
return count;
}
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait){
fd = pollfd->fd;
struct fd f = fdget(fd);
mask = f.file->f_op->poll(f.file, pwait); /* 调用驱动的poll函数 */
pollfd->revents = mask; /* 保存驱动poll的返回值 */
return mask;
}
三、驱动实例。
这是根据下面的示例驱动代码和应用层进程交互的调用过程分析
下面是一个简单的poll机制实现的一个驱动代码
#include <linux/irq.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
static struct class *poll_drv_class;
static struct class_device *poll_drv_devive;
static int queue_flag = 0;
static unsigned char user_date; ;
DECLARE_WAIT_QUEUE_HEAD(poll_drv_queue); /* 初始化等待队列 */
int poll_drv_open(struct inode *inode, struct file *file){
return 0;
}
ssize_t poll_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
wait_event_interruptible(poll_drv_queue, queue_flag); /* 睡眠,等地被唤醒 */
queue_flag = 0;
copy_to_user(buf, &user_date, 1);
return 0;
}
ssize_t poll_drv_write(struct file *file, const char __user *buf, size_t sie, loff_t *ppos){
copy_from_user(&user_date, buf, 1);
queue_flag = 1;
/* 有数据写入,唤醒等待队列,此时do_poll()中的休眠会被唤醒, poll_drv_read()也会被唤醒*/
wake_up_interruptible(&poll_drv_queue);
return 0;
}
/* 驱动poll函数 */
unsigned int poll_drv_poll(struct file * file, poll_table *wait){
int mask = 0;
poll_wait(file, &poll_drv_queue, wait);
if (queue_flag){
queue_flag = 0;
mask |= POLLIN | POLLRDNORM;
}
return mask;
}
int poll_drv_close (struct inode *inode, struct file *file){
return 0;
}
struct file_operations poll_drv_ops = {
.owner = THIS_MODULE,
.open = poll_drv_open,
.read = poll_drv_read,
.write = poll_drv_write,
.release = poll_drv_close,
.poll = poll_drv_poll
};
int poll_drv_major ;
static int __init poll_drv_init(void){
/* 注册字符设备驱动 */
poll_drv_major = register_chrdev(0, "poll_drv", &poll_drv_ops);
if (poll_drv_major < 0){
goto register_err;
}
/* 注册类 */
poll_drv_class = class_create(THIS_MODULE, "poll_drv_dev");
if (IS_ERR(poll_drv_class)){
PTR_ERR(poll_drv_class);
goto class_create_ERR;
}
/* 注册设备 */
poll_drv_devive = class_device_create(poll_drv_class, NULL, MKDEV(poll_drv_major,0),NULL, "poll_drv_dev");
if (IS_ERR(poll_drv_devive)){
PTR_ERR(poll_drv_devive);
goto device_create_ERR;
}
return 0;
device_create_ERR:
class_destroy(poll_drv_class);
class_create_ERR:
unregister_chrdev(poll_drv_major, "poll_drv");
register_err:
return 0;
}
static void __exit poll_drv_exit(void){
class_device_unregister(poll_drv_devive);
class_destroy(poll_drv_class);
unregister_chrdev(poll_drv_major, "poll_drv");
return ;
}
module_init(poll_drv_init);
module_exit(poll_drv_exit);
MODULE_LICENSE("GPL");
四、总结
poll()或select()函数调用进入到内核第一次调用驱动的poll后在do_poll()函数进入休眠,等待被唤醒,当有用户对驱动调用write时唤醒等待队列,do_poll()for循环再次调用驱动poll获取mask然后返回do_sys_poll(),do_sys_poll()将驱动的mask拷贝给用户后返回用户空间,用户空间的poll()或select()返回。
sys_poll()
---->do_sys_poll()
copy_from_user(walk->entries, ufds + nfds-todo, sizeof(struct pollfd) * walk->len)
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, end_time);
---->do_poll()
for (;;) {
for (walk = list; walk != NULL; walk = walk->next) {
for (; pfd != pfd_end; pfd++){
if (do_pollfd(pfd, pt)) {
if (do_pollfd(pfd, pt)){
---->do_polld()
mask = f.file->f_op->poll(f.file, pwait);
count++;
pt->_qproc = NULL;
}
}
}
pt->_qproc = NULL;
if (count || timed_out)
break;
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
}
}
poll_freewait(&table);
__put_user(fds[j].revents, &ufds->revents)
恭喜获得知识点+1
若有讲解不妥之处烦请在评论区指正!
如果有收获那就一键三连鼓励一下吧!