中断是操作系统中至关重要的机制,它能够显著提高系统的响应性能和并发处理能力。
中断是指在 CPU 正常运行期间,由外部或内部事件引起的一种机制。当中断发生时,CPU会停止当前正在执行的程序,并转而执行触发该中断的中断处理程序。处理完中断处理程序后,CPU 会返回到中断发生的地方,继续执行被中断的程序。中断机制允许CPU在实时响应外部或内部事件的同时,保持对其他任务的处理能力。
一、中断的上下半部
中断的执行需要快速响应,但并不是所有中断都能迅速完成。此外,Linux 中的中断不支持嵌套,意味着在正式处理中断之前会屏蔽其他中断,直到中断处理完成后再重新允许接收中断,如果中断处理时间过长,将会引发问题。
而为了让系统可以更好地处理中断事件,提高实时性和响应能力,将中断服务程序划分为上下文两部分:
中断上文是中断服务程序的第一部分,它主要处理一些紧急且需要快速响应的任务。中断上文的特点是执行时间较短,旨在尽快完成对中断的处理。这些任务可能包括保存寄存器状态、更新计数器等,以便在中断处理完成后能够正确地返回到中断前的执行位置。
中断下文是中断服务程序的第二部分,它主要处理一些相对耗时的任务。由于中断上文需要尽快完成,因此中断下文负责处理那些不能立即完成的、需要更多时间的任务。这些任务可能包括复杂的计算、访问外部设备或进行长时间的数据处理等。
二、linux中断的API函数
2.1、request_irq
request_irq 函数是在 Linux 内核中用于注册中断处理程序的函数。它用于请求一个中断号(IRQ number)并将一个中断处理程序与该中断关联起来。
irq 参数用来指定要请求的中断号,中断号需要通过 gpio_to_irq 函数映射GPIO引脚来获得
irq_handler_t handler 参数是一个函数指针,指向了中断处理程序的函数。中断处理程序是在中断事件发生时调用的函数,用于处理中断事件
unsigned long flags:中断处理程序的标志位。这个参数用于指定中断处理程序的行为和属性,如中断触发方式、中断共享等。可以使用不同的标志位进行位运算来组合多个属性。常用的标志位包括:
IRQF_TRIGGER_NONE:无触发方式,表示中断不会被触发。
IRQF_TRIGGER_RISING:上升沿触发方式,表示中断在信号上升沿时触发。
IRQF_TRIGGER_FALLING:下降沿触发方式,表示中断在信号下降沿时触发。
IRQF_TRIGGER_HIGH:高电平触发方式,表示中断在信号为高电平时触发。
IRQF_TRIGGER_LOW:低电平触发方式,表示中断在信号为低电平时触发。
IRQF_SHARED:中断共享方式,表示中断可以被多个设备共享使用。
2.2、gpio_to_irq
gpio_to_irq 函数用于将 GPIO 引脚的编号(GPIO pin number)转换为对应的中断请求号(interrupt request number)。
2.3、free_irq
free_irq 函数用于释放之前通过 request_irq 函数注册的中断处理程序。它的作用是取消对中断的注册并释放相关的系统资源
2.4、中断服务函数
中断处理程序是在中断事件发生时自动调用的函数。它负责处理与中断相关的操作,例如读取数据、清除中断标志、更新状态等。
irqreturn_t handler(int irq, void *dev_id) 是一个典型的中断服务函数的函数原型。
在处理程序中,通常需要注意以下几个方面:
(1)处理程序应该尽可能地快速执行,以避免中断丢失或过多占用CPU时间。
(2)如果中断源是共享的,处理程序需要处理多个设备共享同一个中断的情况。
(3)处理程序可能需要与其他部分的代码进行同步,例如访问共享数据结构或使用同步机制来保护临界区域。
(4)处理程序可能需要与其他线程或进程进行通信,例如唤醒等待的线程或发送信号给其他进程。
三、代码示例
3.1、驱动层程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#define GPIO_PIN 101
// 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
printk(KERN_INFO "Interrupt occurred on GPIO %d\n", GPIO_PIN);
printk(KERN_INFO "This is irq_handler\n");
return IRQ_HANDLED;
}
static int __init interrupt_init(void)
{
int irq_num;
printk(KERN_INFO "Initializing GPIO Interrupt Driver\n");
// 将GPIO引脚映射到中断号
irq_num = gpio_to_irq(GPIO_PIN);
printk(KERN_INFO "GPIO %d mapped to IRQ %d\n", GPIO_PIN, irq_num);
// 请求中断
if (request_irq(irq_num, gpio_irq_handler, IRQF_TRIGGER_RISING, "irq_test", NULL) != 0) {
printk(KERN_ERR "Failed to request IRQ %d\n", irq_num);
// 请求中断失败,释放GPIO引脚
gpio_free(GPIO_PIN);
return -ENODEV;
}
return 0;
}
static void __exit interrupt_exit(void)
{
int irq_num = gpio_to_irq(GPIO_PIN);
// 释放中断
free_irq(irq_num, NULL);
printk(KERN_INFO "GPIO Interrupt Driver exited successfully\n");
}
module_init(interrupt_init);
module_exit(interrupt_exit);
3.2、linux中断使用API要点
// 将GPIO引脚映射到中断号
irq_num = gpio_to_irq(GPIO_PIN);
request_irq(irq_num, gpio_irq_handler, IRQF_TRIGGER_RISING, "irq_test", NULL)