Linux2.6进程切换

来源:互联网 发布:知乎 大野智 编辑:程序博客网 时间:2024/05/16 07:52

在写进程切换前,必须先补充几个重要的知识点:

Linux2.6在80x86的分段:运行在用户态的所有linux进程都使用一对相同的段来对指令数据寻址,即用户代码段和用户数据段。同样,运行在内核态的进程都使用内核代码段和内核数据段。每个段都有相应的段选择符及对应的段寄存器,这里不在赘述。我要讲的是与进程切换有关的一个段寄存器:栈段寄存器ss。当CPL(当前特权值)为3时,它必须指向用户数据数据段的用户栈,当CPL为0时,它必须指向内核数据段中的一个内核栈。当从用户态切换到内核态时,Linux总是确保ss寄存器装有内核数据段的段选择符。

进程切换只发生在内核态。在执行进程切换之前,用户态进程所使用的所有寄存器内容都以保存到内核态堆栈上(包括用户态的ss和esp寄存器的值)。CPU从用户态切换到内核态它需要获得内核态堆栈的地址esp0,内核态堆栈的地址esp0存放在TSS(任务状态段),《深入理解Linux内核》的404页介绍了如何获得TTS的esp0字段的值,获得esp0的值后将其装入esp寄存器,TTS中esp0的值由__switch_to()函数进行赋值(《深入理解Linux内核》112页)。进程再开始保存用户态进程所使用的所有寄存器内容,当然esp寄存器的值也会随着压栈而减小。在后面介绍的switch_to宏的第三步中该值最后会被保存到prev->thread.esp中。

在这里注意区分thread->esp和thread->esp0,esp0始终指向进程内核栈为空时的栈顶,而esp随着push和pop指令而改变。

进程切换由两部分组成:

1、切换也全局目录以安装一个新的地址空间,本文对此不做介绍

2、切换内核堆栈和硬件上下文。主要又switch_to宏完成,其中switch_to宏中调用的__switch_to()函数完成硬件上下文的切换

现在开始介绍进程切换的重点switch_to宏

#define switch_to(prev, next, last) 


do {                                                                    \

        /*                                                              \

         * Context-switching clobbers all registers, so we clobber      \

         * them explicitly, via unused output variables.                \

         * (EAX and EBP is not listed because EBP is saved/restored     \

         * explicitly for wchan access and EAX is the return value of   \

         * __switch_to())                                               \

         */                                                             \

        unsigned long ebx, ecx, edx, esi, edi;                          \

                                                                        \

        asm volatile("pushfl\n\t"               /* save    flags */     \

                     "pushl %%ebp\n\t"          /* save    EBP   */     \

                     "movl %%esp,%[prev_sp]\n\t"        /* save    ESP   */ \

                     "movl %[next_sp],%%esp\n\t"        /* restore ESP   */ \

                     "movl $1f,%[prev_ip]\n\t"  /* save    EIP   */     \

                     "pushl %[next_ip]\n\t"     /* restore EIP   */     \

                     "jmp __switch_to\n"        /* regparm call  */     \

                     "1:\t"                                             \

                     "popl %%ebp\n\t"           /* restore EBP   */     \

                     "popfl\n"                  /* restore flags */     \

                                                                        \

                     /* output parameters */                            \

                     : [prev_sp] "=m" (prev->thread.sp),                \

                       [prev_ip] "=m" (prev->thread.ip),                \

                       "=a" (last),                                     \

                                                                        \

                       /* clobbered output registers: */                \

                       "=b" (ebx), "=c" (ecx), "=d" (edx),              \

                       "=S" (esi), "=D" (edi)                           \

                                                                        \

                       /* input parameters: */                          \

                     : [next_sp]  "m" (next->thread.sp),                \

                       [next_ip]  "m" (next->thread.ip),                \

                                                                        \

                       /* regparm parameters for __switch_to(): */      \

                       [prev]     "a" (prev),                           \

                       [next]     "d" (next));                          \

} while (0)


其实对于理解switch_to也可以略过源代码:

1、在eax和edx寄存器中分别保存prev和next的值

2、把efkags和ebp寄存器的内容保存在prev的内核栈中。(相应的esp寄存器的值也会减小,只要是由push或者POP操作就一定会改变esp寄存器的值,不管esp指向那个进程的内核堆栈段)

3、把esp(指向prev内核堆栈地址)保存到prev->thread.esp中。

4、把next->thread.esp的值装入esp。此时,内核开始在next的内核栈上操作。

5、把标记为1的地址存入prev->thread.eip。当被替换的进程从新恢复执行时,进程执行被标记为1的那条指令。

6、宏把next->thread.eip的值压入next的内核栈

7、跳到__switch_to()函数

8、这里被进程B替换的进程A再次获得CPU(我对这句话的理解是先前一直是调度程序在占有CPU,切换还未完成,A从新获得CPU):它执行一些保存(英文原文单词为restore)eflags和ebp寄存器内容的指令。(我觉得restore翻译成恢复更好,因为此时pop指令弹出的值是B进程被其他进程替换出去时保存的自己的eflags和ebp值)。

9、拷贝eax寄存器的内容到switch_to宏的第三个参数last标示的内存区域中

看不懂,那看图

switch_to之前



切换堆栈之前

切换堆栈之后

push和Jump操作之后


_switch_to汇编返回

switch_to完成



更进一步的进程切换知识,如为什么要有prev.next和last三个参数、__switch_to()函数所做的工作可以参考文章http://blog.csdn.net/yunsongice/article/details/8547107



0 0