进程的描述和进程的创建

进程的描述

1、操作系统内核实现操作系统的三大管理功能:

  • 进程管理
  • 内存管理
  • 文件系统。

    其中最核心的功能是进程管理。

2、对进程的描述:在操作系统原理中,通过进程控制块PCB描述进程。在Linux内核中通过一个数据结构struct task_struct来描述进程,称其为进程描述符。

3、对进程状态的描述:

  • 在操作系统原理中,进程有就绪态、运行态和阻塞态3种基本状态;
  • 在Linux内核中,当时用fork()系统调用来创建一个进程时,新进程的状态是TASK_RUNNING(就绪态但没有运行)。就绪态和运行态在Linux内核中都是TASK_RUNNING状态,此状态下进程是可运行的,也就是就绪态,是否在运行取决于它有没有获得CPU的控制权,即这个进程有没有在CPU中实际执行。如果在CPU中实际执行了,进程状态就是运行态;如果被内核调度出去了,在等待队列里就是就绪态。
  • 对于一个正在运行的进程,调用用户态库函数exit()会陷入内核执行该内核函数do_exit(),进程会进入TASK_ZOMBIE状态,即进程的终止状态。TASK_ZOMBIE状态的进程一般叫做僵尸进程,Linux内核会在适当的时候把僵尸进程处理掉,然后释放进程描述符。
  • 一个正在运行的进程在等待特定事件或资源时会进入阻塞态,阻塞态分为两种:
    • TASK_INTERRUPTIBLE
    • TASK_UNINTERRUPTIBLE。

      前者可以被信号和wake_up()唤醒,后者只能被wake_up()唤醒。

进程的创建

1、0号进程的初始化:

init_task为第一个进程(0号进程)的进程描述符结构体变量,它的初始化是通过硬编码方式固定下来的,除此之外的其它进程的初始化都是通过do_fork复制父进程的方式初始化的。

2、fork、vfork、clone这3个系统调用和kernel_thread内核函数都可以创建一个新进程,而且都是通过do_fork函数来创建进程的,只不过传递的参数不同。

3、进程的创建过程:

利用fork()系统调用来创建新进程,把当前进程的描述符等相关进程资源复制一份,从而产生一个子进程,并根据子进程的需要对复制的进程描述符做一些修改,然后把创建好的子进程放入运行队列。在进程调度时,新创建的子进程处于就绪状态有机会被调度执行。

4、进程创建过程中的重要函数或数据结构:

  • do_fork():主要完成调用copy_process()复制父进程信息、获得pid、调用wake_up_new_task将子进程加入调度器队列等待获得分配CPU资源运行、通过clone_flags标志做一些辅助工作。
  • copy_process():主要完成调用dup_task_struct复制当前进程(父进程)描述符task_struct、信息检查、初始化、把进程状态设置为TASK_RUNNING(就绪态)、采用写时复制技术逐一复制所有其他进程资源、调用copy_thread初始化子进程内核栈、设置子进程pid等。
  • dup_task_struct():复制当前进程(父进程)描述符task_struct和copy_thread初始化子进程内核栈。
  • thread_info:小型的进程描述符,占据连续的两个页框,通过task指针指向进程描述符。内核栈有高地址到低地址增长,thread_info结构有低地址到高地址增长。
  • copy_thread():完成内核栈关键信息的初始化。如果创建的是内核线程,则子进程开始执行的起点是ret_from_kernel_thread;如果创建的是用户态进程,则子进程开始执行的起点是ret_from_fork。

通过实验跟踪分析进程创建的过程

增加fork系统调用

实验方法与上次实验类似,首先删除并克隆一份新的menu,然后将test.c覆盖掉,因为之前用过test.c可能会有影响。进入menu执行make rootfs,命令如下:

  1. cd LinuxKernel
  2. rm -rf menu
  3. git clone https://github.com/mengning/menu.git
  4. cd menu
  5. make rootfs

编译运行出来之后可以查看到列表中增加了fork,执行fork可以看到父进程和子进程输出信息。

启动内核

启动内核,可以看到窗口被冻结起来

gdb跟踪

启动gdb,将内核加载进来,建立连接,并在sys_clone、do_fork、dup_task_struct、copy_process、copy_thread、ret_from_fork处各设置断点。

然后开始执行,发现只输出了一个命令描述,后面并没有执行,而是停在了sys_clone这里。

再继续执行,停在do_fork处。

再继续执行,停在copy_process处。

再继续执行,停在dup_task_struct函数,进入dup_task_struct内部,将当前进程内核压栈压得那一部分寄存器复制到子进程中,以及赋值子进程的起点。

单步执行:

关键代码分析

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;//子进程pid
  10. ...
  11. p = copy_process(clone_flags, stack_start, stack_size,
  12. child_tidptr, NULL, trace);//创建子进程的描述符和执行时所需的其他数据结构
  13. if (!IS_ERR(p)) {//如果copy_process执行成功
  14. struct completion vfork;//定义完成量
  15. struct pid *pid;
  16. ...
  17. pid = get_task_pid(p, PIDTYPE_PID);//获得task结构体中的pid
  18. nr = pid_vnr(pid);//根据pid结构体中获得进程pid
  19. ...
  20. //如果clone_flags包含CLONE_VFORK标志,就将完成量vfork赋值给进程描述符中的vfork_done字段,此处只是对完成量进行初始化
  21. if (clone_flags & CLONE_VFORK) {
  22. p->vfork_done = &vfork;
  23. init_completion(&vfork);
  24. get_task_struct(p);
  25. }
  26. wake_up_new_task(p);//将子进程添加到调度器的队列,使之有机会获得CPU
  27. /* forking complete and child started to run, tell ptracer */
  28. ...
  29. //如果clone_flags包含CLONE_VFORK标志,就将父进程插入等待队列直到子进程调用exec函数或退出,此处是具体的阻塞
  30. if (clone_flags & CLONE_VFORK) {
  31. if (!wait_for_vfork_done(p, &vfork))
  32. ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  33. }
  34. put_pid(pid);
  35. } else {
  36. nr = PTR_ERR(p);//错误处理
  37. }
  38. return nr;//返回子进程pid(父进程fork函数返回值为子进程pid原因)
  39. }

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. tsk = alloc_task_struct_node(node);//为子进程创建进程描述符分配存储空间
  8. ...
  9. ti = alloc_thread_info_node(tsk, node);//创建了两个页,一部分存放thread_info,一部分就是内核堆栈
  10. ...
  11. err = arch_dup_task_struct(tsk, orig);//复制父进程的task_struct信息
  12. ...
  13. tsk->stack = ti;//将栈底的值赋给新结点的stack
  14. ...
  15. //对子进程的thread_info初始化(复制父进程thread_info,然后将task指针指向子进程的进程描述符)
  16. setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
  17. ...
  18. return tsk;//返回新创建的进程描述符指针
  19. ...
  20. }

copy_thread()

  1. int copy_thread(unsigned long clone_flags, unsigned long sp,
  2. unsigned long arg, struct task_struct *p)
  3. {
  4. struct pt_regs *childregs = task_pt_regs(p);
  5. struct task_struct *tsk;
  6. int err;
  7. p->thread.sp = (unsigned long) childregs;
  8. p->thread.sp0 = (unsigned long) (childregs+1);
  9. memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
  10. if (unlikely(p->flags & PF_KTHREAD)) {
  11. /* kernel thread */
  12. memset(childregs, 0, sizeof(struct pt_regs));
  13. //如果创建的是内核线程,则从ret_from_kernel_thread开始执行
  14. p->thread.ip = (unsigned long) ret_from_kernel_thread;
  15. task_user_gs(p) = __KERNEL_STACK_CANARY;
  16. childregs->ds = __USER_DS;
  17. childregs->es = __USER_DS;
  18. childregs->fs = __KERNEL_PERCPU;
  19. childregs->bx = sp; /* function */
  20. childregs->bp = arg;
  21. childregs->orig_ax = -1;
  22. childregs->cs = __KERNEL_CS | get_kernel_rpl();
  23. childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
  24. p->thread.io_bitmap_ptr = NULL;
  25. return 0;
  26. }
  27. //复制内核堆栈(复制父进程的寄存器信息,即系统调用int指令和SAVE_ALL压栈的那一部分内容)
  28. *childregs = *current_pt_regs();
  29. childregs->ax = 0;//将子进程的eax置0,所以fork的子进程返回值为0
  30. ...
  31. //ip指向ret_from_fork,子进程从此处开始执行
  32. p->thread.ip = (unsigned long) ret_from_fork;
  33. task_user_gs(p) = get_user_gs(current_pt_regs());
  34. ...
  35. return err;
  36. }

总结

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

问题是fork、vfork、clone这三种系统调用的不同,通过查阅资料发现:

  • fork():子进程拷贝父进程的数据段,代码段,父子进程的执行次序不确定;
  • vfork():创建的子进程与父进程共享数据段,而且子进程将先于父进程运行;
  • clone():是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享。

2019-2020-1 20199319《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. split 使用

    split作用:把字符串变成列表,这个字符串必须是多行文字.如果是单行文字或一个单词是不行的,实例操作如下: In [46]: output=subprocess.check_output(['df' ...

  2. 1031: [编程入门]自定义函数之字符串反转(python)

    问题 1031: [编程入门]自定义函数之字符串反转 时间限制: 1Sec 内存限制: 128MB 提交: 7225 解决: 3331 题目描述 写一函数,使输入的一个字符串按反序存放,在主函数中输入 ...

  3. NOIp2018D1T2 货币系统【分析&完全背包】

    题目传送门 看到题目瞬间想起某凯的疑惑,感觉不会做....然后观察样例可以知道,去掉原来货币系统中能够被其他币值凑出来的数就是答案(样例分析法),然后就完事了(huaji). 简单理解一下吧: 首先, ...

  4. 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发

    linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  5. ucloud相关

    ucloud申请证书 https://docs.ucloud.cn/domain/ussl/operate/buy 云主机: https://docs.ucloud.cn/compute/uhost/ ...

  6. SIFT图像配准 python3.6 + opencv3.3代码

    opencv3.x 中部分函数有改变: 1. SIFT:可以采用help(cv2.xfeatures2d)查询 2.drawKeypoints: 同样采用help()方法查询 opencv3 版本si ...

  7. rtsp学习----海康RTSP客户端连接深入分析

    转载于:http://blog.csdn.net/zhouyongku/article/details/41546789 海康相机RTSP连接代码分析 最近在做海康相机rtsp连接获取音视频的工作,现 ...

  8. (5.8)mysql高可用系列——MySQL中的GTID复制(实践篇)

    一.基于GTID的异步复制(一主一从)无数据/少数据搭建 二.基于GTID的无损半同步复制(一主一从)(mysql5.7)基于大数据量的初始化 正文: [0]概念 [0.5]GTID 复制(mysql ...

  9. Hadoop简介及架构

    狭义上来说,hadoop就是单独指代hadoop这个软件, 广义上来说,hadoop指代大数据的一个生态圈,包括很多其他的软件 2.hadoop的历史版本介绍 0.x系列版本:hadoop当中最早的一 ...

  10. __metaclass__方法

    metaclass这个属性叫做元类,它是用来表示这个类是由谁来帮他实例化创建的,说白了,就是相当于自己定制一个类. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...