第7章 中断和中断处理

第7章 中断和中断处理

操作系统内核的核心任务,都包含有对连接到计算机上的硬件设备进行管理。轮询(polling)可能会是一种解决办法,但是会做许多无用功。中断机制,让硬件在需要的时候在向内核发出信号。

中断

中断使得硬件发出通知给处理器,如敲击键盘时,键盘控制器会通知操作系统有键按下。
本质是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚。中断控制器是个简单的电子芯片,其作用是将多路中断管线,采用复用技术只通过一个和处理器相连接的管线与处理器通信。
中断值通常被称为中断请求(IRQ)线,每个IRQ都会被关联一个数量值,在经典的PC机上,IRQ 0是时钟中断,IRQ 1是键盘中断。对于PCI总线设备而言,中断是动态分配的。

异常
异常和中断不同,在产生时必须考虑与处理器时钟同步,实际上也常常被称为同步中断。在执行错误指令(如被0除),或特殊情况(如缺页),必须靠内核来处理的时候,处理器就会产生一个异常。因为许多处理器体系结构处理异常和处理中断的方法类似,内核对他们的处理也很类似。对中断(由硬件产生的异步中断)和异常(由处理器本身产生的同步中断)处理大部分类似。

中断处理程序

在相应中断时,内核会执行一个函数,该函数叫中断处理程序(interrupt handler)或中断服务例程(interrupt service routine,ISR)。
运行与中断上下文中(或原子上下文),该上下文中的执行代码不可阻塞。中断可能随时发生,中断处理程序随时肯能随时执行,必须保证中断处理程序能够快速执行,从而保证尽可能快的恢复代码的执行。
对一些场景来说(网络设备),不仅要尽可能的快,还要完成的工作量多。

上半部和下半部

中断处理程序是上半部(top half),接收中断立刻执行,只做有严格时限的工作,例如对接收的中断进行应答或者复位硬件。
能够被允许稍后完成的工作是下半部(bottom half)。
网卡的上半部:网卡接收到数据包时,内核要尽快把数据拷贝到内存,然后读取更多的数据包。

注册中断处理程序

每一个设备都有相关的驱动程序,如果设备使用中断,相应的驱动程序就注册一个中断处理程序。
注册中断处理程序使用request_irq(),<linux/interrupt.h>。

/*
 * @brief 分配一条给定的中断线
 * @param[in] irq 要分配的中断号
 * @param[in] hanler 指针,指向这个中断的实际中断处理程序,只要操作系统接收到中断,该函数就被调用
 * @param[in] flags
 * @param[in] name
 * @param[in] dev
 *
 * 
 */
int requset_irq(unsigned int irq,
				irq_handler_t handler,
				unsigned long flags,
				const char *name,
				void *dev);
typedef irqreturn_t (*irq_handler_t)(int, void *);

中断处理标志

flags可以为0,也可以是一个或多个标志的位掩码。
定义在<linux/interrupt.h>。

标志描述
IRQF_DISABLE处理中断时是否禁用其它中断。一般不会设置,可用于希望快速执行的轻量级中断
IRQF_SAMPLE_RANDOM这个设备的中断是否对内核熵池(entroy pool)是否有贡献。内核熵池负责从各种随机事件中导出真正的随机数,需要的熵源也需要是随机的。中断产生的速率如果不可预知,可以设置该标志。如果设备以预知的速率产生中断(如系统定时器),或者受到外部攻击者的影响(如连网设备),不要设置该标志
IRQF_TIMER特别为系统定时器的中断处理而准备的
IRQF_SHARED多个中断处理程序之间共享中断线

第四个参数name是与中断设备相关的ASCII文本表示。如PC的键盘对应的值为keyboard。这个值会被/proc/irq或者/proc/interrupts文件使用,以便于与用户通信。
第五个参数dev用于共享中断线。dev会提供唯一的标志信息(cookie),用于指定中断处理程序。如果无需共享中断线,设为NULL即可。
request_irq()成功执行返回0。非零值表示出错,中断处理程序不会被注册。最常见的错误是-EBUSY,表示中断线已经被使用(或没有指定IRQF_SHARED)。
该函数可能会睡眠,不能在中断上下文或者其它不允许阻塞的代码中调用该函数。在注册的过程中,内核需要在/proc/irq文件中创建一个与中断对应的项,会调用proc_mkdir(),proc_mkdir()会调用proc_create()设置新的procfs项,proc_create()会调用kmalloc()函数来请求分配内存。kmalloc函数是可以睡眠的。

释放中断处理程序

void free_irq(unsigned int irq,voi*dev);

删除后,如果给定的中断线上已没有其它的中断处理程序,会禁用该中断。

编写中断处理程序

static irqreturn_t intr_handler(int irq,void *dev);

第一个参数如今已没有太大的作用。第二个参数是一个通用指针,和request_irq()的值必须一致。
linux在处理一个中断时会禁用相应的中断线,以防止在同一中断线上接收另一个新的中断。中断处理程序无需重入。

共享的中断处理程序

  1. request_irq()必须设置IRQF_SHARED标志。
  2. dev参数唯一。不能是NULL值。
  3. 中断处理程序必须能区分对应的设备是否真的产生了中断。既需要硬件的支持,也需要处理程序中有相关的处理逻辑。

中断处理程序实例

real-time clock(RTC)驱动程序 ,可以在drivers、char、rtc.c中找到。RTC是一个从系统定时器中独立出来的设备,用于设置系统时钟,提供报警器(alarm)或周期性的定时器。对于大多数体系结构而言,系统时钟的设置,通常只要向某个特定的寄存器或I/O地址写入想要的时间即可。但是报警器或周期性定时器通常靠中断实现。
rtc_init()

if(request_irq(rtc_irq,rtc_interrupt,IRQF_SHARED,"rtc",(void*)&rtc_port)){
	printk(KERN_ERR"rtc:cannot register IRQ %d\n",rtc_irq);
	return -EIO;
}

处理程序:

static irqreturn_t rtc_interrupt(int irq,void *dev)
{
	/* 状态保存在rtc_irq_data的低字节中,上一次中断号保存在其余字节中 */
	spin_lock(&rtc_lock);
	rtc_irq_data+=0x100;
	rtc_irq_data&=~0xff;
	rtc_irq_data|=(CMOS_READ(RTC_INTR_FLAGS)&0xF0);
	
	if(rtc_status&RTC_TIMER_ON)
		mod_timer(&rtc_irq_timer,jiffies+HZ/rtc_irq+2*HZ/100);
	spin_unlock(&rtc_lock);
	
	/* 执行设置好的回调函数 */
	spin_lock(&rtc_task_lock);
	if(rtc_callback)
		rtc_callback->func(rtc_callback->private_data);
	spin_unlock(&rtc_task_lock);
	wake_up_interruptible(&rtc_wait);

	kill_fasync(&rtc_async_queue,SIGIO,POLL_IN);
	
	return IRQ_HANDLED;
}

计算机一接收到RTC中断,就会调用这个函数。
这里使用到了两次自旋锁,第一次为了防止rtc_irq_data被SMP机器上的其它处理器同时访问,第二次避免rtc_calllback出现相同的情况。
rt_irq_data是无符号长整型数,存放有关RTC的信息,每次中断都会更新状态。

中断上下文

进程上下文是一种内核所处的操作模式,此时内核代表进程运行,如执行系统调用或者运行内核线程。在进程上下文中,current宏关联当前进程。进程是已进程上下文的形式连接到内核中的,进程可以睡眠,也可以调用调度程序。

当执行一个中断处理程序时,内核处于中断上下文。中断上下文和进程没有关系,也和current宏无关,current宏会指向被中断的进程。因为没有后备进程,中断上下文不可以睡眠(一旦睡眠,就不能对其进行重新调度)。因此,中断处理程序也不可以调用睡眠的函数。

因为中断程序打断了其它的代码,中断上下文的代码要迅速、简洁。

中断处理机制的实现

中断处理机制在linux中的实现非常依赖于体系结构。
硬件产生中断到内核的过程如下:

  1. 设备产生中断,通过总线把电信号发给中断控制器。
  2. 中断控制器收到电信号,如果对应的中断线是激活的(可以被屏蔽),把中断发送给处理器,大多数体系结构向处理器特定管脚发送给一个信号。
  3. 如果该中断没有被禁止,处理器接收到中断后,会立即停止自己正在做的事情,关闭中断系统,调到内存中预定义的位置开始执行那里的代码。位置有内核设定,是中断处理程序的入口点。
    处理中断:
unsigned int do_IRQ(struct pt_regs regs);

pt_regs结构包含原始寄存器的值,也包含中断的值,计算出中断号后,do_IRQ()对中断进行应答,禁止这条线上的中断传递。在普通的PC上,使用mask_and_ack_8259A()。
接下来,要确定这条中断线上有一个有效的处理程序,调用handle_IRQ_event()来运行为这条中断线所安装的处理程序。

/**
 * handle_IRQ_event -irq action chain handler kernel/irq/handler.c
 * @irq	the interrupt number
 * @action the interrupt action chain for the irq
 *
 * Handle 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;
	if(!(action->flags&IRQF_DISABLE))
		local_irq_enable_in_hardirq();
	do{
		trace_irq_handle_entry(irq,action);
		ret=action->handler(irq,action->dev_id);
		trace_irq_handle_exit(irq,action,ret);
		switch(ret){
		case IRQ_WAKE_THREAD:
			ret=IRQ_HANDLE;
			if(unlikely(!action->thread_fn)){
				warn_no_thread(irq,action);
				break;
			}
			if(likely(!test_bit(IRQTF_DIED,&action->thread——flags))){
				set_bit(IRQTF_RUNTHREAD,&action->thread_flags);
				wake_up_process(action->thread);
			}
		case IRQ_HANDLED:
			status|=action->flags;
			break;
		default:
			break;
		}
		retval|=ret;
		action=action->next;
	}while(action);
	if(status&IRQF_SAMPLERANDOM)
		add_interrupt_randomness(irq);
	local_irq_disable();
	return retval;
}
Created with Raphaël 2.3.0 开始 根据中断标志类型判断是否需要使能中断,如果是允许其它中断线就要把中断打开(处理器在接收到中断时把中断系统禁止) 中断线上所有的处理程序都要被处理(共享或者独占) 如果指定IRQF_SANPLE_RANDOM标志,还要为随机数产生器产生熵。 禁止中断,返回到do_IRQ() 结束

从handle_IRQ_event()返回之后,表明中断已被处理完毕,do_irq()会做清理工作并返回到初始入口点,然后跳转到ret_from_intr()。
ret_from_intr()类似初始入口代码,以汇编语言编写。此时处理完中断处理程序,将要返回用户进程或者内核空间,需要判断是否需要重新调度(进程调度的need_resched标志)。在重新调度的情况下,如果将要返回用户空间(中断了用户进程),会调用schedule()选择一个更合适的程序。如果返回内核空间(中断了内核),只用在preempt_count为0时,才会重新调用schedule()。
在schedule()返回之后,或者如果没有挂起的工作,原来的寄存器被恢复,回到被中断的点。
在x86上,初始的汇编例程位于arch/x86/kernel/entry_64.S(entry_32.S对应32位x86体系架构),C方法位于arch/x86/kernel/irq.c。其它所支持的结构与此类似。

/proc/interrupts

procfs是一个虚拟文件系统,它只存在与内核内存,一般位于/proc目录。
该文件存放的是系统中与中断相关的统计信息。

中断控制

<asm/system.h>、<asm/irq.h>中提供接口。
控制中断系统的原因归根结底是需要提供同步,通过禁止中断确保中断处理程序不被抢占,获取锁来防止其它处理器对共享数据的并发访问

禁止和激活中断

禁止当前处理器(仅是当前处理器)的本地中断

local_irq_disable();

使能本地中断

local_irq_anable();

通常以单个汇编指令实现,依赖体系结构。
不能简单的禁止或使能中断,而应该保存中断的状态且在之后将中断恢复。

unsigned long flag;
loacl_irq_save(flags);

local_irq_restore(flags);

禁止指定中断线

void disable_irq(unsigned int irq);
void disable_irq_noync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);

中断系统的状态

/*
 * 本地中断系统被禁止,返回非0,否则返回0。
 *  定义在<asm/system.h>中。
 */
 irqs_disable()

<linux/hardirq.h>定义的两个宏提供了一个用来检查内核上下文的接口。

/* 该宏最有用,内核处于任何类型的中断系统中,返回非0,说明正在执行中断处理程序,或者正在执行下半部处理程序。 */
in_interrupt()
/* 只有内核确实在执行中断处理程序时才返回非0。 */
in_irq()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值