3.4.5 从中断返回

从前面的讨论我们知道,do_IRQ()这个函数处理所有外设的中断请求。这个函数执行的时候,内核栈栈顶包含的就是do_IRQ()的返回地址,这个地址指向ret_from_intr。实际上,ret_from_intr是一段汇编语言的入口点,为了描述简单起见,我们以函数的形式提及它。虽然我们这里讨论的是中断的返回,但实际上中断、异常及系统调用的返回是放在一起实现的,因此,我们常常以函数的形式提到下面这三个入口点:

ret_from_intr()

终止中断处理程序

ret_from_sys_call( )

终止系统调用,即由0x80引起的异常。

ret_from_exception(  )

终止除了0x80的所有异常

 在相关的计算机课程中,我们已经知道从中断返回时CPU要做的事情,下面我们来看一下Linux内核的具体实现代码(在entry.S中):

ENTRY(ret_from_intr)

        GET_CURRENT(%ebx)

ret_from_exception:

         movl EFLAGS(%esp),%eax          # mix EFLAGS and CS

         movb CS(%esp),%al

         testl $(VM_MASK | 3),%eax       # return to VM86 mode or non-supervisor?

         jne ret_from_sys_call

         jmp restore_all

这里的GET_CURRENT(%ebx)将当前进程task_struct结构的指针放入寄存器EBX中,此时,内核的内容还如图3.6所示。然后两条“mov”指令是为了把中断发生前夕EFALGS寄存器的高16位与代码段CS寄存器的内容拼揍成32的长整数,其目的是要检验:

·      中断前夕CPU是否够运行VM86模式

·      中断前夕CPU是运行在用户空间还是内核空间。

VM86模式是为在i386保护模式下模拟运行DOS软件而设置的,EFALGS寄存器高16位中有个标志位表示CPU是否运行在VM86模式,我们在此不予详细讨论。CS的最低两位表示中断发生时CPU的运行级别CPL,若这两位为3,说明中断发生于用户空间。

如果中断发生在内核空间,则控制权直接转移到标号restore_all。如果中断发生于用户空间(或VM86模式),则转移到ret_from_sys_call

ENTRY(ret_from_sys_call)

         cli                             # need_resched and signals atomic test

         cmpl $0,need_resched(%ebx)

         jne reschedule

         cmpl $0,sigpending(%ebx)

         jne signal_return

restore_all:

         RESTORE_ALL

 

  reschedule:

         call SYMBOL_NAME(schedule)    # test

         jmp ret_from_sys_call

 进入ret_from_sys_call后,首先关中断,也就是说,执行这段代码时CPU不接受任何中断请求。然后,看调度标志是否为非0,其中常量need_resched 定义为20need_resched(%ebx)表示当前进程task_struct结构中偏移量need_resched处的内容,如果调度标志为非0,说明需要进行调度,则去调用schedule()函数进行进程调度,这将在第五章进行详细讨论。

   同样,如果当前进程的task_struct结构中的sigpending标志为非0,就表示该进程有信号等待处理,要先处理完这些信号后才从中断返回,关于信号的处理将在“进程间通信”一章进行讨论。处理完信号,控制权还是返回到restore_allRESTORE_ALL宏操作在前面已经介绍过,也就是恢复中断现场,彻底从中断返回。