MIPS-LINUX-中断处理

本文详细解析了MIPS-Linux平台上的中断和异常处理机制,涵盖了硬件和软件中断的处理流程,包括中断向量、异常处理函数、中断执行路径等关键技术细节。

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

中断(Interrupt)与异常(Exception)

MIPSLINUX上存在Exception,Interrupt。其中Interrupt又有来自芯片内部(各个组件)和外部直接连接过来的中断。
Exception由CP0_CAUSE(ExeCode!=0)表示,共有31中;

Interrupt
CP0_CAUSE(ExeCode!=0)表示,只有一个。但是可以在外设寄存器的INTMASK中表示,又32中可能。并且LINUX提供了中断共享机制。

中断相关的硬件

CPU提供若干个中断线,如Int2--66358最多支持5个外部中断,在causeIP2-6中表示中断的状态,01是软件控制的中断,7 timer中断);CPU的硬件在每个始终周期都会轮询这些中断线的状态(当linux内核禁止中断时例外)。CPU是否原因对某个中断源做出响应取决于 如下内容:

  1. SR(IE)1;否则就是关中断了
  2. SR(EXL) (exception Ievel)SR(ERL) (error level)没有置位。置位的话会禁止中断。
  3. SR(IM)对应于中断源的位置位,否则表示该中断被屏蔽


如果这三个条件都满足了,则CPU做如下操作:

  1. 设置EPC
  2. 设置SR(EXL),此操作强制CPU进入内核模式并且禁止中断;
  3. 设置Cause记录中断原因;如果是地址错误,还需要设置BadAddress
  4. CPU从异常入口点(Exception entry point0x80000180--4个字节在不同的平台上可能不同)取指令并执行;


中断IP2----75个中断源的分配:
2
:处理芯片内部中断,这些中断可能是DGUSB/ETH/EMAC/MPI等。对于 WIRELSS,由于它是通过MPI连接到芯片的,所以他也在IP2上被处理。又由于这些中断源太多,因此需要某个地方来表示那个具体的设备发生了中断, 所有在外设的内部寄存器中有INTMASK来表示那个外设产生了中断。
3
456:直接连接外部中断源。

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相关的一个汇编语言文件----CPUARM,此对应的文件是entry-armv.S;若用 MIPS,则 此文件是genex.S
硬件异常(hardware exception通常称作trap,它不同于软件中断,它是真正由硬件产生的中断。举MIPS为例,traps.c内的trap_init负责将最 底层的except_vec3_generic复制到异常来源区”-位于SDRAMROM内,except_vec3_generic是硬件的异常处 理程序(exception handler),它和其它异常处理程序全部存在于entry-armv.Sgenex.S内。

except_vec3_generic
内有exception_handlers,它是用来储存每一个硬件中断的lower- ISRtrap_init负责将所有的lower-ISR填入exception_handlers中。所有的lower-ISR都是存在于 entry-armv.Sgenex.S内。硬件的lower-ISR称作handle_int,说来奇怪,所有硬件都使用这唯一的lower- ISR,这是因为CPU分配给硬件的中断源只有一个,不管是MIPSARMPPC都是这样的。一有硬件异常或中断发生时,程序计数器(porgam counterPC)会跳到异常来源区,执行except_vec3_generic,再跳到handle_int,最后跳到 plat_irq_dispatch-这就是high-ISR
每一种CPU都有它们各自的plat_irq_dispatch,这是由软件工程师按照CPU缓存器和硬件线路的不同自行设计的,MIPSARMPPCplat_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位中间的5bitExeCode,表明是何种异常
    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 ISRhandle_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_inforegs中;
    PTR_LA    ra, ret_from_irq
 //ra保存ret_from_irq地址,处理完后直接执行ret_from_irq
    j    plat_irq_dispatch
    END(handle_int)

平台级中断分发函数irq_dispatch


相关寄存器CP0_CAUSEExeCode0时表示外部中断(表33列出了所有中断和异常)。外部中断由IP段表示,中断是否使能由IM确定。当然,全局中断使能标志:SR(IE)
使用plat_irq_dispatch(名字可变)的原因:每个半卡有自己的特殊外设,这个外设在CPU中有对应的中断。在CAUSE寄存器的IPinterrupt 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)---->
对应于LETNEVNIltvin_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对应于BRCOMhandle_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------
处理芯片内部的各个中断,包括ETHEMACUSBMPI
irq_dispatch_ext-----
处理外部中断

再看一下中断的流程
except_vec3_generic
  |____handle_int or  ltvinIRQ
          |____ plat_irq_dispatch  or ltvin_irq_dispatch
                  |___irq_dispatch_int
for IP2irq_dispatch_ext[for IP3,4,5,6]
                        |____do_IRQ(irq_num, xx)
现在 大问题是在do_IRQ中,irq_num从那里来?答案是来自外设的内部寄存器INTMASK

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通过此接口
等等

内部寄存器

内部寄存器的基址

内部寄存器专门针对不同的部件进行配置,如配置ETHUSBEMAC 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_irqmask_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;

MIPS-LINUX-中断处理 - liuyinghua - 黑风洞
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_RESTARTSOFTIRQ,若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_pendingtasklet_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( )增加1irq_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包括了softirqhardirq

 

 

抢占标记

顺便说一下抢占标记
只有当抢占计数器是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=

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值