文章目录
1 概述
本文主要是笔者根据《正点原子I.MX6U嵌入式Linux驱动开发》中 “第五十二章 Linux阻塞和非阻塞IO实验” 的程序绘制的流程图,该程序使用非阻塞IO实现了和五十一章实验同样的功能,并对函数流程进行了分析。
《【学习日记】【第五十一章 Linux中断实验】【流程图】——正点原子I.MX6U嵌入式Linux驱动开发》
2 完整流程图
3 关键代码分析
3.1 关键代码流程图
流程图说明
-
应用程序部分:
- 应用程序调用
select()
或poll()
等待按键事件。 - 根据返回值,应用程序决定是处理超时还是读取按键数据。
- 应用程序调用
-
驱动程序部分:
imx6uirq_poll
函数调用poll_wait
挂起进程并加入等待队列。- 如果按键事件发生,中断触发
key0_handler
,启动定时器。 - 定时器到期后调用
timer_function
进行按键消抖处理,最终唤醒等待队列中的进程。 - 被唤醒的进程检查是否超时,并返回相应结果给应用程序。
3.2 关键代码流程分析
3.2.1. 应用程序调用 select()
或 poll()
- 应用程序正在等待某个设备(如一个按键设备)的输入事件,例如按键被按下。它通过调用
select()
或poll()
函数来监视文件描述符fd
(代表设备)的可读状态。 - 内核在处理
select()
或poll()
时,会调用设备驱动程序的poll
函数(在此情况下是imx6uirq_poll
)。
3.2.2. 进入 imx6uirq_poll
函数
unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait) {
unsigned int mask = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
if (atomic_read(&dev->releasekey)) { /* 检查按键是否被按下 */
mask = POLLIN | POLLRDNORM; /* 如果按键被按下,返回可读标志 */
}
return mask;
}
- 挂起进程:在
poll_wait()
这一行,当前进程被挂起,并加入到设备的等待队列r_wait
中。此时,程序的执行停在这里,CPU 资源会被释放,用于执行其他任务。 - 等待事件:进程会在此挂起,直到以下两种情况之一发生:
- 按键事件发生:按键事件发生后,驱动程序会唤醒等待队列中的进程。
- 超时:如果在一定时间内没有事件发生,
select()
或poll()
会超时返回,进程从poll_wait()
继续执行。
3.2.3. 按键被按下,触发中断
- 当用户按下按键时,系统会触发一个硬件中断。驱动程序的中断服务函数
key0_handler
被调用。
static irqreturn_t key0_handler(int irq, void *dev_id) {
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->curkeynum = 0; // 记录当前按键编号
dev->timer.data = (volatile long)dev_id; // 传递设备指针
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); // 设置10ms的定时器
return IRQ_RETVAL(IRQ_HANDLED); // 返回处理中断
}
- 启动定时器:
key0_handler
启动一个 10 毫秒的定时器,用于按键消抖。这意味着按键的状态将在 10 毫秒后再次被检查,以确认按键是否稳定。
3.2.4. 定时器到期,调用 timer_function
- 10 毫秒后,定时器到期,内核调用定时器处理函数
timer_function
。
void timer_function(unsigned long arg) {
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); // 读取 GPIO 引脚的值
if (value == 0) { // 按键被按下
atomic_set(&dev->keyvalue, keydesc->value);
} else { // 按键松开
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); // 设置按键松开标志
}
// 唤醒等待队列中的进程
if (atomic_read(&dev->releasekey)) { // 检查按键是否松开
wake_up_interruptible(&dev->r_wait); // 唤醒等待队列中的进程
}
}
-
消抖处理:
timer_function
读取按键的 GPIO 引脚值来确认按键状态。- 如果按键持续按下,更新
keyvalue
(按键值)并继续等待松开。 - 如果按键松开,更新
keyvalue
并设置releasekey
标志,表示按键操作完成。
- 如果按键持续按下,更新
-
唤醒进程:如果
releasekey
标志被设置(即按键松开),timer_function
调用wake_up_interruptible(&dev->r_wait)
,唤醒在等待队列中挂起的进程。
3.2.5. 进程被唤醒,poll_wait
结束
- 按键事件唤醒:当等待队列中的进程被唤醒后,
poll_wait()
调用结束,poll()
函数返回。此时,imx6uirq_poll
函数中mask
的值为POLLIN | POLLRDNORM
,表示此设备的数据可读,select()
或poll()
函数会返回告诉应用程序设备已准备就绪。
if (atomic_read(&dev->releasekey)) { /* 按键按下 */
mask = POLLIN | POLLRDNORM; /* 返回 POLLIN */
}
return mask;
- 超时唤醒:如果在指定的超时时间内没有按键事件发生,
select()
或poll()
会因超时而返回。进程从poll_wait()
继续执行,但此时poll()
返回值会指示没有数据可读(即超时)。
3.2.6. 应用程序继续执行
- 读取按键数据:应用程序从
select()
或poll()
返回,进而调用read()
函数从设备中读取按键数据,并进行处理。
ret = read(fd, &data, sizeof(data)); // 读取按键数据
if (ret < 0) {
// 读取错误处理
} else {
if (data)
printf("key value=%d\r\n", data); // 处理按键数据
}
- 等待下一个事件:处理完按键事件后,应用程序再次进入循环,调用
select()
或poll()
,等待下一个按键事件的发生。
3.3 关键代码总结
- 进程挂起与唤醒:当设备没有数据可读时,
poll_wait()
会将进程挂起,并将其加入等待队列。此时,CPU 会去执行其他任务,而挂起的进程则处于睡眠状态。 - 中断与定时器:按键按下时触发中断,中断服务函数启动一个定时器来进行按键消抖。定时器到期后,处理按键状态并唤醒等待队列中的进程。
- 进程继续执行:进程被唤醒后,继续执行
poll_wait()
之后的代码。如果按键事件发生,进程从poll()
返回并处理数据;如果超时,进程也会从poll()
返回,但表示没有数据可读。
通过这种机制,系统能够高效地处理按键事件,确保进程在等待事件时不会浪费 CPU 资源,并且能够及时响应事件的发生。