在驱动入门实验班的课程里讲到了APP使用驱动程序的四种方式。分别是:阻塞、非阻塞、poll、异步通知。
本文是在前面所描述的概念之上,对驱动程序交互流程的解读。
1. 非阻塞
应用程序 open 时,传入O_NONBLOCK
标记位
对于 if 判断只需要使用is_key_buf_empty()
判断环形缓冲区是否有数据
- 无数据,
is_key_buf_empty()
为真,立即返回-EAGAIN
- 有数据,
wait_event_interruptible()
函数就不起作用,直接从缓冲区里拷贝数据,然后返回
注意:非阻塞起作用的前提是驱动程序“尊重O_NONBLOCK
标记位。
2. 阻塞
应用程序 open 时,不带 O_NONBLOCK 标记位
对于 if 判断,直接失效。因为(file->f_flags & O_NONBLOCK)
为假
当没有数据时,调用 wait_event_interruptible()
函数,进入等待,直到条件!is_key_buf_empty()
为真!等待时:
- 改变程序状态,休眠的程序不参与调度;
- 把自己记录在 gpio_wait 里,gpio_wait 是等待队列头;
#define wait_event_interruptible(wq_head, condition) \
({ \
int __ret = 0; \
might_sleep(); \
if (!(condition)) \
__ret = __wait_event_interruptible(wq_head, condition); \
__ret; \
})
当有数据时,被唤醒,即被定时器唤醒(按键被按下或松开执行定时器超时处理函数)
//定时器超时处理函数
wake_up_interruptible(&gpio_wait); /* 唤醒等待队列 */
3. POLL
单来说,就是在 read 之前先调用 poll 函数查询一下是否有数据,poll 函数中可以传入 time_out(超时时间),即超时也会返回。
当调用 poll 函数时,APP 有以下几种情况:
- 已有数据,即刻返回
- 无数据,休眠
- 被唤醒
a. 按下/松开按键
b. 超时
POLL 函数的内部机制:
核心是 sys_poll
中的循环,每次循环都先调用驱动程序的 drv_poll
函数,如果(有数据/超时)就返回,否则就休眠。
驱动程序中的 drv_poll()
:简单来说它只负责返回自己的状态,表明自己是否有数据可读/可写
static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{
//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
poll_wait(fp, &gpio_wait, wait); //1.把进程记录在gpio_wait队列里,并没有休眠
return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM; //2.返回状态
}
第 5 步的唤醒,有两种情况:
- 被中断程序唤醒,比如按键按下/松开;
- 超时,被内核唤醒
当醒来之后,sys_poll()
函数的循环会再执行一次,再次调用 drv_poll
函数查看是否有数据,有数据就返回 ok,没有数据就返回超时。
4. 异步通知
之前我们说,异步通知相当于孩子长大了,自己告诉“妈妈”他醒了。儿子告诉妈妈,“儿子”是主语,“告诉”是谓语,“妈妈”是宾语。在驱动程序中,“儿子”指中断/定时器的处理函数,“告诉”也就是发信号,“妈妈”就是对应的 APP进程。
在应用程序中,首先要给信号 SIGIO
和 某个信号处理函数
挂钩,其次需要把自己的进程告诉驱动程序,并使能异步驱动。
在驱动程序中,当我们把 APP 的进程号告诉驱动程序,并且使能异步通知后,就会构造button_fasync变量(struct fasync_struct
),可以从它找到 进程的PID
。
/* 在驱动程序中定义 */
struct fasync_struct *button_fasync;
static int gpio_drv_fasync(int fd, struct file *file, int on)
{
if (fasync_helper(fd, file, on, &button_fasync) >= 0)
return 0;
else
return -EIO;
}
当按键按下/抬起时,执行中断处理程序,修改定时器。在定时器超时处理函数中,把按键值记录后,就会给 进程发信号。
以上是本篇文章的全部内容,谢谢观看。