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