进程与线程
进程基本概念
定义与组成
进程实体由程序段、数据段、PCB三部分组成,一般情况下进程实体称为进程。对于创建进程和撤销进程实际上是PCB的创建与撤销,即PCB是进程存在的唯一标志。
(PCB:进程控制块,描述进程的各种信息)
对进程的定义有:
- 程序的一次执行过程
- 程序及其数据在处理机上顺序执行时所发生的活动
- 具有独立功能的程序在数据集合上运行的过程,是系统资源分配和调度的一个独立单位。
总结起来差不多就是:
进程是进程实体的运行的过程,是系统资源分配和调度的一个独立单位。也就是说进程在概念上是动态的,但进程实体在概念上是一个静态的集合。但一般认为这两个是一样的就行了
组织方式
进程的组织方式分为链接方式和索引方式
链接方式
三个指针,执行指针指向当前执行的进程的PCB,就绪队列指针指当前处于就绪状态的PCB,通常优先级高的PCB会排在前面,阻塞队列指针指向当前处于阻塞态的进程。就绪和阻塞可以储存多个PCB,以类似链表的方式组成。
索引方式
跟链接类似,不过就PCB的储存方式变成了索引表。
特征
- 动态性
- 并发性
- 独立性
- 异步性
- 结构性:PCB
进程的状态与转换
三种基本状态
- 运行态:每个时刻最多只有一个
- 就绪态:没CPU暂时不能运行
- 阻塞态(等待态):因某些原因暂时不能运行
后面两个不是基本状态 - 创建态:进程正在创建
- 终止态:进程正在撤销
状态的转换
开始为创建态,创建完成后进入就绪态,进程被调度后进入运行态,当时间片到了或者处理机被占用进入就绪态,进程用系统调用的方式(也就是说只能从运行态进入阻塞态,就绪态不能进入阻塞态)申请某种系统资源,或者请求等待某个事件发生的时候进入阻塞态。阻塞态只能进入就绪态不能直接进入运行态。
进程控制
定义
主要功能是对系统中所有的进程实施有效的管理,具有创建进程、撤销进程、实现进程之间状态转换等功能。简单来说就是实现进程状态转换。
实现
在进程状态转换的过程中,需要对PCB里面的内容进行修改,包括状态标识等。但是如果在修改过程中发生了中断使得PCB里面的数据没有被完全修改,就会导致系统错乱。所以需要用原语实现进程的控制,原语的概念是指一段指令在执行过程中不允许中断,必须连续完成。
原语的实现:开中断和关中断,在这期间不允许中断。
相关原语:
- 创建原语
- 撤销原语
- 阻塞原语
- 唤醒原语
- 切换原语
进程通信
进程之间的信息交换
共享存储
多个进程分配一个共享空间,大家都可以访问。但是必须互斥,每次只能有一个进程在访问
储存有两种方式
- 基于数据结构的共享:存放大小固定,速度慢限制多
- 基于存储区的共享:内存中画出共享储存区,速度快
管道通信
内存中开辟固定内存的缓冲区,采用半双工通信。访问需要互斥。管道没有写满不允许读取,没有全部情清空不允许写。一旦被读取,数据就消失。
消息传递
以格式化的消息为单位,通过操作系统提供的发送消息/接收消息两个原语进行数据交换
线程概念和多线程模型
线程
线程是一个基本的CPU执行单元,也是程序执行流的最小单位
提升了并发性,并发带来的系统开销降低
线程的属性
- 线程是处理机调度的单位
- 多CPU计算机中,各个线程可以占用不同的CPU
- 每个线程有线程ID,线程控制块(TCB)
- 线程也有就绪、阻塞、运行三种基本状态
- 线程几乎不拥有系统资源
- 同一进程的不同线程间共享进程的资源
- 由于共享内存地址,同一进程中的线程间通信无需系统干预
- 同一进程的线程切换不会引起进程切换
- 切换进程内的线程开销很小
线程实现方式
用户级线程
应用程序通过线程库实现,所有的线程管理工作都由应用程序负责。
用户级线程中线程切换可以在用户态下完成,无需系统干预
在用户层面具有多线程,但是在操作系统内核方面并不具有双线程。
内核级线程
线程管理由操作系统内核完成。线程切换只有在内核态才能完成。
只有内核级线程才是处理机分配的单位。
多线程模型
- 多对一:多个用户级线程对应一个内核级线程。开销小效率高,并发度不高。
- 一对一:并发能力强,一个用户进程会占用多个内核级线程,而且需要切换到核心态,因此管理成本高,开销大。
- 多对多:克服了上面两个的缺点。
处理机调度
调度的概念:确定某种规则来决定处理任务的顺序。
处理机调度就是按照某一算法选择一个进程并将处理机分配给它运行。
高级调度
按照一定的原则从外存上处于后备队列的作业中挑选一个或多个作业,给他们分配内存等资源,建立相应的进程PCB,以使他们获得竞争处理机的权利。每个作业只调入和调出一次,调入创建PCB调出撤销PCB。因为撤销只在作业结束后才运行,所以高级调度实际上是对调入的管理。
中级调度
将暂时不能运行的进程调至内存外,等具备一定的条件后再调入。
提高内存利用率和系统吞吐量。
被调到外存等待的进程状态为挂起状态。
中级调度的任务是决定将哪个处于挂起状态的进程重新调入内存。
低级调度
进程调度,按照某种方法和测量从就绪队列中选取一个进程,将处理机分配给它。是操作系统最基本的一种调度。频率很高,一般几十毫秒一次。
进程调度
进程调度的时机
- 当前进程主动放弃处理机:
进程正常终止
运行过程中发生异常而终止
进程主动请求阻塞
- 进程被动放弃处理机
时间片用完
更紧急的任务要处理
更高优先级的进程进入就绪队列
不能进行进程调度的情况
- 中断处理过程
- 操作系统内核程序临界区
- 原子操作过程
进程调度方式
- 非剥夺调度方式:只允许进程自动放弃
- 剥夺调度方式:更紧急的任务可以暂停正在执行的进程。
进程的切换与过程
狭义的进程调度指从就绪队列中选取一个要运行的进程
广义的线程调度包含了选择进程和进程切换两个步骤
进程切换指的是一个进程让出处理机,由另一个进程占用处理机的过程
进程切换过程完成以下两点:
- 对原来运行进程的各种数据的保存
- 对新的进程各种数据的恢复
切换进程的有代价的,越频繁开销越大
调度算法评价指标
- CPU利用率
CPU忙碌时间/总时间 - 系统吞吐量
单位时间内完成任务的数量 - 周转时间
从作业被提交给系统到作业完成为止的时间
带权周转时间
(作业完成时间-作业提交时间)/作业实际运行的时间 - 等待时间
进程/作业处于等待状态的时间之和 - 响应时间
从用户提交请求到首次产生响应所用的时间
调度算法
先到先服务FCFS
先到先得,非抢占式
优点:公平,实现简单
缺点:对长作业有利,对短作业不利
不会导致饥饿
短作业优先SJF
最短的作业/进程优先得到服务,时间一样的话先到先得,非抢占式
最短剩余时间优先:剩余时间少的优先得到服务
优点:平均等待时间和平均周转时间少
缺点:对短作业有利,对长作业不利,可能产生饥饿
高响应度优先HRRN
选择响应比最高的作业进行服务,非抢占式
响应比=(等待时间+要求服务时间)/要求服务时间
响应比在当前运行进程主动放弃CPU时计算
综合考虑了等待时间,等待时间相同时短作业优先,处理时间相同时等待时间长的优先,避免饥饿
时间片轮转调度算法RR
按照进程到达就绪队列的顺序轮流地让各个进程执行一个时间片,抢占式
如果时间片太大使得每个进程可以在一个时间片内完成,会导致RR退化为FCFS,如果时间片太小,频繁切换会花费大量的时间。所以时间片大小要适中
优点:公平、响应快
缺点:高频切换进程会有一定的开销,不区分任务紧急程度
不会导致饥饿
优先级调度算法
每个进程或作业有优先级,选择优先级最高的进程或作业,抢占式和非抢占式都有
通常情况下,系统进程高于用户进程,前台进程高于后台进程,
优点:可以灵活处理偏好程度
缺点:有可能造成饥饿
多级反馈队列调度算法
设置多级就绪队列,各队列优先级从高到低,时间片从小到大。新进程到达时先进入第一级队列,按照FCFS分配时间片,若用完时间片后还未结束,进入下一级队列队尾,已经是最低级的话就放队尾。只有第K级队列为空时才会为K+1对头的进程分配时间片。抢占式
优点:快速响应,短进程完成时间短,可以调整对进程的偏好
缺点:可能导致饥饿
进程同步和进程互斥
进程同步
读进程和写进程并发地运行,由于并发必然导致异步,在读写的过程中必须按照写数据->读数据的顺序进行,进程同步用来解决这种问题
同步:为完成某种任务而建立两个或多个进程,因为这些进程需要在某些位置上协调它们工作顺序而产生的制约关系。
进程互斥
一个时间段内只允许一个进程使用的资源称为临界资源,对于临界资源的访问必须互斥。(这就是互斥的概念)
进程互斥需要遵循的原则:
- 空闲让进:没人访问可以进入
- 忙则等待:等待其他进程出来
- 有权等待:保证能在有限时间内进入临界区
- 让权等待:让出CPU
进程互斥的软件实现
- 单标志法
两个进程在访问完临界区后会把使用临界区的权限交给另外一个进程。每个进程访问临界区的权限只能被另外一个进程赋予
实现大概是每个进程设置不同的sign,如果sign是自己的话就能进入,当进程从临界区出来后会把sign改成下一个可以访问的进程。
但缺点是如果一个有权限的进程不访问临界区,那么哪怕是临界区处于没人访问状态,其他的进程也不能访问,违反了空闲让进的原则 - 双标志先检查法
弄一个布尔类型数组记录各个进程是否需要访问临界区,进入前先检测其他所有进程是否都不需要进入。
问题是检查和上锁不是在一个指令内完成的,有可能出现多个进程同时检查然后进入临界区的情况,违反忙则等待原则。 - 双标志后检查法
跟前一个一样,先上锁后检查
顺序错乱会导致多个进程都没办法进入临界区,造成饥饿 - Peterson算法
在双标志检查法的基础上加上一个公共变量sign,sign表示允许谁进入临界区。过程是先把自己的flag设置成true表明需要进入临界区,然后设置sign为对方进程的ID表明可以让对方先进入,判断如果sign是对方的ID并且对方也需要访问临界区就等待。
能解决双标志乱序出错的情况,但是仍未遵循让权等待原则。
进程互斥的硬件实现
- 中断屏蔽法
使用开/关中断,使访问不会被中断,一次完成
简单高效,但是不适合多核,只适用于内核进程,不适合用户进程 - TestAndSet
也叫TS指令或者TSL指令
在硬件层面执行上锁并且检查,这个过程无法中断
实现简单,适合多核,但不满足让权等待 - Swap指令
也叫exchange指令/XCHG指令
跟2一样,但是实现函数的方法不一样而已
信号量
信号量本质上是一个变量,可以是整数也可以是其他的记录型变量,用来表示系统中某种资源的数量。信号量机制简单理解就是用信号量记录资源数量,使用两个原语wait和signal对信号量进行操作。其中wait记成P,signal记成V
整形信号量
整数类型的信号量,表示资源的数量。操作只有三种:初始化,P操作,V操作
记录型信号量
semaphore
public void acquire(){
this.count--;
if(this.count<0){
// 将当前线程插入等待队列
// 阻塞当前线程
}
}
public void release(){
this.count++;
if(this.count >= 0) {
// 移除等待队列中的某个线程 T
// 唤醒线程 T
}
}
信号量实现进程互斥
- 划定临界区,设置互斥信号量
- P操作
- V操作
PV必须成对出现
信号量实现进程同步
设置信号量资源为0,先执行V操作再执行P操作
信号量的一些经典问题
生产者-消费者问题
多生产者-多消费者问题
吸烟者问题
读写者问题
哲学家进餐问题
管程
信号量机制程序编写困难,容易出错
管程用来实现进程互斥和同步
定义:一种特殊的软件模块
组成:
- 局部于管程的共享数据结构
- 对于该数据结构进行操作的一组过程
- 对共享数据设置初始值的语句
- 名字
PS:这跟类没什么区别。。。
基本特征:
- 局部于管程的数据只能被局部于管程的过程所访问(所有数据都是private)
- 只有set和get函数能对数据进行操作
- 每次只允许一个进程在管程内执行某个内部过程
死锁
定义:在并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各个进程都在阻塞,都无法向前推进的现象。发生死锁后如果没有外力干涉,所有的进程都无法向前推进
死锁产生的必要条件
- 互斥:资源争抢
- 不剥夺:资源不能被强行剥夺
- 请求和保持:保持了资源并且请求新资源
- 循环等待:等待资源
产生死锁的一些情况
- 对系统资源的竞争
- 进程推进顺序非法
- 信号量使用不当
死锁处理策略
- 预防
- 破坏互斥条件:把只能互斥使用的资源改造成允许共享使用,如spooling技术
- 破坏不剥夺条件:
方案1:当进程申请某些资源但是仍不满足时,自己释放当前资源
方案2:由操作系统负责协助,将优先级低的资源剥夺分配给优先级高的。
实现复杂,只适用于易保存和恢复状态的资源,反复申请会加大开销- 破坏请求和保持条件:
静态分配,进程在运行前申请完所有的资源,未满足前不投入运行。
资源利用率低、有可能导致饥饿- 破坏循环等待条件:
对资源进行编号,只有申请完编号小的才能申请编号大的,所有不会出现大资源等待小资源情况。
不方便新增设备,资源浪费,编程麻烦
- 避免
安全序列:如果系统按照某种序列分配资源使得每个进程都能顺利完成,该序列就叫安全序列
只要能找到一个安全序列那么系统就处于安全状态。
处于安全状态一定不会发生死锁,处于不安全状态不一定发生死锁,但是死锁一定处于不安全状态
**思路:**检查当前进程申请资源后会不会使系统处于不安全状态,如果是,那么挂起当前请求
- 检测和解除
检测:创建一个数据结构实现资源和进程之间的有向图,然后对资源进行分配逐渐消去有向图中的边,如果所有边都能消掉那么就没有死锁,反之死锁。
解除:- 资源剥夺法:挂起进程,抢占资源分配给其他进程
- 撤销进程法:强制撤销部分或者全部死锁进程,代价大
- 进程回退法:让一个或多个进程回退到足以避免死锁的地步
选择被抛弃的进程:
- 进程优先级
- 已执行时间
- 还需要的时间
- 已使用的资源
- 进程是交互式还是批量处理的