中断(Interrupt)与异常(Exception)
在MIPS-LINUX上存在Exception,Interrupt。其中Interrupt又有来自芯片内部(各个组件)和外部直接连接过来的中断。
Exception由CP0_CAUSE(ExeCode!=0)表示,共有31中;
Interrupt由CP0_CAUSE(ExeCode!=0)表示,只有一个。但是可以在外设寄存器的INT/MASK中表示,又32中可能。并且LINUX提供了中断共享机制。
中断相关的硬件
CPU提供若干个中断线,如Int2--6(6358最多支持5个外部中断,在cause的IP2-6中表示中断的状态,0和1是软件控制的中断,7是 timer中断);CPU的硬件在每个始终周期都会轮询这些中断线的状态(当linux内核禁止中断时例外)。CPU是否原因对某个中断源做出响应取决于 如下内容:
- SR(IE)为1;否则就是关中断了
- SR(EXL) (exception Ievel)和SR(ERL) (error level)没有置位。置位的话会禁止中断。
- SR(IM)对应于中断源的位置位,否则表示该中断被屏蔽
如果这三个条件都满足了,则CPU做如下操作:
- 设置EPC
- 设置SR(EXL),此操作强制CPU进入内核模式并且禁止中断;
- 设置Cause记录中断原因;如果是地址错误,还需要设置BadAddress
- CPU从异常入口点(Exception entry point,0x80000180--前4个字节在不同的平台上可能不同)取指令并执行;
中断IP2----7这5个中断源的分配:
2:处理芯片内部中断,这些中断可能是DG/USB/ETH/EMAC/MPI等。对于 WIRELSS,由于它是通过MPI连接到芯片的,所以他也在IP2上被处理。又由于这些中断源太多,因此需要某个地方来表示那个具体的设备发生了中断, 所有在外设的内部寄存器中有INT/MASK来表示那个外设产生了中断。
3/4/5/6:直接连接外部中断源。
Exception entry point
需要说明的是,Exception entry point的是多少?
在不同的模式下(不同的内存中)此入口点的地址是变化的。Linux正常运行时使用的是在RAM中的入口点,并且通常位于KSEG0内。常用的有:
EBASE+0x000---TLB
+0x200+... multiple interrupt entry
+ 0x180 外部中断---外部中断源用到
异常来源区指针ebase--Exception Base, 对应与CP0_EBASE(对应于CP0中的$15.1)
在内核中全局变量ebase指向了保存着中断处理函数的在内存中的位置。ebase由如下语句确定在内存中的位置:
if (cpu_has_veic || cpu_has_vint)
ebase = (unsigned long) alloc_bootmem_low_pages (0x200 + VECTORSPACING*64);
else
ebase = CAC_BASE; //第三种选择??
如果有外部中断控制器或者是使用了vectored interupt,则分配,否则使用固定地址;
vectored interrupt: 八个中断源每个表示一个外部中断。通过编程CPO_IntCtl(VS)*32字节表示各个中断之间的间隔;0表示所有的中断都由一个固定的入口点处理。
存在的问题:最多有8个(只有6个是给外部中断源用的);若同时有2个中断,则只能处理一个(数最大的)
使用场景:只有一两个中断源是经常用到的;实时性高的场合;
EIC:6个中断源组成一个6bit的数字,因此能表示多达63种外部中断(0表示没有中断,因此只有63种)。
特点:多外设场景;问题:实时性不好。
通用异常处理函数
对Linux而言,一个上层ISR可以简单地被视为一个“应用程序”,虽然这种程序仍然存在于Linux核心内。那底层的异常或中断是如何处理的呢?说来话长,但关键在于:traps.c和与traps相关的一个汇编语言文件----若CPU是ARM,此对应的文件是entry-armv.S;若用 MIPS,则 此文件是genex.S。
“硬件异常(hardware exception)”通常称作trap,它不同于软件中断,它是真正由硬件产生的中断。举MIPS为例,traps.c内的trap_init负责将最 底层的except_vec3_generic复制到“异常来源区”-位于SDRAM或ROM内,except_vec3_generic是硬件的异常处 理程序(exception handler),它和其它异常处理程序全部存在于entry-armv.S或genex.S内。
except_vec3_generic 内有exception_handlers,它是用来储存每一个硬件中断的lower- ISR,trap_init负责将所有的lower-ISR填入exception_handlers中。所有的lower-ISR都是存在于 entry-armv.S或genex.S内。硬件的lower-ISR称作handle_int,说来奇怪,所有硬件都使用这唯一的lower- ISR,这是因为CPU分配给硬件的中断源只有一个,不管是MIPS或ARM或PPC都是这样的。一有硬件异常或中断发生时,程序计数器(porgam counter;PC)会跳到“异常来源区”,执行except_vec3_generic,再跳到handle_int,最后跳到 plat_irq_dispatch-这就是high-ISR。
每一种CPU都有它们各自的plat_irq_dispatch,这是由软件工程师按照CPU缓存器和硬件线路的不同自行设计的,MIPS、ARM或PPC的plat_irq_dispatch都会不一样。
中断执行路径:
except_vec3_generic
|____handle_int
|______plat_irq_dispatch
中断处理的入口---kernel中对各个板卡相同的设置
当有中断或异常发生时,CPU就会跳转到一个设置好的地址,这个地址保存了对所有中断或异常的处理函数入口。
中断(异常)处理函数在内存中的位置
trap_init @./arch/mips/kernel/traps.c
|_set_handler(0x180, &except_vec3_generic, 0x80);----0x180的意义在上边已经说明过了。外部中断对应的中断入口地址。在MIPS32中对应地址0x80000180;即
此函数实际操作是memcpy((void *)(ebase + 0x180), except_vec3_generic, 128);
代码注释:Copy the generic exception handlers to their final destination;
所有的中断入口在此处理,所以在代码中也称为generic exception handlers。
此函数实现相当简单(32bit版本):
NESTED(except_vec3_generic, 0, sp)
.set push
.set noat
mfc0 k1, CP0_CAUSE //cause中的内容去处理放到k1中;
andi k1, k1, 0x7c //k1与0x7异常来源区c相与,得出第2到第6位中间的5bit的ExeCode,表明是何种异常
PTR_L k0, exception_handlers(k1) //找到ExeCode对应的中断处理函数地址,定义如下;
jr k0 //蹦过去,开始处理ExeCode定义的32个可能的中断
.set pop
END(except_vec3_generic)
unsigned long exception_handlers[32];-----32个中断处理函数的地址,索引是ExeCode
exception_handlers通过如下函数设置,需要的参数是exeCode和对应的处理函数
void *set_except_vector(int n, void *addr)
{
unsigned long handler = (unsigned long) addr;
unsigned long old_handler = exception_handlers[n];
exception_handlers[n] = handler;
if (n == 0 && cpu_has_divec) { //n=0说明这个Exception是一个Interrupt
*(volatile u32 *)(ebase + 0x200) = 0x08000000 |
(0x03ffffff & (handler >> 2));
flush_icache_range(ebase + 0x200, ebase + 0x204);
}
return (void *)old_handler;
}
在trap_init中,设置所有的
set_except_vector(1, handle_tlbm);
set_except_vector(2, handle_tlbl);
set_except_vector(3, handle_tlbs);
set_except_vector(4, handle_adel);
set_except_vector(5, handle_ades);
。。。。。
set_except_vector(26, handle_dsp);
硬件low level ISR:handle_int
对于中断向量0,各个板卡可以设置自己的
set_except_vector(0, handle_int);
handle_int: ./arch/mips/kernel/traps.c; 标准内核2.6.22;
ltvinIRQ: 在MIPS专用的kernel中这个函数在板卡目录下设置,如arch/mips/ltvin-boards/blue5g/irq.c
如果是中断,则由handle_int来处理的,通过如下语句定义:set_except_vector(0, handle_int);
NESTED(handle_int, PT_SIZE, sp)
mfc0 k0, CP0_STATUS
and k0, ST0_IE
bnez k0, 1f //如果禁止中断,则直接返回
eret
1:
SAVE_ALL
CLI //Move to kernel mode and disable interrupts()
TRACE_IRQS_OFF
LONG_L s0, TI_REGS($28)
LONG_S sp, TI_REGS($28) //当前的sp保存在thead_info的regs中;
PTR_LA ra, ret_from_irq //ra保存ret_from_irq地址,处理完后直接执行ret_from_irq
j plat_irq_dispatch
END(handle_int)
平台级中断分发函数irq_dispatch
相关寄存器CP0_CAUSE:ExeCode为0时表示外部中断(表3-3列出了所有中断和异常)。外部中断由IP段表示,中断是否使能由IM确定。当然,全局中断使能标志:SR(IE)
使用plat_irq_dispatch(名字可变)的原因:每个半卡有自己的特殊外设,这个外设在CPU中有对应的中断。在CAUSE寄存器的IP(interrupt pending)中可以获取外设对应中断的信息。
63XX的实现
./linux/arch/mips/brcom-boards/brcom963xx/irq.c
generic_handle_irq-----include/linux/irq.h
|______plat_irq_dispatch ---brcom-boards/brcom963xx/irq.c
119 asmlinkage void plat_irq_dispatch(void)---->对应于LETNEVNI的ltvin_irq_dispatch
120 {
121 u32 cause;
122
123 while((cause = (read_c0_cause() & read_c0_status() & CAUSEF_IP))) {
124 if (cause & CAUSEF_IP7)------------------------处理的顺序决定了中断的优先级,所以说优先级是由软件决定的。
125 {
126 do_IRQ(MIPS_TIMER_INT);
127 }
128 else if (cause & CAUSEF_IP2)
129 irq_dispatch_int();
130 #if defined(CONFIG_BRCOM96338) || defined(CONFIG_BRCOM96348) || defined(CONFIG_BRCOM96358) || defined(CONFIG_BRCOM96368)
131 else if (cause & CAUSEF_IP3)
132 #ifdef CONFIG_BRCOM_AMP
133 irq_dispatch_int();
134 #else
135 irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_0);----------EXTERNAL-外部中断源;
136 #endif /* CONFIG_BRCOM_AMP */
137 else if (cause & CAUSEF_IP4)
138 irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_1);
139 else if (cause & CAUSEF_IP5)
140 irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_2);
141 else if (cause & CAUSEF_IP6)
142 irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_3);
143 #endif
144 else if (cause & CAUSEF_IP0)
145 irq_dispatch_sw(INTERRUPT_ID_SOFTWARE_0);
146 else if (cause & CAUSEF_IP1)
147 irq_dispatch_sw(INTERRUPT_ID_SOFTWARE_1);
148 }
149 }
LETNEVNI的实现
LETNEVNI使用了自己的名字,BRCOM使用了LINUX常用的名字。只要对应于中断向量的0位置,什么名字都可以;
./ltvin-boards/blue5g/irq.c : set_except_vector(0, ltvinIRQ);------ltvinIRQ对应于BRCOM的handle_irq;名字无所谓,只要对应于vector中的0
./ltvin-boards/generic/int-handler.S: NESTED(ltvinIRQ, PT_SIZE, sp)-----ltvinIRQ在int-handler.S中用汇编实现
/*
* MIPS IRQ Source
* -------- ------
* 0 Software (ignored)
* 1 Software (ignored)
* 2 Combined hardware interrupt (hw0)---所有的外部中断都在这里处理
* 3 Hardware
* 4 Hardware
* 5 Hardware
* 6 Hardware
* 7 R4k timer
*/
.text
.set noreorder
.set noat
.align 5
NESTED(ltvinIRQ, PT_SIZE, sp)
SAVE_ALL
CLI
.set noreorder
.set at
jal ltvin_irq_dispatch
move a0, sp
j ret_from_irq
nop
END(ltvinIRQ)
void ltvin_irq_dispatch(struct pt_regs *regs)
{
u32 cause;
while((cause = (read_c0_cause()& CAUSEF_IP))) {
if (cause & CAUSEF_IP7)
do_IRQ(MIPS_TIMER_INT, regs);
else if (cause & CAUSEF_IP2)----内部中断
irq_dispatch_int(regs);
else if (cause & CAUSEF_IP3)
irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_0, regs);----外部中断
else if (cause & CAUSEF_IP4)
irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_1, regs);
else if (cause & CAUSEF_IP5)
irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_2, regs);
else if (cause & CAUSEF_IP6)
irq_dispatch_ext(INTERRUPT_ID_EXTERNAL_3, regs);
local_irq_disable();
}
}
两个处理函数
irq_dispatch_int------处理芯片内部的各个中断,包括ETH/EMAC/USB/MPI等
irq_dispatch_ext-----处理外部中断
再看一下中断的流程
except_vec3_generic
|____handle_int or ltvinIRQ
|____ plat_irq_dispatch or ltvin_irq_dispatch
|___irq_dispatch_int[for IP2]irq_dispatch_ext[for IP3,4,5,6]
|____do_IRQ(irq_num, xx)
现在 大问题是在do_IRQ中,irq_num从那里来?答案是来自外设的内部寄存器INT/MASK。
static void irq_dispatch_int(struct pt_regs *regs)
{
unsigned int pendingIrqs;
static unsigned int irqBit;
static unsigned int isrNumber = 31;
pendingIrqs = PERF->IrqStatus & PERF->IrqMask;----共有32中可能。
if (!pendingIrqs) {
printk("***no pending IRQ***/n");
return;
}
while (1) {
irqBit <<= 1;
isrNumber++;
if (isrNumber == 32) {
isrNumber = 0;
irqBit = 0x1;
}
if (pendingIrqs & irqBit) {
PERF->IrqMask &= ~irqBit; // mask
do_IRQ(isrNumber + INTERNAL_ISR_TABLE_OFFSET, regs); ----INTERNAL_ISR_TABLE_OFFSET=8,这里处理的是8---39的所有中断
break;
}
}
}
static void irq_dispatch_ext(uint32 irq, struct pt_regs *regs)
{
if (!(PERF->ExtIrqCfg & (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT)))) {
printk("**** Ext IRQ mask. Should not dispatch ****/n");
}
/* disable and clear interrupt in the controller */
PERF->ExtIrqCfg |= (1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_CLEAR_SHFT));
PERF->ExtIrqCfg &= ~(1 << (irq - INTERRUPT_ID_EXTERNAL_0 + EI_MASK_SHFT));
do_IRQ(irq, regs);
}
备注:所有外设的中断号
#define INTERRUPT_ID_SOFTWARE_0 0-----两个软件控制的中断,可用在两个核的通信上
#define INTERRUPT_ID_SOFTWARE_1 1
/*=====================================================================*/
/* BRCOM6348 External Interrupt Level Assignments */
/*=====================================================================*/
#define INTERRUPT_ID_EXTERNAL_0 3-------外设使用的中断
#define INTERRUPT_ID_EXTERNAL_1 4
#define INTERRUPT_ID_EXTERNAL_2 5
#define INTERRUPT_ID_EXTERNAL_3 6
/*=====================================================================*/
/* BRCOM6348 Timer Interrupt Level Assignments */
/*=====================================================================*/
#define MIPS_TIMER_INT 7----------硬件定时器使用的中断,board specific timer interrupt,每次增加一个jiffies
/*=====================================================================*/
/* Peripheral ISR Table Offset */
/*=====================================================================*/
#define INTERNAL_ISR_TABLE_OFFSET 8-------答应这个数的都是某个外设的中断了,这些中断在2--6某个中断上以共享的方式使用,最终通过do_IRQ(irq, xx)的方式执行
/*====================================================================*/
/* Logical Peripheral Interrupt IDs */
/*=====================================================================*/
#define INTERRUPT_ID_TIMER (INTERNAL_ISR_TABLE_OFFSET + 0)
#define INTERRUPT_ID_SPI (INTERNAL_ISR_TABLE_OFFSET + 1)
#define INTERRUPT_ID_UART (INTERNAL_ISR_TABLE_OFFSET + 2)
#define INTERRUPT_ID_ADSL (INTERNAL_ISR_TABLE_OFFSET + 4)
#define INTERRUPT_ID_ATM (INTERNAL_ISR_TABLE_OFFSET + 5)
#define INTERRUPT_ID_USBS (INTERNAL_ISR_TABLE_OFFSET + 6)
#define INTERRUPT_ID_MPI (INTERNAL_ISR_TABLE_OFFSET + 24)------wireless通过此接口
等等
内部寄存器
内部寄存器的基址
内部寄存器专门针对不同的部件进行配置,如配置ETH,USB,EMAC, MPI, AC97等。
这些寄存器分布复杂,查看手册时看到的都是某个地址对应的寄存器。代码引入了一种简单的访问方法。
在文件opensource/include/brcom963xx/6348_map_part.h中定义了内部寄存器的分布情况,即map。不同系列的处理器对应的文件只是名字的不同,如6358_map_part.h。
#define PERF_BASE 0xfffe0000------外设的基地址
#define TIMR_BASE 0xfffe0200------timer的基地址
#define UART_BASE 0xfffe0300
#define GPIO_BASE 0xfffe0400
#define USB_OHCI_BASE 0xfffe1b00
#define USBH_CFG_BASE 0xfffe1c00
#define MPI_BASE 0xfffe2000
#define MEMC_BASE 0xfffe2300
外设对应与结构PerfControl也定要在shared/opensource/include/brcom963xx/6348_map_part.h,它定义了从此地址开始所有的寄存器。使用的时候只需要PERF_BASE->reg_name的方法即可。
do_IRQ
系统初始化期间的相关设置
static struct irq_chip brcom_irq_chip = {
.name = "BRCOM63xx",
.enable = enable_brcom_irq,
.disable = disable_brcom_irq,
.ack = disable_brcom_irq,
.mask = disable_brcom_irq,-------------mask对应与disable
.mask_ack = disable_brcom_irq,
.unmask = enable_brcom_irq-----------unmask对应与enable;如果定义了IRQ_DISABLED,则unmask为空;
};
void __init arch_init_irq(void)
{
int i;
for (i = 0; i < NR_IRQS; i++) {
set_irq_chip_and_handler(i, &brcom_irq_chip, handle_level_irq);----在do_IRQ中被间接调用;do_IRQ-->generic_handle_irq-->desc->handle_irq
}
clear_c0_status(ST0_BEV);
change_c0_status(ST0_IM, ALLINTS_NOTIMER);
}
在plat_dispatch_irq函数中,无论是外部中断还是内部组建的中断,最终都由do_IRQ处理。
---------------------------------------------------------------------------------------------
#define do_IRQ(irq) /
do { /
irq_enter(); /
__DO_IRQ_SMTC_HOOK(irq); /
generic_handle_irq(irq); /
irq_exit(); /
} while (0)
中断处理函数在generic_handle_irq中执行
irq_exit处理中断退出时需要的操作,以及softirq
static inline void generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_desc + irq; //找到注册的中断
if (likely(desc->handle_irq))
desc->handle_irq(irq, desc); //-注册过的,直接调用irq_handler,就是handle_IRQ_event;由set_irq_chip_and_handler(i, &brcom_irq_chip, handle_level_irq);在arch_init_irq时注册
else
__do_IRQ(irq); //默认中断处理函数
#endif
}
/**
* handle_level_irq - Level type irq handler
* @irq: the interrupt number
* @desc: the interrupt description structure for this irq
*
* Level type interrupts are active as long as the hardware line has
* the active level. This may require to mask the interrupt and unmask
* it after the associated handler has acknowledged the device, so the
* interrupt line is back to inactive.
*/
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
struct irqaction *action;
mask_ack_irq(desc, irq);-----desc->chip->mask(irq); & desc->chip->ack(irq); [或desc->chip->mask_ack(irq);合二为一]这里就是disable_brcom_irq
action = desc->action;-----获取IRQ对应的action
desc->status |= IRQ_INPROGRESS;
desc->status &= ~IRQ_PENDING;
action_ret = handle_IRQ_event(irq, action);---在这里处理action
desc->status &= ~IRQ_INPROGRESS;
if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)----和下边的绿色部分一起;如果没有定义IRQ_DISABLED,则
desc->chip->unmask(irq);--------enable_brcom_irq-----如果没有要求在关中断下执行,则需要打开中断;否则的话什么都不做,因为在handle_IRQ_event的开始中断已经打开了
}
对action的处理,这里处理了共享中断的情况。
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
正常到这里是关中断的,在handle_level_irq的mask_ack_irq(desc, irq)中设置。如果设置了IRQ_DISABLED,则依然关中断
如果没有设置IRQ_DISABLED,则依然关中断,则打开中断()
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
do { 是否在中断下执行,需要考虑在request_irq是是否设置IRQF_DISABLED
ret = action->handler(irq, action->dev_id); //驱动注册的中断处理函数
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action); //共享情况下需要执行多次;
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable(); //这里有关中断
return retval;
}
注意开关中断的范围:handle_level_irq的开始到handle_IRQ_event的结束。如果要求关中断时执行ISR,则action->handler在关中断下执行,否则会在开中断下执行。
这里都用到了irq_chip
Linux支持N种可编程中断控制器PIC, 所以有一个struct hw_interrupt_type,实际它与irq_chip =hw_interrupt_type = hw_interrupt_type
#define hw_interrupt_type irq_chip
typedef struct irq_chip hw_irq_controller;
local_irq_enable:
mfc0 $1,$12----move $12[SR] to $1
ori $1,0x1f -----set the lowest 5 bits as 1
xori $1,0x1e-----set bit 0 with value 1, bit 2/3/4/5 with 0
mtc0 $1,$12-----move to SR
local_irq_disable:
mfc0 $1,$12
ori $1,0x1f
xori $1,0x1f----与设置唯一的不同是直接用0x1f来做异或,这样低5bit都被晴空了
.set noreorder--比较奇怪,为啥enable时没有设置
mtc0 $1,$12
后处理
处理完硬件中断后需要处理软件部分了。LINUX提供了一种软件中断机制来实现一些优先级高于被调度的内核线程但低于硬件中断。
硬中断?软中断,肯定有对应的硬中断。硬中断来源于外部元器件,在CPU上的表现就是某个管脚的电平升高。
软中断在irq_exit中被触发。
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();-----------处理软中断
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (!in_interrupt() && idle_cpu(smp_processor_id()) && !need_resched())
tick_nohz_stop_sched_tick();
#endif
preempt_enable_no_resched();
}
在中断上下文中最多能处理MAX_SOFTIRQ_RESTART个SOFTIRQ,若pending的数目多余MAX_SOFTIRQ_RESTART,则在内核线程ksoftirqd中处理--process context
softirq
#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq() __do_softirq() ---softirq在开中断环境下执行
#else
# define invoke_softirq() do_softirq() -----softirq在关中断环境下执行
#endif
__do_softirq()在开中断状态下处理了执行所注册所有softirq对应的handler
tasklet
void __init softirq_init(void)
{
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
tasklet_action与tasklet_hi_action都是在开中断状态下运行的;在中断环境中,但是开中断。
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();-----获取每个CPU的统计数据__softirq_pending;tasklet_schedule时向量的位置TASKLET_SOFTIRQ设置为1
account_system_vtime(current);
__local_bh_disable((unsigned long)__builtin_return_address(0));
trace_softirq_enter();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;---为什么被称为软中断?因为和硬中断一样都是用vector方式来处理
do {
if (pending & 1) {
h->action(h);-------------所有注册的action都是在开中断状态下执行的,这里可能会被外部中断打断。但action中不能阻塞,否则scheduler就没机会执行调度了。这就是为什么tasklet不能阻塞。
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)-------处理了还不到MAX_SOFTIRQ_RESTART个,接着处理。
goto restart;
if (pending)------------已经处理 MAX_SOFTIRQ_RESTART个了,但是还有需要处理的;这些就要在任务环境(thread-context)下执行了,也不能老在interrupt context中阿
wakeup_softirqd();
trace_softirq_exit();
account_system_vtime(current);
_local_bh_enable();
}
3中不同的context:irq/softirq/interrupt
thread_info的preempt_count是判断在interrupt/irq/preempt哪一个上下文中的载体。
#define preempt_count() (current_thread_info()->preempt_count)
preempt_count各字段的含义
Bits | Description |
0---7 | Preemption counter (max value = 255);0表示内核可以被抢占。非零值表示内核抢占被禁止的次数 |
8---15 | Softirq counter (max value = 255); 0表示“ deferrable functions are enabled”。非零值表示禁止延迟操作(deferrable functions)的次数 |
16---27 | Hardirq counter (max value = 4096);有多少个内核嵌套; irq_enter( )增加1;irq_exit( )减少1; |
28 | PREEMPT_ACTIVE flag |
对应代码
* We put the hardirq and softirq counter into the preemption
* counter. The bitmask has the following meaning:
*
* - bits 0-7 are the preemption count (max preemption depth: 256)
* - bits 8-15 are the softirq count (max # of softirqs: 256)
*
* The hardirq count can be overridden per architecture, the default is:
*
* - bits 16-27 are the hardirq count (max # of hardirqs: 4096)
* - ( bit 28 is the PREEMPT_ACTIVE flag. )
*
* PREEMPT_MASK: 0x000000ff
* SOFTIRQ_MASK: 0x0000ff00
* HARDIRQ_MASK: 0x0fff0000
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define PREEMPT_SHIFT 0
#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS)------ =8
#define HARDIRQ_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS) =16
#define PREEMPT_OFFSET (1UL << PREEMPT_SHIFT) =0x1
#define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT) =0x100
#define HARDIRQ_OFFSET (1UL << HARDIRQ_SHIFT) =0x10000
#define PREEMPT_ACTIVE 0x10000000
#define in_irq() (hardirq_count())
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count())
#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
#define softirq_count() (preempt_count() & SOFTIRQ_MASK)
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))
__irq_enter()----->add_preempt_count(HARDIRQ_OFFSET); ------>preempt_count() += val;
__local_bh_disable----->add_preempt_count(SOFTIRQ_OFFSET);
need_resched(before schedule): add_preempt_count(PREEMPT_ACTIVE);-----将抢占计数器加上一个数,禁止抢占再次发生
__irq_exit() ----->sub_preempt_count(HARDIRQ_OFFSET);
__local_bh_enable------>sub_preempt_count(SOFTIRQ_OFFSET);
after shcedule(), sub_preempt_count(PREEMPT_ACTIVE);
判断在那个conext中的标志
3种情况
#define in_irq() (hardirq_count())
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count()) interrupt,软+硬
#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
#define softirq_count() (preempt_count() & SOFTIRQ_MASK)
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK)) <---irq包括了softirq和hardirq
抢占标记
顺便说一下抢占标记
只有当抢占计数器是0时才能抢占;
preemptible() ----> (preempt_count() == 0 && !irqs_disabled())
__schedule 通过PREEMPT_ACTIVE这个标志知道这是一次抢占. 返回内核空间的时候只有peeempt_count==0才能够抢占当前进程. 2.6在返回用户空间的时候检查两个标志:TIF_NEED_RESCHED和新增的TIF_NEED_RESCHED_DELAYED, 而返回内核空间只检查TIF_NEED_RESCHED.
关于2.4--->2.6的变化
http://www.linuxforum.net/forum/showthreaded.php?Cat=&Board=linuxK&Number=693524&page=&view=&sb=&o=