李洋 原创作品转载请注明出处

《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000

系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。

而fork()允许用户态下创建新的进程, fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容,新旧进程使用同一代码段,复制数据段和堆栈段,这里的复制采用了注明的copy_on_write技术,即一旦子进程开始运行,则新旧进程的地址空间已经分开,两者运行独立。

在 Linux 内核中,供用户创建进程的系统调用fork()函数的响应函数是 sys_fork()、sys_clone()、sys_vfork()。这三个函数都是通过调用内核函数 do_fork() 来实现的。根据
调用时所使用的 clone_flags 参数不同,do_fork() 函数完成的工作也各异。下面结合实验过程简要分析do_fork()是怎么工作的,首先是代码以及简析如下:

/*
1618 *  Ok, this is the main fork-routine.
1619 *
1620 * It copies the process, and if successful kick-starts
1621 * it and waits for it to finish using the VM if required.
1622 */
1623long do_fork(unsigned long clone_flags,
1624          unsigned long stack_start,
1625          unsigned long stack_size,
1626          int __user *parent_tidptr,
1627          int __user *child_tidptr)
1628{
1629    struct task_struct *p;
1630    int trace = 0;
1631    long nr;
1632
1633    /*
1634     * Determine whether and which event to report to ptracer.  When
1635     * called from kernel_thread or CLONE_UNTRACED is explicitly
1636     * requested, no event is reported; otherwise, report if the event
1637     * for the type of forking is enabled.
1638     */
1639    if (!(clone_flags & CLONE_UNTRACED)) {
1640        if (clone_flags & CLONE_VFORK)
1641            trace = PTRACE_EVENT_VFORK;
1642        else if ((clone_flags & CSIGNAL) != SIGCHLD)
1643            trace = PTRACE_EVENT_CLONE;
1644        else
1645            trace = PTRACE_EVENT_FORK;
1646
1647        if (likely(!ptrace_event_enabled(current, trace)))
1648            trace = 0;
1649    }
1650  //创建进程描述符以及子进程所需要的其他所有数据结构
1651    p = copy_process(clone_flags, stack_start, stack_size,
1652             child_tidptr, NULL, trace);
1653    /*
1654     * Do this prior waking up the new thread - the thread pointer
1655     * might get invalid after that point, if the thread exits quickly.
1656     */
1657    if (!IS_ERR(p)) {
1658        struct completion vfork;
1659        struct pid *pid;
1660
1661        trace_sched_process_fork(current, p);
1662
1663        pid = get_task_pid(p, PIDTYPE_PID);
1664        nr = pid_vnr(pid);
1665
1666        if (clone_flags & CLONE_PARENT_SETTID)
1667            put_user(nr, parent_tidptr);
1668
1669        if (clone_flags & CLONE_VFORK) {
1670            p->vfork_done = &vfork;
1671            init_completion(&vfork);
1672            get_task_struct(p);
1673        }
1674
1675        wake_up_new_task(p);//新进程加入运行队列,并启动调度程序重新调度,使得新进程获得运行机会
1676
1677        /* forking complete and child started to run, tell ptracer */
1678        if (unlikely(trace))
1679            ptrace_event_pid(trace, pid);
1680
1681        if (clone_flags & CLONE_VFORK) {
1682            if (!wait_for_vfork_done(p, &vfork))
1683                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
1684        }
1685
1686        put_pid(pid);
1687    } else {
1688        nr = PTR_ERR(p);  //出错处理
1689    }
1690    return nr;
1691}

实验中几个关键步骤截图:

首先是运行到copy_process():

代码可以查看这里:http://codelab.shiyanlou.com/xref/linux-3.18.6/kernel/fork.c#copy_process

下面描述其最重要的处理步骤:
1. 检查参数clone_flags所传递标志的一致性。
2. 通过调用security_task_create(clone_flags)函数以及稍后的security_task_alloc(p)函数执行所有附加的安全检查。
3. 调用dup_task_struct(current)函数(也是来自/kernel/Fork.c)为子进程获得进程描述符。
4. 检查存放在p->signal->rlim[RLIMIT_NPROC].rlim_cur变量中的值是否小于或等于用户所拥有的进程数。
6. 查系统中的进程数量(存放在nr_threads变量中)是否超过max_threads变量的值。这个变量的缺省值取决于系统内存容量的大小。总的原则是:所有thread_info描述符和内核栈所占用的空间不能超过物理内存大小的1/8。不过,系统管理员可以通过写/proc/sys/kernel/threads-max文件来改变这个值。
7. 如果实现新进程的执行域和可执行格式的内核函数都包含在内核模块中,则递增它们的使用计数器。
8. 设置与进程状态相关的几个关键字段.
9. 把新进程的PID存入tsk_pid字段。
10. 如果clone_flags参数中的CLONE_PARENT_SETTID标志被设置,就把子进程的PID复制到参数parent_tidptr指向的用户态变量中。
11. 初始化子进程描述符中的list_head数据结构和自旋锁,并为与挂起信号、定时器及时间统计表相关的若干字段赋初值。
12. 调用copy_semundo、copy_files、copy_fs、copy_sighand、copy_signal、copy_mm和copy_namespace来创建新的数据结构,并把父进程的相应数据结构的值复制到新数据结构中,除非clone_flags参数指出它们有不同的值。
13. 调用copy_thread(0, clone_flags, stack_start, stack_size, p, regs),用发出clone()系统调用时CPU寄存器的值来初始化子进程的内核栈。不过,copy_thread把eax寄存器对应的字段(这也是fork和clone系统调用在子进程中的返回值)字段强行置为0。进程描述符的thread.esp字段初始化为子进程内核栈的基地址,汇编语言函数ret_from_fork()的地址存放在thread.eip字段中。如果父进程使用I/O权限位图,则子进程获取该位图的一个拷贝。最后,如果CLONE_SETTLS标志被设置,则子进程获取由clone系统调用的参数tls指向的用户态数据结构所表示的TLS段。
14. 如果clone_flags参数的值被置为CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID,就把child_tidptr参数的值分别复制到tsk->set_child_tid或tsk->clear_child_tid字段。这些标志说明:必须改变子进程用户态地址空间的child_tidptr所指向的变量的值,不过实际的写操作要稍后再执行。
15. 清除子进程thread_info结构的TIF_SYSCALL_TRACE标志,以使ret_from_fork()函数不会把系统调用结束的消息通知给调试进程。
16. 用clone_flags参数低位的信号数字编码初始化tsk-> exit_signal字段,如果CLONE_THREAD标志被设置,就把tsk-> exit_signal字段初始化为-1。正如我们将在下一章进程终止所看见的,只有当线程组的最后一个成员(通常是现在组的头儿)死亡,才会产生一个信号,以通知领头进程的父进程。
17. 调用sched_fork(p)完成对新进程调度程序数据结构的初始化。该函数将新进程的状态设置为TASK_RUNNING,并把thread_info结构的preempt_count字段设置为1,从而禁止内核抢占。此外,为了保证公平的进程调度,该函数在父子进程之间共享父进程的时间片。
18. 把新进程的thread_info结构的cpu字段设置为由smp_processor_id()所返回的本地CPU号。
19. 初始化表示亲子关系的字段。尤其是,如果CLONE_PARENT或CLONE_THREAD被设置,就用current->real_parent的值初始化tsk->real_parent和tskp->parent,因此,子进程的父进程似乎是当前进程的父进程。否则tsk->real_parent和tskp->parent置为当前进程。
20. 如果不需要跟踪子进程(没有设置CLONE_PTRACE标志),就把tsk->ptrrace字段设置为0。 tsk->ptrrace字段会存放一些标志,而这些标志是在一个进程被另外一个进程跟踪时才会用到的。采用这种方式,即使当前进程被跟踪,子进程也不会被跟踪。
21. 执行SET_LINKS宏,把新进程描述符插入进程链表。
22. 如果子进程必须被跟踪(tsk->ptrrace字段的PT_PTRACED标志被设置),就把current->parent赋给tsk->parent,并将子进程插入调试程序的跟踪链表中。
23. 调用attach_pid把新进程描述符PID插入pidhash[PIDTYPE_PID]散列表。
24. 如果子进程是领头进程(CLONE_THREAD标志被清0),否则,如果子进程属于它的父进程的线程组(CLONE_THREAD标志被设置).
25. 现在,新进程已经被加入进程集合:递增nr_threads变量的值。
26. 递增total_forks变量以记录被创建的进程的数量。
27. 终止并返回子进程描述符指针(p,等价于tsk)。

然后执行wake_up_new_task,把新进程加入运行队列,并启动调度程序重新调度,使新进程获得运行机会:

1.调整父进程和子进程的调度参数
2.如果子进程和父进程运行在同一个CPU上,而且父进程和子进程不能共享同一组页表(CLONE_VM标志被清0),那么,就把子进程插入到父进程的运行队列,插入时让子进程恰好在父进程前面,因此迫使子进程优于父进程先运行。如果子进程刷新其地址空间,并且在创建之后执行新程序,那么这种简单的处理会产生较好的性能。而如果我们让父进程先运行,那么写时复制机制将会执行一些不必要的页面复制。
3.否则,如果子进程与父进程运行在不同CPU上,或者父进程和子进程共享同一组页表(CLONE_VM标志被设置),就把子进程插入父进程所在运行队列的队尾。

最后会执行一些出错处理,返回出错信息。

转载于:https://www.cnblogs.com/digital-romance/p/do_fork.html

分析Linux内核创建一个新进程的过程相关推荐

  1. 实验六:分析Linux内核创建一个新进程的过程

    20135108 李泽源 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h ...

  2. linux内核创建用户,分析Linux内核创建一个新进程的过程

    谢文杰 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验目的 阅 ...

  3. 6、分析Linux内核创建一个新进程的过程

    姓名:周毅原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 这篇文章主要分析lin ...

  4. linux搭建一个的过程,Linux内核创建一个新进程的过程

    此文仅用于MOOCLinux内核分析作业 task_struct数据结构 根据wiki的定义,进程是计算机中已运行程序的实体.在面向线程设计的系统(Linux 2.6及更新的版本)中,进程本身不是基本 ...

  5. Linux内核创建一个新进程的过程

    作者:王鹤楼 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 操作系统的三大功 ...

  6. 6. Linux内核创建一个新进程的过程分析

    ##################################### 作者:张卓 原创作品转载请注明出处:<Linux操作系统分析>MOOC课程 http://www.xuetang ...

  7. Linux内核协议栈-一个socket的调用过程,从用户态接口到底层硬件

    用户创建socket 调用内核__sock_create int __sock_create(struct net *net, int family, int type, int protocol,s ...

  8. linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  9. 《深入理解Linux内核》-3.3. 进程切换

    3.3. 进程切换 为了控制进程的执行,内核必须能够挂起正在运行的进程并恢复运行其他之前被挂起的进程.这个活动通过进程切换,任务切换或上下文切换执行这种各样的操作.接下来的章节介绍Linux系统上的进 ...

  10. linux内核调度 0号进程,Linux内核源代码情景分析---第四章 进程与进程调度

    4.1 进程四要素 什么是进程? 1:有一段代码段供其执行,这代码段不一定是进程所专用,可以与其他进程公用. 2:每个进程有其专用的系统空间的堆栈(栈)[这个栈是进程起码的"私有财产&quo ...

最新文章

  1. unity 编辑器存档_Unity教程 | 自制简易的游戏存档系统
  2. win2003 vps IIS6中添加站点并绑定域名的配置方法
  3. linux下删除有锁的文件夹,Linux 文件夹右下角有锁,解锁
  4. 力扣1037.有效的回旋镖
  5. AMIO编辑器开发(四):五一劳动节的编程较量,C++语言的设计模式
  6. date timestamp mysql_MySQL中DATETIME、DATE和TIMESTAMP类型的区别
  7. libevent源码分析:bufferevent
  8. navicat安装(linux)
  9. matlab一键计算平均值与标准偏差
  10. 为什么-关于因果关系的新科学 | 01 因果关系之梯
  11. content=IE=Edge
  12. SpringBoot整合Docker实现一次构建到处运行
  13. php 页面日历形式显示,日历页面展示-PHP制作阴阳历转换的日历插件-PHP中文网教程...
  14. 解读iOS 11新版App Store:如何玩转新版App Store,提升产品下载量?
  15. 正运动控制器编程出现错误后,修改后,错误还在。
  16. shell程序设计小知识
  17. C#蓝牙链接+传输文件
  18. 金山办公 服务端开发岗位 面经 2019.11.11(秋招)
  19. 【C语言】PAT(Basic Level) 1003 “答案正确”是自动判题系统给出的最令人欢喜的回复。 只要读入的字符串满足下列条件,系统就输出“答案正确”,否则输出“答案错误”。
  20. 最坏的不是面试被拒,而是没面试机会,以面试官视角分析哪些简历至少能有面试机会

热门文章

  1. VS编译NPAPI:jref类型出错
  2. 解决办法:GTK_OBJECT、GTK_SIGNAL_FUNC未声明
  3. 全网首发:OPPO推送:服务器端的参考代码,JAVA版
  4. 中兴事件不会对中国高科技产生什么改变
  5. 如何在一个bat批处理文件中调用另一个bat批处理文件?
  6. python代码缩进中是否支持tab键和空格混用_python自测——编码规范
  7. MySQL设置mysqld_MySQL指定mysqld启动时所加载的配置文件
  8. 实现列表CListCtrl可点击编辑
  9. 微博api unexpected response status: 403_抖音直播监控Api:开播查询
  10. 软件测试一个月工作总结范文,2019年最新软件测试师工作总结范文