【学习日记】【第五十二章 Linux非阻塞IO实验】【流程图】——正点原子I.MX6U嵌入式Linux驱动开发

1 概述

本文主要是笔者根据《正点原子I.MX6U嵌入式Linux驱动开发》中 “第五十二章 Linux阻塞和非阻塞IO实验” 的程序绘制的流程图,该程序使用非阻塞IO实现了和五十一章实验同样的功能,并对函数流程进行了分析。
《【学习日记】【第五十一章 Linux中断实验】【流程图】——正点原子I.MX6U嵌入式Linux驱动开发》

《Linux I/O 多路复用机制详解》

2 完整流程图

应用程序
驱动程序
按下
松开
打开设备文件
等待按键事件
读取按键数据
参数是否正确?
main
打印错误信息并退出
打开设备文件
设备文件是否成功打开?
打印打开失败信息并退出
继续等待下一个事件
是否有事件发生?
超时处理
读取按键数据
数据是否有效?
处理错误
处理按键数据并打印
检查是否有按键事件
imx6uirq_poll
挂起进程并加入等待队列
等待事件或超时
cpu切换到其他任务
启动定时器以进行消抖
中断处理函数 key0_handler
返回处理中断状态
读取按键状态
定时器服务函数 timer_function
按键状态是否稳定?
更新按键值
设置释放按键标记
唤醒等待队列中的进程
是否超时?
进程被唤醒
返回超时结果
返回按键事件结果

3 关键代码分析

3.1 关键代码流程图

应用程序
驱动程序
按下
松开
超时唤醒
按键主动唤醒
超时
按键事件
等待按键事件
读取按键数据
检查返回值
继续等待下一个事件
处理超时逻辑
读取按键数据
处理按键数据
调用 imx6uirq_poll 函数
应用程序调用 select 或 poll
调用 poll_wait 挂起进程并加入等待队列
CPU 切换到其他任务
同时等待超时或按键事件
中断触发, 调用 key0_handler
按键事件发生
启动定时器, 延迟 10ms 进行按键消抖
定时器到期, 调用 timer_function
读取按键状态, 进行消抖处理
按键状态是否稳定?
更新按键值
设置释放按键标记
唤醒等待队列中的进程
超时唤醒 or 按键主动唤醒?
进程被唤醒
返回超时结果给应用程序
返回按键事件结果给应用程序

流程图说明

  1. 应用程序部分:

    • 应用程序调用 select()poll() 等待按键事件。
    • 根据返回值,应用程序决定是处理超时还是读取按键数据。
  2. 驱动程序部分:

    • 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 资源,并且能够及时响应事件的发生。

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__Witheart__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值