进程的描述

  • 操作系统内核实现操作系统的三大管理功能

    • 进程管理(内核中最核心的功能)
    • 内存管理
    • 文件系统
  • 在操作系统中,我们通过进程控制块PCB描述进程。

  • 为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。

  • 用数据结构struct task_struct 来描述进程(涉及内容过于庞杂)

  • 进程描述符的示意图

  • Linux内核管理的进程状态转换图

  • 开始实验,更新menu

  • 对重要sys_clone等处各设置断点
  1. (gdb)b sys_clone
  2. (gdb)b do_fork
  3. (gdb)b copy_process
  4. (gdb)b dup_task_struct
  5. (gdb)b copy_thread
  6. (gdb)b ret_from_fork

  • 断点常用命令合集

  • 开始调试







do_fork函数

  1. long do_fork(unsigned long clone_flags,
  2. unsigned long stack_start,
  3. unsigned long stack_size,
  4. int __user *parent_tidptr,
  5. int __user *child_tidptr)
  6. {
  7. struct task_struct *p;
  8. int trace = 0;
  9. long nr;
  10. // ...
  11. // 复制进程描述符,返回创建的task_struct的指针
  12. p = copy_process(clone_flags, stack_start, stack_size,
  13. child_tidptr, NULL, trace);
  14. if (!IS_ERR(p)) {
  15. struct completion vfork;
  16. struct pid *pid;
  17. trace_sched_process_fork(current, p);
  18. // 取出task结构体内的pid
  19. pid = get_task_pid(p, PIDTYPE_PID);
  20. nr = pid_vnr(pid);
  21. if (clone_flags & CLONE_PARENT_SETTID)
  22. put_user(nr, parent_tidptr);
  23. // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行
  24. if (clone_flags & CLONE_VFORK) {
  25. p->vfork_done = &vfork;
  26. init_completion(&vfork);
  27. get_task_struct(p);
  28. }
  29. // 将子进程添加到调度器的队列,使得子进程有机会获得CPU
  30. wake_up_new_task(p);
  31. // ...
  32. // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间
  33. // 保证子进程优先于父进程运行
  34. if (clone_flags & CLONE_VFORK) {
  35. if (!wait_for_vfork_done(p, &vfork))
  36. ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  37. }
  38. put_pid(pid);
  39. } else {
  40. nr = PTR_ERR(p);
  41. }
  42. return nr;
  43. }

调用copy_process,将当期进程复制一份出来为子进程,并且为子进程设置相应地上下文信息。初始化vfork的完成处理信息(如果是vfork调用)调用wake_up_new_task,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中,得以运行。

  • copy_process的部分代码:
  1. static struct task_struct *copy_process(unsigned long clone_flags,
  2. unsigned long stack_start,
  3. unsigned long stack_size,
  4. int __user *child_tidptr,
  5. struct pid *pid,
  6. int trace)
  7. {
  8. int retval;
  9. struct task_struct *p;
  10. ...
  11. retval = security_task_create(clone_flags);//安全性检查
  12. ...
  13. p = dup_task_struct(current); //复制PCB,为子进程创建内核栈、进程描述符
  14. ftrace_graph_init_task(p);
  15. ···
  16. retval = -EAGAIN;
  17. // 检查该用户的进程数是否超过限制
  18. if (atomic_read(&p->real_cred->user->processes) >=
  19. task_rlimit(p, RLIMIT_NPROC)) {
  20. // 检查该用户是否具有相关权限,不一定是root
  21. if (p->real_cred->user != INIT_USER &&
  22. !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
  23. goto bad_fork_free;
  24. }
  25. ...
  26. // 检查进程数量是否超过 max_threads,后者取决于内存的大小
  27. if (nr_threads >= max_threads)
  28. goto bad_fork_cleanup_count;
  29. if (!try_module_get(task_thread_info(p)->exec_domain->module))
  30. goto bad_fork_cleanup_count;
  31. ...
  32. spin_lock_init(&p->alloc_lock); //初始化自旋锁
  33. init_sigpending(&p->pending); //初始化挂起信号
  34. posix_cpu_timers_init(p); //初始化CPU定时器
  35. ···
  36. retval = sched_fork(clone_flags, p); //初始化新进程调度程序数据结构,把新进程的状态设置为TASK_RUNNING,并禁止内核抢占
  37. ...
  38. // 复制所有的进程信息
  39. shm_init_task(p);
  40. retval = copy_semundo(clone_flags, p);
  41. ...
  42. retval = copy_files(clone_flags, p);
  43. ...
  44. retval = copy_fs(clone_flags, p);
  45. ...
  46. retval = copy_sighand(clone_flags, p);
  47. ...
  48. retval = copy_signal(clone_flags, p);
  49. ...
  50. retval = copy_mm(clone_flags, p);
  51. ...
  52. retval = copy_namespaces(clone_flags, p);
  53. ...
  54. retval = copy_io(clone_flags, p);
  55. ...
  56. retval = copy_thread(clone_flags, stack_start, stack_size, p);// 初始化子进程内核栈
  57. ...
  58. //若传进来的pid指针和全局结构体变量init_struct_pid的地址不相同,就要为子进程分配新的pid
  59. if (pid != &init_struct_pid) {
  60. retval = -ENOMEM;
  61. pid = alloc_pid(p->nsproxy->pid_ns_for_children);
  62. if (!pid)
  63. goto bad_fork_cleanup_io;
  64. }
  65. ...
  66. p->pid = pid_nr(pid); //根据pid结构体中获得进程pid
  67. //若 clone_flags 包含 CLONE_THREAD标志,说明子进程和父进程在同一个线程组
  68. if (clone_flags & CLONE_THREAD) {
  69. p->exit_signal = -1;
  70. p->group_leader = current->group_leader; //线程组的leader设为子进程的组leader
  71. p->tgid = current->tgid; //子进程继承父进程的tgid
  72. } else {
  73. if (clone_flags & CLONE_PARENT)
  74. p->exit_signal = current->group_leader->exit_signal;
  75. else
  76. p->exit_signal = (clone_flags & CSIGNAL);
  77. p->group_leader = p; //子进程的组leader就是它自己
  78. p->tgid = p->pid; //组号tgid是它自己的pid
  79. }
  80. ...
  81. if (likely(p->pid)) {
  82. ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);
  83. init_task_pid(p, PIDTYPE_PID, pid);
  84. if (thread_group_leader(p)) {
  85. ...
  86. // 将子进程加入它所在组的哈希链表中
  87. attach_pid(p, PIDTYPE_PGID);
  88. attach_pid(p, PIDTYPE_SID);
  89. __this_cpu_inc(process_counts);
  90. } else {
  91. ...
  92. }
  93. attach_pid(p, PIDTYPE_PID);
  94. nr_threads++; //增加系统中的进程数目
  95. }
  96. ...
  97. return p; //返回被创建的子进程描述符指针P
  98. ...
  99. }

创建进程描述符以及子进程所需要的其他所有数据结构,为子进程准备运行环境。调用dup_task_struct复制一份task_struct结构体,作为子进程的进程描述符。复制所有的进程信息。调用copy_thread,设置子进程的堆栈信息,为子进程分配一个pid。

  • dup_task_struct的代码:
  1. static struct task_struct *dup_task_struct(struct task_struct *orig)
  2. {
  3. struct task_struct *tsk;
  4. struct thread_info *ti;
  5. int node = tsk_fork_get_node(orig);
  6. int err;
  7. // 分配一个task_struct结点
  8. tsk = alloc_task_struct_node(node);
  9. if (!tsk)
  10. return NULL;
  11. // 分配一个thread_info结点,其实内部分配了一个union,包含进程的内核栈
  12. // 此时ti的值为栈底,在x86下为union的高地址处。
  13. ti = alloc_thread_info_node(tsk, node);
  14. if (!ti)
  15. goto free_tsk;
  16. err = arch_dup_task_struct(tsk, orig);
  17. if (err)
  18. goto free_ti;
  19. // 将栈底的值赋给新结点的stack
  20. tsk->stack = ti;
  21. ...
  22. /*
  23. * One for us, one for whoever does the "release_task()" (usually
  24. * parent)
  25. */
  26. // 将进程描述符的使用计数器置为2
  27. atomic_set(&tsk->usage, 2);
  28. #ifdef CONFIG_BLK_DEV_IO_TRACE
  29. tsk->btrace_seq = 0;
  30. #endif
  31. tsk->splice_pipe = NULL;
  32. tsk->task_frag.page = NULL;
  33. account_kernel_stack(ti, 1);
  34. // 返回新申请的结点
  35. return tsk;
  36. free_ti:
  37. free_thread_info(ti);
  38. free_tsk:
  39. free_task_struct(tsk);
  40. return NULL;
  41. }

先调用alloc_task_struct_node分配一个task_struct结构体。调用alloc_thread_info_node,分配了一个union。这里分配了一个thread_info结构体,还分配了一个stack数组。返回值为ti,实际上就是栈底。tsk->stack = ti将栈底的地址赋给task的stack变量。最后为子进程分配了内核栈空间。执行完dup_task_struct之后,子进程和父进程的task结构体,除了stack指针之外,完全相同

总结

进程的创建过程大致是复制进程描述符、一一复制其他进程资源(采用分时复制技术)、分配子进程的内核堆栈并对内核堆栈关键信息进行初始化。

2019-2020-1 20199312《Linux内核原理与分析》第七周作业的更多相关文章

  1. 2019-2020-1 20199329《Linux内核原理与分析》第九周作业

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

  2. 2019-2020-1 20199329《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...

  3. 20169212《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...

  4. 20169210《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...

  5. 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

  6. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  7. 2019-2020-1 20209313《Linux内核原理与分析》第二周作业

    2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...

  8. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  9. 《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  10. 2018-2019-1 20189221《Linux内核原理与分析》第二周作业

    读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...

随机推荐

  1. spring 循环引用问题,在一次问题调试过程中发现有个小伙伴竟然把循环引用设置成false了。估计是百度的时候没小心额外的代码吧。。。

    循环引用属性操作: 1)AbstractAutowireCapableBeanFactory类中的allowCircularReferences被设置为了false. 2)代码: Annotation ...

  2. SpringBoot整合AbstractRoutingDataSource实现读写分离

    在配置数据源时候,已经把主库和从库的数据源配置到DynamicDataSource里了 利用AbstractRoutingDataSource实现动态切换数据源,可以通过注解或者根据方法名前缀切换要使 ...

  3. [转帖]Latch

    Latch (转) http://blog.csdn.net/tianlesoftware/article/details/5263238 2013-05-24 15:33:09 huashanlun ...

  4. 嵌入式02 STM32 实验04跑马灯

    开学STM32 跑马灯的实验主要就是了解GPIO口的配置及使用,我这里是使用库函数进行编程,主要需要设置以下两方面: 1.使能需要使用的IO口的时钟,一共有A.B.C.D.E.F.G七组IO口 2.初 ...

  5. MyBatis框架的详解

    一.MyBatis的介绍 在使用的时候,需要配置文件的方式告知框架需要的信息,多数会使用XML文件作为框架的配置文件. 框架都是由第三方提供的,提供的都是jar包.因此,使用框架前,必须将框架涉及的j ...

  6. 面试题:在一个文件中有 10G 个整数,乱序排列,要求找出中位数(内存限制为2G)

    假设整数为32bit,4个字节存储 这种题目,首先想到的是分而治之.将文件中数字分组.然后遍历文件中的数字,按分组进行计数.最后找到中位数所在的分组区间 1.如果10G个整数都为同一个,那么10G整数 ...

  7. 【C#】上级实验四

    1.虚方法练习 设计一个控制台应用程序,定义一个Shape类,具体要求如下: ()类中定义2个私有字段长度(length).宽度(breadth). ()类中定义相应公有属性分别对应上述2个字段: ( ...

  8. wireshark抓包新手教程(win10空包问题)

    首先下载官网的wireshark,下载地址https://www.wireshark.org/ 下载完按照提示一步步安装 安装完打开wireshark,安装中文包 安装之前首先讲一下win10截图工具 ...

  9. [LOJ2290] [THUWC2017] 随机二分图

    题目链接 LOJ:https://loj.ac/problem/2290 洛谷:https://www.luogu.org/problemnew/show/P4547 Solution 首先考虑只有第 ...

  10. COCOeval接口使用

    COCOeval类简介 class COCOeval: # Interface for evaluating detection on the Microsoft COCO dataset. # # ...