二、进程与线程
文章目录
- 二、进程与线程
- 1.程序
- 1.1顺序执行的特征
- 1.2并发执行的特征
- 2.进程Process
- 2.1定义(组织)
- 2.1.1程序段
- 2.1.2数据段
- 2.1.3进程控制块PCB
- 1)内容
- 2)作用
- 3)进程组织方式
- 2.2特征
- 2.3进程的状态与转换
- 2.3.1挂起(7状态模型)
- 2.4进程控制
- 原语
- 2.4.1创建
- 2.4.2终止
- 2.4.3阻塞、唤醒
- 2.4.4切换
- 2.5进程间通信IPC
- 2.5.1共享存储器
- 2.5.2管道通信
- 2.5.3消息传递
- 3.线程thread
- 3.1组织与控制
- 3.1.1线程控制块TCB
- 3.2实现方式
- 3.2.1用户级线程ULT
- 3.2.2内核级线程KLT
- 3.3多线程模型
程序是 静态的。一个指令序,就是个存放在磁盘里的可执行文件,就是一系列的指令集合。
进程是动态的。程序的一次执行过,同一个程序多次执行,也会有多个进程。
1.程序
1.1顺序执行的特征
- 顺序性
- 封闭性
- 可再现性
1.2并发执行的特征
- 间断性
- 失去封闭性
- 不可再现性
2.进程Process
2.1定义(组织)
为了使程序可以并发执行,并且可以对并发的程序进行描述和控制。
当进程被创建,操作系统会为该进程分配一个唯一的、不重复的“身份证号”PID(Process ID,进程ID)。
3个定义:
-
进程是程序的依次执行。
-
进程是一个程序及其数据在处理机上的顺序执行时发生的活动。
**【注意】**并发进程的运行结果具有不可再现性(每次都不一样)。
-
进程是具有独立功能的程序在一个数据集上的执行过程,它是系统进行资源分配和调度的一个独立的单位。
进程是动态的;
进程实体(进程映像)是静态的。
一个进程实体(进程映像)由PCB、程序段、数据段组成。进程实体反应了进程在某一时刻的状态。进程实体可以看作进程在某一时刻的快照。
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
PCB是给操作系统用的。程序段、数据段是给进程自己用的,与进程自身的运行逻辑有关。
2.1.1程序段
程序的代码,静态的(指令序列)。
2.1.2数据段
运行过程中产生的各种数据(如:程序中定义的变量)
2.1.3进程控制块PCB
process control block,PCB
创建进程实质上就是创建PCB,撤销进程就是撤销PCB。它是进程存在的唯一标志。
1)内容
-
进程描述
基本的进程描述信息,可以让操作系统区分各个进程。
- 进程标识符PID
- 用户标识符UID
-
进程调度(资源分配)信息
给进程分配了哪些资源,可用于实现操作系统对资源的管理。
- 在使用哪些内存区域(分配了多少内存)
- 正在使用哪些I/O设备
- 正在使用哪些文件
- 代码段、数据段、堆栈段指针
- 文件描述符
-
进程控制和管理信息
-
进程当前状态
- 就绪态
- 阻塞态
- 稳定态
-
进程优先级
-
代码运行入口地址
-
程序外存地址
-
进入内存时间
-
CPU、磁盘、网络使用情况
-
信号量使用
-
-
处理机状态
进程的运行情况,可用于实现操作系统对进程的控制、调度、进程切换。
- 通用寄存器、地址寄存器、控制寄存器PC、标志寄存器PSW
- 状态字
可以查看Linux源码中的
task struct
:Linux的进程控制块。
2)作用
使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能够独立运行的基本单位,即一个能与其他进程并发执行的进程。
3)进程组织方式
- 线性
- 链接
- 索引
为什么OS引入进程的概念?
为了使程序可以并发执行,并且可以对并发的程序进行描述和控制。
2.2特征
- 动态性:(进程基本特征)
- 进程是程序的一次执行过程,是动态地产生、变化和消亡的。
- 并发性:
- 内存中有多个进程实体,各进程可并发执行。
- 独立性:
- 进程是能独立运行、独立获得资源、独立接受调度的基本单位。
- 异步性:
- 各进程按各自独立的、不可预知的速度向前推进,会导致并发程序执行结果的不确定性。操作系统要提供"进程同步机制"来解决异步问题。
- 结构性:
- 每个进程都会配置一个PCB。结构上看,进程由程序段、数据段、PCB组成。
2.3进程的状态与转换
3种基本状态:
-
就绪态(ready)- 就绪队列。
-
执行态(running,运行态):单核的活就只能同时有一个在执行。
-
阻塞态(blocked/waiting,等待态)- 阻塞队列。
阻塞态是进程为了I/O或其他资源主动发起的。
加入创建终止:
- 创建态(new,新建态):分配资源、初始化PCB。
- 终止态(terminated,结束态):一个进程可以执行exit系统调用,请求操作系统终止该进程。此时该进程会进入“终止态”,OS会让该进程下CPU,并回收内存空间、PCB等资源。终止工作完成进程彻底消失。
2.3.1挂起(7状态模型)
挂起:将内存中的进程放入外存。
-
挂起原语suspend
-
激活原语active
加入创建终止:
2.4进程控制
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。
【简化理解】进程控制就是要实现进程状态转换。
- 如何实现进程控制?
使用“原语”实现。
原语
原语具有原子性,在执行期间不允许中断,一气呵成。采用关中断和开中断这两个特权指令实现原子性。
2.4.1创建
创建步骤(创建原语):
- 申请空白PCB;
- 申请资源。运行所需要的物理、逻辑资源;
- 初始化PCB。把资源分配给PCB;
- 如果进程就绪队列能够接纳新进程,就把新进程插入就绪队列。
引起创建的事件:
-
用户登录:分时系统中,用户登录成功,系统会建立为其建立一个新的进程。
-
作业调度:多道批处理系统中,有新的作业放入内存时,会为其建立一个新的进程。
-
提供服务:用户向操作系统提出某些请求时,会新建一个进程处理该请求。
-
应用请求:由用户进程主动请求创建一个子进程。
2.4.2终止
终止过程(撤销原语):
- 根据被终止进程的标识符PID,从PCB集合中检索出该进程的PCB,从该进程PCB中读出该进程的状态;
- 若进程是running,则立即终止进程执行。调度标志为真,指示该进程被终止后应该重新调度;
- 若有子进程,应终止所有子进程,防止它们变成不可控进程;
- 被终止的进程拥有的所有资源,全部归还给父进程或者系统;
- 将被终止的进程的PCB从队列或链表中移除。
引起终止的事件:
- 正常结束:进程自己请求终止(exit系统调用)。
- 异常结束:整数除以0、非法使用特权指令,然后被操作系统强行杀掉。
- 外界干预:Ctrl+Alt+delete,用户选择杀掉进程。
2.4.3阻塞、唤醒
阻塞、唤醒就是一正一反,必须成对使用。
阻塞原语:
- 找到要阻塞的进程对应的PCB;
- 保护进程运行现场,将PCB状态信息设置为“阻塞态",暂时停止进程运行;
- 将PCB插入相应事件的等待队列。
唤醒原语:
-
在事件等待队列中找到PCB;
-
将PCB从等待队列移除,设置进程为就绪态;
-
将PCB插入就绪队列,等待被调度。
引起的事件:
- 向系统请求共享资源失败
- 等待某种操作的完成
- 等待新数据到达
- 等待新任务到达
2.4.4切换
把运行态的进程进行切换。
切换原语:
- 将运行环境(寄存器存放的内容环境)信息存入PCB1;
- PCB1移入相应队列;
- 选择另一个进程执行,并更新其PCB2;
- 根据PCB2恢复新进程所需的运行环境。
引起的事件:
- 当前进程时间片到
- 有更高优先级的进程到达
- 当前进程主动阻塞
- 当前进程终止
2.5进程间通信IPC
进程间通信(Inter-Process Communication,IPC)是指两个进程之间产生数据交互。进程之间的信息交换(通信),通常有低级、高级之分。
一个进程不被允许直接访问其他进程的地址空间。
2.5.1共享存储器
shared-memory system
两个进程有一个共享空间。两者对其的访问是互斥的(如PV操作)。
注:通过“增加页表项/段表项”即可将同一片共享内存区映射到各个进程的地址空间中(第三章内容)。
-
基于数据结构共享
比如共享空间里只能放一个长度为10的数组。这种共享方式速度慢、限制多,是一种低级通信方式
-
低级通信
-
在这个空间只能固定存储一种数据结构,速度慢。
-
-
基于存储区共享
操作系统在内存中划出一块共享存储区,数据的形式、存放位置都由通信进程控制,而不是操作系统。这种共享方式速度很快,是一种高级通信方式。
- 高级通信
- 存储更自由,速度更快。
2.5.2管道通信
“管道”是一个特殊的共享文件,又名pipe文件。其实就是在内存中开辟一个大小固定的内存缓冲区。
- 管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
- 各进程要互斥地访问管道。
- 数据以字符流的形式写入管道,
- 当管道写满时,写进程的write()系统调用将被阻塞,等待读进程将数据取走。
- 当读进程将数据全部取走后,管道变空,此时读进程的read()系统调用将被阻塞。
- 管道中的数据一旦被读出,就彻底消失。因此,当多个进程读同一个管道时,可能会错乱。对此,通常有两种解决方案:
- 一个管道允许多个写进程,一个读进程(2014年408真题高教社官方答案);
- 允许有多个写进程,多个读进程,但系统会让各个读进程轮流从管道中读数据(Linux的方案)。
【注意】只要没满,就可以写;只要没空,就可以读。
2.5.3消息传递
进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的“发送消息send / 接收消息receive”两个原语进行数据交换。
-
直接通信方式
发送进程利用OS所提供的发送原语,直接把消息发送给目标进程。
-
间接通信方式
发送进程、接收进程都通过共享中间体(信箱)的方式进行消息的发送、接收,完成进程间通信。
3.线程thread
线程(thread)是操作系统能够进行运算调度的最小单位。用于处理相同程序的不同片段的并发执行。
它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
父进程可以打开子进程或者线程,使用线程共享的数据更多,占用空间更小。
【注意】线程没有自己独立的地址空间,它共享其所属的进程的空间。
引入线程之后变化:
- 资源分配、调度
- 传统进程机制,进程是资源分配、调度的基本单位。
- 引入线程之后,线程是资源分配、调度的基本单位,线程成为了一个基本的CPU执行单元,程序执行流的最小单位。进程成为了除CPU外系统资源的分配单元(如打印机、内存地址空间等都是分配给进程的)。
- 并发性
- 传统进程机制,只能进程间并发。
- 引入线程之后,各线程间也能并发,提升了系统并发度
- 系统开销
- 传统的进程间并发,需要切换进程的运行环境,系统开销很大。
- 引入线程之后,线程间并发,如果是同一进程内的线程切换,则不需要切换进程环境,系统开销小。
- 线程的属性:
- 线程是处理机调度的单位
- 多CPU计算机中,各个线程可占用不同的CPU
- 每个线程都有一个线程ID、线程控制块(TCB)
- 线程也有就绪、阻塞、运行三种基本状态
- 线程几乎不拥有系统资源
- 同一进程的不同线程间共享进程的资源
- 由于共享内存地址空间,同一进程中的线程间通信甚至无需系统干预
- 同一进程中的线程切换,不会引起进程切换
- 不同进程中的线程切换,会引起进程切换
- 切换同进程内的线程,系统开销很小
- 切换进程,系统开销较大
3.1组织与控制
线程的组织和进程一样。进程有进程控制块PCB,线程有线程控制块(TCB,thread control block)。
3.1.1线程控制块TCB
包含的内容:
其中程序计数器PC、其他寄存器、堆栈指针是线程切换时要保存/恢复。
- 线程标识符:TID,与PID类似
- 程序计数器PC:线程目前执行到哪里
- 其他寄存器:线程运行的中间结果
- 堆栈指针:堆栈保存函数调用信息、局部变量等
- 线程运行状态:运行/就绪/阻塞
- 优先级:线程调度、资源分配的参考
多个TCB(线程)组织成一张线程表(thread table),构成进程。
控制就是不同线程间的状态转换,也是就绪态、运行态、阻塞态之间的转换,和进程一样。
3.2实现方式
3.2.1用户级线程ULT
用户级线程(User-Level thread,ULT)
历史背景:早期的操作系统(如:早期Unix)只支持进程,不支持线程。当时的“线程”是由线程库实现的。
用户可以看到的线程。
- 线程的管理工作由谁来完成?
应用程序通过线程库完成,不是操作系统。
- 线程切换是否需要CPU变态(用户态→内核态)?
不需要。因为应用程序是在用户态的,没有涉及内核态的操作系统。
- 操作系统是否能意识到用户级线程的存在?
不能。
- 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高。
- 缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行。
3.2.2内核级线程KLT
内核级线程(Kernel-Level thread,KLT)也叫内核级支持线程(kernel supported thread,KST)
如现在的Windows、Linux。
在核心态完成,内核级线程才是处理机分配的单位(多线程情况下也是)。
- 优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。
- 缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大,效率低。
在支持内核级线程的系统中,根据用户级线程和内核级线程的映射关系(将ULT和KLT结合),可以划分为几种多线程模型。
3.3多线程模型
- 多对一
多个ULT映射到1个KLT中,但看一个KLT就是普通的ULT。
-
系统开销小,效率高。ULT的切换只需要在用户空间就可以实现,不需要切换到核心态。
-
并发度不高。一个ULT阻塞,整个KLT也会被阻塞。
- 一对一
变成了纯粹的内核级线程
- 线程管理成本高,开销大。一个用户进程会占用一个KLT,每次都要切换到操作系统内核完成。
- 并发性高。
- 多对多
克服了并发度不高,解决了开销太大。
有3个用户级线程,映射到了2个内核级线程(KLT),那么就是2个线程单位,4核处理机最多分配它2核。