1、进程(process)描述——静态表示部分
1.1 进程的定义
一个具有一定独立功能的程序在一个数据集合上的一个动态执行过程。
指令/程序是静态的,通过系统执行起来的指令,对调用数据进行处理,完成一定的功能,这是一个动态执行的过程,也就是进程。
1.2 进程的组成
一个进程应该包括:
- 程序的代码
- 程序处理的数据
- 程序计数器中的值,只是下一条运行的指令(记录)
- 一组通用的寄存器的当前值,堆、栈(执行中使用)
- 一组系统资源(如打开的文件)(执行需要的数据)
总之,进程包含了正在运行的一个程序的所有状态信息
1.3 进程与程序的关系
- 程序是产生进程的基础
- 程序的每次运行构成不同的进程
- 进程是程序功能的体现
- 通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。
1.4 进程与程序的区别
- 进程是动态的,程序是静态的:程序是有序代码的集合;进程是程序的执行,进程有核心态/用户态
- 进程是暂时的,程序是永久的:进程是一个状态变化的过程,程序可永久保存
- 进程与程序的组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)
CPU会在处理进程的此过程中根据不同进程的重要程度,进行进程切换。
1.5 进程的特点
- 动态性:可动态地创建、结束进程
- 并发性:进程可以被独立调度并占用处理机运行;并发并行
- 独立性:不同进程的工作不相互影响
- 制约性:因访问共享数据/资源或进程间同步而产生制约
程序 = 算法 + 数据结构
描述进程的数据结构:进程控制块(process control block,PCB)
操作系统为每一个进程都维护了一个PCB,用来保护与该进程有关的各种状态信息。
1.6 进程控制结构
进程控制块:操作系统管理控制进程运行所用的信息集合。操作系统用PCB来描述进程的基本情况以及运行变换的过程,PCB是进程存在的唯一标志。
进程的创建:为该进程生成一个PCB;
进程的终止:回收该进程的PCB
进程的组织管理:通过对PCB的组织管理实现
PCB具体包含什么信息?如何进行组织?进程的状态转换?
进程包含三大类信息:
- 进程标识信息
- 如进程的标识,进程产生者标识(父进程标识);用户标识
- 处理机(CPU)状态信息保护区:保存进程的运行现场信息(进程状态信息)
- 用户可见寄存器。用户程序可以使用的数据,地址等寄存器
- 控制和状态寄存器。如程序计数器(PC),程序状态字(PSW)
- 栈指针。过程调用/系统调用/中断处理和返回时需要用到它
- 进程控制信息
- 调度和状态信息:用于操作系统调度进程并占用处理机使用
- 进程间通信信息:为支持进程间的与通信相关的各种知识、信号、信件等,这些信息存在接收方的进程控制块中
- 存储管理信息:包含有指向本进程映像存储空间的数据结构
- 进程所用资源:说明由进程打开、使用的系统资源,如打开的文件等
- 有关数据结构连接信息:进程可以连接到一个进程队列中,或连接到相关的其他进程的PCB
PCB的组织方式
链表:同一状态的进程其PCB成一链表,多个状态对应多个不同的链表。(各状态的进程形成不同的链表:就绪链表,阻塞链表)使用链表可以更方便地进行动态的插入和删除。
索引表:同一状态的进程归入一个index表(由index指向PCB),多个状态对应多个不同的index表。(各状态的进行形成不同的索引表:就绪索引表,阻塞索引表)如果没有频繁的插入删除动作,索引表是一种快捷的组织方式。
总结:根据操作系统的特点(通用还是特殊?),会有不同的PCB的进程模块组织方式。
2、进程状态(state)——动态表示部分
2.1 进程的生命期管理
- 创建:引起进程创建有3个主要事件
- 系统初始化时:系统初始化时会创建第一个进程(init/root)
- 用户请求创建一个新进程:,init进程根据用户请求创建新进程(用户向操作系统提出请求,由操作系统完成)
- 正在运行的进程执行了创建进程的系统调用
- 运行:创建进程之后不一定能执行,需要操作系统选择一个可以执行的进程(就绪进程)去执行。多个就绪进程需要操作系统根据调度算法等进行选择
- 等待(阻塞):处于waiting状态的进程不占用CPU;进程只能自己阻塞自己,因为只有进程自身才知道何时需要等待某种事件发生。包括下列三种情况
- 请求并等待系统服务,无法马上完成
- 启动某种操作,无法马上完成
- 需要的数据没有到达
- 唤醒:进程只能被别的进程或操作系统唤醒。包括三方面原因
- 被阻塞进程需要的资源可被满足
- 被阻塞进程等待的事件到达
- 将该进程的PCB插入到就绪队列
- 结束:在以下四种情形中,进程结束
- 正常退出(自愿的)
- 错误退出(自愿的)
- 致命错误(强制性的)
- 被其他进程所杀(强制性的)
2.2 进程状态变化模型
进程的三种基本状态:进程在生命结束前处于且仅处于三种基本状态之一,不同系统设置的进程状态数目不同
- 运行状态(running):当一个进程正在处理机上运行时
- 就绪状态(ready):一个进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行
- 等待状态(又称阻塞状态blocked):一个进程正在等待某个事件而暂停运行时。如等待某资源,等待输入/输出完成
进程其他的基本状态:
- 创建状态(new):一个进程正在被创建,还没被转到就绪状态之前的状态
- 结束状态(exit):一个进程正在从系统中消失时的状态,这是因为进程结束或由于其他原因导致的
可能的状态变化如下:
- NULL–>New:一个新进程被产生出来执行一个程序
- New–>Ready:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态。不会持续很久
- Running–>Ready:处于运行状态的进程在其运行过程中,由于分配给它的处理机时间片用完而让出处理机(实现多道程序处理)
- Running–>Blocked:当进程请求某样东西且必须等待时
- Blocked–>Ready:当进程要等待某事件到来时,它从阻塞状态变到运行状态(操作系统完成这个状态转变)
2.3 进程挂起模型
进程在挂起状态时,意味着进程没有占用内存空间。处于挂起状态的进程映像在磁盘上。
目的:合理且充分地利用系统资源
挂起的状态:
- 阻塞挂起状态(blocked-suspend):进程处于阻塞状态被挂起。进程在外存并等待某事件的出现
- 就绪挂起状态(ready-suspend):进程处于就绪状态被挂起。进程在外存,但只要进入内存,即可运行
与挂起有关的状态转换
挂起:即将一个进程从内存转到外存;可能有以下几种情况:
- 阻塞到阻塞挂起:没有进程处于就绪状态或就绪状态进程要求更多内存资源时,会进行这种转换,以提交新进程或运行进程就绪进程
- 当有高优先级阻塞(系统认为很快就绪的)进程和低有限就绪进程时,系统会选择挂起低优先级就绪进程
- 运行到就绪挂起:对抢先式分时系统,当有高优先级阻塞挂起进程因事件出现而进入就绪挂起时,系统可能会把运行进程转到就绪挂起状态
在外存时的状态转换
- 阻塞挂起到就绪挂起:当有阻塞挂起进程因相关事件出现时,系统会把阻塞挂起进程转换成就绪挂起进程(这个过程只是一个状态转变,进程存储位置并没有转变,仍然在外存中)
解挂/激活(activate):把一个进程从外存转到内存;可能有以下几种情况
- 就绪挂起到就绪:没有就绪进程或挂起就绪进程优先级高于就绪进程时,会进行这种转换
- 阻塞挂起到阻塞:当一个进程释放足够内存时,系统会把一个高优先级阻塞挂起(系统认为很快会出现所等待事件)进程转换为阻塞进程
思考:OS怎么通过PCB和定义的进程状态来管理PCB,帮助完成进程的调度过程?
- 从进程的观点看OS:用户进程、磁盘管理、中断进程……
- 以进程为基本结构的OS:
- 最底层为CPU调度程序(包括中断处理等)(主要完成选择)
- 上一层为一组各式各样的进程
状态队列
- 由操作系统来维护一组队列,用来表示系统当前所有进程的当前状态
- 不同的状态分别用不同的队列来表示(就绪队列、各种类型的阻塞队列)
- 每一个进程的PCB都根据它的状态加入到相应的队列中,当一个进程的状态发生变化时,它的PCB从一个状态队列中脱离出来,加入到另外一个队列中
3、线程(thread)管理
(60年代)独立运行的基本单位:进程
(80年代)更小的能独立运行的基本单位:线程
3.1 为什么使用线程
单进程中各函数之间不是并发执行的,影响资源的使用效率
–>多进程实现单个程序的多函数同时调用
–>问题:进程之间如何通信、共享数据?
–>需要提出一种新的实体:1)实体之间可以并发地执行;2)实体之间共享相同的地址空间
–>线程
3.2 什么是线程
thread:进程当中的一条执行流程
- 从资源组合角度看:进程把一组相关的资源组合起来,构成了一个资源平台(环境),包括地址空间(代码段、数据段)、打开的文件、访问的网络等各种资源
- 从运行的角度看:代码在这个资源平台上的一条执行流程(线程)
一个进程可以拥有多个线程,多线程共享进程提供的资源平台,线程可以直接访问进程的代码、数据、内存等。线程有自己的控制模块TCB,包含寄存器信息等,这部分是独立的。
线程 = 进程 - 共享资源
线程的优点:
- 一个进程中可以同时存在多个线程
- 各个线程之间可以并发地执行
- 各个线程之间可以共享地址空间和文件等资源
线程的缺点:
需要根据程序的特点选用不同的方式设定程序。例如谷歌浏览器使用进程方式,当一个网页崩溃的时候,不会影响其他网页。
不同的操作系统对线程的支持也不一定相同。
- MS-DOS:单进程单线程
- Unix:多进程单线程
- Windows NT/Linux:多进程多线程
线程所需的资源
线程与进程的比较
- 进程是资源分配单位,线程是CPU调度单位
- 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈
- 线程同样具有就绪、阻塞和执行三种基本状态,同样具有状态之间的转换关系
- 线程能减少并发执行的时间和空间开销
- 线程的创建时间比进程短:在进程的基础上创建
- 线程的终止时间比进程短
- 同一进程内的线程切换时间比进程短:不需要进行页表切换
- 由于同一进程的各线程之间共享内存和文件资源,可直接进行不通过内核的通信
3.3 线程的实现
主要有三种线程的实现方式:
- 用户线程:在用户空间实现。操作系统看不到的线程,由用户程序管理,构建用户线程库
POSIX Pthreas,Mach C-threads,Solaris threads
- 内核线程:在内核实现。操作系统管理起来的线程
Windows,Solaris,Linux
- 轻量级线程:在内核实现,支持用户线程
Solaris,Linux
用户线程与内核线程的对应关系
用户线程:操作系统看不到PCB,只能看到整个进程,但是整个线程信息操作系统看不到,而是由线程库实现调度和管理。即用户线程是在用户空间实现的线程机制,它不依赖于操作系统的内核,由一组用户级线程库函数来完成线程的管理,包括线程的创建、终止、同步和调度等。
- 由于用户线程的维护由相应进程完成(通过线程库函数),不需要操作系统内核了解用户线程的存在,可用于不支持线程技术的多进程操作系统
- 每一个进程都需要它自己私有的线程控制块(TCB)列表,用来跟踪记录它的各个线程的状态信息(PC、栈指针、寄存器),TCB由线程库函数维护
- 用户线程的切换也是由线程库函数来完成,无需用户态/核心态切换,所有速度特别快
- 允许每个进程拥有自定义的线程调度算法
用户线程缺点
- 阻塞性的系统调用会导致整个进程等待(操作系统只能看到进程)
- 进程中的线程开始执行后,只能由该线程主动交出CPU使用权,否则它所在的进程当中的其他线程将无法运行
- 由于时间片分配给进程,故与其他进程相比,在多线程执行时,每个线程得到的时间片较少,执行会较慢
内核线程:PCB、TCB放在内核中。是指在操作系统内核当中实现的一种线程机制,由操作系统的内核来完成线程的创建、终止和管理
- 在支持内核线程的操作系统中,由内核来维护进程和线程的上下文信息(PCB和TCB)
- 线程的创建、终止和切换都是通过系统调用/内核函数的方式进行,由内核完成,因此系统开销比较大
- 在一个进程当中,如果某个内核线程发起系统调用而被阻塞,并不会影响其他内核线程的运行
- 时间片分配给线程,多线程的进程获得更多的CPU时间
- Windows NT和Windows 2000/XP支持内核线程
轻量级进程(LightWeight Process):内核支持的用户线程。一个进程可有一个或多个轻量级进程,每一个量级进程由一个单独的内核线程来支持。
4、进程控制——进程的上下文切换
停止当前运行进程(从运行状态改变成其他状态)并且调度其他进程(转变成运行状态)
- 必须在切换之前存储许多部分的进程上下文
- 必须能够在之后恢复他们,所以进程不能显示它曾经被暂停过
- 必须快速(上下文转换时非常频繁)
需要存储什么上下文
- 寄存器(PC,SP, …),CPU状态……
- 一些时候可能会费时,所以我们应该尽可能避免
操作系统的作用:
- 操作系统为活跃进程准备了进程控制块PCB
- 操作系统将进程控制块PCB放置在一个合适的队列里
- 就绪队列
- 等待I/O队列(每个设备的队列)
- 僵尸队列
5、进程控制——加载和执行进程
exec():令当前的进程执行新的程序。调用允许一个进程“加载”一个不同的程序并且在main开始执行
- 允许一个进程制定参数的数量(argc)和它字符串参数数组(argv)
- 代码、stack(栈)&heap(堆)重写
fork():
- 对子进程分配内存
- 复制父进程的内存和CPU寄存器到子进程中
- 开销昂贵
- 大多数情况下在调用fork()之后调用exec(),即创建一个进程的目的一般是为了执行不同的程序
由于调用exec()之后会发生重写,那么fork()操作中内存复制是没有作用的,子进程可能关闭打开的文件和连接。那么如何将它们结合在一个调用中呢?
- vfork():轻量级fork调用,一个创建进程的系统调用,不需要创建一个同样的内存映像。子进程应该几乎立即调用exec()
- COW技术(copy on write):按需调用思想。并没有把整个地址空间真实地复制,而是只复制了父进程与管理相关的原数据(一些页表),它指向同一块地址空间。当父进程/子进程对某一单元进行写操作时,会触发异常使得触发异常的页复制成两份,使得父进程和子进程有不同的地址。即根据是否完成写操作进行复制,效率高。
6、进程控制——等待和终止进程
在执行exec()的时候进程可能处于不同状态
wait()系统调用是被父进程用来等待子进程的结束
- 一个子进程向父进程返回一个值,所以父进程必须接受这个值并处理
- wait()系统调用担任这个要求
- 它使父进程去睡眠来等待子进程的结果
- 当一个子进程调用exit()的时候,操作系统解锁父进程,并且将通过exit()传递得到返回值作为wait调用的一个结果(连同子进程的pid一起)如果这里没有子进程存活,则wait()立即返回
- 如果这里有为父进程的僵尸等待,wait()立即返回其中一个值(并且解除僵尸状态)
僵尸状态(zombie/defunct):进程已经执行完毕,处于等待回收状态,即处于exit()和wait()之间
进程结束执行之后,调用exit()系统调用:
- 将这个程序的“结果”作为一个参数
- 关闭所有打开的文件,连接等待
- 释放内存
- 释放大部分支持进程的操作系统结构
- 检查父进程是否存活
- 如果是,它保留结果的值知道父进程需要它;这种情况下,进程没有真正死亡,而是进入僵尸状态
- 如果没有,它释放所有数据结构,这个程序死亡
- 清理所有等待的僵尸进程
- 如果父进程先于子进程死亡,那么由init进程定时扫描清楚僵尸进程
进程的终止是最终的垃圾收集(资源回收)
7、进程互斥、同步与通信
来自链接:https://blog.csdn.net/weixin_41413441/article/details/80548683
在多道程序设计系统中,同一时刻可能有许多进程,这些进程之间存在两种基本关系:竞争关系和协作关系。进程的互斥、同步、通信都是基于这两种基本关系而存在的。
- 为了解决进程间竞争关系(间接制约关系)而引入进程互斥;
- 为了解决进程间松散的协作关系( 直接制约关系)而引入进程同步;
- 为了解决进程间紧密的协作关系而引入进程通信。
7.1 竞争关系
系统中的多个进程之间彼此无关,它们并不知道其他进程的存在,并且也不受其他进程执行的影响。例如,批处理系统中建立的多个用户进程, 分时系统中建立的多个终端进程。由于这些进程共用了一套计算机系统资源,因而, 必然要出现多个进程竞争资源的问题。当多个进程竞争共享硬设备、存储器、处理器 和文件等资源时,操作系统必须协调好进程对资源的争用。
资源竞争出现了两个控制问题:
- 一个是死锁 (deadlock )问题,一组进程如果都获得了部分资源,还想要得到其他进程所占有的资源,最终所有的进程将陷入死锁。
- 另一个是饥饿(starvation )问题,这是指这样一种情况:一个进程由于其他进程总是优先于它而被无限期拖延。
操作系统需要保证诸进程能互斥地访问临界资源,既要解决饥饿问题,又要解决死锁问题。
进程的互斥(mutual exclusion )是解决进程间竞争关系( 间接制约关系) 的手段。 进程互斥指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。
7.2 协作关系
某些进程为完成同一任务需要分工协作,由于合作的每一个进程都是独立地以不可预知的速度推进,这就需要相互协作的进程在某些协调点上协 调各自的工作。当合作进程中的一个到达协调点后,在尚未得到其伙伴进程发来的消息或信号之前应阻塞自己,直到其他合作进程发来协调信号或消息后方被唤醒并继续执行。这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。
进程间的协作可以是双方不知道对方名字的间接协作,例如,通过共享访问一个缓冲区进行松散式协作;也可以是双方知道对方名字,直接通过通信机制进行紧密协作。允许进程协同工作有利于共享信息、有利于加快计算速度、有利于实现模块化程序设计。
进程的同步(Synchronization)是解决进程间协作关系( 直接制约关系) 的手段。
进程同步指两个以上进程基于某个条件来协调它们的活动。一个进程的执行依赖于另一个协作进程的消息或信号,当一个进程没有得到来自于另一个进程的消息或信号时则需等待,直到消息或信号到达才被唤醒。
不难看出,进程互斥关系是一种特殊的进程同步关系,即逐次使用互斥共享资源,也是对进程使用资源次序上的一种协调。
7.3 进程通信(inter-process communication)
下面是根据《操作系统教程》3.5 中的介绍,整理的进程通信的概念。
并发进程之间的交互必须满足两个基本要求:同步和通信。
进程竞争资源时要实施互斥,互斥是一种特殊的同步,实质上需要解决好进程同步问题,进程同步是一种进程通信,通过修改信号量,进程之间可建立起联系,相互协调运行和协同工作。但是信号量与PV操作只能传递信号,没有传递数据的能力。有些情况下进程之间交换的信息量虽很少,例如,仅仅交换某个状态信息,但很多情况下进程之间需要交换大批数据,例如,传送一批信息或整个文件,这可以通过一种新的通信机制来完成,进程之间互相交换信息的工作称之为进程通信IPC (InterProcess Communication)(主要是指大量数据的交换)。
进程间通信的方式很多,包括:
1 mmap(文件映射)
2 信号
3 管道
4 共享内存
5 消息队列(重要)
6 信号量集(与signal无关)
7 网络(套接字)
进程同步的方法
前面提到,进程互斥关系是一种特殊的进程同步关系,下面给出常见的进程同步的方法,实际上也可用于进程的互斥(个人理解)。线程的同步方法:
1、信号量
2、互斥量
3、临界区
4、事件
同步机制:四种进程或线程同步互斥的控制方法
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
科普:
1.临界资源
临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。
2.临界区:
每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。
临界区是一种轻量级的同步机制,与互斥和事件这些内核同步对象相比,临界区是用户态下的对象,即只能在同一进程中实现线程互斥。因无需在用户态和核心态之间切换,所以工作效率比较互斥来说要高很多。虽然临界区同步速度很快,但却只能用来同步本 进程内的线程,而不可用来同步多个进程中的线程。
7.4 进程通信方式
进程间通信方式主要包括管道、FIFO、消息队列、信号量、共享内存。
1.管道,还有命名管道和非命名管道(即匿名管道)之分,非命名管道(即匿名管道)只能用于父子进程通讯,命名管道可用于非父子进程,命名管道就是FIFO,管道是先进先出的通讯方式。半双工管道仍然是最常用的IPC形式。
2.消息队列,是用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据!!!!
3.信号量,它与WINDOWS下的信号量是一样的,所以就不用多说了
4.共享内存,类似于WINDOWS下的DLL中的共享变量,但LINUX下的共享内存区不需要像DLL这样的东西,只要首先创建一个共享内存区,其它进程按照一定的步骤就能访问到这个共享内存区中的数据,当然可读可写
以上几种方式的比较:
1.管道:速度慢,容量有限,只有父子进程能通讯
2.FIFO:任何进程间都能通讯,但速度慢
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存