文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>第五篇 Linux内核及驱动编程-深入剖析Linux中断机..

第五篇 Linux内核及驱动编程-深入剖析Linux中断机..

时间:2009-07-12  来源:embededgood

【摘要】本文详解了Linux内核的中断实现机制。首先介绍了中断的一些基本概念,然后分析了面向对象的Linux中断的组织形式、三种主要数据结构及其之间的关系。随后介绍了Linux处理异常和中断的基本流程,在此基础上分析了中断处理的详细流程,包括保存现场、中断处理、中断退出时的软中断执行及中断返回时的进程切换等问题。最后介绍了中断相关的API,包括中断注册和释放、中断关闭和使能、如何编写中断ISR、共享中断、中断上下文中断状态等。 【关键字】中断,异常,hw_interrupt_type,irq_desc_t,irqaction,asm_do_IRQ,软中断,进程切换,中断注册释放request_irq,free_irq,共享中断,可重入,中断上下文     1       Linux对异常和中断的处理
1.1    异常处理
Linux利用异常来达到两个截然不同的目的:
²      给进程发送一个信号以通报一个反常情况 ²      管理硬件资源   对于第一种情况,例如,如果进程执行了一个被0除的操作,CPU则会产生一个“除法错误”异常,并由相应的异常处理程序向当前进程发送一个SIGFPE信号。当前进程接收到这个信号后,就要采取若干必要的步骤,或者从错误中恢复,或者终止执行(如果这个信号没有相应的信号处理程序)。   内核对异常处理程序的调用有一个标准的结构,它由以下三部分组成: ²      在内核栈中保存大多数寄存器的内容(由汇编语言实现) ²      调用C编写的异常处理函数 ²      通过ret_from_exception()函数从异常退出。   1.2    中断处理
当一个中断发生时,并不是所有的操作都具有相同的急迫性。事实上,把所有的操作都放进中断处理程序本身并不合适。需要时间长的、非重要的操作应该推后,因为当一个中断处理程序正在运行时,相应的IRQ中断线上再发出的信号就会被忽略。另外中断处理程序不能执行任何阻塞过程,如I/O设备操作。因此,Linux把一个中断要执行的操作分为下面的三类:
²      紧急的(Critical) 这样的操作诸如:中断到来时中断控制器做出应答,对中断控制器或设备控制器重新编程,或者对设备和处理器同时访问的数据结构进行修改。这些操作都是紧急的,应该被很快地执行,也就是说,紧急操作应该在一个中断处理程序内立即执行,而且是在禁用中断的状态下。 ²      非紧急的(Noncritical) 这样的操作如修改那些只有处理器才会访问的数据结构(例如,按下一个键后,读扫描码)。这些操作也要很快地完成,因此,它们由中断处理程序立即执行,但在启用中断的状态下。 ²      非紧急可延迟的(Noncritical deferrable) 这样的操作如,把一个缓冲区的内容拷贝到一些进程的地址空间(例如,把键盘行缓冲区的内容发送到终端处理程序的进程)。这些操作可能被延迟较长的时间间隔而不影响内核操作,有兴趣的进程会等待需要的数据。   所有的中断处理程序都执行四个基本的操作: ²      在内核栈中保存IRQ的值和寄存器的内容。 ²      给与IRQ中断线相连的中断控制器发送一个应答,这将允许在这条中断线上进一步发出中断请求。 ²      执行共享这个IRQ的所有设备的中断服务例程(ISR)。 ²      跳到ret_to_usr( )的地址后终止。   1.3    中断处理程序的执行流程
1.3.1      流程概述
现在,我们可以从中断请求的发生到CPU的响应,再到中断处理程序的调用和返回,沿着这一思路走一遍,以体会Linux内核对中断的响应及处理。
  假定外设的驱动程序都已完成了初始化工作,并且已把相应的中断服务例程挂入到特定的中断请求队列。又假定当前进程正在用户空间运行(随时可以接受中断),且外设已产生了一次中断请求,CPU就在执行完当前指令后来响应该中断。   中断处理系统在Linux中的实现是非常依赖于体系结构的,实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计及机器本身。   设备产生中断,通过总线把电信号发送给中断控制器。如果中断线是激活的,那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号。除非在处理器上禁止该中断,否则,处理器会立即停止它正在做的事,关闭中断系统,然后跳到内存中预定义的位置开始执行那里的代码。这个预定义的位置是由内核设置的,是中断处理程序的入口点。   对于ARM系统来说,有个专用的IRQ运行模式,有一个统一的入口地址。假定中断发生时CPU运行在用户空间,而中断处理程序属于内核空间,因此,要进行堆栈的切换。也就是说,CPU从TSS中取出内核栈指针,并切换到内核栈(此时栈还为空)。  
 
若当前处于内核空间时,对于ARM系统来说是处于SVC模式,此时产生中断,中断处理完毕后,若是可剥夺内核,则检查是否需要进行进程调度,否则直接返回到被中断的内核空间;若需要进行进程调度,则svc_preempt,进程切换。  190        .align  5  191__irq_svc:  192        svc_entry  197#ifdef CONFIG_PREEMPT  198        get_thread_info tsk  199        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count  200        add     r7, r8, #1                      @ increment it  201        str     r7, [tsk, #TI_PREEMPT]  202#endif  203  204        irq_handler  205#ifdef CONFIG_PREEMPT  206        ldr     r0, [tsk, #TI_FLAGS]            @ get flags  207        tst     r0, #_TIF_NEED_RESCHED  208        blne    svc_preempt  209preempt_return:  210        ldr     r0, [tsk, #TI_PREEMPT]          @ read preempt value  211        str     r8, [tsk, #TI_PREEMPT]          @ restore preempt count  212        teq     r0, r7  213        strne   r0, [r0, -r0]                   @ bug()  214#endif  215        ldr     r0, [sp, #S_PSR]                @ irqs are already disabled  216        msr     spsr_cxsf, r0  221        ldmia   sp, {r0 - pc}^                  @ load r0 - pc, cpsr  222  223        .ltorg     当前处于用户空间时,对于ARM系统来说是处于USR模式,此时产生中断,中断处理完毕后,无论是否是可剥夺内核,都调转到统一的用户模式出口ret_to_user,其检查是否需要进行进程调度,若需要进行进程调度,则进程切换,否则直接返回到被中断的用户空间。    404        .align  5  405__irq_usr:  406        usr_entry  407  411        get_thread_info tsk  412#ifdef CONFIG_PREEMPT  413        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count  414        add     r7, r8, #1                      @ increment it  415        str     r7, [tsk, #TI_PREEMPT]  416#endif  417  418        irq_handler  419#ifdef CONFIG_PREEMPT  420        ldr     r0, [tsk, #TI_PREEMPT]  421        str     r8, [tsk, #TI_PREEMPT]  422        teq     r0, r7  423        strne   r0, [r0, -r0]       @ bug()  424#endif  428  429        mov     why, #0  430        b       ret_to_user  432        .ltorg     1.3.2      保存现场
 105/*
 106 * SVC mode handlers  107 */  108  115        .macro  svc_entry  116        sub     sp, sp, #S_FRAME_SIZE  117 SPFIX( tst     sp, #4          )  118 SPFIX( bicne   sp, sp, #4      )  119        stmib   sp, {r1 - r12}  120  121        ldmia   r0, {r1 - r3}  122        add     r5, sp, #S_SP           @ here for interlock avoidance  123        mov     r4, #-1                 @  ""  ""      ""       ""  124        add     r0, sp, #S_FRAME_SIZE   @  ""  ""      ""       ""  125 SPFIX( addne   r0, r0, #4      )  126        str     r1, [sp]                @ save the "real" r0 copied  127                                        @ from the exception stack  128  129        mov     r1, lr  130  131        @  132        @ We are now ready to fill in the remaining blanks on the stack:  133        @  134        @  r0 - sp_svc  135        @  r1 - lr_svc  136        @  r2 - lr_<exception>, already fixed up for correct return/restart  137        @  r3 - spsr_<exception>  138        @  r4 - orig_r0 (see pt_regs definition in ptrace.h)  139        @  140        stmia   r5, {r0 - r4}  141        .endm   1.3.3      中断处理
因为C的调用惯例是要把函数参数放在栈的顶部,因此pt- regs结构包含原始寄存器的值,这些值是以前在汇编入口例程svc_entry中保存在栈中的。
linux+v2.6.19/include/asm-arm/arch-at91rm9200/entry-macro.S   18        .macro  get_irqnr_and_base, irqnr, irqstat, base, tmp   19        ldr     \base, =(AT91_VA_BASE_SYS)              @ base virtual address of SYS peripherals   20        ldr     \irqnr, [\base, #AT91_AIC_IVR]          @ read IRQ vector register: de-asserts nIRQ to processor (and clears interrupt)   21        ldr     \irqstat, [\base, #AT91_AIC_ISR]        @ read interrupt source number   22        teq     \irqstat, #0                            @ ISR is 0 when no current interrupt, or spurious interrupt   23        streq   \tmp, [\base, #AT91_AIC_EOICR]          @ not going to be handled further, then ACK it now.   24        .endm     26/*   27 * Interrupt handling.  Preserves r7, r8, r9   28 */   29        .macro  irq_handler   301:      get_irqnr_and_base r0, r6, r5, lr   31        movne   r1, sp   32        @   33        @ routine called with r0 = irq number, r1 = struct pt_regs *   34        @   35        adrne   lr, 1b   36        bne     asm_do_IRQ   58        .endm     中断号的值也在irq_handler初期得以保存,所以,asm_do_IRQ可以将它提取出来。这个中断处理程序实际上要调用do_IRQ(),而do_IRQ()要调用handle_IRQ_event()函数,最后这个函数才真正地执行中断服务例程(ISR)。下图给出它们的调用关系:   asm_do_IRQ
 
 
 
    do_IRQ()
 
 
 
handle_IRQ_event()
 
 
 
中断服务
例程1             例程
 
 
 
中断服务
例程2             例程
 
 
 
 
 
 
                                                                 中断处理函数的调用关系   1.3.3.1          asm_do_IRQ
 112asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
 113{  114        struct pt_regs *old_regs = set_irq_regs(regs);  115        struct irqdesc *desc = irq_desc + irq;  116  121        if (irq >= NR_IRQS)  122                desc = &bad_irq_desc;  123  124        irq_enter(); //记录硬件中断状态,便于跟踪中断情况确定是否是中断上下文  125  126        desc_handle_irq(irq, desc); ///////////////////desc_handle_irq   33static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)   34{   35        desc->handle_irq(irq, desc); //通常handle_irq指向__do_IRQ   36} ///////////////////desc_handle_irq  130  131        irq_exit(); //中断退出前执行可能的软中断,被中断前是在中断上下文中则直接退出,这保证了软中断不会嵌套  132        set_irq_regs(old_regs);  133}   1.3.3.2          __do_IRQ
 157 * __do_IRQ - original all in one highlevel IRQ handler
 167fastcall unsigned int __do_IRQ(unsigned int irq)  168{  169        struct irq_desc *desc = irq_desc + irq;  170        struct irqaction *action;  171        unsigned int status;  172  173        kstat_this_cpu.irqs[irq]++;  186  187        spin_lock(&desc->lock);  188        if (desc->chip->ack) //首先响应中断,通常实现为关闭本中断线  189                desc->chip->ack(irq);  190         194        status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);  195        status |= IRQ_PENDING; /* we _want_ to handle it */  196  201        action = NULL;  202        if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) {  203                action = desc->action;  204                status &= ~IRQ_PENDING; /* we commit to handling */  205                status |= IRQ_INPROGRESS; /* we are handling it */  206        }  207        desc->status = status;  208  215        if (unlikely(!action))  216                goto out;  217  218        /*  219         * Edge triggered interrupts need to remember  220         * pending events.  227         */  228        for (;;) {  229                irqreturn_t action_ret;  230  231                spin_unlock(&desc->lock);//解锁,中断处理期间可以响应其他中断,否则再次进入__do_IRQ时会死锁  233                action_ret = handle_IRQ_event(irq, action);  237                spin_lock(&desc->lock);  238                if (likely(!(desc->status & IRQ_PENDING)))  239                        break;  240                desc->status &= ~IRQ_PENDING;  241        }  242        desc->status &= ~IRQ_INPROGRESS;  243  244out:  249        desc->chip->end(irq);  250        spin_unlock(&desc->lock);  251  252        return 1;  253}     该函数的实现用到中断线的状态,下面给予具体说明: #define IRQ_INPROGRESS  1   /* 正在执行这个IRQ的一个处理程序*/ #define IRQ_DISABLED    2    /* 由设备驱动程序已经禁用了这条IRQ中断线 */ #define IRQ_PENDING     4    /* 一个IRQ已经出现在中断线上,且被应答,但还没有为它提供服务 */ #define IRQ_REPLAY      8    /* 当Linux重新发送一个已被删除的IRQ时 */ #define IRQ_WAITING     32   /*当对硬件设备进行探测时,设置这个状态以标记正在被测试的irq */ #define IRQ_LEVEL       64    /* IRQ level triggered */ #define IRQ_MASKED      128    /* IRQ masked - shouldn't be seen again */ #define IRQ_PER_CPU     256     /* IRQ is per CPU */ 这8个状态的前5个状态比较常用,因此我们给出了具体解释。   经验表明,应该避免在同一条中断线上的中断嵌套,内核通过IRQ_PENDING标志位的应用保证了这一点。当do_IRQ()执行到for (;;)循环时,desc->status 中的IRQ_PENDING的标志位肯定为0。当CPU执行完handle_IRQ_event()函数返回时,如果这个标志位仍然为0,那么循环就此结束。如果这个标志位变为1,那就说明这条中断线上又有中断产生(对单CPU而言),所以循环又执行一次。通过这种循环方式,就把可能发生在同一中断线上的嵌套循环化解为“串行”。   在循环结束后调用desc->handler->end()函数,具体来说,如果没有设置IRQ_DISABLED标志位,就启用这条中断线。   1.3.3.3          handle_IRQ_event
当执行到for (;;)这个无限循环时,就准备对中断请求队列进行处理,这是由handle_IRQ_event()函数完成的。因为中断请求队列为一临界资源,因此在进入这个函数前要加锁。
handle_IRQ_event执行所有的irqaction链表:  130irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)  131{  132        irqreturn_t ret, retval = IRQ_NONE;  133        unsigned int status = 0;  134  135        handle_dynamic_tick(action);  136       // 如果没有设置IRQF_DISABLED,则中断处理过程中,打开中断  137        if (!(action->flags & IRQF_DISABLED))  138                local_irq_enable_in_hardirq();  139  140        do {  141                ret = action->handler(irq, action->dev_id);  142                if (ret == IRQ_HANDLED)  143                        status |= action->flags;  144                retval |= ret;  145                action = action->next;  146        } while (action);  147  150        local_irq_disable();  151  152        return retval;  153}           这个循环依次调用请求队列中的每个中断服务例程。这里要说明的是,如果设置了IRQF_DISABLED,则中断服务例程在关中断的条件下进行(不包括非屏蔽中断),但通常CPU在穿过中断门时自动关闭中断。但是,关中断时间绝不能太长,否则就可能丢失其它重要的中断。也就是说,中断服务例程应该处理最紧急的事情,而把剩下的事情交给另外一部分来处理。即后半部分(bottom half)来处理,这一部分内容将在下一节进行讨论。   不同的CPU不允许并发地进入同一中断服务例程,否则,那就要求所有的中断服务例程必须是“可重入”的纯代码。可重入代码的设计和实现就复杂多了,因此,Linux在设计内核时巧妙地“避难就易”,以解决问题为主要目标。   1.3.3.4          irq_exit()
中断退出前执行可能的软中断,被中断前是在中断上下文中则直接退出,这保证了软中断不会嵌套
//////////////////////////////////////////////////////////// linux+v2.6.19/kernel/softirq.c  285void irq_exit(void)  286{  287        account_system_vtime(current);  288        trace_hardirq_exit();  289        sub_preempt_count(IRQ_EXIT_OFFSET);  290        if (!in_interrupt() && local_softirq_pending())  291                invoke_softirq(); ////////////  276#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED  277# define invoke_softirq()       __do_softirq()  278#else  279# define invoke_softirq()       do_softirq()  280#endif ////////////  292        preempt_enable_no_resched();  293} ////////////////////////////////////////////////////////////   1.3.4      从中断返回
asm_do_IRQ()这个函数处理所有外设的中断请求后就要返回。返回情况取决于中断前程序是内核态还是用户态以及是否是可剥夺内核。
²      内核态可剥夺内核,只有在preempt_count为0时,schedule()才会被调用,其检查是否需要进行进程切换,需要的话就切换。在schedule()返回之后,或者如果没有挂起的工作,那么原来的寄存器被恢复,内核恢复到被中断的内核代码。 ²      内核态不可剥夺内核,则直接返回至被中断的内核代码。 ²      中断前处于用户态时,无论是否是可剥夺内核,统一跳转到ret_to_user。   虽然我们这里讨论的是中断的返回,但实际上中断、异常及系统调用的返回是放在一起实现的,因此,我们常常以函数的形式提到下面这三个入口点: ret_to_user() 终止中断处理程序 ret_slow_syscall ( ) 或者ret_fast_syscall 终止系统调用,即由0x80引起的异常 ret_from_exception(  ) 终止除了0x80的所有异常    565/*  566 * This is the return code to user mode for abort handlers  567 */  568ENTRY(ret_from_exception)  569        get_thread_info tsk  570        mov     why, #0  571        b       ret_to_user     57ENTRY(ret_to_user)   58ret_slow_syscall:   由上可知,中断和异常需要返回用户空间时以及系统调用完毕后都需要经过统一的出口ret_slow_syscall,以此决定是否进行进程调度切换等。   linux+v2.6.19/arch/arm/kernel/entry-common.S   16        .align  5   17/*   18 * This is the fast syscall return path.  We do as little as   19 * possible here, and this includes saving r0 back into the SVC   20 * stack.   21 */   22ret_fast_syscall:   23        disable_irq                             @ disable interrupts   24        ldr     r1, [tsk, #TI_FLAGS]   25        tst     r1, #_TIF_WORK_MASK   26        bne     fast_work_pending   27   28        @ fast_restore_user_regs   29        ldr     r1, [sp, #S_OFF + S_PSR]        @ get calling cpsr   30        ldr     lr, [sp, #S_OFF + S_PC]!        @ get pc   31        msr     spsr_cxsf, r1                   @ save in spsr_svc   32        ldmdb   sp, {r1 - lr}^                  @ get calling r1 - lr   33        mov     r0, r0   34        add     sp, sp, #S_FRAME_SIZE - S_PC   35        movs    pc, lr                @ return & move spsr_svc into cpsr   36   37/*   38 * Ok, we need to do extra processing, enter the slow path.   39 */   40fast_work_pending:   41        str     r0, [sp, #S_R0+S_OFF]!          @ returned r0   42work_pending:   43        tst     r1, #_TIF_NEED_RESCHED   44        bne     work_resched   45        tst     r1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING   46        beq     no_work_pending   47        mov     r0, sp                          @ 'regs'   48        mov     r2, why                         @ 'syscall'   49        bl      do_notify_resume   50        b       ret_slow_syscall                @ Check work again   51   52work_resched:   53        bl      schedule   54/*   55 * "slow" syscall return path.  "why" tells us if this was a real syscall.   56 */   57ENTRY(ret_to_user)   58ret_slow_syscall:   59        disable_irq                             @ disable interrupts   60        ldr     r1, [tsk, #TI_FLAGS]   61        tst     r1, #_TIF_WORK_MASK   62        bne     work_pending   63no_work_pending:   64        @ slow_restore_user_regs   65        ldr     r1, [sp, #S_PSR]                @ get calling cpsr   66        ldr     lr, [sp, #S_PC]!                @ get pc   67        msr     spsr_cxsf, r1                   @ save in spsr_svc   68        ldmdb   sp, {r0 - lr}^                  @ get calling r1 - lr   69        mov     r0, r0   70        add     sp, sp, #S_FRAME_SIZE - S_PC   71        movs    pc, lr                @ return & move spsr_svc into cpsr   进入ret_slow_syscall后,首先关中断,也就是说,执行这段代码时CPU不接受任何中断请求。然后,看调度标志是否为非0(tst     r1, #_TIF_NEED_RESCHED),如果调度标志为非0,说明需要进行调度,则去调用schedule()函数进行进程调度。    
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sailor_8318/archive/2008/08/28/2841002.aspx
相关阅读 更多 +
排行榜 更多 +
盒子小镇2游戏手机版下载

盒子小镇2游戏手机版下载

冒险解谜 下载
世界盒子模组版下载最新版本

世界盒子模组版下载最新版本

模拟经营 下载
音乐搜索app最新版本下载

音乐搜索app最新版本下载

趣味娱乐 下载