一、中断理论部分学习笔记:
1.中断图示:
上图列举了实现中断所需的中断控制器、EFLAGS寄存器、INTR以及NMI引脚,接下来一一解释一下每个部件的作用:
部件 | 作用 |
---|---|
中断控制器 | 通过引脚接收外部设备发来的中断并且通过INTR引脚传递给CPU |
IMR(中断屏蔽寄存器) | IMR在中断控制器中,共8位,每一位对应一个中断信号,若该位为0,信号为不可屏蔽 |
EFLAGS寄存器 | 该寄存器中有一个IF位,若该位是一,则INTR传来的中断不会被屏蔽 |
NMI引脚 | 中断控制器以及EFLAGS寄存器都对其不起作用,只有系统出现了严重的错误,才会通过NMI引脚传达,比如:内存电路校验错误,UPS电源发来的停电通知。CPU必须马上处理这些中断。 |
2.为每个中断进行编号:
其中0x1A——0xFF为自定义中断。
3.中断向量表:
实模式下,中断号由一个字节编码,故共有256个中断。地址需要两个16位寄存器来表示,故中断向量表中的每个表项(中断程序的入口地址)需要4字节来存储。
BIOS程序提供了基本输入输出的代码,其地址BIOS也是知道的,故BIOS会向中断向量表中填入自己代码的入口地址。并且其他硬件也会有BIOS程序,他们会把BIOS代码映射到内存中,接下来主板BIOS会将这些代码映射到中断向量表中。而剩余的中断号并没有定义入口地址,故BIOS写入一个统一的地址——iret(interrupt return)。
4.中断号产生的方式:
产生方式 | 举例 |
---|---|
自动生成 | 运算时,碰到除数为0,则自动生成0x00号中断 |
硬件产生 | 通过INTR和NMI来产生中断号 |
软件产生 | 若需要打断点,则在程序中通过指令int 0x03来产生中断 |
5.中断请求队列:
问题 | 回答 |
---|---|
产生的原因 | 由于硬件的限制,很多设备必须共享中断线。 |
如何注册 | 使用request_irq()函数将相应的中断服务程序挂入中断请求队列中。 |
中断线共享的数据结构 | 课本P138 |
6.中断执行的整个过程:
7、中断的下半部分处理机制:
上半部是处理硬件发出的请求,它必须在一个新的中断产生之前终止,这一部分做的工作比较少。而下半部分是运行时是允许中断请求的,而上半部分运行时是关中断的。下半部分(就是一些内核函数)留着稍后处理。
下半部分的执行机制目前也叫软中断机制。这中机制有两种实现方式,分别为小任务机制和工作队列机制。
-
中断的小任务机制:
中断是计算机系统中的一种重要机制,用于处理外部事件,例如硬件设备的状态变化、定时器的触发、网络数据包的到达等。 中断的小任务机制是一种用于处理中断事件的高效方法,其主要思想是在中断处理程序中尽量减少执行的代码量,以便尽快恢复到正常程序执行。这种机制通常包括以下关键步骤:
-
中断响应:当发生中断事件时,CPU会立即暂停正在执行的任务,跳转到中断处理程序的入口点。
-
中断处理程序:中断处理程序是专门设计的短小代码片段,用于快速响应中断并采取必要的措施,例如读取传感器数据、处理网络数据包、更新定时器等。这些处理程序通常执行得非常快速,以便尽早恢复正常操作。
-
中断服务例程(ISR):中断处理程序可能会调用一个或多个中断服务例程,这些例程执行特定的任务,并且也应该尽量短小和高效。
-
中断返回:一旦中断处理程序和相关的例程完成,CPU会执行中断返回指令,将控制权还给原来的任务,从中断事件中恢复正常运行。
中断的小任务机制的优点是响应速度快,适用于需要快速处理外部事件的应用场景。
-
-
工作队列机制:
工作队列机制是一种用于异步处理任务的方法,通常在内核级别或操作系统中使用。这种机制的目的是将耗时的任务从主程序流中分离出来,以提高系统的响应性和效率。以下是工作队列机制的关键要点:
-
工作队列:工作队列是一个任务队列,用于存储需要异步执行的任务。这些任务可以是任何需要后台处理的工作,如文件系统操作、设备驱动程序操作、数据处理等。
-
工作者线程:工作队列通常与一组工作者线程相关联。这些线程负责从工作队列中获取任务并执行它们。工作者线程可以是内核线程或用户级线程,取决于操作系统的实现。
-
任务调度:当有新任务添加到工作队列时,系统会安排工作者线程来处理这些任务。这允许主程序流继续执行而不受阻塞,因为工作队列中的任务会在后台执行。
-
完成通知:一旦任务完成,工作者线程通常会发送通知或信号,以便主程序或其他组件知道任务已完成。
工作队列机制的优点是它可以提高系统的并发性和响应性,允许将耗时的任务异步执行,而不会阻塞主程序。这对于需要处理多任务和多线程的系统非常有用。
总结起来,中断的小任务机制侧重于快速响应外部事件,而工作队列机制侧重于异步处理长时间运行的任务,以提高系统的效率和响应性。选择哪种机制取决于应用的需求和性能要求。
-
二、实践部分:
1.向内核中插入一个自定义中断:
#include <linux/interrupt.h>
#include<linux/irq.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
static int irq=13; //13号中断
// 自定义中断处理函数
static irqreturn_t myhandler(int irq, void *dev_id)
{
printk("the irq is :%d\n", irq); //irq是对应中断的中断号
printk("in the interrupt handler function\n");
return IRQ_HANDLED;
}
static int __init request_irq_init(void)
{
int result=0;
printk("into request_irq_init\n");
/*调用函数request_irq( )申请中断,irq指中断编号,irq_handler是中断处理函数,IRQF_DISABLED
是中断类型,“A_New_Device”指中断设备名,NULL指设备,设备为NULL说明设备不真实存在,NULL这个位置
与共享设备有关,用于标识中断服务程序*/
result=request_irq(irq, myhandler, IRQF_NO_SUSPEND, "A_New_Device", NULL);
printk("the result of the request_irq is: %d\n", result); //显示申请结果
printk("out request_irq_init\n");
return 0;
}
static void __exit request_irq_exit(void)
{
free_irq(irq, NULL); //释放申请的中断
printk("Goodbye request_irq\n");
return;
}
module_init(request_irq_init);
module_exit(request_irq_exit);
obj-m := interval.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
结果:
2.查看网卡每次产生中断的时间间隔:
#include<linux/module.h>
#include<linux/init.h>
#include<linux/interrupt.h>
#include<linux/kernel.h>
static int irq; //申请的中断号
static char *interface; //设备名称,本实验中使用ens33(网卡)
static int count = 0; //统计的总次数
//传递参数
module_param(interface,charp,0644);
module_param(irq,int,0644);
//中断服务程序
static irqreturn_t intr_handler(int irq,void *dev_id){
static long interval = 0; //记录时间间隔
if(count == 0)
interval = jiffies; //特殊处理第一次记录
interval = jiffies - interval; //时间差
printk("The interval between two interrupts is %ld\n",interval);
interval = jiffies;
count++; //总共统计的次数
return IRQ_NONE;
}
//模块初始化函数
static int __init intr_init(void){
//interface表示与中断相关的设备名,&irq提供唯一的标志信息
if(request_irq(irq,&intr_handler,IRQF_SHARED,interface,&irq)){
printk(KERN_ERR"Fails to register IRQ %d\n",irq);
return -EIO;
}
printk("%s Request on IRQ %d succeeded\n",interface,irq); //表示申请成功
return 0;
}
static void __exit intr_exit(void){
printk("The %d interrupts happened on irq %d",count,irq);
free_irq(irq,&irq); //删除自定义的中断
printk("Freeing IRQ %d\n",irq);
return;
}
module_init(intr_init);
module_exit(intr_exit);
MODULE_LICENSE("GPL");
解释一下参数:interface=ens33表示我们绑定的设备是网卡,irq=19表示申请该中断服务程序到19号中断中,即当网卡每次收发数据包时,都会触发19号中断,而19号中断线是共享中断线,并且共享中断线的数据结构irqaction中有属性dev_id会唯一地标识某个设备。因此,当中断发生时就会在19号中断的中断请求队列中执行dev_id为ens33的中断服务程序。