【Linux驱动编程】Linux 字符驱动之poll

1.写在前面

  在此之前写了“软驱动”作进程通信,该驱动只提供了基本的open、read、write、close、ioctl等功能,如果作为进程通信或者多个进程调用,只能借助Linux 进程同步机制,如信号量、互斥锁等。如果不借助进程同步机制,我们可以提供驱动的“poll”接口,如有数据可读/可写时通知调用进程。

  Linux系统提供了多路IO复用机制,通过select、poll、epoll接口实现,前提是该驱动支持poll。这样同一个进程可以监听多个文件描述符,并及时对资源可用的文件描述符处理。


2.实现


2.1 poll调用过程

  应用程序调用poll到最后调用驱动的poll实体函数,经过一系列函数调用过程,进程poll —> SYSCALL_DEFINE3(sys_poll) —> do_sys_poll —> poll_initwait—>do_poll— > do_pollfd—>f_ops —> 驱动poll函数实体,具体详细的调用及功能可以翻阅内核源码。关于上述poll相关函数,可以在“fs/select.c”中查阅。关于“sys_poll”,网络上的总结多为次函数,应该为2.6内核版本函数,3.08内核版本开始就没发现该函数,进而是“SYSCALL_DEFINE3”。

  关于应用到驱动的详细调用过程,即使不是全部理解透彻,有几个关键的点需要知道。

【1】poll_wait进程加入等待队列

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    if (p && wait_address)
        p->qproc(filp, wait_address, p);
}

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
                poll_table *p)
{
    struct poll_table_entry *entry = poll_get_entry(p);
    if (!entry)
        return;
    get_file(filp);
    entry->filp = filp;
    entry->wait_address = wait_address;
    init_waitqueue_entry(&entry->wait, current);
    add_wait_queue(wait_address, &entry->wait);
}

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;
}

  翻阅源码知道,调用“poll_wait”即是调用“__pollwait”将当前调用进程挂载到“wait_address”队列中。即:poll_wait—>poll_table—>__pollwait—>add_wait_queue。

【2】进程休眠

  进程调用poll后如果没有超时事件、驱动唤醒事件、错误事件等,则在“do_poll”函数中调用“poll_schedule_timeout”后进入休眠。

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 = select_estimate_accuracy(end_time);

	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++) {
				/*
				 * 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.
				 */
				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;
}

【3】进程唤醒

  进程唤醒同样位于“do_poll”函数中,根据上面源码,

1)文件描述符资源可用(驱动唤醒)

if (do_pollfd(pfd, pt)) {
	count++;
	pt = NULL;
}

2)poll超时唤醒

	if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
		pt = NULL;
		timed_out = 1;
	}

3)错误事件

	if (!count) {
		count = wait->error;
		if (signal_pending(current))
			count = -EINTR;
	}

2.2 驱动支持poll


  与之前写的“dev_mem.c”代码相比,首先是实现“struct file_operations”结构体的“poll”函数实体。

static unsigned int memory_poll(struct file *pfile, poll_table *wait)
{
	unsigned int mask = 0;
	struct memory_device *p;
	
	p = pfile->private_data;
    poll_wait(pfile, &p->r_queue, wait);	/* 将进程挂在'r_queue'队列上 */
    if(p->r_en)         
	{
		mask |= POLLIN | POLLRDNORM;  		/* 数据可读 */
    }
	
    return mask;
}

2.3 唤醒休眠进程

  驱动底层唤醒休眠进程,即是文件描述符(fd)资源可用(读/写)。

static ssize_t memory_write(struct file * pfile, const char __user *buffer, size_t size, loff_t *offset)
{
	unsigned long of = 0;
	struct memory_device *p;

	p = pfile->private_data;
	of = *offset;
	if(of > p->mem_size)
	{
		return 0;
	}
   	if (size > (p->mem_size - of))
    {
    	size = p->mem_size -of;
    }
   
    if (copy_from_user(p->mem_buf+of, buffer, size))
    {
    	printk("write memory falied.\n");
        return -EFAULT;
    }
	else
	{
		*offset += size;
	}
	
	p->r_en = true;
    wake_up_interruptible(&(p->r_queue));	/* 唤醒休眠进程(读) */
	
    return size;
}

3.源码及测试

【1】https://github.com/Prry/linux-drivers/tree/master/devmem_poll


4.参考文章

【1】http://www.21ic.com/tougao/article/24050.html
【2】https://www.cnblogs.com/zhaobinyouth/p/6252511.html

原创文章 128 获赞 147 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/102772941
今日推荐