《Tsinghua os mooc》第11~14讲 进程和线程

第十一讲 进程和线程

  1. 进程 vs 程序

    • 程序 = 文件 (静态的可执行文件)
    • 进程 = 执行中的程序 = 程序 + 执行状态
    • 进程的组成包括程序、数据和进程控制块
    • 同一个程序的多次执行过程对应为不同进程
  2. 三状态进程模型:就绪、运行、等待

  3. 挂起(Suspend):把一个进程从内存转到外存。

  4. 线程

    • 为什么需要引入线程?因为需要并行执行而又能共享资源的场景。
    • 线程是进程的一部分,描述指令流执行状态。它是进程中的指令执行流的最小单元,是CPU调度的基本单位。
    • 线程允许进程内多个执行流并行运行,并且能共享。
  5. 进程 vs 线程

    • 进程是资源分配单位,线程是CPU调度单位。进程由一组相关资源构成,包括地址空间(代码段、数据段)、打开的文件等各种资源。线程描述在进程资源环境中的指令流执行状态。
    • 进程拥有一个完整的资源平台,而线程只独享指令流执行的必要资源,如寄存器和栈。
    • 线程具有就绪、等待和运行三种基本状态和状态间的转换关系。
    • 线程能减少并发执行的时间和空间开销。线程的创建时间比进程短,线程的终止时间比进程短,同一进程内的线程切换时间比进程短。而且由于同一进程的各线程间共享内存和文件资源,可不通过内核进行直接通信。
  6. 进程控制块(PCB)包含三种信息:

    • 进程标识信息。如pid,name
    • 处理机现场保存。如context和tf
    • 进程控制信息
      • 调度和状态信息。如state,runs, need_resched,flags
      • 进程间通信信息。如parent,cptr,yptr,optr
      • 存储管理信息。如kstack, mm, cr3
      • 进程所用资源。
      • 有关数据结构连接信息。如list_link, hask_link

第十二讲 进程控制

  1. 内核为每个进程维护了对应的进程控制块(PCB),并且将相同状态的进程的PCB放置在同一队列,比如有就绪队列、I/O等待队列和僵尸队列。

  2. 同一个等待队列里的各个进程等待的事件相同吗?为什么发生事件后此队列的所有进程都被唤醒?

  3. 进程创建

    • Windows进程创建API: CreateProcess(filename)
    • Unix进程创建系统调用:fork/exec
    • exec: 允许进程“加载”一个完全不同的程序,并从main开始执行(即_start);允许进程加载时指定启动参数(argc, argv);exec调用成功时,它是相同的进程,但是运行了不同的程序;代码段、堆栈和堆(heap)等完全重写。
  4. 进程等待:wait()系统调用用于父进程等待子进程的结束。具体而言,子进程结束时通过exit()向父进程返回一个值,父进程通过wait()接受并处理返回值。父进程调用wait时的具体行为如下:

    • 有子进程存活时,父进程进入等待状态,等待子进程的返回结果。当某子进程调用exit()时,唤醒父进程,将exit()返回值作为父进程中wait的返回值。
    • 有僵尸子进程等待时,wait()立即返回其中一个值。
    • 无子进程存活时,wait()立刻返回。
  5. 进程终止:进程结束执行时调用exit(),完成进程资源回收。exit系统调用的行为:

    • 将调用参数作为进程的“结果”
    • 关闭所有打开的文件等占用资源
    • 释放内存
    • 释放大部分进程相关的内核数据结构
    • 检查是否父进程是存活着的。如存活,保留结果的值直到父进程需要它,进入僵尸(zombie/defunct)状态;如果没有,它释放所有的数据结构,进程结束。
    • 清理所有等待的僵尸进程
  6. 其他进程控制系统调用

    • nice()指定进程的初始优先级
    • Unix系统中进程优先级会随执行时间而衰减
    • ptrace()允许一个进程控制另一个进程的执行,设置断点和查看寄存器等
    • sleep()可以让进程在定时器的等待队列中等待指定

第十三讲 实验四 内核线程管理

  1. 对x86系统而言,进程/线程上下文就是CPU内部的一堆寄存器的信息。
struct context {
    uint32_t eip;
    uint32_t esp;
    uint32_t ebx;
    uint32_t ecx;
    uint32_t edx;
    uint32_t esi;
    uint32_t edi;
    uint32_t ebp;
};
  1. trap_frame:用于保存前一个被(中断或异常)打断的进程的状态信息。
struct trapframe {
    struct pushregs tf_regs;
    uint16_t tf_gs;
    uint16_t tf_padding0;
    uint16_t tf_fs;
    uint16_t tf_padding1;
    uint16_t tf_es;
    uint16_t tf_padding2;
    uint16_t tf_ds;
    uint16_t tf_padding3;
    uint32_t tf_trapno;
    /* below here defined by x86 hardware */
    uint32_t tf_err;
    uintptr_t tf_eip;
    uint16_t tf_cs;
    uint16_t tf_padding4;
    uint32_t tf_eflags;
    /* below here only when crossing rings, such as from user to kernel */
    uintptr_t tf_esp;
    uint16_t tf_ss;
    uint16_t tf_padding5;
} __attribute__((packed));

第十四讲 实验五 用户进程管理

  1. ELF文件加载到内存:加载各个section、分配BSS段、分配堆栈空间。

  2. 如何理解ucore_os_docs lab 5的这句话?有了文件系统后会怎么加载?直接读取obj/__user_exit.out文件,然后写到指定内存空间吗?

“而到了与文件系统相关的实验后,ucore会提供一个简单的文件系统,那时所有的用户程序就都不再用这种方法进行加载了,而可以用大家熟悉的文件方式进行加载了。”

  1. ucore把用户进程的虚拟地址空间分了两块,一块与内核线程一样,是所有用户进程都共
    享的内核虚拟地址空间,映射到同样的物理内存空间中,这样在物理内存中只需放置一份内
    核代码,使得用户进程从用户态进入核心态时,内核代码可以统一应对不同的内核程序;另
    外一块是用户虚拟地址空间,虽然虚拟地址范围一样,但映射到不同且没有交集的物理内存空间中。这样当ucore把用户进程的执行代码(即应用程序的执行代码) 和数据(即应用程序的全局变量等) 放到用户虚拟地址空间中时,确保了各个进程不会“非法”访问到其他进程的物理内存空间。

  2. 当进程执行完它的工作后,就需要执行退出操作,释放进程占用的资源。ucore分了两步来完成这个工作,首先由进程本身完成大部分资源的占用内存回收工作,然后由此进程的父进程完成剩余资源占用内存的回收工作。为何不让进程本身完成所有的资源回收工作呢?这是因为进程要执行回收操作,就表明此进程还存在,还在执行指令,这就需要内核栈的空间不能释放,且表示进程存在的进程控制块不能释放。所以需要父进程来帮忙释放子进程无法完成的这两个资源回收工作。

  3. “硬”构造出第一个用户进程:建立用户代码/数据段 -> 创建内核线程 -> 创建用户进程“壳” -> 填写用户进程“肉” -> 执行用户进程 -> 完成系统调用 -> 结束用户进程

posted on 2019-03-04 15:50  whl1729  阅读(306)  评论(0编辑  收藏  举报