操作系统——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恢复新进程所需的运行环境
- 引起进程切换的时间
- 当前进程的时间片计时结束
- 有更高优先级的进程到达
- 当前进程主动阻塞
- 当前进程终止
无论哪个进程要控制原语,要做的无非三类事情:
-
更新PCB中的信息
-
将PCB插入合适的队列
-
分配/回收资源
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)
-
由应用程序在用户控件完成,用户才能感知到线程的存在,而内核意识不到线程的存在。
应用于早期操作系统,线程的实现是使用线程库实现的。
很多编程语言提供了强大的线程库,可以实现线程的创建、销毁、调度等功能。
-
用户级线程有应用程序通过线程库实现,所有的线程管理工作都有应用程序负责(包括线程切换)
-
用户级线程中,线程切换可以在用户态下即可完成,无需操作系统干预
-
在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。“用户级线程” 就是 从用户视角看,能看到的线程。
-
优点:
- 用户级线程的切换在用户控件即可完成,不需要切换到核心态。
- 线程管理的系统开销小,效率高。
- 调度算法可以是进程专用的,不同的进程可以根据自人需要,对自己的线程选择不同的调度算法。
- 用户级线程的实现与操作系统无关,对象池管理的代码是属于用户程序的一部分。
-
缺点:
- 当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。
- 多个线程不可在多核处理机上并行运行。不能发挥多处理机的优势,内核每次分配给一个进程的只有一个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,线程控制块)
-
优点:
- 能发挥多处理机的优势,并发能力强。多线程可以在多核处理机上并行执行。
- 如果进程中的一个线程被阻塞,内核可以调度该进程中的其他线程占用处理机。
- 内核支持线程具有很小的数据结构和堆栈,线程切换比较快、开销小。
- 内核本身也可以采用多线程技术,提高系统的执行速度和效率。
-
缺点:
同一进程中的线程切换,需要从用户态转为核心态,系统开销较大。
因为用户进程的线程在用户态执行,而线程调度和管理需要在内核实现
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执行
- 内核级线程中可以运行任意一个有映射关系的用户级线程代码
- 只有两个内核级线程中正在运行的代码逻辑全都阻塞,这个进程才会阻塞。