一、中断
学习单片机中的中断是非常重要的,因为中断可以让单片机在执行程序的同时响应外部事件,提高系统的实时性和效率。大家在学习单片机的时候例如stm32,了解了中断的概念、原理和种类。学习了中断的工作方式、中断向量表、中断优先级等基本知识。用开发板或仿真软件搭建了一个简单的单片机系统,然后编写了一些简单的程序来了解如何配置和使用中断。并尝试编写中断服务程序(ISR)来处理不同类型的中断请求。也通过一些实验来验证中断的工作原理,例如通过外部按键触发外部中断、定时器中断等。我也尝试了串口中断、ADC中断等不同类型的中断应用。其实有了这个基础,再来学习Linux中的中断就开朗了。
通过阅读内核源码中关于中断处理的部分,理解中断的注册、处理和响应流程。尝试编写一些简单的内核模块,包括注册中断处理程序和处理中断请求的代码。学习如何在内核中注册中断处理函数,并与设备驱动程序结合来实现中断处理。我了解到Linux 内核提供了完善的中断框架,我们只需要申请中断,然后注册中断处理函数即可,使用非常方便,不需要一系列复杂的寄存器配置。
在本书中,我们进行了一个实验:如何在 I.MX6U-ALPHA 开发板上使用中断的方式驱动 KEY0 按键,并通过定时器来实现按键消抖,最终在应用程序中读取按键值并通过终端打印出来。这个实验将帮助我们学习 Linux 内核中断的使用方法。编写一个内核模块,注册中断处理函数,并在中断处理函数中实现按键消抖逻辑。同时,配置定时器来定时检测按键状态。编译完成后,将模块加载到内核中,并检查是否加载成功。编写一个应用程序,通过读取设备节点来获取按键值,并将按键值通过终端打印出来。最后按键值正确地被读取和打印出来了。通过这个实验学习,深入理解了 Linux 内核中断处理的机制和流程。
二、阻塞与非阻塞
在Linux中,阻塞和非阻塞是用来描述I/O操作的两种模式。
这里的“IO”并不是我们学习 STM32 或者其他单片机的时候所说的“GPIO”(也就是引脚)。这里的 IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂
起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。
阻塞模式: 在阻塞模式下,当一个I/O操作发起后,程序会一直等待直到操作完成才返回结果。这意味着程序在执行I/O操作期间会被挂起,无法继续执行其他任务。在这种模式下,如果I/O操作耗时较长,程序就会被阻塞,影响整体的性能。
非阻塞模式: 在非阻塞模式下,程序在发起一个I/O操作后会立即返回,而不会等待操作完成。程序可以继续执行其他任务,然后通过轮询或者回调等机制来查询I/O操作的状态。这样可以避免程序被长时间阻塞,提高系统的并发性能。还可以通过设置文件描述符的属性来实现阻塞和非阻塞模式。使用系统调用fcntl
或者ioctl
可以设置文件描述符为非阻塞模式。另外,也可以使用select
、poll
、epoll
等多路复用机制实现非阻塞I/O操作。
在上一节中断实验中imx6uirqApp 这个应用程序的 CPU 使用率竟然高达 99.6%,这仅仅是一个读取按键值的应用程序,这么高的 CPU 使用率显然是有问题的,原因就在于我们是直接在 while 循环中通过 read 函数读取按键值,因此 imx6uirqApp 这个软件会一直运行,一直读取按键值, CPU 使用率肯定就会很高。最好的方法就是在没有有效的按键事件发生的时候,imx6uirqApp 这个应用程序应该处于休眠状态,当有按键事件发生以后 imx6uirqApp 这个应用程序才运行,打印出按键值,这样就会降低 CPU 使用率,本小节我们就使用阻塞 IO 来实现此功能。一直在 while 循环中主动读取按键值,导致 CPU 被持续占用。为了降低 CPU 使用率,书中又使用阻塞 I/O 的方式来实现按需读取按键值的功能,让应用程序在没有按键事件时处于休眠状态,只有当有按键事件发生时才唤醒应用程序进行处理。
在应用程序中初始化一个等待队列,例如命名为 r_wait,用于在没有按键事件发生时让应用程序处于休眠状态。当定时器中断处理函数执行时,表示有按键按下事件发生。在处理函数中,首先判断是否是一次有效的按键事件。在判断为有效按键事件时,调用 wake_up 或 wake_up_interruptible 函数来唤醒等待队列 r_wait。应用程序的主循环中,可以在等待队列 r_wait 上进行等待,当被唤醒时再去读取按键值并处理。当没有按键事件发生时,应用程序会在等待队列上休眠,不会占用 CPU 资源,只有当有按键事件发生时才会被唤醒。通过使用 wake_up 或 wake_up_interruptible 唤醒等待队列的方式,可以实现按需唤醒应用程序处理按键事件,避免应用程序一直处于忙碌状态导致 CPU 使用率过高的问题。这种方法也符合事件驱动的编程思想,使系统更加高效和稳定。采用这种方法来改进按键事件处理逻辑,提高系统的效率和性能,同时降低 CPU 使用率,使系统更加节能和可靠。
还可以在应用程序中使用 select 函数,监听文件描述符的状态变化,当按键事件发生时再去读取按键值。在 select 函数中设置适当的超时时间,以便定时唤醒应用程序检查按键事件。应用程序的主循环中,首先调用 select 函数阻塞等待按键事件的发生,当有按键事件发生时再调用 read 函数读取按键值并打印。当没有按键事件发生时,应用程序会处于阻塞状态,不会占用 CPU 资源,只有当有按键事件发生时才会被唤醒。使用阻塞 I/O 的方式,可以有效降低应用程序的 CPU 使用率,让应用程序在需要处理事件时才会被唤醒,提高系统的效率和性能。
三、异步
异步通知是指在事件发生时通知接收方,而无需接收方主动轮询或等待事件发生。常见的异步通知方式包括信号、回调函数、消息队列等。信号是一种软件中断,用于通知进程发生了某种事件。当进程接收到信号时,会中断当前执行的程序流,转而执行信号处理函数。信号可以同步或异步发送给进程。在操作系统中,信号是一种常用的实现异步通知的方式。当某个事件发生时,可以通过发送信号的方式通知接收方进行处理。当进程接收到信号时,会立即中断当前执行的程序流,转而执行信号处理函数。这种机制实现了异步通知的效果,使得进程可以及时响应事件。使用异步通知可以避免进程不断轮询或阻塞等待事件发生,提高系统效率和响应速度。信号作为一种异步通知机制,在进程间通信和事件处理中发挥着重要作用。使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal 函数来设置指定信号的处理函数。
示例代码 Linux 信号
#define SIGHUP 1 /* 终端挂起或控制进程终止 */
#define SIGINT 2 /* 终端中断(Ctrl+C 组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4 /* 非法指令 */
#define SIGTRAP 5 /* debug 使用,有断点指令产生 */
#define SIGABRT 6 /* 由 abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT 指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号 1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号 2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z 组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过 CPU 资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */
实验是根据上一节阻塞实验的实现按键按下后驱动程序向应用程序发送 SIGIO 信号,应用程序接收到信号后读取并打印按键值。在驱动程序中,设置按键触发时发送 SIGIO 信号给应用程序。可以通过 fasync_helper 函数来实现异步通知的注册。在应用程序中,注册处理 SIGIO 信号的处理函数。可以使用 signal 函数来注册信号处理函数。在信号处理函数中,处理接收到的 SIGIO 信号。在信号处理函数中读取按键值并打印。应用程序的主循环中,可以继续在等待队列 r_wait 上等待,同时等待接收到 SIGIO 信号时执行相应的处理。
小结:
此次阅读解锁了诸多概念,具体还得多在虚拟机上实践才能消化,初次阅读,本文中内容如有不当还请大佬们指出。