进程和线程

进程

  • 定义
    • 进程是指一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程
  • 进程的组成
    • 进程包含了正在运行的一个程序的所有状态信息
      • 代码
      • 数据
      • 状态寄存器:CPU 状态 CR0 、指令指针 IP
      • 通用寄存器:AX、BX、CX…
      • 进程占用系统资源:打开文件、已分配内存…
  • 进程的特点
    • 动态性:可动态地创建,结束进程
    • 并发性:进程可以被独立调度并占用处理机运行
    • 独立性:不同进程的工作不相互影响
    • 制约性:因访问共享数据/资源或进程间同步而产生制约
  • 进程与程序的联系
    • 进程是操作系统处于执行状态程序的抽象
      • 程序 = 文件 (静态的可执行文件)
      • 进程 = 执行中的程序 = 程序 + 执行状态
    • 同一个程序的多次执行过程对应为不同进程
      • 如命令 “ ls “ 的多次执行对应多个进程
    • 进程执行需要的资源
      • 内存:保存代码和数据
      • CPU:执行指令
  • 进程与程序的区别
    • 进程是动态的,程序是静态的
      • 程序是有序代码的集合
      • 进程是程序的执行,进程有核心态/用户态
    • 进程是暂时的,程序的永久的
      • 进程是一个状态变化的过程
      • 程序可长久保存
    • 进程与程序的组成不同
      • 进程的组成包括程序、数据和进程控制块

进程控制块( PCB,Process Control Block )

  • 定义:操作系统管理控制进程运行所用的信息集合
    • 操作系统用 PCB 来描述进程的基本情况以及运行变化的过程
    • PCB 是进程存在的唯一标志
      • 每个进程都在操作系统中有一个对应的 PCB
    • 进程控制块的使用
      • 进程创建:生成该进程的 PCB
      • 进程终止:回收它的 PCB
      • 进程的组织管理:通过对 PCB 的组织管理来实现
  • 进程控制块内容
    • 进程标识信息
    • 处理机现场保存
    • 进程控制信息
      • 调度和状态信息:进程和处理机制使用情况调度
      • 进程间通信信息:进程间通信相关的各种标识
      • 存储管理信息:指向进程映像存储空间数据结构
      • 进程所用资源:进程使用的系统资源,如打开文件等
      • 有关数据结构连接信息:与 PCB 相关的进程队列
  • 进程控制块的组织
    • 链表:同一状态的进程其 PCB 成一链表,多个状态对应多个不同的链表
      • 各状态的进程形成不同的链表:就绪链表、阻塞链表
    • 索引表:同一状态的进程归入一个索引表(由索引指向 PCB ),多个状态对应多个不同的索引表
      • 各状态的进程形成不同的索引表:就绪索引表、阻塞索引表

进程状态

  • 进程的生命周期划分
    • 进程创建
      • 引起进程创建的情况
        • 系统初始化时
        • 用户请求创建一个新进程
        • 正在运行的进程执行了创建进程的系统调用
    • 进程执行
      • 内核选择一个就绪的进程,让它占用处理机并执行
    • 进程等待
      • 进入等待(阻塞)的情况
        • 请求并等待系统服务,无法马上完成
        • 启动某种操作,无法马上完成
        • 需要的数据没有到达
      • 只有进程自身才能知道何时需要等待某种事件的发生
    • 进程抢占
      • 进程会被抢占的情况
        • 高优先级进程就绪
        • 进程执行当前时间用完
    • 进程唤醒
      • 唤醒进程的情况
        • 被阻塞进程需要的资源可被满足
        • 被阻塞进程等待的事件到达
      • 进程只能被别的进程或者操作系统唤醒
    • 进程结束
      • 进程结束的情况
        • 正常退出(自愿的)
        • 错误退出(自愿的)
        • 致命错误(强制性的)
        • 被其他进程扼杀(强制性的)

三状态进程模型

  • 三个主要状态
    • 运行状态( Running ):进程正在处理机上运行
    • 就绪状态( Ready ):进程获得了除处理机之外的所需资源,得到处理机即可运行
    • 等待状态( 又称阻塞状态 Blocked ):进程正在等待某一事件的出现而暂停运行
  • 辅助状态
    • 创建状态( New ):一个进程正在被创建,还没被转到就绪状态之前的状态
    • 结束状态( Exit ):一个进程正在从系统中消失时的状态,这是因为进程结束或由于其他原因所导致
  • 状态变迁
    • NULL > 创建:一个新进程被产生出来执行一个程序
    • 创建 > 就绪:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态
    • 就绪 > 运行:处于就绪状态的进程被进程调度程序选中后,就分配到处理机上来运行
    • 运行 > 结束:当进程表示它已经完成或者因出错,当前运行进程会由操作系统作结束处理
    • 运行 > 就绪:处于运行状态的进程在其运行过程中,由于分配给它的处理机时间片用完而让出处理机
    • 运行 > 等待:当进程请求某资源且必须等待时
    • 等待 > 就绪:当进程要等待某事件到来时,它从阻塞状态变到就绪状态

挂起进程模型

  • 进程挂起:处于挂起状态的进程映像在磁盘上,目的是减少进程占用内存
  • 新增状态
    • 等待挂起状态( Blocked-suspend ):进程在外存并等待某事件的出现
    • 就绪挂起状态( Ready-suspend ):进程在外存,但只要进入内存即可运行
  • 新增状态变迁
    • 挂起( Suspend ):把一个进程从内存转到外存
      • 等待到等待挂起:没有进程处于就绪状态或就绪进程要求更多内存资源
      • 就绪到就绪挂起:当有高优先级等待(系统认为会很快就绪的)进程和低优先级就绪进程
      • 运行到就绪挂起:对抢先式分时系统,当有高优先级等待挂起进程因事件出现而进入就绪挂起
    • 在外存时的状态转换
      • 等待挂起到就绪挂起:当有等待挂起进程因相关事件出现
    • 激活( Activate ):把一个进程从外存转到内存
      • 就绪挂起到就绪:没有就绪进程或挂起就绪进程优先级高于就绪进程
      • 等待挂起到等待:当一个进程释放足够内存,并有高优先级等待挂起进程
  • 状态队列
    • 由操作系统来维护一组队列,表示系统中所有进程的当前状态
    • 不同队列表示不同状态
      • 就绪队列、各种等待队列
    • 根据进程状态不同,进程 PCB 加入相应队列
      • 进程状态变化时,它所在的 PCB 会从一个队列换到另一个

线程

  • 定义:线程是进程的一部分,描述指令流执行状态。它是进程中的指令执行流的最小单元,是 CPU 调度的基本单位
    • 进程的资源分配角色:进程由一组相关资源构成,包括地址空间(代码段、数据段)、打开的文件等各种资源
    • 线程的处理机调度角色:线程描述在进程资源环境中的指令流执行状态
  • 进程和线程的关系
  • 线程 = 进程 - 共享资源
    • 线程的优点
      • 一个进程中可以同时存入多个线程
      • 各个线程之间可以并发地执行
      • 各个线程之间可以共享地址空间和文件等资源
    • 线程的缺点
      • 一个线程崩溃,会导致其所属进程的所有线程崩溃
  • 线程和进程的比较
    • 进程是资源分配单位,线程是 CPU 调度单位
    • 进程拥有一个完整的资源平台,而线程只独享指令流执行的必要资源,如寄存器和栈
    • 线程具有就绪、等待和运行三种基本状态和状态间的转换关系
    • 线程能减少并发执行的时间和空间开销
      • 线程的创建时间比进程短
      • 线程的终止时间比进程短
      • 同一进程内的线程切换时间比进程短
      • 由于同一进程的各线程间共享内存和文件资源,可不通过内核进行直接通信
  • 线程的三种实现方式
    • 用户线程:在用户空间实现
      • POSIX Pthreads,Mach C-threads,Solaris threads
    • 内核线程:在内核中实现
      • Windows,Solaris,Linux
    • 轻量级进程:在内核中实现,支持用户线程
      • Solaris ( LightWeight Process )

用户线程

  • 定义:由一组用户级的线程库函数来完成线程的管理,包括线程的创建、终止、同步和调度等
  • 特征
    • 不依赖于操作系统的内核
      • 内核不了解用户线程的存在
      • 可用于不支持线程的多进程操作系统
    • 在用户空间实现的线程机制
      • 每个进程有私有的线程控制块( TCB )列表
      • TCB 有线程库函数维护
    • 同一进程内的用户线程切换速度快
      • 无需用户态/内核态切换
    • 允许每个进程拥有自己的线程调度算法
  • 不足
    • 线程发起系统调用而阻塞时,则整个进程进入等待
    • 不支持基于线程的处理机抢占
      • 除非当前运行线程主动放弃,它所在进程的其他线程无法抢占 CPU
    • 只能按进程分配 CPU 时间
      • 多个线程进程中,每个线程的时间片较少

内核线程

  • 定义:由内核通过系统调用实现的线程机制,由内核完成线程的创建、终止和管理
  • 特征
    • 由内核维护 PCB 和 TCB
    • 线程执行系统调用而被阻塞不影响其他线程
    • 线程的创建、终止和切换相对较大
      • 通过系统调用/内核函数,在内核实现
    • 以线程为单位进行 CPU 时间分配
      • 多线程的进程可获得更多 CPU 时间

轻权进程( LightWeight Process )

内核支持的用户线程。一个进程可有一个或多个轻量级进程,每个轻权进程由一个单独的内核线程来支持。(Solaris/Linux)

用户进程与内核线程的对应关系

  • 实际实现中,一对一效果最好

#进程管理

进程切换

  • 进程切换(上下文切换)
    • 暂停当前运行进程,从运行状态变为其他状态
    • 调度另一个进程从就绪状态变成运行状态
  • 进程切换的要求
    • 切换前,保存进程上下文
    • 切换后,恢复进程上下文
    • 快速切换
  • 进程生命周期的信息
    • 寄存器( PC,SP,…)
    • CPU 状态
    • 内存地址空间
  • 上下文切换图示
  • 进程控制块 PCB :内核的进程状态记录
    • 内核为每个进程维护了对应的进程控制块 ( PCB )
    • 内核将相同状态的进程的 PCB 放置在同一队列
      • 就绪队列
      • I/O 等待队列
        • 每个设备一个队列
      • 僵尸队列

进程创建

  • 创建新进程

    • Windows 进程创建 API:CreateProcess(filename)
      • 创建时关闭所有在子进程里的文件描述符
        • CreateProcess(filename, CLOSE_FD)
      • 创建时改变子进程的环境
        • CreateProcess(filename, CLOSE_FD, new_envp)
      • 等等
    • Unix 进程创建系统调用:fork/exec

      • fork() 把一个进程复制成两个进程

        • parent (old PID),child (new PID)
      • exec() 用新程序来重写当前进程

        • PID 没有改变
      • 用 fork 和 exec 创建进程的示例

        1
        2
        3
        4
        5
        int pid = fork();				//创建子进程
        if(pid == 0){
        //Do anything (unmap memory, close net connections...)
        exec("program",argc,argv0,argv1,...)
        }
      • fork() 创建一个继承的子进程

        • 复制父进程的所有变量和内存
        • 复制父进程的所有 CPU 寄存器(有一个寄存器例外)
      • fork() 的返回值
        • 子进程的 fork() 返回0
        • 父进程的 fork() 返回子进程标识符
        • fork() 返回值可方便后续使用,子进程可使用 getpid() 获取 PID
      • fork() 的地址空间复制
        • fork() 执行过程对于子进程而言,是在调用时间对父进程地址空间的一次复制
          • 对于父进程 fork() 返回 child PID,对于子进程返回值为0
      • 程序加载和执行

        • 系统调用 exec() 加载新程序取代当前运行进程
        • exec() 示例代码
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          main()
          ...
          int pid = fork(); //创建子进程
          if(pid == 0){ //子进程在这里继续
          exec_status = exec("calc",argc,arg0,arg1,...);
          printf("Why would I execute?");
          } else { //父进程在这里继续
          printf("Whose your daddy?");
          ...
          child_status = wait(pid);
          }
          if(pid < 0){/*error occurred*/}
      • fork() 的开销

        • fork() 的实现开销
          • 对子进程分配内存
          • 复制父进程的内存和 CPU 寄存器到子进程里
          • 开销昂贵!!
        • 在 99% 的情况里,我们在调用 fork() 之后调用 exec()
          • 在 fork() 操作中内存复制是没有作用的
          • 子进程将可能关闭打开的文件和连接
          • 为什么不能结合它们在一个调用中?
        • vfork()
          • 创建进程时,不再创建一个同样的内存映像
          • 一些时候称为轻量级 fork()
          • 子进程应该几乎立即调用 exec()
          • 现在使用 Copy on Write (COW) 技术

进程加载

  • 程序加载和执行系统调用 exec()
    • 允许进程”加载”一个完全不同的程序,并从 main 开始执行(即_start)
    • 允许进程加载时指定启动参数(argc,argv)
    • exec 调用成功时
      • 它是相同的进程…
      • 但是运行了不同的程序
    • 代码段、堆栈和堆(heap)等完全重写

进程等待与退出

  • 父进程等待子进程
    • wait() 系统调用用于父进程等待子进程的结束
      • 子进程结束时通过 exit() 向父进程返回一个值
      • 父进程通过 wait() 接受并处理返回值
    • wait() 系统调用的功能
      • 有子进程存活时,父进程进入等待状态,等待子进程的返回结果
        • 当某子进程调用 exit() 时,唤醒父进程,将 exit() 返回值作为父进程中 wait 的返回值
      • 有僵尸子进程等待时,wait()立即返回其中一个值
      • 无子进程存活时,wait()立刻返回
  • 进程的有序终止 exit()
    • 进程结束执行时调用 exit(),完成进程资源回收
    • exit() 系统调用的功能
      • 将调用参数作为进程的”结果”
      • 关闭所有打开的文件等占用资源
      • 释放内存
      • 释放大部分进程相关的内核数据结构
      • 检查是否父进程是存活着的
        • 如存活,保留结果的值直到父进程需要它,进入僵尸( zombie / defunct )状态
        • 如果没有,它释放所有的数据结构,进程结果
      • 清理所有等待的僵尸进程
    • 进程终止是最终的垃圾收集(资源回收)

其他进程控制系统调用

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

进程控制 v.s. 进程状态

  • exec() 实际上是执行代码过程中的一种状态