linux fork返回两次的问题

我们接触linux用户编程时,在做多进程程序处理时绕不过去的就是fork调用,经常被告诉它的特殊性:一次调用两次返回,父进程中返回子进程的pid,子进程返回0,和同事讨论的时候,走查代码的时候竟然没有找到子进程是如何返回的。
父进程返回的路径非常清晰,进入内核中do_fork->copy_process,成功之后将新的进程wakeup放到运行队列上,返回子进程的pid。但是子进程从哪返回的呢?

在整个fork的过程中,do_fork只是检查了一些参数的合法性,具体的工作还是交给了copy_process去完成

为子进程分配和初始化一个新的task_struct结构,基本过程是先分配一个新的task_struct,然后将父进程的所有内容统统复制过去,
其中所有的值信息和父进程中一致,指针指向父进程中的资源;
之后就开始分裂,为进程独立的信息分配对象,拷贝父进程的对象信息,对进程中某些信息进行清零,特别是统计信息,初始化自己的管理结构。
    1) 从父进程中复制进程独立的信息
        1.1) 进程组和会话信息
        1.2) 信号状态(忽略、捕获、阻塞信号的掩码)
        1.3) nice调度参数
        1.4) 对父进程用户凭据的引用
        1.5) 对父进程打开文件的引用(文件句柄表及相关引用数据结构,所以父子进程可以共享打开的文件)
        1.6) 对父进程限制(resources limitation)的引用
        1.7) 拷贝父进程的mm信息,复制mm_struct,vma_area_struct和页表信息,其中在复制pte时实现cow特性,将父子进程中的pte写权限全部清除
        1.8) 构建内核栈信息,因为用户栈信息已经在1.7中复制过了
    2) 清零统计信息
        2.1) cpu利用率统计时间
        2.2) 定时器
        2.3) ptrace,trace信息
        2.4) 挂起信号的信息
     3)初始化子进程管理信息
       分配新的pid,初始化task_struct管理链表:
       1.挂到全局的进程链表中,
       2.和父进程链接形成树状管理,
       3.放到hash表中可以通过pid快速查找

其中在1.8中在拷贝内核栈信息的时候通过copy_thread_tls,最重要的就是构建内核栈信息,一个是如何返回到用户空间,因为父进程接下来还有继续修饰子进程,它不能共享这部分过程,所以设置了它的ret_addr为ret_from_fork。内核的实现会反复变化,早期的内核通过直接设置rip寄存器为ret_from_fork。

	设置内核堆栈,尤其是返回地址、栈指针寄存器,帧基址寄存器
     p->thread.sp0 = (unsigned long)task_stack_page(p) + THREAD_SIZE;
     childregs = task_pt_regs(p);
     fork_frame = container_of(childregs, struct fork_frame, regs);
     frame = &fork_frame->frame;
     frame->bp = 0;
     frame->ret_addr = (unsigned long) ret_from_fork;         //call指令会自动将返回地址入栈,现在栈上存放返回地址的地方设置成它,当ret指令时触发。它清理标志能够被抢占和调度,之后就通过系统调用放回路径回到用户空间
     p->thread.sp = (unsigned long) fork_frame;      //栈指针指向fork_frame,里面低地址存存放一帧的寄存器,高地址存放用户空间寄存器
     //复制父进程的寄存器状态
     savesegment(gs, p->thread.gsindex);
     p->thread.gsbase = p->thread.gsindex ? 0 : me->thread.gsbase;
     savesegment(fs, p->thread.fsindex);
.。。。
     frame->bx = 0;               //

下面设置用户空间时寄存器为0,也就是fork之后子进程返回0

     *childregs = *current_pt_regs();            //复制父进程的用户空间寄存器状态:指令寄存器,栈寄存器rsp,由于父子进程的代码和数据是共享的,所以在返回后将继续执行
     childregs->ax = 0;                          //设置返回值为0,也就是fork在子进程中返回0

总结一下fork的过程:

参考链接:https://www.cnblogs.com/LittleHann/p/3853854.html
  1. 父子进程从同一个代码位置开始继续执行: 因为它们的"进程上下文"相同
  2. 父进程调用fork()返回子进程的PID: 父进程是正常调用
  3. 子进程返回0,因为内核态的EAX被设置为了0
  4. 父子进程不一定同时开始执行,但会有从内核态返回2次,一次是父进程,一次是子进程
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页