fork()是如何创建新进程的

       进程,我们一般都把它看做是程序执行的一个实例,是进程实体的运行过程,是系统进行资源分配和调度的一个独立的单位。有产生、有消亡。当一个进程创建时,子进程可以继承父进程所有资源,撤销时,也要归还所有资源。

一、进程描述符

       我们一般所说的进程控制块PCB对应到linux内核里面是一个task_struct的类型结构,它的字段包含了与一个进程相关的所有信息。下图即是一个linux进程描述符的概况:



二、创建进程

       之前的博客中我们大致分析了在start_kernel中的0号和1号进程,也分析过进程的切换。在用户看来,每当他输入了一条指令,shell进程就创建了一个新进程,那么进程到底是怎么创建的呢?现在,我们来看看进程的创建。
       linux系统中提供了三个系统调用可以创建新进程:clone()、fork()、vfork()。实际上,不管是我们比较熟悉的fork()还是剩下的两个在linux中都是通过clone()实现的。clone()是在c语言库中定义的一个封装函数,它负责建立进程堆栈并且调用对程序员隐藏的clone()系统调用。
       进一步观察发现,linux内核中又是用do_fork()来处理这三个系统调用的。(参考博客:do_fork

三、fork()系统调用分析

        先来看看fork()函数在系统中执行过程,也就是进程是怎样创建的。
       直观来看,fork产生新进程时是复制父进程资源,并给新进程分配相应的内核堆栈,然后更改一些数据。那么实际是不是这样呢?我们结合代码来分析。
       通过fork()系统调用的实例,用gdb跟踪调试。体会创建新进程的过程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
    int pid;
    /* fork another process */
    pid = fork();
    if (pid < 0) 
    { 
        /* error occurred */
        fprintf(stderr,"Fork Failed!");
        exit(-1);
    } 
    else if (pid == 0) 
    {
        /* child process */
        printf("This is Child Process!\n");
    } 
    else 
    {  
        /* parent process  */
        printf("This is Parent Process!\n");
        /* parent will wait for the child to complete*/
        wait(NULL);
        printf("Child Complete!\n");
    }
}

       执行这段代码会发现输出了两条信息,“This is Parent Process!” 和“This is Child Process!”、“Child Complete!”。为什么会出现这种情况呢?执行了else语句后又执行了else if语句。这就和fork这个函数有关了。因为fork在创建了子进程后,子进程执行了else if语句。那么fork创建子进程是从哪开始返回的呢?
       我们在sys_clone()、do_fork、dup_task_struct、copy_process、copy_thread、ret_from_fork处设置断点。





       单步执行,一条条语句的查看,就会发现和之前描述的基本符合。具体到内核代码来分析:当调用do_fork()时,这里的关键代码是copy_process()复制进程描述符。copy_process()创建进程描述符以及进程执行所需的所有其他数据结构。而这个函数里面有dup_task_struct()操作,为子进程获取进程描述符。还会获取一块内存区,存放新进程thread_info结构和内核栈。还有一个关键函数就是copy_thread()。用发出clone()系统调用时cpu寄存器的值来初始化进程的内核栈。
       执行完do_fork()后,系统有了处于可运行态的完整子进程,但是这个子进程并没有实际运行,调度程序决定了何时运行它。比如会把esp值装入寄存器,把函数ret_from_fork()的地址放入eip中,然后返回用户态等等。在fork()系统调用结束时,新进程开始执行。返回给子进程的值是0,返回给父进程的值是子进程的PID号。

四、总结

       新的进程通过复制父进程而建立。为了创建新进程,首先在系统的物理内存中为新进程创建一个 task_struct 结构,将旧进程的 task_struct 结构内容复制到其中,再修改部分数据。接着,为新进程分配新的堆栈,分配新的进程标识符 pid。然后,将这个新 task_struct 结构的地址填到 task 数组中,并调整进程链关系,插入运行队列中。于是,这个新进程便可以在下次调度时被选择执行。此时,由于父进程的进程上下文 TSS 结构复制到了子进程的 TSS 结构中,通过改变其中的部分数据,便可以使子进程的执行效果与父进程一致,都是从系统调用中退出,而且子进程将得到与父进程不同的返回值(返回父进程的是子进程的 pid,而返回子进程的是 0)。
       

       卢鹏  原创作品转载请注明出处  
      (部分内容引用网上资源,如有不当之处,可与本人联系)



  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值