当一个中断信号到达时,CPU必须停止它当前正做的工作,转而去做中断要求其做的事情。
中断分为同步中断和异步中断两种。
1、同步中断又称异常,是由CPU执行指令时由CPU控制单元产生的。异常又分两种:
(1)、 一种是由程序执行出错造成的,内核通过发送一个unix的信号来处理异常。
(2)、一种是由内核必须处理的异常条件产生的,比如缺页异常,内核执行恢复异常的所有步骤。
2、异步中断,通常我们就叫中断。由其他硬件设备按照CPU时钟信号随机产生。
中断处理程序的一般步骤:
一个中断处理程序的几个中断服务例程之间是串行执行的,并且在一个中断处理程序结束前,不应该再次出现这个中断,所有一般中断处理程序是先禁止该中断,然后处理中断,处理完成后在使能该中断。
有一些中断是可以延迟处理的,这种可延迟中断可以在开中断的情况下执行,执行时允许其他中断抢占他。把可延迟中断从中断处理程序中抽出来有助于使内核保持较短的响应时间。
Linux内核使用三种方法来处理这种可延迟的中断任务:可延迟函数(软中断和tasklets)以及工作队列。工作队列是工作在进程上下文中,可以睡眠,软中断和tasklets 是工作在中断上下文,不可以睡眠。本节只讨论软中断和tasklets。
软中断:
Linux 2.6 版本使用如下几个软中断,不同版本之间略有差异。但一下几个不同版本都包含。
HI_SOFTIRQ=0, 处理高优先级的tasklet
TIMER_SOFTIRQ, 时钟中断相关的tasklet.
NET_TX_SOFTIRQ, 内核把数据报文传送给网卡。
NET_RX_SOFTIRQ, 内核从网卡接收数据报文。
TASKLET_SOFTIRQ, 处理常规tasklet。
低下标代表高优先级。
内核中定义了softirq_vec数组来存放各种软中断。定义如下:
static struct softirq_action softirq_vec[32]__cacheline_aligned_in_smp;
数组元素为 softirq_action,一个元素代码一个软中断。不同的软中断号对应不同的数组的下标。
struct softirq_action
{
void (*action)(struct softirq_action *); //软中断发生时执行软中断的处理函数。
void *data; //软中断的处理函数的参数指针。
};
初始化软中断时调用函数 open_softirq().如下代码。
void open_softirq(int nr, void (*action)(struct softirq_action*),
void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}
另外一个跟软中断相关的关键字段是 32 位的 preempt_counte字段,用它来跟踪内核抢占和内核控制路径的嵌套,该字段放在每个进程描述符的 thread_info 字段中。用函数preempt_count()来返回该字段的值。
preempt_count字段 | |
位 | 描述 |
0~7 | 抢占计数器,记录显示禁用本地cpu内核抢占的次数,值为0时代表内核允许抢占。 |
8~15 | 软中断计数器。记录软中断被禁用的次数,0表示软中断被激活。 |
16~27 | 硬中断计数器。记录硬中断嵌套的层数。irq_entry()增加它的值,irq_exit()递减它的值。 |
28 |
当内核明确不允许发生抢占或内核正在中断上下文中运行时,必须禁止内核的抢占功能。为了确定当前进程是否能够被抢占,内核快速检查preempt_counte字段是否等于0.
另一个跟软中断相关的字段是每个CPU都有一个32位掩码的字段
typedef struct {
unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;
他描述挂起的软中断。每一位对应相应的软中断。比如0位代表HI_SOFTIRQ.
宏local_softirq_pending()来获取该字段的值。
使用函数raise_softirq()来激活软中断。
即把响应的软中断号对应的__softirq_pending中的位置1.表示该软中断被挂起。如果当前CPU不在中断上下文中,唤醒内核线程ksoftirqd来检查被挂起的软中断,然后执行相应软中断处理函数。
内核在如下几个点上检查被挂起的软中断:
1、当调用local_bh_enable()函数激活本地CPU的软中断时。条件满足就调用do_softirq() 来处理软中断。
2、当do_IRQ()完成硬中断处理时调用irq_exit()时调用do_softirq()来处理软中断。
3、当一个特殊内核线程ksoftirq/n被唤醒时,处理软中断。
软中断处理函数详解:
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
/*如果当前处于硬中断中,在硬中断处理函数退出时会调用irq_exit()函数来处理软中断,或当前软中断被禁用.所以in_interrupt()返回不为1 就没必要处理软中断,直接返回*/
if (in_interrupt())
return;
/*保持中断寄存器的状态并禁用本地CPU的中断*/
local_irq_save(flags);
/*取得当前cpu上__softirq_pending字段,获取本地CPU上挂起的软中断*/
pending = local_softirq_pending();
/*如果当前CPU上有挂起的软中断,执行__do_softirq()来处理软中断*/
if (pending)
{
__do_softirq();
}
/*恢复中断寄存器的状态*/
local_irq_restore(flags);
}
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART; //10
int cpu;
/*取得当前cpu上__softirq_pending字段,获取本地CPU上挂起的软中断*/
pending = local_softirq_pending();
/*debug 用,不讨论*/
account_system_vtime(current);
/*禁止本地cpu的软中断,现在本地cpu上挂起的软中断已经存入pending临时变量中了*/
__local_bh_disable((unsigned long)__builtin_return_address(0));
/*debug 用,不讨论*/
trace_softirq_enter();
/*取本地cpu id 号*/
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
/*清空本地cpu的__softirq_pending字段*/
set_softirq_pending(0);
/*开启本地cpu的硬中断*/
local_irq_enable();
/*循环执行被挂起的软中断处理函数。相应的软中断的处理函数存在数组softirq_ver[nr]中的元素 softirq_action->action中*/
h = softirq_vec;
do {
if (pending & 1) {
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
/*禁止本地CPU的硬中断*/
local_irq_disable();
/*取本地CPU的__softirq_pending,查看是否还有新的被挂起的软中断并且检查被挂起软中断的次数小于10次,如果条件满足,继续处理新的被挂起的软中断*/
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
/*如果有新的挂起的软中断并且处理循环次数已经够了10次,唤醒ksoftirq内核线程来处理软中断*/
if (pending)
wakeup_softirqd();
/*debug 用,不讨论*/
trace_softirq_exit();
account_system_vtime(current);
/*使能本地CPU的软中断*/
_local_bh_enable();
}