linux内核V2.6.11学习笔记(6)--中断处理

linux内核V2.6.11学习笔记(6)--中断处理

每个中断处理的函数存放在entry.S中的interrupt数组中,该数组有NR_IRQS个元素.
每个元素做的工作有:
ENTRY(irq_entries_start)
.rept NR_IRQS
    ALIGN
1 :    pushl $vector - 256
    jmp common_interrupt
.data
    .
long  1b
.text
vector
= vector + 1
.endr

    ALIGN
common_interrupt:
    SAVE_ALL
    movl 
% esp, % eax
    call do_IRQ
    jmp ret_from_intr

首先将中断向量- 256保存在栈中
其中的SAVE_ALL做的工作包括:
#define  SAVE_ALL \
    cld; \
    pushl 
% es; \
    pushl 
% ds; \
    pushl 
% eax; \
    pushl 
% ebp; \
    pushl 
% edi; \
    pushl 
% esi; \
    pushl 
% edx; \
    pushl 
% ecx; \
    pushl 
% ebx; \
    movl $(__USER_DS), 
% edx; \
    movl 
% edx,  % ds; \
    movl 
% edx,  % es;

也就是保存一些寄存器, 然后调用do_IRQ函数:

do_IRQ函数首先调用irq_enter()函数:
#define  irq_enter()                    \
    
do  {                        \
        account_system_vtime(current);        \
        add_preempt_count(HARDIRQ_OFFSET);    \
    } 
while  ( 0 )

其中要注意的是函数add_preempt_count, 它改变的是当前进程中thread_info中的成员preempt_count,它是一个32位的字段,分为几个部分,:
0-7位: 抢占计数器, 最大值255
8-15位: 软中断计数器, 最大值255
16-27位: 硬中断计数器, 最大值4096
28位: PREEMPT_ACTIVE标志

因此,在hardirq.h中定义了几个宏:
#define  PREEMPT_BITS    8
#define  SOFTIRQ_BITS    8
#define  HARDIRQ_BITS    12

#define  PREEMPT_SHIFT    0
#define  SOFTIRQ_SHIFT    (PREEMPT_SHIFT + PREEMPT_BITS)
#define  HARDIRQ_SHIFT    (SOFTIRQ_SHIFT + SOFTIRQ_BITS)

#define  PREEMPT_OFFSET    (1UL << PREEMPT_SHIFT)
#define  SOFTIRQ_OFFSET    (1UL << SOFTIRQ_SHIFT)
#define  HARDIRQ_OFFSET    (1UL << HARDIRQ_SHIFT)

因此, 函数调用add_preempt_count(HARDIRQ_OFFSET)是增加其中硬中断的计数.

回到do_IRQ函数调用中,接下来:
#ifdef CONFIG_4KSTACKS

    curctx 
=  (union irq_ctx  * ) current_thread_info();
    irqctx 
=  hardirq_ctx[smp_processor_id()];

    
/*
     * this is where we switch to the IRQ stack. However, if we are
     * already using the IRQ stack (because we interrupted a hardirq
     * handler) we can't do that and just have to keep using the
     * current stack (which is the irq stack already after all)
     
*/
    
if  (curctx  !=  irqctx) {
        
int  arg1, arg2, ebx;

        
/*  build the stack frame on the IRQ stack  */
        isp 
=  (u32 * ) (( char * )irqctx  +   sizeof ( * irqctx));
        irqctx
-> tinfo.task  =  curctx -> tinfo.task;
        irqctx
-> tinfo.previous_esp  =  current_stack_pointer;

        asm 
volatile (
            
"        xchgl   %%ebx,%%esp      \n "
            
"        call    __do_IRQ         \n "
            
"        movl   %%ebx,%%esp      \n "
            : 
" =a "  (arg1),  " =d "  (arg2),  " =b "  (ebx)
            :  
" 0 "  (irq),    " 1 "  (regs),   " 2 "  (isp)
            : 
" memory " " cc " " ecx "
        );
    } 
else
#endif

这段代码仅在线程栈大小是4K的情况下被调用, 有一个名为hardirq_ctx的数组保存硬中断的请求栈,它的定义是:
union irq_ctx {
    
struct  thread_info      tinfo;
    u32                     stack[THREAD_SIZE
/ sizeof (u32)];
};

static  union irq_ctx  * hardirq_ctx[NR_CPUS];
static  union irq_ctx  * softirq_ctx[NR_CPUS];
也就是说, 这两个数组的元素数量由CPU数量来决定.
在系统初始化的时候, 调用函数irq_ctx_init, 分别把这两个数组中的元素(irq_ctx *类型指针)指向hardirq_stack和softirq_stack:
static   char  softirq_stack[NR_CPUS  *  THREAD_SIZE]
        __attribute__((__aligned__(THREAD_SIZE)));

static   char  hardirq_stack[NR_CPUS  *  THREAD_SIZE]
        __attribute__((__aligned__(THREAD_SIZE)));
因此, 上面的那段do_IRQ函数中的代码做的工作比较当前thread_info描述符地址(通过调用current_thread_info()函数)与hardirq_ctx
的内容, 如果相同, 说明内核已经在使用硬件中断请求栈了, 否则如果不相等那么就要切换内核栈,需要保存当前进程描述符指针和esp寄存器.

接着, do_IRQ函数调用__do_IRQ函数,这个函数的主要工作有:
//  加锁
spin_lock( & (irq_desc[irq]. lock ));
//  响应
irq_desc[irq].handler -> ack(irq);

//  当前状态既不是IRQ_REPLAY:The IRQ line has been disabled but the previous IRQ occurrence has not yet been acknowledged to the PIC
//  也不是IRQ_WAITING:The kernel is using the IRQ line while performing a hardware device probe; moreover, the corresponding interrupt has not been raised
irq_desc[irq].status  &=   ~ (IRQ_REPLAY  |  IRQ_WAITING);

//  当前状态为IRQ_PENDING:An IRQ has occurred on the line; its occurrence has been acknowledged to the PIC, but it has not yet been serviced by the kernel
irq_desc[irq].status  |=  IRQ_PENDING;

if  ( ! (irq_desc[irq].status  &  (IRQ_DISABLED  |  IRQ_INPROGRESS))  //  如果当前状态不是IRQ_DISABLED 或者 IRQ_INPROGRESS
             &&  irq_desc[irq].action) {                             //  action指针有效
        irq_desc[irq].status  |=  IRQ_INPROGRESS;                     //  设置当前当前状态为IRQ_INPROGRESS: A handler for the IRQ is being executed
         do  {
            irq_desc[irq].status 
&=   ~ IRQ_PENDING;                 //  设置当前当前状态不是IRQ_PENDING,因为下面要开始处理了
            spin_unlock( & (irq_desc[irq]. lock ));
            handle_IRQ_event(irq, regs, irq_desc[irq].action);    
//  处理事件
            spin_lock( & (irq_desc[irq]. lock ));
        } 
while  (irq_desc[irq].status  &  IRQ_PENDING);             //  如果当前状态还是IRQ_PENDING循环继续
        irq_desc[irq].status  &=   ~ IRQ_INPROGRESS;                 //  设置当前状态不是IRQ_INPROGRESS
}

irq_desc[irq].handler
-> end(irq);
spin_unlock(
& (irq_desc[irq]. lock ));

在循环处理IRQ请求的时候, 最开始要设置状态为 IRQ_INPROGRESS同时不是IRQ_PENDING, 这个循环处理IRQ请求的过程在当前状态是IRQ_PENDING则一直进行下去,
当该循环处理完毕之后, 再将状态设置为IRQ_INPROGRESS.

在从__do_IRQ函数返回后, 调用irq_exit函数:
void  irq_exit( void )
{
    account_system_vtime(current);
    sub_preempt_count(IRQ_EXIT_OFFSET);
    
if  ( ! in_interrupt()  &&  local_softirq_pending())
        invoke_softirq();
    preempt_enable_no_resched();
}

该函数首先调用sub_preempt_count减少抢占计数, 然后如果当前不在中断状态以及当前有未处理的软中断(softirq)则调用invoke_softirq函数(其实就是do_softirq函数)
处理软中断,最后调用preempt_enable_no_resched允许内核抢占.

你可能感兴趣的:(linux内核V2.6.11学习笔记(6)--中断处理)