中断机制

一、中断概述
中断的目的就是让CPU能响应某种突发事件并做出处理。
如网卡收到了一个报文,需要通知CPU对该报文进行处理。这种一般称之为外部中断,让CPU与其他硬件设备能够配合工作。
另外一类中断(或者说异常),是由CPU内部触发的,如代码执行过程中发生了除0错误,访问了不存在的地址等,这种一般称之为内
部中断。
由于这两类中断的处理机制与流程基本一致,因此,从中断处理代码来说,通常不加以严格区分,简单的处理流程如下:保护现场->
中断处理->恢复现场。依赖于不同的处理,通常还伴有切换到中断栈,切换特权级等操作。


说起来是如此简单,但仔细考虑就会发现并不容易。

中断的一个特点就是不可预知,你不知道它什么时候来,会来多少个?不确定的事情通常来说是比较可怕的,你没办法确定某种处理

是否可靠有效。
由于中断处理程序会打断当前正在执行的代码,如果中断非常频繁(想象一个非常大的数量级),那么cpu的主要精力都在处理中断上,正常的
程序没有机会运行了,这在某些实时的嵌入式系统上是很可怕的。比如某个医疗设备会间隔一段时间(如毫秒级)检查病人的生命体征,发现
有不对劲的时候紧急报警,如果中断延迟了,本来希望在1s内能检测出来的结果到了几秒后才告警,已经晚了。


因此,有些系统对中断的响应采用了线程化, 中断到来仅仅是触发一个信号,让处理线程得到调度运行。线
程化带来的另外一个问题就是中断处理不及时、中断丢弃等。这就是软件设计的魅力,不停的有问题等着去解决。

另外一种处理就是轮询机制,如网口收到一个报文时,硬件会把报文放入BD队列中,但是并不触发一个中断,而是由

某个线程定时轮询BD队列,发现有报文到达才进行处理。网口只会在出错的情况下才触发中断(这个也是可选的,可配置网口是否使能)。


所以,中断处理程序要求尽可能的快也就不足为奇了。
外部事件也是分优先级的,检测到火灾的告警就要比很多普通事件更需要立即处理,所以,通常还要求中断处理是可被打断的,即低优


先级的中断处理能被高优先级的打断。但是,从代码角度看,很多操作是需要做互斥保护的,可能需要关中保护,在这期间,就很可能会丢失
了另外一个中断,甚至是“火灾告警类”的中断。想想后果吧:)
另外,由于中断处理程序不是在任务的上下文中运行,是不允许阻塞的。

如何把中断代码写得简单快速,真不是一件容易的事情。


突然想起一句话:天下武功,唯快不破。


二、LINUX下的中断处理机制
典型的中断硬件结构图如下:


各类设备<--->中断控制器<--->CPU。


相应地,内核对各部分的抽象如下:
1,设备响应中断:
/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @flags: flags (see IRQF_* above)
* @name: name of the device
* @dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @dir: pointer to the proc/irq/NN/name entry
* @thread_fn: interupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @thread_flags: flags related to @thread
*/
struct irqaction {
irq_handler_t handler;
unsigned long flags;
void *dev_id;
struct irqaction *next;
int irq;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;


各字段参考注释是比较清楚的,name和dev_id唯一的标识了一个设备。特别地,在共享irq的情况下(中断号是有限的,但设备可能比


中断号还多,只能多个设备共享一个中断号了),next指针指向了处理同一个中断号的下一个设备,


2,中断控制器
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);


void (*ack)(unsigned int irq);
void (*mask)(unsigned int irq);
void (*mask_ack)(unsigned int irq);
void (*unmask)(unsigned int irq);
void (*eoi)(unsigned int irq);


void (*end)(unsigned int irq);
int (*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int (*retrigger)(unsigned int irq);
int (*set_type)(unsigned int irq, unsigned int flow_type);
int (*set_wake)(unsigned int irq, unsigned int on);


void (*bus_lock)(unsigned int irq);
void (*bus_sync_unlock)(unsigned int irq);
};


通过函数名就可以看出,irq控制器主要是对某个中断进程操作,如启动、关闭、使能、去使能、屏蔽、设置中断相应的CPU等等。


3,CPU管理各类中断:
CPU是如何组织管理各个中断的呢?内核通过一个全局变量struct irq_desc irq_desc[NR_IRQS]对系统的中断进行管理,每个中断号


对应一个irq_desc[irq], struct irq_desc定义如下:
struct irq_desc {
struct irq_chip *chip;
void *handler_data;
void *chip_data;
...


irq_flow_handler_t handle_irq;
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */


unsigned int depth; /* nested irq disables */
unsigned int irq_count; /* For detecting broken IRQs */
...
} ____cacheline_internodealigned_in_smp;
可见,对于每一个中断,这里包含了中断处理程序,状态,统计值等。系统状态信息可通过如下命令查询:cat /proc/interrupts


相关的中断处理代码:
entry_64.S: 中断入口,汇编部分的处理。

irq.c:do_IRQ函数是中断处理的通用C代码,这里最终会调用到各个设备的中断处理函数。


三、中断的延迟执行
在前文说过,中断处理尽可能的快,但由于互斥、或者其他长路径代码的处理,或者一些不可预知的代码(如定时器的回调函数,大
量超时定时器等),都可能导致中断处理速度与预计的不一致。因此,内核引入了另外一种处理机制:把中断处理区分为两部分,俗称的上半
部与下半部。上半部指中断过来必须紧急处理的,下半部指不是那么紧急需要处理的。如时钟中断过来后更新系统jiffies值等必须的操作,
然后触发一个软中断(置标志位),在中断返回前会判断是否有未处理的软中断,有者处理软中断。
所以,系统的各类活动的优先级如下:
高优先级中断->低优先级中断->软中断(软中断有32个,从0开始扫描,也可以认为是有优先级的)->高优先级任务->低优先级任务。


软中断的主要处理代码在softirq.c,其主要处理函数为__do_softirq,该函数的处理非常简单,就是扫描标志位(一个UINT32的值)
,如果对应的bit置位了,说明该类软中断需要处理,则调用挂接的处理函数。
为防止不停的有中断过来并触发软中断,进行软中断处理,最终导致任务得不到调度饿死。系统目前最多只循环10轮,每轮扫描32个
比特位,如果发现超过10轮还有软中断待处理,则唤醒软中断守护线程。这种情况最容易出现的应该就是网口收发包的情况了,如网口不停的
接收到报文。
软中断处理函数在设计上要求是可重入的,即该处理函数可能会在多个CPU上同时调用,处理函数的编写就需要考虑互斥问题了。这对
于很多驱动代码来说就带来了复杂性。因此,系统基于软中断机制,引入了tasklet机制,该机制允许某个tasklet只在一个cpu上允许,当然,
不同的tasklet是可以同时运行的。既然tasklet是基于软中断,而软中断又是可同时触发的,tasklet又要求只能在一个CPU上运行,那怎么做
到呢?非常简单,CPU在运行某一个tasklet前,先判断该tasklet是否运行即可。代码实现如下:
参考tasklet_action的实现:运行一个tasklelt前先调用tasklet_trylock(t),如果成功,才执行真正的执行函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值