linux内核之进程切换

进程切换

      前面所介绍的schedule()中调用了switch_to宏,这个宏实现了进程之间的真正切换,其代码存放于include/ i386/system.h:

1   #define switch_to(prev,next,last) do {                                  \

2         asm volatile("pushl %%esi\n\t"                                  \

3                      "pushl %%edi\n\t"                                  \

4                      "pushl %%ebp\n\t"                                  \

5                      "movl %%esp,%0\n\t"        /* save ESP */          \


6                      "movl %3,%%esp\n\t"        /* restore ESP */       \      

 // 将要运行进程next的内核栈指针next->thread.esp置入esp寄存器中。从现在开始,内核对next的内核栈    进行操作,因此,这条指令执行从prev到next真正的上下文切换,因为进程描述符的地址与其内核栈的地址   紧紧地联系在一起(参见第四章),因此,改变内核栈就意味着改变当前进程。如果此处引用current的话, 那就已经指向next的task_struct结构了。从这个意义上说,进程的切换在这一行指令执行完以后就已经完 成。但是,构成一个进程的另一个要素是程序的执行,这方面的切换尚未完成。


    第七行用mov,因为是老的进程,在6行时栈就被切换了

7                      "movl $1f,%1\n\t"          /* save EIP */          \                            

   //这里用的是movl把11行的pop %%ebp这句话的地址保存到pre->eip(下次将要执行的地址)


        第八行用pushl ,因为是新的进程,在6行时栈就切换到了新进程栈      

8                      "pushl %4\n\t"             /* restore EIP */       \             

    //    将next->thread.eip压入next的内核栈(第六行已经切换栈了)。那么,next->thread.eip究竟指向那个地  址?实际上,它就是 next   上一次被调离时通过第7行保存的地址,也就是第11行popl指令的地址。因  为,每个进程被调离时都要执行 这里的第7行,这就决定了每个进程(除了新创建的进程)在受到调度而恢复  执行时都从这里的第11行开始。

9                      "jmp __switch_to\n"                                \               

//      通过jump指令(而不是 call指令,call会保存返回地址,而这里事先压入了保存地址,见8行)转入一个函数__switch_to()。这个函数的具体实现将在下面介绍。当  CPU执行到__switch_to()函数的ret指令时,最后进  入堆栈的next->thread.eip就变成了返回地址,这就是标号     “1”的地址。

10                      "1:\t"                                             \

11                     "popl %%ebp\n\t"                                   \

12                     "popl %%edi\n\t"                                   \

13                     "popl %%esi\n\t"                                   \

14                      :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  \              输出       %0,%1

15                       "=b" (last)                                       \                                                        %2

16                      :"m" (next->thread.esp),"m" (next->thread.eip),    \                   输入         %3,%4

17                       "a" (prev), "d" (next),                           \

18                       "b" (prev));                                      \

19 } while (0)




下面我们来讨论__switch_to()函数。

在调用__switch_to()函数之前,对其定义了fastcall :

extern void FASTCALL(__switch_to(struct task_struct *prev, struct task_struct *next));


表5.1

   参数类型

参数名

内存变量

寄存器

函数参数

输出参数

0%

prev->thread.esp

 

 

1%

prev->thread.eip

 

 

2%

 

ebx

last

输入参数

3%

next->thread.esp

 

 

4%

next->thread.eip

 

 

5%

 

eax

prev

6%

 

edx

next

7%

 

ebx

prev


     fastcall对函数的调用不同于一般函数的调用,因为__switch_to()从寄存器(如表5.1)取参数,而不像一般函数那样从堆栈取参数,也就是说,通过寄存器eax和edx把prev和next 参数传递给__switch_to()函数。

 

void __switch_to(struct task_struct *prev_p, struct task_struct *next_p)

{

        struct thread_struct *prev = &prev_p->thread,

                                *next = &next_p->thread;

        struct tss_struct *tss = init_tss + smp_processor_id();

 

      unlazy_fpu(prev_p);/* 如果数学处理器工作,则保存其寄存器的值*/

 

  /* 将TSS中的内核级(0级)堆栈指针换成next->esp0,这就是next 进程在内核

     栈的指针

  

      tss->esp0 = next->esp0;

 

   /* 保存fs和gs,但无需保存es和ds,因为当处于内核时,内核段

总是保持不变*/

 

       asm volatile("movl %%fs,%0":"=m" (*(int *)&prev->fs));

       asm volatile("movl %%gs,%0":"=m" (*(int *)&prev->gs));

 

     /*恢复next进程的fs和gs */

 

         loadsegment(fs, next->fs);

        loadsegment(gs, next->gs);

 

/* 如果next挂起时使用了调试寄存器,则装载0~7个寄存器中的6个寄存器,其中第4、5个寄存器没有使用 */

 

        if (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);

        }

 

         if (prev->ioperm || next->ioperm) {

                if (next->ioperm) {

              

/*把next进程的I/O操作权限位图拷贝到TSS中 */

                       memcpy(tss->io_bitmap, next->io_bitmap,

IO_BITMAP_SIZE*sizeof(unsigned long));

 

/* 把io_bitmap在tss中的偏移量赋给tss->bitmap */

                         tss->bitmap = IO_BITMAP_OFFSET;

                 } else

                  

/*如果一个进程要使用I/O指令,但是,若位图的偏移量超出TSS的范围,

*         就会产生一个可控制的SIGSEGV信号。第一次对sys_ioperm()的调用会

*         建立起适当的位图  */

                        

                        tss->bitmap = INVALID_IO_BITMAP_OFFSET;

         }

}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值