LINUX内核分析第六周学习总结——进程的描述和进程的创建

张忻(原创作品转载请注明出处)

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

一、知识概要

进程的描述

  1. 进程描述符task_struct数据结构(一)
  2. 进程描述符task_struct数据结构(二)

进程的创建

  1. 进程的创建概览及fork一个进程的用户态代码
  2. 理解进程创建过程复杂代码的方法
  3. 浏览进程创建过程相关的关键代码
  4. 创建的新进程是从哪里开始执行的?
  5. 使用gdb跟踪创建新进程的过程

二、学习笔记

进程的描述

1.进程描述符task_struct数据结构(一)

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

  • struct task_struct数据结构很庞大
  • Linux进程的状态与操作系统原理中的描述的进程状态似乎有所不同,比如就绪状态和运行状态都是TASK_RUNNING,为什么呢?
  • 进程的标示pid
  • 所有进程链表struct list_head tasks;     内核的双向循环链表的实现方法 - 一个更简略的双向循环链表
  • 程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系
  • Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈

      进程处于内核态时使用,不同于用户态堆栈,即PCB中指定了内核栈,那为什么PCB中没有用户态堆栈?用户态堆栈是怎么设定的?

      内核控制路径所用的堆栈很少,因此对栈和Thread_info来说,8KB足够了

  • struct thread_struct thread; //CPU-specific state of this task
  • 文件系统和文件描述符
  • 内存管理——进程的地址空间

Linux内核状态转换图:

2.进程描述符task_struct数据结构(二)

双向循环链表图如下:

进程的父子关系直观图:

进程的创建

1.进程的创建概览及fork一个进程的用户态代码

(1)进程的起源再回顾

  • 道生一(start_kernel...cpu_idle)
  • 一生二(kernel_init和kthreadd)
  • 二生三(即前面的0、1、2三个进程)
  • 三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)

(2)0号进程手工写,1号进程复制、加载init程序

(3)shell命令行是如何启动进程的

fork一个子进程的代码:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid < ) 出错处理
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-);
}
else if (pid == )
{
/* child process */ 子进程 pid=0时 if和else都会执行 fork系统调用在父进程和子进程各返回一次
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");
}
}

2.理解进程创建过程复杂代码的方法

(1)系统调用再回顾

(2)fork的子进程是从哪里开始执行的?

与基于mykernel写的精简内核对照起来。

(3)创建一个新进程在内核中的执行过程

  • fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
  • Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
  • 复制一个PCB——task_struct
    err = arch_dup_task_struct(tsk, orig);
  • 要给新进程分配一个新的内核堆栈
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
  • 要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
  • 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process
1 *childregs = *current_pt_regs(); //复制内核堆栈
2 childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
3
4 p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
5 p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

(4)理解复杂事物要预设一个大致的框架。

(5)创建新进程是通过复制当前进程来实现的。

(6)设想创建新进程过程中需要做哪些事

3.浏览进程创建过程相关的关键代码

(1)系统调用内核处理函数sys_fork、sys_clone、sys_vfork

最终都是执行do_fork()。

do_fork()里的复制进程的函数:

具体:

打开复制PCB的具体函数:

打开alloc_thread_info():

拷贝内核堆栈数据和指定新进程的第一条指令地址。

4.创建的新进程是从哪里开始执行的?

(1)复制内核堆栈时

打开pt_regs:

int指令和SAVE_ALL压到内核栈的内容。

下面分析entry_32.S,也就是总控程序。

5.使用gdb跟踪创建新进程的过程(见作业)

三、课后作业

1.理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235

具体代码分析过程见第二部分学习过程 进程的描述 部分——2.进程描述符task_struct数据结构(二)

2.分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构;

具体代码分析过程见第二部分学习过程 进程的创建 部分——3.浏览进程创建过程相关的关键代码(1)系统调用内核处理函数sys_fork、sys_clone、sys_vfork

理解如下:

fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建,do_fork完成了创建中的大部分工作,该函数调用copy_process()函数,然后让进程开始运行。copy_process()函数工作如下:

  • 1、调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同
  • 2、检查
  • 3、子进程着手使自己与父进程区别开来。进程描述符内的许多成员被清0或设为初始值。
  • 4、子进程状态被设为TASK_UNINTERRUPTIBLE,以保证它不会投入运行
  • 5、copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置
  • 6、调用alloc_pid()为新进程分配一个有效的PID
  • 7、根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等
  • 8、最后,copy_process()做扫尾工作并返回一个指向子进程的指针

3.使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证您对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环境下完成实验。

分析过程如下:

更新menu代码到最新版、make rootfs,用help查看,新添加fork命令:

使用gdb跟踪调试内核,在一些重要函数处设置断点:

执行一个fork,会发现只输出一个fork的命令描述,后面并没有执行,因为它停在了sys_clone这个位置。

4.特别关注新进程是从哪里开始执行的?为什么从哪里能顺利执行下去?即执行起点与内核堆栈如何保证一致。

答:ret_from_fork;决定了新进程的第一条指令地址。

原因如下:

copy_process()主要完成进程数据结构,各种资源的初始化。

p = dup_task_struct(current);
  • (省略的IF语句)检查clone_flags参数,防止无效的组合进入
  • p = dup_task_struct(current);调用dup_task_struct()为新进程创建一个内核栈
  • 判断权限及允许范围的代码
  • 对子进程的描述符初始化和复制父进程的资源给子进程
    • retval = sched_fork(clone_flags, p);完成调度相关的设置,将这个task分配给CPU
    • if (retval)语句群,复制共享进程的的各个部分
    • retval = copy_thread(clone_flags, stack_start, stack_size, p);复制父进程堆栈的内容到子进程的堆栈中去.这其中,copy_thread()函数中的语句p->thread.ip = (unsigned long) ret_from_fork;决定了新进程的第一条指令地址.
总结原因也就是说:

  • 在ret_from_fork之前,也就是在copy_thread()函数中*childregs = *current_pt_regs();该句将父进程的regs参数赋值到子进程的内核堆栈,
  • *childregs的类型为pt_regs,里面存放了SAVE ALL中压入栈的参数
  • 故在之后的RESTORE ALL中能顺利执行下去.

5.根据本周所学知识分析fork函数对应的系统调用处理过程,撰写一篇署名博客,并在博客文章中注明“真实姓名(与最后申请证书的姓名务必一致) + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,

总结:

  • Linux通过复制父进程来创建一个新进程,通过调用do_fork来实现
  • Linux为每个新创建的进程动态地分配一个task_struct结构.
  • 为了把内核中的所有进程组织起来,Linux提供了几种组织方式,其中哈希表和双向循环链表方式是针对系统中的所有进程(包括内核线程),而运行队列和等待队列是把处于同一状态的进程组织起来
  • fork()函数被调用一次,但返回两次

 

LINUX内核分析第六周学习总结——进程的描述和进程的创建的更多相关文章

  1. LINUX内核分析第六周学习总结——进程的描述与创建

    LINUX内核分析第六周学习总结--进程的描述与创建 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc ...

  2. linux内核分析第六周学习笔记

    LINUX内核分析第六周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...

  3. Linux内核分析第六周学习笔记——分析Linux内核创建一个新进程的过程

    Linux内核分析第六周学习笔记--分析Linux内核创建一个新进程的过程 zl + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

  4. Linux内核分析——第六周学习笔记20135308

    第六周 进程的描述和进程的创建 一.进程描述符task_struct数据结构 1.操作系统三大功能 进程管理 内存管理 文件系统 2.进程控制块PCB——task_struct 也叫进程描述符,为了管 ...

  5. Linux内核分析第六周学习总结:进程的描述和进程的创建

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.进程的描述 ...

  6. Linux内核分析——第六周学习笔记

    进程的描述和进程的创建 前言:以下笔记除了一些讲解视频中的概念记录,图示.图示中的补充文字.总结.分析.小结部分均是个人理解.如有错误观点,请多指教! PS.实验操作会在提交到MOOC网站的博客中写.

  7. LINUX内核分析第七周学习总结——可执行程序的装载

    LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...

  8. LINUX内核分析第七周学习总结:可执行程序的装载

    LINUX内核分析第七周学习总结:可执行程序的装载 韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...

  9. LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程

    LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...

随机推荐

  1. Alpha冲刺报告(10/12)(麻瓜制造者)

    今日已完成 邓弘立: 实现了登录的所有功能 符天愉: 有点事没有参与冲刺- 江郑: 刘双玉: 写商品下架接口说明的时候,就发现一个bug,因为私人原因中间离开了很长时间,最后修改完成就很晚了. 困难: ...

  2. JQuery 学习笔记-2017.05.22

    JQuery jQuery 是一个 JavaScript 函数库. jQuery 库包含以下特性: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaScript 特效 ...

  3. JavaScript中的单例模式

    单例模式 在JavaScript中,单例(Singleton)模式是最基本又最有用的模式之一.这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问.确保单例 ...

  4. python第二十七课——os模块

    演示os模块中常用的属性和函数: 1.os模块: 作用:管理文件和目录 属性: os.name:返回系统类型 常用的windows系统 --> nt os.environ:返回当前系统所有的环境 ...

  5. 利用 Settings Sync 同步vs code配置

    vs code上有各种各样不同的插件,如果要在不同的电脑上使用 vs code 配置是件比较麻烦的事情,使用 Settings Sync 将 vs code 配置备份起来,当需要在其他电脑使用  vs ...

  6. rpm安装MySQL5.5后配置,在centos5上;mysql编译安装在centos6.5上;

    [1] 没有/etc/my.cnf: rpm包安装的MySQL是不会安装/etc/my.cnf文件的:处理:cp /usr/share/mysql/my-huge.cnf /etc/my.cnf [2 ...

  7. Python2.7-heapq

    heapq 模块,实现了堆序列算法,也叫优先序列算法.heap(堆)是每个父节点都小于等于子节点的树,同时所有节点k都满足 heap[k] <= heap[2*k+1] 和 heap[k] &l ...

  8. 启动hbase shell报错:org.apache.hadoop.hbase.ipc.ServerNotRunningYetException: Server is not running yet

    查看日志发现:Waiting for dfs to exit safe mode 这说明HDFS目前处于安全模式,需要退出才行,于是进入Namdenode节点,执行命令: hdfs dfsadmin ...

  9. 大数据入门第二十一天——scala入门(二)并发编程Akka

    一.概述 1.什么是akka Akka基于Actor模型,提供了一个用于构建可扩展的(Scalable).弹性的(Resilient).快速响应的(Responsive)应用程序的平台. 更多入门的基 ...

  10. 20155325 Exp7 网络欺诈防范

    实践内容(3.5分) 本实践的目标理解常用网络欺诈背后的原理,以提高防范意识,并提出具体防范方法.具体实践有 (1)简单应用SET工具建立冒名网站 (1分) (2)ettercap DNS spoof ...