我的愿望是 好好学习Linux

一、书本第六章知识总结【进程的描述和进程的创建】

基础知识1

  • 操作系统内核实现操作系统的三大管理功能,即进程管理功能,内存管理和文件系统。对应的三个抽象的概念是进程,虚拟内存和文件。
    其中,操作系统最核心的功能是进程管理。
  • 进程标识值:内核通过唯一的PID来标识每个进程。
  • 进程状态:进程描述符中state域描述了进程的当前状态。
  • iret与int 0x80指令对应,一个是离开系统调用弹出寄存器值,一个是进入系统调用压入寄存器的值。
  • fork()函数最大的特点就是被调用一次,返回两次,在父进程中返回新创建子进程的 pid;在子进程中返回 0。
  • 在Linux中,fork,vfork和clone这3个系统调用都通过do_fork来实现进程的创建。
  • 在Linux中1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先。

基础知识2

  • 在操作系统原理中,通过进程控制块PCB描述进程;在Linux内核中,通过一个数据结构struct task_struct来描述进程。
  • 在操作系统原理中,进程有就绪态、运行态和阻塞态;在Linux内核中,就绪态和运行态都是相同的TASK_RUNNING状态另加上一个阻
    塞态。在Linux内核中,当进程是TASK_RUNNING状态时,它是可运行的,就是就绪态,是否在运行取决于它有没有获得CPU的控制权。
  • 对于一个正在运行的进程,调用用户态库函数exit()会陷入内核执行该内核函数do_exit(),进程会进入TASK_ZOMBIE状态,即中止状态,
    Linux内核会在适当的时候把该进程处理掉,后释放进程描述符。一个正在运行的进程在等待特定事件或资源时会进入阻塞态,阻塞态分为两
    种:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE。前者可以被信号和wake_up()唤醒,后者只能被wake_up()唤醒。总结来说分这四种:
  • TASK_RUNNING:包括两种状态:进程就绪且没有运行、进程正在运行。这两种状态的区分取决于进程有没有获得CPU的分配权。
  • TASK_ZOMBIE:进程的终止状态,此状态的进程被称作僵尸进程,在此状态下的进程会被Linux内核在适当的时候处理掉,同时进程描述符也将被释放。
  • TASK_INTERRUPTIBLE :可以被信号或者是wake_up()唤醒。信号来临时,进程会被设置为TASK_RUNNING(仅仅是就绪状态而没有执行)
  • TASK_UNINTERRUPTIBLE:只能被wake_up()唤醒
    进程状态转换图如下图所示:

  • 进程控制块PCB——task_struct,为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。
struct task_struct {
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped   进程状态,-1表示不可执行,0表示可执行,大于1表示停止*/
    void *stack; //内核堆栈
    atomic_t usage;
    unsigned int flags; /* per process flags, defined below  进程标识符 * /
    unsigned int ptrace;

如下图:

进程的创建

  • start_ kernel,rest_init,kernel_ init kthreadd;
    start_ kernel创建了rest_init,也就是0号进程。而0号进程又创建了两个线程,一个是kernel_ init,也就是1号进程,这个进程最终启动了用户态;
    另一个是kthreadd内核线程是所有内核线程的祖先,负责管理所有内核线程。
    0号进程是固定的代码,1号进程是通过复制0号进程PCB之后在此基础上做修改得到的。

创建进程的三个函数

  • fork,创建子进程。
  • vfork,与fork类似,但是父子进程共享地址空间,而且子进程先于父进程运行。
  • clone,主要用于创建线程。

Linux通过复制父进程来创建一个新进程,通过调用do_ fork来实现。然后对子进程做一些特殊的处理。而Linux中的线程,又是一种特殊的进程。根
据代码的分析,do_ fork中,copy_ process管子进程运行的准备,wake_ up_ new_ task作为子进程forking的完成。

进程创建过程中的四个函数

  • do_fork():创建进程;
  • copy_process():创建进程内容(调用dup_task_struct、信息检查、初始化、更改进程状态、复制其他进程资源、调用copy_thread初始化子进
    程内核栈、设置子进程pid等);
  • dup_task_struct():复制当前进程(父进程)描述符task_struct,分配子进程内核栈;
  • copy_thread():内核栈关键信息初始化;

解析do_fork()

fork、vfork和clone这三个函数最终都是通过do_fork函数实现的。
do_fork的步骤:

  1. 调用copy_process,将当期进程复制一份出来为子进程,并且为子进程设置相应地上下文信息。
  2. 初始化vfork的完成处理信息(如果是vfork调用)
  3. 调用wake_up_new_task,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中,得以运行。
  4. 如果是vfork调用,需要阻塞父进程,知道子进程执行exec。
    do_fork的代码:
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0;
    long nr;

    // ...

    // 复制进程描述符,返回创建的task_struct的指针
    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace);

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

        trace_sched_process_fork(current, p);

        // 取出task结构体内的pid
        pid = get_task_pid(p, PIDTYPE_PID);
        nr = pid_vnr(pid);

        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);

        // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行
        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }

        // 将子进程添加到调度器的队列,使得子进程有机会获得CPU
        wake_up_new_task(p);

        // ...

        // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间
        // 保证子进程优先于父进程运行
        if (clone_flags & CLONE_VFORK) {
            if (!wait_for_vfork_done(p, &vfork))
                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
        }

        put_pid(pid);
    } else {
        nr = PTR_ERR(p);
    }
    return nr;
}

关于copy_process函数

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

关于dup_task_struct

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

关于copy_thread函数

  1. 获取子进程寄存器信息的存放位置
  2. 对子进程的thread.sp赋值,将来子进程运行,这就是子进程的esp寄存器的值。
  3. 如果是创建内核线程,那么它的运行位置是ret_from_kernel_thread, - 将这段代码的地址赋给thread.ip,之后准备其他寄存器信息,退出
  4. 将父进程的寄存器信息复制给子进程。
  5. 将子进程的eax寄存器值设置为0,所以fork调用在子进程中的返回值为0.
  6. 子进程从ret_from_fork开始执行,所以它的地址赋给thread.ip,也就是将来的eip寄存器。

新的进程从ret_from_fork处开始执行

  1. dup_task_struct中为其分配了新的堆栈。
  2. copy_process中调用了sched_fork,将其置为TASK_RUNNING。
  3. copy_thread中将父进程的寄存器上下文复制给子进程,这是非常关键的一步,这里保证了父子进程的堆栈信息是一致的。
  4. 将ret_from_fork的地址设置为eip寄存器的值,这是子进程的第一条指令。

整个详细过程:

二、实验部分【跟踪分析进程创建的过程】

(1)给操作系统MenuOS添加命令(fork功能)

(2)使用gdb调试跟踪

回到LinuxKernel目录下,fork指令实际上执行的就是sys_clone,我们可以在sys_clone、do_fork、dup_task_struct、copy_process、
copy_thread、ret_from_fork处设置断点,如下图所示:


*********************************************

*********************************************

*********************************************

*********************************************

最后通过函数syscall_exit退出;

三、实验收获

1. 小总结1

fork,vfork,clone都是linux的系统调用,这三个函数分别调用了sys_fork、sys_vfork、sys_clone,最终都调用了do_fork函数,差别在于参数
的传递和一些基本的准备工作不同,主要用来linux创建新的子进程或线程(vfork创造出来的是线程)。

  • fork()函数调用成功:返回两个值; 父进程:返回子进程的PID;子进程:返回0;失败:返回-1;
    fork 创造的子进程复制了父亲进程的资源(写时复制技术),包括内存的内容task_struct内容(2个进程的pid不同)。
    这里是资源的复制不是指针的复制。

  • vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。
    故vfork创建出来的不是真正意义上的进程,而是一个线程,因为它缺少独立的内存资源。

  • int clone(int (fn)(void ), void child_stack, int flags, void arg)
    • clone和fork的区别:

      1. clone和fork的调用方式很不相同,clone调用需要传入一个函数,该函数在子进程中执行。
      2. clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。 (void *child_stack,)也就是第二个参数,需要分配栈指针
        的空间大小,所以它不再是继承或者复制,而是全新的创造。

2. 小总结2

进程在创建时具有父子关系,通过调用fork()来创建一个新进程。创建的新进程是从return_from_fork开始执行的,复制内核堆栈只复制了一部分,int
指令和save_all压到内核栈的内容。参数,系统调用号等都进行压栈。fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork
来实现进程的创建。

2018-2019-1 20189201 《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. xadmin 组件拓展自定义使用

    xadmin 组件相关可选自定义字段 list_display 功能 设置默认的显示字段(列) 配置 list_display = ['name', 'desc', 'detail', 'degree ...

  2. Hive SQL 分类

    题目: 请使用Hive SQL实现下面的题目. 下面是一张表名为user_buy_log的表,有三个字段,user(用户),grp(分组编号),time(购物时间). 需要将用户按照grp分组,对ti ...

  3. Netty 源码分析

    https://segmentfault.com/a/1190000007282628 netty社区-简书闪电侠 :https://netty.io/wiki/related-articles.ht ...

  4. ubuntu14.04 网络配置

    流程分析: 在Ubuntu系统网络设备启动的流程中,会依赖/etc/network/interface的配置文件初始化网络接口,所以直接在/etc/network/interface之中配置好对应的d ...

  5. 源码来袭:call、apply手写实现与应用

    关于this指向可以了解我的另一篇博客:JavaScript中的this指向规则. 一.call与apply的使用 回顾call与apply的this指向: var value = "win ...

  6. NOI-OJ 1.13 ID:34 确定进制

    整体思路 对于任意的p,q,r,可能使得p*q=r的最小进制应该是p,q,r三个数的所有数位中最大的数字+1,例如,6,9,42三个数中所有数位中最大的数字是9,故可能成立的最小进制是9+1,即10. ...

  7. 第十七节: EF的CodeFirst模式的四种初始化策略和通过Migration进行数据的迁移

    一. 四种初始化策略 EF的CodeFirst模式下数据库的初始化有四种策略: 1. CreateDatabaseIfNotExists:EF的默认策略,数据库不存在,生成数据库:一旦model发生变 ...

  8. 基于STM32F1的时钟芯片DS1302驱动

    目录 DS1302.h源代码 DS1302.c源代码 DS1302.h源代码 /** ********************************************************* ...

  9. Java 线程安全LocalTime 和LocaldateTime 新的Date和Time类 -JDK8新时间类的简单使用

    不可变类且线程安全 LocalDate .java.time.LocalTime 和LocaldateTime  新的Date和Time类 DateTimeFormatter ==https://ww ...

  10. Ubuntu 16.04 总出现红色圆圈警告和检测到系统程序出现问题

    这种问题不可忽视!不可忽视!不可忽视!重要的事情说三遍!!!(一次死机,好多文件丢失,真是痛苦的经历) 自从从第三方安装了Python3.6,并将默认3.5改为3.6,导致ubuntu16.04右上角 ...