文档介绍:张华强
2004-08
1 LINUX 调度机制概述
LINUX 的进程调度机制的设计主要是从下面三个方面来考虑的。
调度的时机, LINUX 的进程调度分为主动调度和被动的调度。其中主动的调度在内核 空间通过调用 schedule可知由于 CPU 每次使用内核堆栈时对其所作的操作都是均衡的,因此每 次从用户空间堆栈切换到系统空间堆栈时,该堆栈都是空的。
穿过中断门后系统进入中断服务程序的入口。在 LINUX 系统中所有中断服务程序的总 入口都是由 gcc 预生成的,具有如下形式:
asmlinkage void IRQ0xYY_interrupt(;
__asm__(
"\n"
“ IRQ_0xYY_interrupt:\n\t"
"pushl $"#nr"-256\n\t" \
"jmp common_interrupt";
上述代码中的 YY 代表外部中断 0-254的 16进制值 (其中系统调用 0x80除外, 它是由 系 统 另 外 单 独 初 始 化 的 。 根 据 上 述 代 码 可 以 看 出 所 有 的 外 部 中 断 都 是 统 一 进 入 common_interrup的地方进行处理的。同样该段由 gcc 预处理出来的代码如下:
asmlinkage void call_do_IRQ(void;
__asm__(
"\n" __ALIGN_STR"\n"
"common_interrupt:\n\t"
SA VE_ALL
"call_do_IRQ:\n\t"
"call do_IRQ "\n\t"
"jmp ret_from_intr\n";
上述代 码的主 要作 用就是 通过 SA VE_ALL宏将 CPU 的 当前 寄存器 内容 入栈。 SA VE_ALL宏的定义如下:
#define SAVE_ALL \
"cld\n\t" \
"pushl %es\n\t" \
"pushl %ds\n\t" \
"pushl %eax\n\t" \
"pushl %ebp\n\t" \
"pushl %edi\n\t" \
"pushl %esi\n\t" \
"pushl %edx\n\t" \
"pushl %ecx\n\t" \
"pushl %ebx\n\t" \
"movl $" STR(__KERNEL_DS ",%edx\n\t" \
"movl %edx,%ds\n\t" \
"movl %edx,%es\n\t"
保护完 CPU 的寄存器,就通过一条 CALL 指令调用 do_IRQ进入真正的中断服务程序 中。这样 CPU 在进入中断服务程序 do_IRQ时系统的堆栈情况如图 2所示:
图 2 进入中断服务程序 do_IR时的中断
从图 2可知,当系统处理完中断从 do_IRQ(函数返回时将跳转到 ret_from_intr处开 始执行代码。 (由于系统设计使得在中断服务过程中不会调用 schedule(函数产生任务切换。 因而中断服务结束后一定会返回到图 2中的返回点 。
图 3 中断返回后的堆栈
其中的 ret_from_intr处的代码如下:
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
在上述代码中,系统首先取出当前进程(进程 1的 task_struct结构指针放入 ebx 寄存 器中。 然后在判断中断是发生在用户空间还是系统空间, 如果发生在系统空间, 则直接跳转 到 restore_all处中断返回。由于本例中中断发生在进程 1的用户空间中,因而程序跳转到 ret_from_syscall处执行,该段代码如下:
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
到达 ret_from_syscall后, 系统将判断当前进程 (进程 1 的 need_resched字段值是否为 零,如果非零则调