Linux fork源码分析

fork就是复制进程,所以我们要先了解进程的概念。进程是一个正在运行的程序,由程序代码、数据、变量(占用着系统内存)、打开的文件(文件描述符)和环境组成,进程是资源分配的最小单位,由进程控制块管理。每个进程的产生分两步:一、分配PCB;二、准备进程实体,如分配内存空间等。
fork调用一次返回2次,子进程的返回0,父进程返回新子进程的PID。下图是fork调用流程:

这里写图片描述

由下图可知,fork、vfork和clone三个系统调用所对应的系统调用服务例程都调用了do_fork,但调用时传递的参数不同,参数不同正好导致子进程与父进程对资源的共享程度不同。fork是全部复制父进程,传给子进程,所以fork不带参数;clone是复制部分父进程资源,就是说复制是有选择的;而vfork创建的是线程不是进程,并且vfork创建的子进程先于父进程执行,子进程执行结束或退出父进程才被唤醒。

这里写图片描述

do_fork源代码重点分析:
1、定义PCB指针struct task_struct *p;
作用:接收子进程所分配的进程描述符(这儿是用copy_process实现的)。
这里写图片描述
解释以上几个参数:
clone_flags:该标志位的4个字节分为两部分。最低的一个字节为子进程结束时发送给父进程的信号代码,通常为SIGCHLD;剩余的三个字节则是各种clone标志的组合; 通过clone标志可以有选择的对父进程的资源进行复制;fork,vfork和clone就是因为flag标志不同才加以区分的。clone常用标志如下表:
这里写图片描述

statck_start:子进程用户态堆栈的地址;

regs:指向pt_regs结构体的指针。当系统发生系统调用,即用户进程从用户态切换到内核态时,该结构体保存通用寄存器中的值,并被存放于内核态的堆栈中;

stack_size:未被使用,通常被赋值为0;

parent_tidptr:父进程在用户态下pid的地址,只有在CLONE_PARENT_SETTID标志被设定时才有意义;

child_tidptr:子进程在用户态下pid的地址,和上面的一样,也是在CLONE_CHILD_SETTID标志被设定时有意义;

2、分配PID,cat /proc/sys/kernel/pid_max命令可以查看一个系统支持的最大进程数,进程数的范围0~32768,理论值。分配完毕后,判断pid是否分配成功:
这里写图片描述

3、检查当前进程是否被跟踪
跟踪与否是看ptrace的值。父进程非0则未被跟踪。如果父被跟踪,就得判断子进程是否被跟踪。
如果trace为1,那么就将跟踪标志CLONE_PTRACE加入标志变量clone_flags中。

这里写图片描述

4、 通过copy_process()创建子进程的描述符,并创建子进程执行时所需的其他数据结构,最终则会返回这个创建好的进程描述符。
这里写图片描述

5、若copy_process执行不成功,则先释放已分配的pid,根据ptr_err得到错误码,保存在pid中,后面还会详细分析copy_process()。
这里写图片描述

如果成功,则执行如下代码:
首先定义了一个完成量vfork,如果clone_flags包含CLONE_VFORK标志,那么将进程描述符中的vfork_done字段指向这个完成量,之后再对vfork完成量进行初始化。
这里写图片描述

6、 如果子进程被跟踪(在父进程被跟踪的前提下,一般来说父进程是很少被跟踪的)或者设置了CLONE_STOPPED标志,就为子进程增加挂起信号。
具体操作是,将SIGSTOP信号所对应的那一位置1。
这里写图片描述

7、如果子进程未设置CLONE_STOPPED标志,那么通过wake_up_new_task函数使得父子进程之一优先运行;否则,将子进程的状态设置为TASK_STOPPED。
这里写图片描述

8、 如果父进程被跟踪,则将子进程的pid赋值给父进程,存于进程描述符的pstrace_message字段。使得当前进程停止运行,并向父进程的父进程发送SIGCHLD信号。
这里写图片描述

9、如果CLONE_VFORK标志被设置,则通过wait操作将父进程阻塞,直至子进程调用exec函数或者退出。(这儿显示的就是vfork的作用)
这里写图片描述

最后返回pid。这也是fork系统调用会返回子进程pid的原因。

copy_process(),完成创建子进程的PCB:
1、参数检查:标志位合法性和安全性检查
这里写图片描述

2、dup_tsk_struct():
为子进程分配PCB,并继承父进程的PCB中的值,但只将特有的信息改过来。每个进程都有task_thread,thread_info结构体保存的是进程上下文的信息。为子进程申请一个thread_info结构体,并用父进程的thread_info初始化子进程的thread_info结构体,用父进程的task_struct初始化子进程的task_struct,并将子进程task_struct中的thread_info指针指向自己的thread_info结构体,由于thread_info结构体中有指向当前进程的task_struct指针,所以也要修改这个指针的指向,让其指向当前子进程的task_struct结构体。
这里写图片描述

3、 复制进程所有信息
这里写图片描述

其中copy_files()复制父进程打开的文件描述符
这里写图片描述
这里写图片描述

4、除此之外还要复制父进程的内核栈,copy_thread(),将子进程的eax寄存器置0
子进程描述符的thread.esp字段初始化为子进程内核栈的基地址。ret_from_fork的地址存放在thread.eip中。 最后,如果CLONE_SETTLS标志被置位,则子进程获取由CLONE系统调用的参数tls指向的用户态数据结构所表示的TLS段。
这就是为什么父子进程沿着统一位置执行,以及子进程的返回值是0。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值