Linux字符设备驱动poll机制的原理和实现

目录

一、简介

二、sys_poll解析

三、驱动实例。

四、总结


内核版本: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

若有讲解不妥之处烦请在评论区指正!

如果有收获那就一键三连鼓励一下吧!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值