2009年(49)
分类: LINUX
2009-06-03 22:22:25
Linux中的任务切换
参考资料:
1.Understanding Linux Kernel 3nd
2.Linux Kernel sourcecode. version:2.6.11.10
一.任务切换的相关知识
一个进程中的硬件上下文(hardware context)信息被保存在进程描述符(process descriptor)和内核栈(Kernel Mode Stack)当中.
进程切换只会发生在内核态。
Task State Segment: x86下面用来存储进程中硬件信息的。但Linux通过的是软切换,所以用不到X86里面提供的硬件切换。但是TSS在Linux中还是需要被正确的设置,一是为了进程从用户态切换到内核态的时候,从TSS中获取内核栈(Kernel Mode Stack)的地址;二是通过TSS中的IO mapbit验证用户态进程访问IO的合法性。
需要注意的是,Linux中每个进程都共用一个TSS。
在进程的进程描述符(process descriptor)中,有一个数据段thread_struct,就是用来存储硬件寄存器的信息的。
二.进程切换过程
任务切换从函数schedule()开始。
任务切换通常需要完成两个部分:
1. 切换PGD(page global directory)来为新进程建立新的地址空间;
2. 切换内核栈(Kernel Mode Stack)和硬件寄存器信息。
这里,我主要介绍第二个部分的内容,所以假定第一个部分已经切换完成,所有的操作都是在新进程的地址空间进行的。
第二部分的内容从switch_to这个宏开始:
#define switch_to(prev,next,last) do { \
unsigned long esi,edi; \
asm volatile("pushfl\n\t" \
"pushl %%ebp\n\t" \
"movl %%esp,%0\n\t" /* save ESP */ \
"movl %5,%%esp\n\t" /* restore ESP */ \
"movl $
"pushl %6\n\t" /* restore EIP */ \
"jmp __switch_to\n" \
"1:\t" \
"popl %%ebp\n\t" \
"popfl" \
:"=m" (prev->thread.esp),"=m" (prev->thread.eip), \
"=a" (last),"=S" (esi),"=D" (edi) \
:"m" (next->thread.esp),"m" (next->thread.eip), \
"2" (prev), "d" (next)); \
} while (0)
1.存储状态寄存器的值
pushfl\n\t"
pushl %%ebp\n\t"
这两部是存储eflags和ebp寄存器的值,把这两个值保存到内核栈中,这么早保存的目的就是为了保证这两个寄存器里面的值不会因为后面执行任务切换而改变状态从而影响进程恢复。
2.存储切出进程的内核栈头指针
"movl %%esp,%0\n\t" /* save ESP */
将切出进程的内核栈头的指针存储到其本身task_struct结构中的thread.esp中,即prev->thread.esp中。
3.加载切进进程的内核栈头指针
"movl %5,%%esp\n\t" /* restore ESP */
切进进程内核栈加载,即加载next->thread.esp。
4.存储切出进程的EIP
"movl $
即存储1编号出的指针运行地址到prev->thread.eip中。
5.加载切进的进程EIP
"pushl %6\n\t" /* restore EIP */
这里是把next->thread.eip的值压入内核栈中,这样的目的是因为下一步要调用__switch_to函数,这样,当这个函数返回时,会执行ret指令,这个指令的目的就是从栈中推出第一个值加载到EIP当中,这样就相当于加载了新进程的EIP。这样当__switch_to函数返回的时候,就正式的执行切进进程切出时候的程序了。
6.进入__switch_to函数
"jmp __switch_to\n"
现在看起来是处理协议保存协处理器的值的一些功能
7.恢复状态寄存器的值
"1:\t"
"popl %%ebp\n\t"
"popfl"
注意,执行到这里的时候,进程又切回到原来的那个prev进程了,就是上面即将切出的那个进程。
再看看前面,发现了吧,原来压入的EIP正式这个“1”开头的地址。