Lec 09 进程

Lec 09 process

本内容版权归上海交通大学并行与分布式系统研究所所有
使用者可以将全部或部分本内容免费用于非商业用途
使用者在使用全部或部分本内容时请注明来源
资料来自上海交通大学并行与分布式系统研究所+材料名字
对于不遵守此声明或者其他违法使用本内容者,将依法保留追究权
本内容的发布采用 Creative Commons Attribution 4.0 License
完整文本

回顾

分时复用有限的CPU资源

  • 分时复用CPU
  • 让多个应用程序轮流使用处理器核心
  • 何时切换:操作系统决定
    • 运行时间片(例如100ms)
    • 时间片:每个程序每次最长连续运行时间。
  • 高频切换:看起来是多个应用“同时”执行

img

应用程序和进程

  • 通常一个应用程序对应一个进程
    • 在shell中输入可执行文件的名称
      • shell创建新进程,新进程执行可执行文件
    • 在图形界面双击应用图标
  • 多进程程序:应用程序可以自己创建新进程
    • 创建新进程,在新进程只能够运行其他应用程序或者运行与自己一样的程序。

进程状态数据

  • 操作系统提供给进程的抽象用于管理应用程序
    • 进程标识号,(process ID,PID)
    • 运行状态
      • 处理器上下文
    • 地址空间
    • 打开文件

处理器上下文

  • 操作系统为每个进程维护处理器上下文
    • 包含回复进程执行需要的状态
    • 进程A执行到main函数任意一条指令的时候,切换到进程B执行,一段时间后再一次切换回进程A执行。
    • 需要保存以下状态:PC寄存器值,栈寄存器值,通用寄存器值,状态寄存器值

1 进程的表示:PCB

进程:运行中的程序

  • 进程是计算机程序运行时的抽象

    • 静态部分:程序运行需要的代码和数据
    • 动态部分:程序运行期间的状态(程序计数器,栈,堆)
  • 进程具有独立的虚拟地址空间

    • 每个进程都具有“独占全部内存”的假象
    • 内核中同样包含内核栈,内核代码,数据。

如何表示进程:进程控制块

  • 每个进程都对应一个元数据,称为进程控制块PCB。

    • 进程控制块存储在内核态,因为它由内核管理,不应被用户态的程序访问
      独立的虚拟地址空间是进程的标志性特征
      独立的执行上下文表明进程具有独立的执行能力。
  • 进程控制块内应该保存:

    • 对立的虚拟地址空间
    • 独立的处理器上下文
    • 内核栈

内核栈和用户栈

  • 应用需要内核栈

    • 进程在内核中依然需要执行代码,有读写临时数据的需求。
    • 进程在用户态和内核态的数据应该相互隔离,增强安全性。
  • AArch64实现:两个栈指针寄存器

    • SP_EL1, SP_EL0
    • 进程进入内核后,自动完成栈寄存器切换。
    • x86直邮一个栈寄存器,需要保存恢复。

img

2 进程的创建

如何实现进程的创建?

  • 进程包含:
    • 用户视角:代码,数据,堆栈
    • 内核视角:PCB,虚拟地址空间,上下文
    • 创建进程就是创建和初始化以上内容的过程

img

进程创建1:PCB相关初始化

  • PCB及其包含的内容都需要创建以及初始化。
    • PCB本身数据结构的分配
    • 初始化PCB:虚拟内存
      • 创建和初始化vmspace数据结构
      • 分配一个物理页作为顶级页表
    • 内核栈:分配物理页,作为进程内核栈
    • 处理器上下文在应用运行前才进行初始化。

img

进程创建2:可执行文件的加载

  • 可执行文件具有固定的存储格式
  • 以ELF(Executable and Linkable Format)为例:
    • 程序头部表可以获取需要的所在的位置
    • 通常只有代码段和数据端需要被加载(loadable)
    • 加载即从ELF文件中映射到虚拟地址空间的过程

img

进程创建3:准备运行环境

  • 在返回用户态之前,还需要为进程准备运行所需要的环境
    • 分配用户栈(分配物理内存,映射到虚拟地址空间)
    • 准备程序运行时的环境
      • main函数:
        int main(int argc, char *argv[], char *envp[])

img

进程创建4:处理器上下文初始化

  • 为什么直到最后才初始化处理器上下文?
    • 其包含的内容直到前序操作完成才确定。
      • SP:用户栈分配后才确定地址
      • PC(保存在ELR_EL1):加载ELF后才了解到入口地址。
    • 大部分的寄存器初始值可以直接赋予0

img

3 进程退出,等待

Version 1: 销毁PCB及其所有内容

  • 销毁PCB以及内部所有内容。
  • 告知内核选择其他进程执行。

img

  • 假设内核使用进程列表维护所有进程
    • 仍然调用schedule,让其他进程执行,自己在while循环中等待。

img

缺陷

  • 信息太少,ls进程退出时的返回值,shell进程无法获取。

img

改进-1: 为进程添加退出状态支持

  • 分别修改PCB和process_exit的实现。

改进-2: 修改process_waitpid

  • 如果进程已经设置为退出,则记录其退出状态并回收。
    • 进程资源的回收操作从exit移到了waitpid

img

改进-3: 安全性,限制进程等待的范围

  • 目标:只有创建进程的程序才能监控进程
  • 实现:引入父(创建者)子(被创建者)进程概念
  • 进程之间的创建关系构建了进程树
    • 第一个进程(树根)通常由内核主动创建。

img

关于process_waitpid的疑问

  • 编程时,我们并没有强制创建者调用waitpid
    • 如果shell不调用waitpid,会出现什么情况呢
    • ls进程退出,但是没有销毁,称为zombie(僵尸)进程
    • 解决方案:init进程代为管理并回收资源。

4 进程的状态

进程的睡眠需求

  • 等待其他事件:进入进程睡眠。轮询方式查看时间,没有到达规定的时间则继续等待。

img

进程的五种典型执行状态

  • 新生(new): 刚刚调用process_create
  • 就绪(ready): 随时准备执行(但是暂时没有执行)
  • 运行(running): 正在执行
  • 僵尸(zombie): 退出但未回收
  • 终止(terminated): 退出且被回收

img

调度

  • 目的:选出下一个可以执行 的进程
    • 可以执行=就绪(ready)

img

5 案例分析:Linux进程创建

fork()

  • 语义:为调用进程创建一个一模一样的新进程

    • 调用进程为父进程,新进程为子进程
    • 接口简单,无需任何参数
  • fork后的两个进程均为独立进程

    • 拥有不同的进程id
    • 可以并行执行,互不干扰(除非使用特定的接口)
    • 父进程和子进程会共享部分数据结构(内存、文件等)
  • 通过返回值来判断父进程/子进程。0(子进程)非0(父进程)

  • 父子进程执行的顺序由调度策略决定。

  • 将父进程的PCB拷贝一份,实现比较简单

优缺点分析

  • 优点:

    • 接口简介,(过去)实现简单
    • 创建进程和执行进程解耦,提高了灵活度。
  • 缺点:

    • 创建拷贝的复杂度和PCB复杂度相关。
    • 完全拷贝过于粗暴(不如clone)
    • 性能差,可扩展性差。(不如vfork和spawn)
    • 不可组合性。(例如:fork()+pthread())

fork的替代接口

  • vfork:类似于fork,但是父子进程共享同一个地址空间

  • 优点:映射无需拷贝,性能更好

  • 缺点:

    • 只能用在fork+exec场景中
    • 共享地址空间存在安全问题
    • 写时拷贝提供了vfork的优点给fork。
  • posix_spawn:相当于fork+exec

  • 优点:可扩展性,性能较好

  • 缺点:不如fork灵活。

  • clone:fork进阶版,可以选择性不拷贝内存

    • 优点:高度可控,可以按照需求调整
    • 缺点:接口复杂,选择性拷贝容易出错。

6 ChCore微内核进程管理

PCB解耦设计

  • 内核态:Cap_group(能立组)
  • 将资源(进程,内核栈,虚拟地址空间)均抽象成为对象
  • 访问对象的凭证成为能力,而进程时分配和管理资源的基本单位。

img

  • 用户态:proc
  • 除内核中的资源(如vmspace)均移动到用户态管理

ChCore进程创建

  • 逻辑和process_state相似,但使用微内核风格,很多功能都在用户态完成。

img

7 Chcore 进程切换

五个步骤

  • 进出内核的五个步骤
  1. 进程1进入内核态
  2. 进程1处理器上下文保存
  3. 进程上下文切换
  4. 进程2处理器上下文恢复
  5. 进程2返回用户态

img

处理器上下文和进程上下文

  • 处理器上下文:用于保存切换时的寄存器状态(硬件),每个PCB中均有保存。
  • 进程上下文:表示目前操作系统正在以哪个进程的身份运行(软件),通常使用一个指向PCB的全局指针表示。

img

切换节点位于所有调用schedule的地方。告知内核选择下一个执行的进程,涉及到进程的切换。

进程切换全过程

1. p0进入内核态

硬件完成部分寄存器保存,PC和PSTATE分别保存到ELR_EL1和SPSR_EL1

2. 上下文保存

将处理器中的寄存器值保存到处理器上下文对应的位置

3. 切换

  • 虚拟地址空间切换
    • 设置页表相关的寄存器(TTBR0_EL1)
    • 使用PCB中保存的页表基地址赋值给TTBR0_EL1
  • 内核栈切换
    • 设置内核中的栈寄存器SP_EL1
    • 使用PCB中保存的内核栈顶地址赋值给SP_EL1
  • 进程上下文切换
    • 设置cur_proc为之后要执行的进程(p1)
    • 表明操作系统将以p1的身份运行。

4. p1处理器上下文恢复

  • 从处理器上下文中加载各个寄存器的值,放入对应的寄存器中。
  • 硬件自动恢复部分寄存器,将ELR_EL1和SPSR_EL1中的值自动保存到PC和PSTATE中。

img

8.附加:ChCore进程切换实现

img

posted @   木木ちゃん  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示