操作系统之进程

操作系统也是软件,区别于应用软件的最大特点具有进程管理、内存管理等功能。

一 进程

1.1 什么是进程(process)

进程指的就是正在运行中的程序。进程也是有生命周期,当程序运行结束,则进程结束。如果程序没有运行呢就是代码。所以我们判断是不是进程的最主要区别就是看程序是否正在运行。

1.2 进程分类

1.2.1 按照运行在不同的态分为用户进程和系统进程

第一:运行在用户态的进程就属于用户进程,一般是没有权限操作系统资源的
第二:运行在内核态的进程就属于系统进程,一般是有权限操作系统资源的

1.2.2 按照对CPU的依赖程度

第一:对于CPU依赖较重,则属于计算型的进程,侧重于计算,比较消耗CPU
第二:对于CPU依赖较弱,则属于偏I/O型进程,侧重于IO读写,比较消耗I/O

1.3 进程的组成

1.3.1 代码区

1.3.2 数据区

1.3.3 进程控制块(PCB)

1.4 进程的状态(5种)

新建态:刚刚创建的进程就处于新建状态
就绪态:处于就绪队列,等待被CPU执行的进程的状态就是就绪态。比如新创建的进程放入就绪队列或者时钟到期CPU将未执行完的进程放入就绪队列
运行态:如果被CPU调度执行,就是运行状态
阻塞态:运行中被阻塞,则处于阻塞态,比如I/O读,或者等待键盘输入等
终止态:进程被终止,则处于中止态

1.6进程创建和撤销

1.6.1 操作系统是创建一个进程

第一:为新进程分配一个唯一的进程标识符
第二:为进程分配地址空间
第三:初始化进程控制块(PCB),PCB的进程状态为New等等
第四:将进程的PCB放入到一个就绪队列中

在Unix下创建进程的主要操作是fork和exec; windows下主要创建进程的操作是CreateProcess

1.6.2 操作系统撤销进程

第一:回收进程占用的资源。比如关闭打开的文件、断开 络连接、回收分配的内存等等
第二:撤销该进程的PCB(比如Unix下exit或者Windows下调用TerminateProcess)

1.7 进程的调度和分派

#1 进程的调度肯定是由调度器实现的,根据调度优先算法来决定哪一个进程来占用CPU,这一行为肯定发生在内核态(调度都是发生在内核态的)
非剥夺调度:只有当进程主动放弃才重新调度,比如阻塞或者进程结束,CPU在之前的进程无法继续运行了,然后去调用调度程序,运行其他程序
剥夺调度:原来的进程本来可以继续运行,可以被操作系统以某种原因剥夺器其被调度的权利。

#2 所有的进程在运行时,都包含就绪态、运行态、阻塞态和终止态,每一个进程参与调度的时候,都会在这些进程状态之间来回切换
#3 只有当进程获取CPU的执行权时,进程才会进入运行态
#4 进程运行的时间片到后就会调度其他进程运行,每一个时间片大概在10-20毫秒左右

1.8 进程切换

1.8.1 进程切换的时间

第一:时钟中断。即执行的时间,超过了允许的最大时间片
第二:I/O中断。比如进程发起系统读文件调用,那么该进程就会处于阻塞等到状态,操作系统把阻塞态的进程放进就绪队列
第三:进程正常结束,则要调度其他进程

1.8.2 进程切换流程图

管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向的同时通信,则需要设置两个管道。

1.9.3 消息传递

进程之间会以格式化的消息进行传递,进程通过操作系统的提供的发送消息和接收消息2个原语来进行数据交换。

顾名思义,在用户态或者用户空间建立的线程库,提供一组管理线程的过程
由一个运行时系统完成线程的管理操作。操作系统内核管理的是进程,并不知道线程的存在。
当然,线程的切换也就不需要在内核态完成,在用户态就可以完成线程切换
比如Unix操作系统,就是这种,实现了POSIX线程库编程接口(Portable Operating System Interface),以线程库的方式提供给用户,也就是提供若干个函数来支持多线程。比如:
PThread_create: 创建新线程
PThread_exit: 终止线程
PThread_join: 等待一个线程死亡
PThread_yield:释放CPU执行权,让其他线程执行(主动释放)

优点:
#1 线程切换快
#2 调度算法可以按照应用程序的需求来进行调度
#3 可以运行在任何操作系统上(只需要实现线程库)
缺点:
#1 内核只是将CPU分配给进程,同一进程的2个线程不能同时运行在2个处理器上
#2 大多数系统调用时阻塞的,因为内核阻塞进程,即使是某一个线程系统调用,但是内核是不知道线程,只知道进程,故进程中所有线程也被阻塞

2.2.2 内核级线程(kernel-level thread)

三 同步和互斥

我们知道,在多任务的系统中,是可以同时运行多个进程的,如果进程之间需要通信,可以通过共享存储或者数据的方式实现通信,即共享资源,而这些资源具有排他性,各进程之间竞争使用这些资源,这种行为叫做进程互斥。

3.1 概念

3.1.1什么是同步

同步主要是用来解决多进程或者多线程的情况下的进程或者线程的协作关系,一个进程或者线程的执行需要依赖另外的线程的消息或者信 ,如果没有收到来自其他进程或者线程的信 ,则该进程或者线程则一直处于等待或者阻塞状态。比如常见的通知和唤醒操作就是为了协调线程或者进程的执行

3.1.2什么是互斥

互斥主要是为了解决多进程或者多线程并发的时候对临界资源的竞争问题。所以为了避免多个线程同时操作临界资源(共享变量)带来的问题,需要保证一个临界资源只能被单个进程或者线程访问,在访问期间,其余的进程或者线程必须等到当前正在操作临界资源的进程或者线程结束以后才可以访问。

3.1.3 临界资源(互斥资源) (Critical Resource)

系统中某一个资源一次只允许一个进程使用,这样的资源就被称为临界资源或者共享变量。

3.1.4 临界区(critical section)

各个进程对某个邻接资源的操作的代码片段或者程序片段就是临界区或者是互斥区

3.1.5 进入区(entry section)

负责检查是否可以进入临界区,锐可以进入,则应该设置正确的访问临界资源的标志

3.1.6 退出区(exit section)

比如释放锁,表示退出临界区

3.1.7 剩余区(reminder section)

退出临界区之后还有要执行的代码

3.2 进程互斥解决方案

3.2.1软件方案

3.2.1.1单标志法

两个进程在访问完临界资源之后,会把使用临界区的权限交给另外一个进程,每一个进程想要进入临界区只能被另外一个进程赋予权限

3.2.2 硬件方案或者硬件指令

因为软件方案的弊端,经常被中断,所以我们如果在处理同步互斥的的时候关闭中断就好了。但是关中又有一个问题,就是多CPU问题,因为你只能关闭当前一个CPU的中断,没办法全部关闭,所以在多处理器的架构中,还是不能解决问题。

3.2.2.1 单处理器系统中可以使用屏蔽中断,多处理器不行

屏蔽所有中断,包括IO中断或者时钟中断,disableInterrupt和enableInterrupt之间的代码段不会被打断。

使用TAS指令:

用Swap实现互斥:

操作系统之进程

优点:实现简单,把上锁和检查用用硬件的方式封装成了原子操作,适合于多处理器机器
缺点:如果要求进程有限等待,比如等待M毫秒,就超时不等待,没有实现;而且如果大量进程或者线程自旋,浪费CPU资源

3.3 同步机制

3.3.1 信 量机制

信 量(Semaphore)和PV操作
信 量是一种特殊的变量,用于进程之间传递信息的一个整数值,他可以解决同步和互斥问题,比如生产者和消费者问题、读写问题
对信 量可以实施的操作:
init:
P: 给信 量的值减1,比如count–,如果信 量的值小于0,则处于阻塞等待状态;然后将该进程插入到等待队列末尾

V:给信 量的值加1,比如count++,如果信 量小于或者等于0,则唤醒相应等待队列中的一个进程,改变其状态为就绪态,插入到就绪队列
注意:P V 操作是原子操作,执行过程中不允许被中断

缺点:程序编写技巧要求高,易出错

3.3.2 管程(monitor)

管程是一种高级同步机制,由关于共享资源的数据机构及在其上操作的一组过程组成。即它是管理共享资源的,在管理过程中提供了各种的各样操作。
进程只能够调用管程中的操作来间接访问管程中的共享数据结构。

管程要解决两个问题:
第一:互斥
管程是互斥进入的,只能有一个进程调用管程操作,编译器负责保证管程互斥性
第二:同步
管程中设置条件变量以及等待/唤醒操作

可以让一个进程或者线程在条件变量上等待,也可以发送信 将等待在条件变量上的进程或者线程唤醒

遇到的问题:
第一:进程A进入管程,但是需要阻塞等待其他进程的数据
第二:进程A进入等待队列,释放CPU
第三:进程B进入等待队列,但是进程B需要唤醒A,唤醒之后那就是两个进程同时存在
一般多线程是没有这个问题,因为临界区的代码都一样

3.3.3 PThread同步机制

PThread_mutex_lock 有则获取锁,没有阻塞等待
PThread_mutex_tryLock 要么获取锁,要么获取锁失败
PThread_mutex_unlock 释放一个锁

PThread_mutex_wait 阻塞等待,直到被唤醒
PThread_mutex_signal 被其他先程唤醒
PThread_mutex_broadcast 同时唤醒其他所有的线程

3.4 死锁和饥饿

3.4.1 什么是死锁

并发环境下,各进程或者线程因为竞争资源而导致都在等待获取对方手里的资源,导致各进程或者线程阻塞,都没办法向前推进,所以这就是死锁。造成这种死锁的情况,比如因为抛出异常,导致锁或者资源没有被释放,又或者是因为获取资源的顺序。举个例子:
例子1:锁嵌套导致的死锁
如以上代码所示:
第一:当进程A或者线程A执行leftRight的时候,先获取临界资源left,正准备获取right临界资源的时候,CPU切换到进程B或者线程B上,执行righteft的时候,获取 right临界资源
第二:当进程B或者线程B获取left资源的时候,发现left资源已经被占用,所以需要等待
第三:当CPU又切回到进程A或者线程A的时候,发现right资源已经被占用,这样也会等待right被释放
第四:这样两个进程或者线程,都处于等待状态,等待对方释放资源,但是都有没人释放,就造成死锁

例子2:转账问题
比如我们转账的时候,为了防止并发带来的问题,我们对账 加锁独占,进行操作,即转出账户和转入账户都要加锁。

当用户A给用户B转账的时候,首先对自己账 from加锁,然后对转入账 加锁,一般情况下没什么问题,但是如果在A给B转账的同时 B也在给A转账,比如:
第一:A转账,对A账户加锁,此时正准备给B账户加锁的时候,时钟中断,CPU切换到了B给A转账的线程
第二:B开始给自己账户加锁,然后给A账户加锁,发现A账户已经被别的线程持有锁,所以就等待
第三:时钟中断,CPU切换到A,A获取B账户的锁,发现B账户被别的线程加了锁,然后就等待
第四:这样也是互相等待释放锁,从而产生了死锁

3.4.2 进程死锁、饥饿和死循环有什么区别

饥饿:指的是长期得不到想要资源,某进程或者线程无法向前推进,比如调度算法中的优先级调度,优先级低的可能很久都不会被调度,从而发生进程或者线程饥饿
死循环:某进程或者线程执行过程中,一直跳不出某个循环的现象,这个和并发没有关系,更多的是逻辑上的错误;而死锁更多是因为并发带来问题

3.4.3 死锁和活锁有啥区别

进程或者线程执行的时候,没有被阻塞,但是无法满足某种条件,导致一直重复的进行操作或者尝试,但是程序就是无法前进。这种就是活锁。

活锁和死锁的区别:
#1 死锁是必须阻塞等待对方线程释放锁资源;活锁则不是阻塞等待,而是重复运行或者重试
#2 死锁是进程或者线程必须持有一个资源,然后去获取另外一个资源;但是活锁没有这个限制

比如ZK中,在阶段1的时候,提案者1提出了M1的方案,然后提交给接收者,返回过半票数;然后提案者2提出了M2的方案,也提交了,因为M2>M1,所以也返回过半票数
当在第二阶段的时候,提案者1提交的时候,发现自己的提案不是最新的,则重新发起新的一阶段请求,提交提案M3,,M3大于M2,同样M3批准
然后提案者在二阶段的时候,发现自己的提案也不是最新的,则重新发起新的一阶段请求,提交提案M4,M4大于M3
如此这样,一直反复。这就是活锁。

3.4.4 死锁产生的必要条件

#1 进程必须是互斥的,即存在竞争临界资源
#2 进程获取资源之后,不能在未结束之前,强行被别的进程夺走,只可以主动释放
#3 进程持有多个资源。即 这个进程在持有某个资源不放的同时,还希望持有别的资源,这时候才会被阻塞
#4 存在资源循环等待

3.4.5 死锁的处理策略

3.4.5.1预防死锁(静态策略)

第一:破坏互斥条件
将互斥资源改造成可以共享的资源,比如使用SPOOLing技术

第二:破坏不剥夺条件
方案1: 当某个进程请求新的资源得不到满足的时候,他必须理科释放掉所保持的资源,待以后重新申请,也就是说先申请的资源即使没有使用,也需要主动释放
方案2:可以借助操作系统强行剥夺,比如优先级调度

第三:破坏请求和保持条件
进程一次性申请完他所需要的全部资源,在资源没有满足的时候,不让其运行;一旦获取资源这个进程,该进程就不会再请求别的任何资源

第四:破坏循环等待条件

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年2月24日
下一篇 2021年2月24日

相关推荐