进程的概念
程序是静态的,只是一个存放在磁盘里面的可执行文件,是一系列指令的集合。 而进程是动态的,是程序的一次执行过程。 同一个程序的多次执行对应着的是多个进程 那么操作系统如何来区分同一个程序的多个进程呢?解决方案是,当一个进程被创建的时候,操作系统会为该进程分配一个唯一的、不重复的 PID 。(现在小杜知道传输层之间通信时,数据报是如何精准地仅靠 socket 就发送到接收端的进程了的。)除了用来区分进程的 PID ,操作系统还会记录进程所属的用户 UID 、记录给进程分配了哪些资源。这些信息都被保存在一个数据结构进程控制块PCB中。 因此,PCB是进程存在的唯一标志。当进程被创建时,操作系统会为其创建PCB;当进程结束时,操作系统会回收其PCB。
进程的组成
一个进程可以视为由以下三个部分组成。
| -- | -- |
|---|---|
| PCB | 给操作系统用的,包含操作系统内记录的进程信息 |
| 程序段 | 给进程自己使用,包含程序指令 |
| 数据段 | 给进程自己使用,包含运行过程中产生的各种数据 |
进程是能独立运行、独立获得资源、独立接收调度的基本单位。
进程的状态
| -- | -- |
|---|---|
| 创建态 | 当进程正在被创建时所处的状态,此时操作系统会为进程分配资源、初始化PCB |
| 就绪态 | 当进程创建完成后所处的状态,此时进程已经具备运行条件,但是由于CPU不空闲而暂时不能执行 |
| 运行态 | 当一个进程在CPU上运行时所处的状态,此时CPU会执行该进程对应的指令序列 |
| 阻塞态 | 当运行中的进程遇到等待某个资源或输入时,此时操作系统会让这个进程下CPU,并进入阻塞态;当它等待的东西到了,它就会再次进入就绪态 |
| 终止态 | 当进程运行结束时会发出exit系统调用,此时进程进入终止态,操作系统对该进程同时执行下CPU、回收空间内存、回收PCB等工作 |
运行态到阻塞态是进程自身做出的主动行为。 阻塞态只能从运行态转换得到,阻塞态的下一个状态一定是就绪态。当运行时间片用完,进程也可以从运行态转换为就绪态。 单个 CPU 的进程中,同一时刻只会有一个进程处于运行态。 在进程 PCB 中,会有一个变量 state 来表示进程的当前状态。
原语
原语是一种特殊的程序,它的执行具有原子性。即这个程序的运行必须一次性运行完毕,不可以被中断。 那么是如何实现原子性的呢?原理是使用了两个特权指令————关中断和开中断。
进程控制
进程控制就是要实现进程状态转换。 在实现进程控制的过程中,必须用原语实现,也就是必须一次性运行完毕。 当一个进程下 CPU 的时候,会把自己的运行环境信息(包括必要的寄存器信息)保留在自己的 PCB 当中。 进程控制原语概述:首先更新 PCB 中的信息,然后将 PCB 插入合适的队列,最后分配或回收资源。
进程的创建
使用的原语是创建原语,这期间要完成的事情是:
- 申请空白 PCB
- 为新进程分配所需的资源
- 初始化 PCB
- 将 PCB插入就绪队列,此时进程从创建态来到了就绪态。
进程的终止
使用的原语是终止原语,这期间要完成的事情是:
- 从 PCB 集合中找到终止进程的 PCB
- 若进程正在运行,则立刻把该进程下 CPU ,将 CPU 分配给其他进程
- 终止这个进程的所有子进程
- 将该进程拥有的所有资源归还给父进程or操作系统
- 删除 PCB
进程的阻塞
使用的原语是阻塞原语,这期间要完成的事情是:
- 从 PCB 集合中找到要阻塞的进程的 PCB
- 保护进程运行现场,将其 PCB 状态信息设置为阻塞态,暂时停止进程运行
- 将 PCB 插入相应事件的等待队列
进程的唤醒
使用的原语是唤醒原语,这期间要完成的事情是:
- 从 PCB 集合中找到要唤醒的进程的 PCB
- 将 PCB 从等待队列中移除,设置进程为就绪态
- 将 PCB 插入就绪队列,等待被调度
进程的切换
使用的原语是切换原语,这期间要完成的事情是:
- 将运行环境信息存入旧进程的 PCB
- 将旧 PCB 移入相应就绪队列
- 选择另一个进程来执行,并更新其 PCB
- 根据新进程的 PCB 内的运行环境信息恢复新进程所需的运行环境
进程通信
进程通信指的是两个进程之间产生数据交互。 为何进程的通信需要操作系统支持呢?因为各进程拥有的内存地址空间相互独立,其他进程不能读取或修改其他进程的地址空间在,这是为了保证安全。
共享存储
实现原理:进程还是有一个不能被其他进程访问的内存地址空间,但是每个进程都可以申请一个可以被其他进程访问的共享存储区,把需要通信的数据写到共享存储区内就可以被其他进程读取了。 各个进程对共享空间的访问应该是互斥的 两种实现方式:
- 基于存储区实现,数据由通信进程控制,速度很快
- 基于数据结构实现,比如共享空间里只能放一个固定长度的数组,这样的方式速度慢、限制多
消息传递
实现原理:数据以格式化的消息为单位,通过操作系统提供的原语“发送消息”“接收消息”来实现。 两种实现方式:
- 直接通信:发送消息的进程要直接指明接收消息的进程的 PID ,操作系统接收到之后把消息复制到位于内核空间里面的接收信息进程的消息队列里面,接收消息的进程也要指明自己是接收来自哪个进程的消息。
- 间接通信:发送端先在内存中申请一块空间“信箱”,打包完消息后指明的是消息要发往的信箱,而不是要发往的接收端进程的 PID 。接收端也是指明自己要接收的消息来自哪个信箱。
管道通信
“管道”是一个特殊的共享文件即 pipe 文件。其实就是在内存中开辟一个大小固定的内存缓冲区。 与共享存储的区别在于:管道类似于一个循环队列,先被发送端扔进管道的数据会先被接收端拿到,且某一时段内数据只能单向流动。 各个进程对管道的访问也是互斥的,管道塞满数据时——写进程阻塞,管道没有数据时——读进程阻塞。
| -- | -- |
|---|---|
| 半双工通信 | 管道在一个时间段内只能实现单向的传输,尽管方向可以改变 |
| 全双工通信 | 双向可以同时通信的传输 |
Linux系统处理管道通信:允许一个管道有多个写进程和多个读进程,但是操作系统会让各个读进程轮流从管道中读取数据。
线程的由来
有的进程可能需要同时做很多事情,而传统的进程只能串行地执行一系列程序,完成的功能固定且单一。因此引入线程来增加并发度。一个进程之中可能包含多个线程。 本来只是各进程可以并发,进程内部还是串行执行;现在进程内部的线程也可以并发,使得一个进程内也可以并发处理各种任务(如微信聊天、语音通话、听歌同时执行)。 引入线程后, CPU 调度和服务的对象就变成了线程,线程成了程序执行流的最小单位。
引入线程之后的不同
| -- | -- | -- |
|---|---|---|
| 方面 | 传统进程机制 | 引入线程后 |
| 资源分配调度 | 进程是资源分配、调度的基本单位 | 进程是资源分配的基本单位,线程是调度的基本单位 |
| 并发性 | 只能进程之间并发 | 各线程之间也能并发 |
| 系统开销 | 每次需要切换进程的运行环境,系统开销很大 | 若是同一进程内的线程切换,则不需要切换进程环境,系统开销减小 |
线程的属性
- 线程是系统调度的基本单位
- 多 CPU 计算机中,各线程可以占用不同的 CPU
- 每个线程都有一个线程 ID ,线程控制块 TCB
- 线程也有就绪、阻塞、运行三种基本状态
- 线程几乎不拥有系统资源
- 同一进程的不同线程之间共享进程的资源
- 由于共享内存地址空间,同一进程中的线程通信甚至无需系统干预
- 同一进程中的线程切换,不会引起进程切换;只有属于不同进程的线程切换才会引起进程切换。总的系统开销是减小了。
线程的实现方式
用户级线程
背景:早期Unix只支持进程不支持线程,因此当时的线程是由线程库实现的。 实现过程:所有的线程管理工作(包括线程切换)都由应用程序完成,不经过操作系统干预。也就是说,这里的线程是仅能从用户视角看到的线程,从操作系统内核看来是不存在线程的。
| -- | -- |
|---|---|
| 优点 | 用户级线程的切换在用户空间即可完成,不需要切换到核心态,系统开销小、效率高 |
| 缺点 | 当一个用户级线程被阻塞后,整个进程都会被阻塞,且由于分配资源的单位还是进程,因此多个线程不能在多核处理机上并行运行 |
内核支持的线程
此时从操作系统内核视角可以看到线程,线程的管理工作由操作系统内核来完成。
| -- | -- |
|---|---|
| 优点 | 当一个线程被阻塞后,其他线程还可以继续执行。多线程可以在多核处理机上并行执行 |
| 缺点 | 一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成、需要切换到核心态,因此线程管理成本高、开销大 |
多线程模型
一对一模型
一个用户级线程映射到一个内核级线程。每个用户进程有与用户级线程相同数量的内核级线程。
| -- | -- |
|---|---|
| 优点 | 当一个线程被阻塞后,其他线程还可以继续执行。多线程可以在多核处理机上并行执行 |
| 缺点 | 一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成、需要切换到核心态,因此线程管理成本高、开销大 |
多对一模型
多个用户级线程映射到一个内核级线程,且一个进程只被分配一个内核级线程。 相当于退化成用户级线程的方式。
| -- | -- |
|---|---|
| 优点 | 用户级线程的切换在用户空间即可完成,不需要切换到核心态,系统开销小、效率高 |
| 缺点 | 当一个用户级线程被阻塞后,整个进程都会被阻塞,且由于分配资源的单位还是进程,因此多个线程不能在多核处理机上并行运行 |
多对多
n个用户级线程映射到m个内核级线程(n>=m),这n个用户级线程属于同一个用户进程,而每个用户进程对应着m个内核级线程。 每个内核级线程可以运行任意一个与其有映射关系的用户级线程,而只有属于同一个进程的多个内核级线程在执行的用户级线程都阻塞的时候,这个进程才会阻塞。
| -- | -- |
|---|---|
| 总结 | 克服了多对一模型并发度不高的缺点,又克服了一对一模型一个开销太大的缺点 |
线程的状态和转换
只关注就绪、运行、阻塞三个状态。与进程的状态及其转换可谓一模一样。
调度的概念
由于资源有限,对于大量任务无法同时处理。此时就需要规则来决定处理任务的顺序。这个规则就是调度。