大区经理招聘信息:一个硬中断的完整处理过程(2.4.24版本)

来源:百度文库 编辑:偶看新闻 时间:2024/04/27 21:41:07


  • CPU收到中断/异常信号;
  • CPU判断当前CPL级别如果等于3,则导致堆栈切换3->0,堆栈切换过程:
    a. CPU从当前TR指向的TSS中读取SS0和ESP0;
    b. CPU将当前的【SS:ESP】寄存器内容临时保存起来,假设为SSt和ESPt;
    c. CPU将SS0和ESP0恢复到【SS:ESP】寄存器中;
    d. CPU将在b中临时保存的SSt和ESPt压入当前的堆栈【SS:ESP】中(其实就是SS0和ESP0);
  • CPU判断当前CPL级别如果等于0,则不会有2中的步骤;
  • CPU将EFLAGS、CS、EIP依次压入当前的堆栈【SS:ESP】中;
  • 如果当前是异常,则CPU将异常码error code压入当前的堆栈【SS:ESP】中,否则会省略该步骤;
  • 对于中断,CPU清零当前EFLAGS->IF位,即关闭CPU中断使能,对于异常,CPU则不会清零该位;
  • 执行对中断/异常处理程序的调用;

    注:对中断/异常处理程序的要求,为了正常的从中断/异常处理程序中返回,中断/异常处理程序必须使用IRET指令返回,该指令会依次出栈EIP、CS和EFLAGS,比RET多一个EFLAGS,当EFLAGS恢复后,由于原来保存时IF位为1,因此这里相当于CPU中断使能又打开了;
★ Linux内核做的工作:
  • 1. 中断向量表-->common_interrupt:
    common_interrupt:
         SAVE_ALL
         pushl $ret_from_intr
         SYMBOL_NAME_STR(call_do_IRQ):
              jmp SYMBOL_NAME_STR(do_IRQ);

    SAVE_ALL保存所有CPU没有保存的寄存器,由于do_IRQ是函数,这里直接调用jmp,(一般用call来调用函数,call会导致pushEIP,但jmp不会)这样当do_IRQ返回调用ret(ret相当于popEIP)时,会弹出栈中最后一个元素到EIP,很显然这里就是ret_from_intr,也就是说,从do_IRQ中返回后,会跳转到ret_from_intr处继续执行;
  • 2. 来到do_IRQ:
    a. 首先给硬中断计数加1,irq_enter(cpu, irq)也就是:++local_irq_count(cpu);每进入一个硬中断处理函数前,local_irq_count(cpu)计数便被加1,处理完毕后减1;
    b. 如果当前设备中断处理函数可以在中断打开的情况下运行,则调用sti将EFLAGS.IF置位,打开硬中断使能;
    c. 调用request_irq注册的设备硬中断处理函数;
    d. 无论EFLAGS.IF是否为0,都调用cli将EFLAGS.IF清零,将硬中断使能关闭;
    e. 给硬中断计数减1,irq_enter(cpu, irq);该函数其实就是:--local_irq_count(cpu);
    f. 如果此时有软中断需要运行(如在前面的硬中断处理函数中调用__cpu_raise_softirq),则进入do_softirq中处理软中断,关于do_softirq中的处理步骤见3;
    e. do_IRQ执行ret,返回到ret_from_intr。
  • 3. do_softirq:
    a. 首先判断当前是否还有没有处理完毕的硬中断处理程序或软中断处理程序,如果有,则直接退出该函数。判断方法见附注【文(6)】。
    b.将软中断处理计数加1,local_bh_disable()也就是local_bh_count(cpu)++;每进入do_softirq准备进行处理软中断前,local_bh_count(cpu)计数便被加1,软中断处理函数处理完毕后,在do_softirq返回前,将其值减1;
    c. 无论EFLAGS.IF是否为0,都调用cli将EFLAGS.IF清零,将硬中断使能关闭,处理些软中断标志位的问题,随后调用sti将EFLAGS.IF置位,打开硬中断使能;
    d. 执行软中断处理函数;
    e. 调用cli将EFLAGS.IF清零,将硬中断使能关闭,处理些软中断标志位的问题;
    f. 将软中断处理计数减1,local_bh_enable()也就是local_bh_count(cpu)--;
    g. 返回到2.e中;
  • 4. ret_from_intr:
    ENTRY(ret_from_intr)
           GET_CURRENT(%ebx)
           movl EFLAGS(%esp),%eax
           movb CS(%esp),%al
           testl $(VM_MASK | 3),%eax
           jne ret_with_reschedule
           jmp restore_all

    在这段代码中,通过"testl $(VM_MASK |3),%eax"检测中断前夕寄存器EFLAGS的高6位和代码段寄存器CS的内容,来判断中断前夕CPU是否运行于VM86模式、用户空间还是系统空间,对VM86这里不讲了,但是我们知道CS最低两位代表着中断发生时CPU的运行级别CPL,我们知道Linux只采用两种运行级别,系统为0,用户为3,所以,如果CS的最低两位为非0,那就说明中断发生于用户空间。如果中断发生于系统空间,控制就直接转移到restore_all,而如果发生于用户空间,则转移到ret_with_reschedule。在restore_all中恢复1中保存的寄存器,随后调用iret恢复EIP、CS、EFLAGS返回到中断发生时的状态。
  • 5. ret_with_reschedule:
    a. 如果发现当前进程的need_resched==1,则会调用schedule;
    b. 如果发现还有待需要处理的软中断,则会调用do_softirq;
          
    说明:能够走到ret_with_reschedule处,从4中可知,该中断前一定位于用户层,而且不可能有可中断的硬中断或软中断没有执行,也就是说到达这里in_interrupt必然为0。为什么这里要说不可能有可中断的硬中断或软中断没有执行呢?可中断的硬中断或软中断必然是被中断或者异常所打断的,当后者处理完毕后,从4中可知,由于后者发生前位于内核态(也就是可中断的硬中断或软中断处理过程中的那个点),故控制就直接转移到restore_all,即返回到了可中断的硬中断或软中断被打断时的那个点,继续处理完毕,可见,到达这里,必然不存在可中断的硬中断或软中断未被执行。
  • 附注:关于in_interrupt宏,也就是(local_irq_count(__cpu) +local_bh_count(__cpu) !=0)。什么情况下会导致进入do_softriq时,in_interrupt不为0呢?第一种,如果当前正在处理可中断的硬中断处理函数,假设此时来了另一个通道的硬中断,将导致当前硬中断处理函数被中断,进入do_IRQ,随后处理新来的硬中断处理函数,当处理完毕后,到达do_softirq,由2中可知,此时local_irq_count(__cpu)被原先的硬中断加1,由于其还没有处理完毕,故--local_irq_count(cpu)还没来得及执行,因此local_irq_count(__cpu)>0,也就是in_interrupt!=0;第二种,如果当前正在do_softirq中处理软中断处理函数,现在来了个硬中断,将导致当前硬中断处理函数被中断,进入do_IRQ,随后处理新来的硬中断处理函数,当处理完毕后,又来带到了do_softirq,由3中可知,此时local_bh_count(cpu)被前一个do_softirq加1了,但是由于其是中途被中断的,故local_bh_count(cpu)--还没来得及执行,因此local_bh_count(__cpu)>0,也就是in_interrupt!=0;第三种就是综合第一种和第二种两种情况。