您的位置:首页 > 运维架构 > Linux

Linux kernel 分析之九:fork()系统调用

2015-07-22 21:28 302 查看
不仅进入系统调用时要伪装现场,fork系统调用时返回时也需要伪装现场。因为是“无中生有”。

例如在fork创建新进程时,系统要保证新进程与旧进程一样,从相同的代码开始执行。比如:

#include<stdio.h>

#include<unistd.h>

int main()

{

pid_t pid;

if((pid=fork())>0)

{

printf("parent\n");

}

else if(pid==0)

{

printf("child\n");

}

else

printf("error\n");

return 0;

}

在fork()处,也就是执行call指令的过程中,产生了一个新进程。

80483f0: e8 ef fe ff ff call 80482e4 <fork@plt>执行完fork()后,新旧两个进程都执行相同的指令:

80483f5: 89 45 fc mov %eax,0xfffffffc(%ebp)

80483f8: 83 7d fc 00 cmpl $0x0,0xfffffffc(%ebp)

给人的感觉是新进程和旧进程一样,也是从main函数开始执行,只是执行到fork()处,返回值不一样而已。内核是如何做到的呢?当内核执行fork()系统调用时,内核堆栈里保存有很多寄存器的值。其中包括了eax等几个通用寄存器。通过SAVE_ALL和RESTORE_ALL这两个宏,pt_regs结构体里的变量与对应的寄存器建立了一一对应关系。只要修改pt_regs结构体里的变量,就可以达到修改系统调用结束后,进程执行那一刹那的寄存器值。

这样就简单了,因为fork()时首先把PCB复制了一份(sys_fork()->do_fork()->copy_process->dup_task_struct)。所以内核堆栈pt_regs结构体里的值也跟着复制了一份。之后,在copy_thread()中,把新进程的pt_regs的eax,也就是新进程的返回值设置

为0,eip不变。这样当新进程返回到用户态时,就仿佛刚执行完fork()返回0。

这是伪装用户态的现场。还有内核态的现场呢?copy_thread()中有以下代码:

468 childregs = (struct pt_regs *) ((unsigned long) childregs - 8);469 *childregs = *regs;470 childregs->eax = 0;471 childregs->esp = esp;472

473 p->thread.esp = (unsigned long) childregs;

当子进程被调度到时,子进程处于内核态。经过switch_to()进程切换,eip设置为thread.eip,esp设置为thread.esp。 eip是ret_from_fork,esp是 childregs。伪装成刚执行完系统调用fork准备从内核态返回到用户态。

474 p->thread.esp0 = (unsigned long) (childregs+1);

thread.esp0在switch_to()->__switch_to()中,会通过load_esp0设置为

tss_struct中的esp0。这个字段是在CPU运行状态变化时(如系统调用从用户态进入内核态)保存0级也就是内核态的堆栈指针。这样的话不是有两个地方有内核态堆栈指针了么?不过他们不矛盾。thread.esp在进程切换时使用。thread.esp0在CPU运行状态变化时使用。当子进程返回到用户态后又发生中断或者系统调用。这时使用的是 esp0,内核堆栈是空的。正好指向(unsigned long) (childregs+1)。

如何同步,我也不清楚。475

476 p->thread.eip = (unsigned long) ret_from_fork;

p->thread中保存了进程切换所需的很多信息。包括内核态的esp,eip

伪装的现场,给人的感觉是新进程在内核态正好快执行到 ret_from_fork时由于中断等原因被抢占,所以在fork()完毕后可能进行的进程调度中,新进程可能被调度到,在执行完ret_from_fork中的一些代码,返回到用户态后,开始执行fork()之后的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: