《linux kernel develpoment》第七章

本文深入探讨《linux内核设计与实现》中关于中断和中断处理的内容,包括中断概念、中断处理程序、中断上下文、中断处理机制的实现、注册和释放中断处理程序以及中断控制。介绍了中断处理的上下半部、共享中断处理程序和如何编写中断处理程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《linux内核设计与实现》

中断和中断处理

1、中断

中断是硬件发出通知给处理器。
键盘输入->处理器接收到中断->向操作系统反映此信号到来->操作系统做出处理。
对于不同的设备对应的中断不同,每个中断都会有一个唯一的数字标志,这使得系统才能给不同中断提供对应的中断处理程序。
中断值通常被称为中断请求(IRQ)线,每个IRQ线都会被关联一个数值量,如PC机上,IRQ 0是时钟中断,IRQ 1是键盘中断,但有些并不是这么严格定义的,如在PCI总线上的设备而言,中断是动态分配的,无论如何,都需要让内核知道对应的信息。

2、中断处理程序

在处理特定中断时,内核需要执行一个中断服务程序(ISR)。中断处理程序与其他内核函数区别于前者是被内核用来响应中断,运行于中断上下文的特殊的上下文中,中断上下文也可称为原子上下文,该上下文的代码不可阻塞。

3、上半部与下半部的对比

想要中断运行快,又想中断处理程序工作量多,因此将中断分为上下半部分,上半部分主要是对接收的中断进行应答,这一部分是对其他所有中断禁止情况下实现的,能够允许稍后执行的推到下半部分执行,此后内核会在合适的时机执行。
如网卡:当网卡接收到数据包之后,需要通过中端通知内核,由于数据包一般都很大,而内存缓冲区是固定的,如果不及时处理可能就会导致内存溢出,因此内核上半部就需要及时缓存数据,而对于数据包的解析和处理就放于下半部。

4、注册中断处理程序

中断处理程序是管理硬件的驱动程序的组成部分,如果需要使用中断,则需要注册一个中断处理程序。

/***********************************************************
* irq表示要分配的中断号,1、通过探测获取,2、通过动态确定
* handler指向实际的中断处理程序在第5节
* 其他参数在4.1小节
***********************************************************/
int request_irq(unsigned int irq,irq_handler_t handler, unsigned long flags, const char *name, void *dev);
4.1 中断处理程序标志

1.flags
在request_irq函数中有一个flags参数,其决定了触发方式,其是一个或者多个标志的位掩码,定义在<linux/interrupt.h>中
IRQF_DISABLED:标志位被设置后,意味着内核在处理中断处理程序期间要禁用其他中断,如果没设置则表示出本身之外其他任何中断可以同时执行。
IRQF_SAMPLE_RANDOM:标志这个设备产生的中断对内核的熵池有贡献。内核熵池负责提供从各种随机事件导出的真正的随机数,那么来自该设备的中断间隔时间就会最为熵填充到熵池。若我们的设备是以一种预知的速率产生中断(如定时器中断)或者受到外部供给,那么就不需要设置,反之大部分的设备中断速率都是不可知的,则可能成为一个较好的熵源。
IRQF_TIMER:系统定时器中断处理。
IRQF_SHARED:表示可以在多个中断处理程序之间共享中断线,如果给定的irq相同则必须设置该位。
IRQF_TRIGGER_FALLING 下降沿
IRQF_TRIGGER_RISING 上升沿
IRQF_TRIGGER_LOW 低电平
IRQF_TRIGGER_HIGH 高电平
2.name
与中断相关的设备的ASCII文本表示,如键盘中断对应的值为"keyboard"。
3.dev
用于共享中断线。如果中断线是被共享的,那么就需要设置,反之设置为NULL。
内核每次调用中断处理程序handler时会把该参数传给他,在实际中,常常是将设备结构体传入进去。
4、request_irq
成功返回0,失败返回非0
如果有错误发生,指定的中断程序不会被注册,常见的是-EBUSY,表明该中断线正在被使用。

4.2 一个中断例子

注意在注册中断处理函数之前就必须先对硬件进行初始化。

request_irq():
if (request_irq(irqn, my_interrupt, IRQF_SHARED, "my_device", my_dev)) { 
	printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn); 
	return -EIO;
}
4.3 释放中断处理程序

在写在驱动程序时,需要注销相应的中断处理程序,并且释放中断线。

//irq为申请的中断号
//如果irq为非共享的则设置为NULL,或者直接传入我们的设备结构体
void free_irq(unsigned int irq, void *dev);

如果指定的中断线不共享,则该函数删除处理程序的同时将禁用这条中断线,如果是共享的则仅仅删除dev对应的程序。

5、编写中断处理程序

中断处理程序声明:

/*** 中断处理程序handler的原型 ***/
typedef irqrenturn_t (*irq_handler_t)(int,void *);
//中断处理程序声明
//第一个为中断号,第二个为通用指针,必须与request_irq的dev同个类型。
static irqreturn_t intr_handler(int irq, void *dev)

注意在中断处理程序声明中,handler要求的参数类型必须和request_irq所匹配,对于dev而言主要可以给同一个中断处理函数处理不同的设备。
在这里加入static标记主要是该函数从来不会被其他的.c或者.h文件代码直接使用。

5.1 共享的中断处理程序

1、在共享的中断处理程序中,flags标志必须设置IRQF_SHARED标志。
2、对于注册中断处理程序,dev必须是唯一的。
3、中断程序必须能够判断设备是否发生中断,需要硬件支持也需要程序上相关的逻辑。
当内核接收到中断之后,他会依次调用共享中断线上每一个处理程序,因此一个处理程序必须知道其是否为这个中断负责。

5.2 中断处理程序实例

6、中断上下文

相比于进程上下文执行,进程上下文是指一种内核所处的操作模式,此时内核代表进程执行,在进程上下文中用current宏关联当前的进程。进程上下文可以休眠也可调用调度程序。
中断上下文与进程没有什么关系,也没有current宏,其没有后备进程,也不允许睡眠。 因为中断程序是打断了其他代码的执行甚至可能是打断了其他的中断程序的执行,这就要求中断程序尽可能迅速、简介,把繁琐的工作放在中断的下半部来执行,由内核觉定在更合适的时间执行。
最初,中断处理程序并不拥有自己的栈,他们共享所有中断进程的内核栈,在32位体系结构上有两页,一页为4KB。在2.6版本之后,内核栈大小减小为了1页,这样子减轻了内存的压力,但为了应对栈的减小,中断处理也有了自己的中断栈,大小为1页,每个处理器有一个。

7、中断处理机制的实现

正如下图所示,若有一个中断产生且该中断线是激活的,中断控制器就会把中断发往处理器,处理器就会跳到预先设置好的中断处理程序的入口点。
在这里插入图片描述
在内核中,对于每条中断线,处理器都会跳到对应的唯一的位置,接着保存我们被打断任务的寄存器的值,调用do_IRQ()函数,pt_regs结构包含原始寄存器的值,中断的值也会得以保存,因此do_IRQ可以将其提取得到。

//do_IRQ函数
unsigned int do_IRQ(struct pt_regs regs)

计算出中断号后,do_IRQ会对所接受的中断应答,禁止这条线上中断传递,do_IRQ需要确保在这条线上有效的处理程序,而且这个程序已经启动,但没有执行。由handle_IRQ_event来运行这个中断程序,该函数存放于kernel/irq/handler.c

/**
* handle_IRQ_event - irq action chain handler
* @irq: the interrupt number
* @action: the interrupt action chain for this irq
*
* Handles the action chain of an irq event
*/ 
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) 
{
	irqreturn_t ret, retval = IRQ_NONE; 
	unsigned int status = 0;
	//如果指定了IRQF_DISABLED就必须让中断处理程序在中断禁止的状态下运行
	if (!(action->flags & IRQF_DISABLED)) 	
		local_irq_enable_in_hardirq();
	do {
		trace_irq_handler_entry(irq, action); 
		ret = action->handler(irq, action->dev_id); 
		trace_irq_handler_exit(irq, action, ret);
		switch (ret) { 
			case IRQ_WAKE_THREAD:
				/*
				* Set result to handled so the spurious check
				* does not trigger.
				*/ 
				ret = IRQ_HANDLED;
				/*
				* Catch drivers which return WAKE_THREAD but
				* did not set up a thread function
				*/ 
				if (unlikely(!action->thread_fn)) {
					warn_no_thread(irq, action); 
					break;
				}
				/*
				* Wake up the handler thread for this
				* action. In case the thread crashed and was
				* killed we just pretend that we handled the
				* interrupt. The hardirq handler above has
				* disabled the device interrupt, so no irq
				* storm is lurking.
				*/ 
				if (likely(!test_bit(IRQTF_DIED,&action->thread_flags))) { 
					set_bit(IRQTF_RUNTHREAD, &action->thread_flags); 
					wake_up_process(action->thread);
				}
			/* Fall through to add to randomness */ 
			case IRQ_HANDLED:
				status |= action->flags; 
				break;
			default:
				break; 
			}
		retval |= ret; 
		action = action->next;
	} while (action);
	if (status & IRQF_SAMPLE_RANDOM) 
		add_interrupt_randomness(irq);
	local_irq_disable();
	return retval; 
}

8、/proc/interrupts

procfs是一个虚拟文件系统,只存在于内核内存,一般安装于/proc目录,在procfs中读写文件都需要调用内核函数,模拟真实文件的读写。与此相关例子为/proc/interrupts文件,统计着系统中与中断相关的统计信息。
CPU0
0: 3602371 XT-PIC timer
1: 3048 XT-PIC i8042
2: 0 XT-PIC cascade
4: 2689466 XT-PIC uhci-hcd, eth0
5: 0 XT-PIC EMU10K1
12: 85077 XT-PIC uhci-hcd
15: 24571 XT-PIC aic7xxx
NMI: 0
LOC: 3602236
ERR: 0
以上输出信息打印的是单处理器上PC输出信息:
第一列是中断器,第二列是接收中断数目的计数器,第三列是处理这些中断的中断控制器,第四列是中断相关的设备名字。

9、中断控制

9.1 禁止和激活中断

以下函数仅仅是禁止或激活当前处理器的本地中断。

//可在中断调用,也可以在进程上下文调用
local_irq_disable();
local_irq_enable();

以上两个函数若被两个函数调用:一个函数禁止中断,一个函数不禁止中断,那么随着内核的增长,想知道到达这个函数所有代码路径变得越来越困难,因此需要在禁止中断前保存中断系统的状态:

//这两个函数必须在同一个函数中使用,即保证flags必须驻留在同一个栈中,且flags不可传递给另外一个函数。
unsigned long flags;
local_irq_save(flags);
local_irq_restore(flags);
9.2 禁止指定中断线
//禁止中断控制器上指定的中断线
void disable_irq(unsigned int irq);
//禁止中断控制器指定的中断线,不等待当前的中断处理程序执行完毕就禁止 
void disable_irq_nosync(unsigned int irq); 
//使能特定的中断线
void enable_irq(unsigned int irq);
//等待特定的一个中断处理程序的退出,如果该处理程序正在执行,则该函数阻塞到该函数结束才返回 
void synchronize_irq(unsigned int irq);

注意,当我们调用了两次disable之后,相对应也要调用两次enable才能真正激活中断线。

9.3 中断系统的状态

在中断系统中,irqs_disable()函数用来询问本地处理器上的中断系统是否被禁止,若被禁止返回非0,禁止则返回0。
在linux/hardirq.h中定义了以下两个宏来检查内核的当前上下文的接口:

//如果正在中断上下文中,返回非0,正在进程上下文中,返回0
in_interrupt();
//如果当前正在执行中断处理程序,返回非0,否则返回0
in_irq();

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值