操作系统概述(2.1进程与线程)


操作系统——2.1 进程与线程

2.1.1 进程

  • 程序,是静态的,就是一系列的指令集合。

  • 进程,是动态的,是程序的一次执行过程。

  • 当进程被创建的时候,操作系统会给该进程分配一个唯一的、不重复的ID——PID(Process ID),进程ID

  • 每个进程,操作系统会记录:

    • 其PID,进程所属用户ID(UID)
    • 还要记录给每个进程分配了多少资源(如内存,正在使用哪些IO设备,正在使用那些文件,可用于实现操作系统对资源的管理。)
    • 记录进程的运行情况(CPU使用时间、磁盘使用情况、网络流量使用情况等等,可用于实现操作系统对进程的控制、调度)
    • 这些信息都会同意保存在一个数据结构PCB(process control block)中,即进程控制块,存放管理时需要的信息。
graph LR
PCB(PCB) -->A(进程描述信息)
    A -->A1(进程标识符PID)
    A -->A2(用户标识符UID)
PCB -->B(进程控制和管理信息)
    B -->B1(CPU/磁盘/网络流量 使用统计)
    B -->B2(进程当前状态 就绪/阻塞/运行)
PCB -->C(资源分配清单)
    C -->C1(正在使用的文件)
    C -->C2(正在使用的内存区域)
    C -->C3(正在使用的IO设备)
PCB -->D(处理机相关信息)
	D -->D1(PSW/PC等 寄存器的值)
  • 当进程被创建时,操作系统为期创建一个PCB。当进程运行结束,操作系统会将其收回。操作系统对进程进行管理工作所需信息都存在PCB中。
graph LR
S(进程的组成) -->PCB
PCB -->P1(进程描述信息)
PCB -->P2(进程控制和管理信息)
PCB -->P3(资源分配清单)
PCB -->P4(处理机相关信息)
S -->A(程序段)
A -->A1(程序的代码  指令序列)
S -->B(数据段)
B -->B1(运行过程中产生的各种数据)
B -->B2(如 程序中定义的变量)
S1[准确来说, 应该是 进程实体的组成]

PCB通常包含的内容:

进程描述信息 进程控制和管理信息 资源分配清单 处理机相关信息
进程标识符PID 进程当前状态 代码段指针 通用寄存器值
用户标识符UID 进程优先级 数据段指针 地址寄存器值
代码运行入口地址 堆栈段指针 控制寄存器值
程序的外存地址 文件描述符 标志寄存器值
进入内存时间 键盘 状态字
处理机占用时间 鼠标
信号量使用
  • PCB是给操作系统用的。PCB是进程存在的唯一标志!
  • 程序段、数据段是给进程自己使用的。和自身的运行逻辑有关

进程,是动态的,是程序进行资源分配和调度的一个独立个体。

进程实体(进程映像):是静态的,是进程某一时刻运行时的快照(照片)。进程实体可以反映进程在某一时刻的状态。

  • 一个进程的调度,就是操作系统决定让这个进程上CPU运行。

进程的特征

  • 动态性:

    是进程的最基本特征。进程是程序的一次执行过程,动态产生、变化和消亡

  • 并发性:

    内存中有多个进程实体。各个进程可以并发执行。

  • 独立性:

    课接受独立运行、调度、获得资源的基本单位

    各个进程拥有的内存地址空间相互独立

  • 异步性:

    各自独立,不可预知的速度运行。操作系统要提供 进程同步机制 来解决异步问题

  • 结构性:

    每个进程都配有一个PCB。

2.1.2 进程的状态

进程的状态

  • 创建态:进程被创建时的状态。系统会为其分配资源,初始化PCB。

  • 就绪态:进程创建完成的状态。但由于没有CPU,暂时不能运行

  • 运行态:进程正在CPU上运行。

进程运行过程中,可能会请求等待某个时间的发生(如等待某种系统资源的分配,或者等待其他进程的响应)。所以还有:

  • 阻塞态:在这个事件发生之前,该进程无法继续运行。操作系统会将其设置为阻塞态。
graph LR
A((新建)) --> |创建|B(就绪)
B -->|调度|C(运行)
C -->|时间到|B
C -->|退出|E(终止)
C -->|事件等待|D(阻塞)
D -->|事件发生|B
  • 就绪态 -> 运行态:进程被调度

  • 运行态 -> 阻塞态:是一种主动行为,等待资源分配

  • 阻塞态 -> 就绪态:是一种被动行为,资源分配到位

  • 运行态 -> 就绪态:时间片结束时候会有适中中断,或者CPU被其他优先级更高的进程抢占

  • 三种基本状态:运行态、就绪态、阻塞态。

  • PCB中会有个state表示进程的当前状态

进程的组织——链接方式

  • 主要使用到了:执行指针、就绪队列指针、阻塞队列指针

链式方式(系统管理一系列的队列,每个队列都会指向相应状态的PCB)大多数操作系统使用该方式

​ 执行指针指向当前运行态(执行态)的进程

索引方式

​ 给各个相应进程建立索引表,每个索引表的表项指向PCB

2.1.3 进程控制

实现进程的状态转换。进程控制需要用原语来实现。

原语的执行具有原子性,即允许过程只能一气呵成,期间不允许被中断。

可以用“关中断指令“和”开中断指令“这两个特权指令来实现原子性。

  • 没有关闭中断指令的时候,CPU每执行一句话就会检查是否有中断信号,有的换就中断程序,去执行中断信号所对应的程序。
  • 关闭了中断指令之后,即使有中断指令进来,CPU就不会理会,继续执行自己当前的指令。
  • 执行了“开中断指令” ,才会检查刚才是否有中断信号。有的话,就马上转去中断处理程序。
  • 在”关中断“与“开中断”之间的指令就是原子性的,不可被中断。

2.1.3.1 进程的创建:

  • 创建原语:
    • 申请空白的PCB
    • 为新进程分配所需资源
    • 初始化PCB
    • 将PCB插入 就绪队列
  • 引起进程创建的事件
    • 用户登录:分时系统中,用户登录成功,系统会为其建立一个新的进程
    • 作业调度:多道批处理系统中,有新的作业放入内存时,会为其建立一个新的进程
    • 提供服务:用户向操作系统提出某些请求时,会新建一个进程处理这个请求
    • 应用请求:由用户进程主动请求创建的一个子进程

2.1.3.2 进程的摧毁:

  • 撤销原语:
    • 从PCB集合中找到终止进程的PCB
    • 若该进程正在运行,立即剥夺CPU,将CPU分配给其他进程
    • 终止其所有子进程(进程之间的关系是树形的结构)
    • 将该进程拥有的所有资源归还给父进程或者操作系统
    • 删除PCB
  • 引起进程终止的时间
    • 正常结束:进程自己请求终止(exit系统调用)
    • 异常结束:整数除0、非法使用特权指令然后被操作系统强行杀掉
    • 外接干预:命令行ctrl+c键直接中断代码运行

2.1.3.3 进程的阻塞和唤醒:

  • 进程的阻塞
    • 阻塞原语
      • 找到要阻塞的进程 对应的PCB
      • 保护进程运行现场,将PCB状态信息设置为 “阻塞态” ,暂停进程的运行
      • 将PCB插入相应事件的等待队列
    • 引起进程阻塞的事件
      • 需要等待系统分配某种资源
      • 需要等待相互合作的其他进程完成工作
  • 进程的唤醒
    • 唤醒原语
      • 在事件等待队列中找到PCB
      • 将PCB从等待队列溢出,设置进程为就绪态
      • 将PCB插入就绪队列,等待被调度
    • 引起进程唤醒的事件:等待的事件发生了。 一个进程因为什么事情被阻塞,就应该由这个事件来唤醒

2.1.3.4进程的切换:

  • 切换原语
    • 将运行环境信息存入PCB
    • PCB移入相应队列
    • 选择另一个进程执行,更新其PCB
    • 根据PCB恢复新进程所需的运行环境
  • 引起进程切换的时间
    • 当前进程的时间片计时结束
    • 有更高优先级的进程到达
    • 当前进程主动阻塞
    • 当前进程终止

无论哪个进程要控制原语,要做的无非三类事情:

  1. 更新PCB中的信息

  2. 将PCB插入合适的队列

  3. 分配/回收资源

2.1.4 进程通信

graph LR
A(进程通信) -->B(共享存储)
B -->B1(基于数据结构的共享)
B -->B2(基于存储区的共享)
A -->C(消息传递)
C -->C1(直接通信)
C -->C2(间接通信)
A -->D(管道通信)

2.1.4.1进程通信

进程之间的信息交换。为了保证安全,一个进程不能直接访问另一个进程的地址空间

  • 共享空间:两个进程对共享空间的访问必须是互斥的(互斥访问通过操作系统给的工具实现)。操作系统只提供共享空间和同步互斥工具(如P、V操作)

    • 基于数据结构共享:比如共享空间只能存放长度为10的数组,这种方式速度慢,限制多,是一种低级的通信方式
    • 基于存储区的共享:在内存中划出一块共享存储区,数据的形式、存放的位置都是由进程来控制,而不是操作系统。该共享方式更快,是一种高级的通信方式。
  • 管道通信:

    管道是指用于连接读写进程的一个共享文件,又叫做pipe文件。其实就是在内存中开辟一个大小固定的缓冲区。

    • 一个管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
    • 各个进程要互斥地访问管道。
    • 数据以字符流的形式进入管道。
      • 当管道写满时,写进程的write()系统调用将被阻塞,等待读进程将数据取走
      • 当读进程将数据全部取走后,管道变空,此时读进程的read()系统调用将被阻塞。
    • 如果没写满,就不允许读;如果没读空,就不允许写
    • 数据一旦被读出,就会从管道中被抛弃。这就意味着读进程最多只能有一个。否则可能会出现读错数据的情况

2.1.4.2 消息传递

进程之间的数据交换以**格式化的消息(message)**为单位。进程通过操作系统提供的“发送消息/接收消息”两个原语进行数据交换。

消息分为消息头和消息体两个板块

  • 消息头:包括发送进程ID、接受进程ID、消息类型、消息长度等等信息(计算机网络中发送的“报文”,就是一种格式化消息)
  • 消息体:消息本体

消息传递有两种方式:

  • 直接通信方式:消息直接挂到接受进程的消息缓冲队列上
  • 间接通信方式:消息先发送到中间实体(信箱)中,也成为“信箱通信方式”,如:计算机网络中的电子邮件系统

2.1.5 线程的概念

有的进程可能需要 “同时” 做很多事情,传统的进程只能串行执行一系列程序。所以引入了 “线程” ,来增加并发度。

可以把线程理解为“轻量级进程”

线程是一个基本的CPU执行单元,也是程序执行流的最小单位。

引入线程后,进程只作为除CPU之外的系统资源的分配单元(如打印机、内存地址空间等等都是分配给进程的)

带来的变化:

  • 资源分配、调度
    • 传统进程机制中,进程是资源分配、调度的基本单位
    • 引入线程后,进程是资源分配的基本单位,线程是调度的基本单位
  • 并发性
    • 传统进程机制中,只能进程间并发
    • 引入线程后,各个线程之间也能并发,提升了并发度
  • 系统开销
    • 传统的进程间并发,需要切换进程的运行环境,系统开销很大
    • 引入线程后,并发所带来的系统开销减小。
    • 线程间并发,如果是统一进程内的线程切换,则不需要切换进程环境,系统开销小

线程的属性:

  • 线程是处理机调度的单位。

    • 同一个进程中线程切换,不引起进程切换。
    • 不同的进程中线程切换,会引起进程切换。
    • 切换同进程内的线程,系统开销很小
    • 切换进程,系统开销较大
  • 多CPU计算机中,各个线程可以占用不同CPU

  • 每个线程都拥有一个线程ID,线程控制块TCB,TCB记录了线程执行的寄存器和栈等现场状态。

    • 线程也有就绪、阻塞、运行三种基本状态。
    • 不同线程可以执行相同的程序
  • 线程几乎不拥有系统资源。但线程可以访问其隶属进程的系统资源。属于同一进程的所有线程都具有相同的地址空间

  • 同一进程的不同线程间贡献进程的资源

  • 由于共享内存地址空间,同一进程中的线程间通信甚至无需系统干预

2.1.6 多线程

graph LR
A(线程) -->B(实现方式)
B -->B1(用户级线程)
B -->B2(内核级线程)
A -->C(多线程模型)
C -->C1(一对一模型)
C -->C2(多对一模型)
C -->C3(多对多模型)

2.1.6.1用户级线程(User-Level-Thread,ULT)

  • 由应用程序在用户控件完成,用户才能感知到线程的存在,而内核意识不到线程的存在。

    应用于早期操作系统,线程的实现是使用线程库实现的。

    很多编程语言提供了强大的线程库,可以实现线程的创建、销毁、调度等功能。

  • 用户级线程有应用程序通过线程库实现,所有的线程管理工作都有应用程序负责(包括线程切换)

  • 用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预

  • 在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。“用户级线程” 就是 从用户视角看,能看到的线程

  • 优点:

    1. 用户级线程的切换在用户控件即可完成,不需要切换到核心态。
    2. 线程管理的系统开销小,效率高。
    3. 调度算法可以是进程专用的,不同的进程可以根据自人需要,对自己的线程选择不同的调度算法。
    4. 用户级线程的实现与操作系统无关,对象池管理的代码是属于用户程序的一部分。
  • 缺点:

    1. 当一个用户级线程被阻塞后,整个进程都会被阻塞并发度不高
    2. 多个线程不可在多核处理机上并行运行。不能发挥多处理机的优势,内核每次分配给一个进程的只有一个CPU,因此进程中仅有一个线程能执行。
graph TB
A1(用户级线程) ---|用户空间|S(线程库)
A2(用户级线程) ---|用户空间|S
A3(用户级线程) ---|用户空间|S
subgraph kernel_area
    P(内核级线程)
end
S ---|内核空间|P
T((用户级方式))

2.1.6.2 内核级线程(KLT)

  • 内核级线程的管理工作由操作系统内核完成

  • 线程调度、切换等工作都由内核负责。因此内核级线程的切换必须在核心态下才能完成

  • 操作系统会为每个内核级线程创建TCB(Thread Control Bolck,线程控制块)

  • 优点:

    1. 能发挥多处理机的优势,并发能力强。多线程可以在多核处理机上并行执行。
    2. 如果进程中的一个线程被阻塞,内核可以调度该进程中的其他线程占用处理机。
    3. 内核支持线程具有很小的数据结构和堆栈,线程切换比较快、开销小。
    4. 内核本身也可以采用多线程技术,提高系统的执行速度和效率。
  • 缺点:

    同一进程中的线程切换,需要从用户态转为核心态,系统开销较大。

    因为用户进程的线程在用户态执行,而线程调度和管理需要在内核实现

graph TB
A1(用户级线程) ---|用户空间|P1
A2(用户级线程) ---|用户空间|P2
A3(用户级线程) ---|用户空间|P3
subgraph kernel_area
    P1 ---|内核空间|P(内核级线程)
    P2 ---|内核空间|P
    P3 ---|内核空间|P
end
T((内核级方式))

2.1.6.3 组合方式

同一个进程中的多个线程可以同时在多处理机上并行执行,且在阻塞一个线程时不需要将整个进程阻塞,结合了ULT与KLT的优势,并克服了各自的不足

实现线程库主要方式有

  • 在用户空间中提供一个没有内核支持的库
  • 实现由操作系统直接支持的内核级的库
graph TB
A1(用户级线程) ---|用户空间|S1(线程库1)
A2(用户级线程) ---|用户空间|S1
A3(用户级线程) ---|用户空间|S1
A4(用户级线程) ---|用户空间|S2(线程库2)
subgraph kernel_area
    P1(内核级线程)
    P2(内核级线程)
    P3(内核级线程)
end
S1 ---|内核空间|P1
S1 ---|内核空间|P2
S2 ---|内核空间|P3
T((组合 方式))

2.1.6.4 多线程模型

1、一对一模型

类比内核级线程图

一个用户级线程映射到一盒内核级线程。每个用户进程有和用户线程一样多的内核级线程。

  • 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可以在多核处理机上运行

  • 缺点:一个用户进程会占用多个内核级线程,线程切换又需要操作系统来完成,需要切换到核心态。线程管理的成本高,开销大

2、多对一模型

类比用户级线程图

多个用户级线程映射到一个内核级线程,且一个进程只能被分配到一个内核级线程。

  • 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,系统管理的开销小,效率高
  • 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行
  • !重点:操作系统只 “看得见” 内核级线程,因此只有内核级线程才是处理机分配的单位

3、多对多模型

类比组合模型图

  • n个用户级线程映射到m个内核级线程**(n>=m)**。每个用户级进程对应m个内核级线程。

  • 克服了多对一模型并发度不高的缺点(一个阻塞全体阻塞)

  • 克服了一对一模型中一个用户占用太多线程,开销太大的缺点

  • 可以理解为:

    • 用户级线程是 “代码逻辑”的载体
    • 内核级线程是 “运行机会”的载体
    • 内核级线程才是处理机分配的单位
  • 一段 “代码逻辑”只有获得 “运行机会”,才能被CPU执行

    • 内核级线程中可以运行任意一个有映射关系的用户级线程代码
    • 只有两个内核级线程中正在运行的代码逻辑全都阻塞,这个进程才会阻塞。

文章作者: 拓佑豪
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 拓佑豪 !
评论
  目录