从前面的讨论我们知道,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 定义为20,need_resched(%ebx)表示当前进程task_struct结构中偏移量need_resched处的内容,如果调度标志为非0,说明需要进行调度,则去调用schedule()函数进行进程调度,这将在第五章进行详细讨论。
同样,如果当前进程的task_struct结构中的sigpending标志为非0,就表示该进程有信号等待处理,要先处理完这些信号后才从中断返回,关于信号的处理将在“进程间通信”一章进行讨论。处理完信号,控制权还是返回到restore_all。RESTORE_ALL宏操作在前面已经介绍过,也就是恢复中断现场,彻底从中断返回。