fork()的真正执行采用的是do_fork()函数,所以下文将从do_fork()函数对fork()进行源码解析。下图是do_fork()的源码函数设计:

从上图我们可以看到do_fork()涉及到众多的参数。所以在进入do_fork函数进行分析之前,很有必要了解一下它的参数。

clone_flags:克隆标识;

*
* cloning flags:
*/
#define CSIGNAL 0x000000ff /* signal mask to be sent at exit */
/**
* 共享内存描述符和所有页表
*/
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
/**
* 共享根目录和当前工作目录所在的表,以及用于屏蔽新文件初始许可权的位掩码值
*/
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
/**
* 共享打开文件表
*/
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
/**
* 共享信号处理程序的表、阻塞信号表和挂起信号表
* 如果该标志为1,那么一定要设置CLONE_VM
*/
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
/**
* 如果父进程被跟踪,那么,子进程也被跟踪。
* 尤其是,debugger程序可能希望以自己作为父进程来跟踪子进程。在这种情况下,内核把该标志强设为1
*/
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
/**
* 在发出vfork系统调用时设置
*/
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
/**
* 设置子进程的父进程为调用进程的父进程。
*/

#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
/**
* 把子进程插入到父进程的同一线程组中。并使子进程共享父进程的信号描述符。因此也设置子进程的tgid字段和group_leader字段。
* 如果这个标志位为1,则必须设置CLONE_SIGHAND标志。
*/
#define CLONE_THREAD 0x00010000 /* Same thread group? */
/**
* 当clone需要自己的命名空间时,设置该标志。不能同时设置本标志和CLONE_FS。
*/
#define CLONE_NEWNS 0x00020000 /* New namespace group? */
/**
* 共享System V IPC取消信号量的操作。
*/
#define CLONE_SYSVSEM 0x00040000 /* share system V SEM_UNDO semantics */
/**
* 为轻量级进程创建新的线程局部存储段(TLS),该段由参数tls所指向的结构进行描述。
*/
#define CLONE_SETTLS 0x00080000 /* create a new TLS for the child */
/**
* 把子进程的PID写入由ptid参数所指向的父进程的用户态变量。
*/
#define CLONE_PARENT_SETTID 0x00100000 /* set the TID in the parent */
/**
* 如果该标志被设置,则内核建立一种触发机制,用在子进程要退出或要开始执行新程序。

* 在这些情况下,内核将清除由参数ctid所指向的用户态变量,并唤醒等待这个事件的任何进程
*/
#define CLONE_CHILD_CLEARTID 0x00200000 /* clear the TID in the child */
/**
* 遗留标志,将被内核忽略
*/
#define CLONE_DETACHED 0x00400000 /* Unused, ignored */
/**
* 内核设置这个标志以使CLONE_PTRACE标志推动作用(禁止内核线程跟踪进程)
*/
#define CLONE_UNTRACED 0x00800000 /* set if the tracing process can't force CLONE_PTRACE on this clone */
/**
* 把子进程的PID写入由ctid参数所指向的子进程的用户态变量中
*/
#define CLONE_CHILD_SETTID 0x01000000 /* set the TID in the child */
/**
* 强迫子进程开始于TASK_STOPPED状态
*/

#define CLONE_STOPPED 0x02000000 /* Start in stopped state */

statck_start:子进程用户态堆栈的地址,即栈的起始位置。

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

stack_size:栈的大小;

parent_tidptr:父进程在用户态下pid的地址;

child_tidptr:子进程在用户态下pid的地址;

了解了参数之后,我们已经知道PID代表了各进程的进程ID。也就是说,PID就是各进程的身份标识。 只要运行一程序,系统会自动分配一个标识! 是暂时唯一:进程中止后,这个号码就会被回收,并可能被分配给另一个新进程。 只要没有成功运行其他程序,这个pid会继续分配给当前要运行的程序!! 如果成功运行一个程序,然后再运行别的程序时,系统会自动分配另一个pid! 所以,这个函数内部实际进行了以下过程:

(1)我们要为子进程分配新的pid参数。 在一开始,该函数定义了一个task_struct类型的指针p,用来接收即将为新进程(子进程)所分配的进程描述符。紧接着使用alloc_pidmap函数为这个新进程分配一个pid。由于系统内的pid是循环使用的,所以采用位图方式来管理。简单的说,就是用每一位(bit)来标示该位所对应的pid是否被使用。分配完毕后,判断pid是否分配成功,其中,EAGAIN表示当前的进程数已经达到了系统规定的上限。

(2) 接下来检查当前进程(父进程)的ptrace字段。ptrace是用来标示一个进程是否被另外一个进程所跟踪。所谓跟踪,最常见的例子就是处于调试状态下的进程被debugger进程所跟踪。父进程的ptrace字段非0时说明debugger程序正在跟踪父进程,那么接下来通过fork_traceflag函数来检测子进程是否也要被跟踪。如果trace为1,那么就将跟踪标志CLONE_PTRACE加入标志变量clone_flags中。 通常上述的跟踪情况是很少发生的,因此在判断父进程的ptrace字段时使用了unlikely修饰符。使用该修饰符的判断语句执行结果与普通判断语句相同,只不过在执行效率上有所不同。正如该单词的含义所表示的那样,current->ptrace很少为非0。因此,编译器尽量不会把if内的语句与当前语句之前的代码编译在一起,以增加cache的命中率。与此相反,likely修饰符则表示所修饰的代码很可能发生。

(3)创建一个子进程的进程描述符。主要使用的函数设计原型是:p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);如果成功的话,则返回该进程描述符。

(4)创建描述符成功之后,定义一个vfok来表示完成量,如果clone_flags中和CLONE_VFORK一样,则将这个完成量赋给vfork_done.并初始化这个完成量。

if (!IS_ERR(p)) {
struct completion vfork;

if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}

(5)如果子进程被跟踪或者设置了CLONE_STOPPED标志,那么通过sigaddset函数为子进程设置挂起信号。其中,signal对应一个unsigned long类型的变量,该变量的每个位分别对应一种信号。具体的操作是,将SIGSTOP信号所对应的那一位置1。并将p所代表的子线程的线程标志置为有挂起信号。

/**
* 如果设置了CLONE_STOPPED,或者必须跟踪子进程.
* 就设置子进程为TASK_STOPPED状态,并发送SIGSTOP信号挂起它.
*/
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}

sigaddset源代码如下:

/**
* 把nsig信号在set变量中对应的位置为1
*/
static inline void sigaddset(sigset_t *set, int _sig)//19
{
unsigned long sig = _sig - 1;  //18
if (_NSIG_WORDS == 1) //2
set->sig[0] |= 1UL << sig;
else
set->sig[sig / _NSIG_BPW] |= 1UL << (sig % _NSIG_BPW);
}

其中

#define _NSIG 64
#define _NSIG_BPW 32
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)

/**
* 进程有挂起信号
*/
#define TIF_SIGPENDING 2 /* signal pending */

(6)没有设置CLONE_STOPPED,就调用wake_up_new_task,使得父子进程之一优先运行;否则,将子进程的状态设置为TASK_STOPPED。

(7)如果子进程正被跟踪,则把子进程的PID赋给父进程的ptrace_message,并调用ptrace_notify。ptrace_notify使当前进程停止运行,并向当前进程的父进程发送SIGCHLD信号.子进程的祖父进程是跟踪父进程的debugger进程.如此一来,祖父进程可获取到当前子进程的PID

(8) 如果CLONE_VFORK标志被设置,则通过wait操作将父进程阻塞,直至子进程调用exec函数或者退出.如果copy_process()在执行的时候发生错误,则先释放已分配的pid;再根据PTR_ERR()的返回值得到错误代码,保存于pid中。

fork()相关的源码解析的更多相关文章

  1. 《Flink 源码解析》—— 源码编译运行

    更新一篇知识星球里面的源码分析文章,去年写的,周末自己录了个视频,大家看下效果好吗?如果好的话,后面补录发在知识星球里面的其他源码解析文章. 前言 之前自己本地 clone 了 Flink 的源码,编 ...

  2. Flink 源码解析 —— 源码编译运行

    更新一篇知识星球里面的源码分析文章,去年写的,周末自己录了个视频,大家看下效果好吗?如果好的话,后面补录发在知识星球里面的其他源码解析文章. 前言 之前自己本地 clone 了 Flink 的源码,编 ...

  3. Spring中AOP相关的API及源码解析

    Spring中AOP相关的API及源码解析 本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 谈谈Spring ...

  4. [源码解析] PyTorch 分布式 Autograd (3) ---- 上下文相关

    [源码解析] PyTorch 分布式 Autograd (3) ---- 上下文相关 0x00 摘要 我们已经知道 dist.autograd 如何发送和接受消息,本文再来看看如何其他支撑部分,就是如 ...

  5. react 中间件相关的一些源码解析

    零.随便说说中间件 在react的使用中,我们可以将数据放到redux,甚至将一些数据相关的业务逻辑放到redux,这样可以简化我们组件,也更方便组件抽离.封装.复用,只是redux不能很好的处理异步 ...

  6. 【JUC源码解析】ForkJoinPool

    简介 ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性.对于那种大任务分割小任务的场景(分治)尤其有用. 框架图 ...

  7. Android源码解析系列

    转载请标明出处:一片枫叶的专栏 知乎上看了一篇非常不错的博文:有没有必要阅读Android源码 看完之后痛定思过,平时所学往往是知其然然不知其所以然,所以为了更好的深入Android体系,决定学习an ...

  8. [源码解析]Oozie来龙去脉之提交任务

    [源码解析]Oozie来龙去脉之提交任务 0x00 摘要 Oozie是由Cloudera公司贡献给Apache的基于工作流引擎的开源框架,是Hadoop平台的开源的工作流调度引擎,用来管理Hadoop ...

  9. [源码解析] 当 Java Stream 遇见 Flink

    [源码解析] 当 Java Stream 遇见 Flink 目录 [源码解析] 当 Java Stream 遇见 Flink 0x00 摘要 0x01 领域 1.1 Flink 1.2 Java St ...

随机推荐

  1. web中cookie的使用

    一:cookie在浏览器中什么地方查找写入成功 二:如何用js写 function addCookie(name,value,expireHours){ var cookieString=name+& ...

  2. Failed global initialization:FileNotOpen: Failed to open "C:\MongoDB\data\log\mongo.log" 安装MongoDB时卡死

    在安装MongoDB的时候,下载了3.6版本,安装过程中发现到一半就卡死了,后面换了一个较低版本的才安装成功 这里是所有MongoDB版本的下载地址: https://www.mongodb.org/ ...

  3. web服务-3、epoll高效率实现并发服务器

    知识点: 之前写的四种方法实现并发服务效率都还是低,早期的服务器采用的是select和poll方式,select这种方式的特点是轮询所有套接字去一个个看有没有事件发生,但是装套接字的列表长度是有限制的 ...

  4. POJ 1201 Intervals (经典) (差分约束)

    <题目链接> 题目大意:给你$n$段区间,$a_i,b_i,c_i$ 表示在 $[a_i,b_i]$ 区间内至少要选择$c_i$个点.现在问你在满足这n个条件的情况下,最少要选多少个点? ...

  5. 【安全性测试】Android测试中的一点小发现

    在执行某个项目中的APP测试发现的两个问题,自然也是提供参考,作为经验记录下来. 一.通过apk的xml文件获取到某项目APP的账号和密码 使用eclipsel或者drozer,获得apk的xml文件 ...

  6. TypeError: 'module' object is not callable

    pkuseg.py 内容如下: import pkusegseg = pkuseg.pkuseg()text = seg.cut('我爱北京天安门')print(text) 原因是py文件名于包名一样 ...

  7. 转 InnoDB Error Handling

    14.20.4 InnoDB Error Handling Error handling in InnoDB is not always the same as specified in the SQ ...

  8. 图片编辑工具GIMP

    今天修改图片: 给图片添加alpha通道,选中要删去的部分,就会变成透明,要保存为png格式 文库参考: http://wenku.baidu.com/link?url=HR1lKoBKS1xbhUJ ...

  9. STL中的二分查找

    本文转载于https://blog.csdn.net/riba2534/article/details/69240450 使用的时候注意:必须用在非递减的区间中 二分查找的原理非常简单,但写出的代码中 ...

  10. DEV中右键菜单如何只在非空单元格上显示?

    问题: 1. 开发时,我的winform程序中有很多gridview,我希望右键菜单只在我点击非空的行时才显示,点击其他空白区域时不显示: 2. 有一个树状导航图,treelist 中的节点都有右键菜 ...