linux 2.6源代码情景分析笔记之进程8

内核分两部执行一个进程切换
1.切换页全局目录以安装一个新的地址空间;
2.切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含cpu寄存器。

切换内核堆栈的硬件上下文是由宏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 $1f,%1/n/t"          /* save EIP */          /
                     "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)

宏的参数prev,next,是局部变量的占用符,是输入参数,分别表示被替换进程和新进程描述符的地址在内存中的位置。
在任何进程切换中,涉及到三个进程而不是两个。假设内核决定暂停进程a而激活进程b.在schedule()函数中,prev指向a的描述符而next指向b的描述符。switch_to宏一旦使a暂停,a的执行流就冻结。
当内核想再次激活a,就必须暂停另一个进程c(这通常不同于b),就要用prev指向c,而next指向a来执行另一个switch_to宏。当a恢复它的执行流时,就会找到它原来的内核栈,于是prev局部变量还是指向a的描述符而next指向b的描述符。此时,代表进程a执行的内核就失去了对c的任何引用。
last是输出参数,表示宏把进程c的描述符地址写在内存的什么位置(这是在a恢复执行之后完成)。在进程切换之前,宏把第一个输入参数prev(在a的内核堆栈中分配的prev局部变量)表示的变量的内容存入cpu的eax寄存器。在完成进程切换,a已经恢复执行时,宏把cpu的eax寄存器的内容写入由第三个输出参数——last所指示的a在内存中的位置。因为cpu寄存器不会在切换点发生变化,所以c的描述符地址也存在内存的这个位置。在schedule()执行过程中,参数last指向a的局部变量prev,所以,prev被c的地址覆盖。

pushfl
pushl %ebp
将 eflags和 ebp寄存器的内容保存在prev内核栈中。编译器认为在switch_to结束之前它们的数值应当保持不变。
"movl %%esp,%0/n/t"
把esp的内容保存到prev->thread.esp中以使该字段指向prev内核栈的栈顶。
"movl %5,%%esp/n/t"
把next->thread.esp装入esp。此时内核开始在next的内核栈上操作,因此这指令完成的是从prev到next的切换。由于进程描述符的地址和内核栈的地址紧挨着,所以改变内核栈意味着改变当前进程。
"movl $1f,%1/n/t"
将标记为1的地址存入prev->thread.eip。当被替换的进程重新恢复时,进程执行被标记为1的那条指令
"pushl %6/n/t"             /* restore EIP */
把next->thread.eip的值,压入next的内核栈。
"jmp __switch_to/n"
跳入c函数。
"1:/t"                                             /
"popl %%ebp/n/t"                                   /
"popfl"  
然后被进程b替换的进程a再次获得cpu:它执行一些保存eflags和ebp寄存器内容的指令,这两条指令的第一条指令被标记为1.


此函数作用于prev_p,next_p参数,这两个参数表示前一个进程和新进程。这个函数的调用不同于一般函数的调用,因为__switch_to从eax和edx取参数prev_p和next_p,而不似大多数函数一样从栈中取参数。
struct task_struct fastcall * __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
        struct thread_struct *prev = &prev_p->thread,*next = &next_p->thread;
        int cpu = smp_processor_id();获得本地cpu的下标,执行代码的cpu。从当前进程的thread_info结构的cpu字段获得下标并将它保存到cpu局部变量。
        struct tss_struct *tss = &per_cpu(init_tss, cpu);
        __unlazy_fpu(prev_p);//有选择地保存prev_p进程的FPU,MMX,XMM寄存器内容。
        load_esp0(tss, next);//将next_p->thread.esp0装入对应与本地cpu的tss的esp0字段。
        load_TLS(next, cpu);//next_p进程使用的线程局部存储(TLS)段装入本地cpu的全局描述符表;
fs和gs段寄存器的内容分别存放在prev_p-> thread.fs和prev_p->thread.gs中。esi寄存器指向prev_p->thread结构。
        asm volatile("movl %%fs,%0":"=m" (*(int *)&prev->fs));
        asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs));
        if (unlikely(prev->fs | prev->gs | next->fs | next->gs)) {如果fs或者gs段寄存器已经被prev_p或者next_p进程中的任何一个使用,则将next_p进程的thread_struct描述符中保存的数值装入这些寄存器中。
                loadsegment(fs, next->fs);
                loadsegment(gs, next->gs);
        }
用next_p->thread.debugreg数组的内容装载dr0......dr7中的六个调试寄存器。只有在next_p被挂起时,正在使用调试寄存器(next_p->thread.debugreg[7]字段不为0),这种操作才能进行。这些寄存器不需要被保存,因为只有当一个调试器想要监控prev时prev_p->thread.debugreg才会被修改。
        if (unlikely(next->debugreg[7])) {
                loaddebug(next, 0);
                loaddebug(next, 1);
                loaddebug(next, 2);
                loaddebug(next, 3);
                /* no 4 and 5 */
                loaddebug(next, 6);
                loaddebug(next, 7);
        }
当prev,next有自己的定制i/o权限位图时必须这么做,因为进程很少修改权限位图,所以该位图在“懒”模式中被处理:当且仅当一个进程在当前时间片内实际访问i/o端口时,真实位图才被拷贝到本地cpu的tss中。
进程的定制i/o权限位图被保存在thread_info结构的io_bitmap_ptr字段指向的缓冲区中。
        if (unlikely(prev->io_bitmap_ptr || next->io_bitmap_ptr))
                handle_io_bitmap(next, tss);为next_p进程设置本地cpu使用的tss的io_bitmap字段如下:
如果next_p进程不拥有自己的i/o权限位图,则tss的io_bitmap字段被设为0x8000.
如果next_p进程拥有自己的i/o权限位图,则tss的io_bitmap字段被设为0x9000.
        return prev_p;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值