Linux内核任务切换

  #define switch_to(prev,next,last) do {                  \
      asm volatile("pushl %%esi\n\t"                      \
               "pushl %%edi\n\t"                          \
               "pushl %%ebp\n\t"                          \
               "movl %%esp,%0\n\t"    /* save ESP */      \
               "movl %3,%%esp\n\t"    /* restore ESP */   \
               "movl $1f,%1\n\t"      /* save EIP */      \
               "pushl %4\n\t"         /* restore EIP */   \
               "jmp __switch_to\n"  /* 当__switch_to 从 ret返回时, 实际上相当于pop eip , 将22 行压入的地址 prec->thread.eip 放入eip, 也就是 1: 处 */  \
               "1:\t"                                     \
               "popl %%ebp\n\t"                           \
               "popl %%edi\n\t"                           \
               "popl %%esi\n\t"                           \
               :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  \
                "=b" (last)                   \
               :"m" (next->thread.esp),"m" (next->thread.eip),    \
                "a" (prev), "d" (next),               \
                "b" (prev));                  \
  } while (0)

这段代码玄机挺大的,这里面经过特殊有处理的寄存器有两个,一个是esp,一个是eip

先说esp,esp寄存器是很重要的寄存器,因为它代表了整个栈空间的首地址,栈中的局部变量的寻址全是靠esp寄存器,前面需要将edi,ebp,esp压入当前进程的内核空间栈中,所以esp寄存器是最后切换的,关于esp的地址没有压在堆栈中没有压在堆栈中的原因是:假使现在有3个进程ABC,每个进程的内核空间都有一个自己的栈,假如把A进程内核空间的栈,压入B进程的内核空间,那么从C进程切换到B进程的内核空间就无法切换,所以esp寄存器存放在了prev->thread.esp中,

关于restore eip,有一个问题是为什么使用jmp而不是使用call,其实几乎所有的任务换后,前者的eip都会指向1f处,但是有一个例外,如果prev进程也被switch_to出去过,那没问题,使用call也行,但如果该prev进程刚刚被创建,之前没有被switch_to出去过,那么next->thread.eip里存的将是ret_ftom_fork(参看copy_thread()函数)。这就是这里为什么不用call__switch_to而用jmp,因为call会导致自动把下面这句话的地址(也就是1f)压栈,然后__switch_to()就必然只能ret到这里,而无法根据需要ret到ret_from_fork。

  int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,
      unsigned long unused,
      struct task_struct * p, struct pt_regs * regs)
  {
      struct pt_regs * childregs;
  
      childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p)) - 1;
      struct_cpy(childregs, regs);
      childregs->eax = 0;
      childregs->esp = esp;
  
      p->thread.esp = (unsigned long) childregs;
      p->thread.esp0 = (unsigned long) (childregs+1);
  
      p->thread.eip = (unsigned long) ret_from_fork;      //第一次创建的进程
  
      savesegment(fs,p->thread.fs);
      savesegment(gs,p->thread.gs);
  
      unlazy_fpu(current);
      struct_cpy(&p->thread.i387, &current->thread.i387);
  
      return 0;
  }

这里说的是子进程,第一次调度到子进程的时候,子进程的SAVE_ALL保存的寄存器内容和regs实际上被复制进入了子进程,而子进程的esp和esp0是被刚初始化好的,这时候还没有进行过任务切换,所以不存在之前的现场,所以switch_to中的pop操作执行就会有问题,所以如果要是切换到这个进程的时候,子进程直接从返回到ret_from_fork就行。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值