操作系统-进程的描述和控制

前驱图和程序执行

程序执行的方式有两种:顺序执行和并发执行
并发就是同一间隔两个或者多个程序同时发生.

为了描述程序运行的顺序关系,就产生了前驱图…

1.程序顺序执行

在这里插入图片描述

缺点:因为是封闭性的,也就是在程序执行的过程中独占cpu,跟单道批处理操作系统一致,在输入的时候,cpu很空闲,得不到利用,所以资源利用率很低.

2.程序并发执行

这个并发是有前提条件:在没有前驱关系的程序之间才能并发执行,为了同一个任务而努力
在这里插入图片描述

可以由图看出 在第一次输入程序进行完,即可以执行第一次计算程序又可以执行第二次输入程序; S1和S2可以同时赋值,因为他们之间没有前驱关系.

并发的特征:
1.间断性:如果C1计算完,I2尚未完成数据的输入,C2就不能执行,所以必须暂停运行程序,只有当I2完成输入后,才能运行.
对于这个特征与顺序执行的顺序性有什么相似点吗?都是因为彼此有牵制,只不过并发的牵制很多,而顺序的牵制只有一个,结果都是一致的因为前驱操作没有执行,而中断了进程.所以为什么要叫间断性… 可能是为了好听…

2.失去封闭性:顺序执行下资源在一段时间内只由一个程序控制,他的状态只有本程序才能改变.而并发是多个程序,比如说你的家里面的钥匙只你一个有,如果你家的钥匙在一段时间内有很多个人有,那还是个人财产吗?

3.不可再现性:简单的理解,结果已经不是单一的.

进程的描述

进程为什么产生??
因为以前顺序执行的时候,cpu只跑一个程序,但是并发执行,一次跑多个程序,为了描述和控制程序,引入了进程这个概念

进程是什么??
书上说:进程是具有独立功能的程序在一个数据集合上的一次动态的执行.所以说进程就是程序的运行的一次过程.

进程和程序
  • 联系
    进程由程序段,相关的数据段,PCB(进程控制块)三部分组成进程实体,进程实体是用来描述进程(进程是一个过程),程序的每次运行构成不同的进程,为什么这样说?程序不应该是一样的吗?(输入信息和输出信息在程序中是没有的)如果数据段不一样,那每次运行的结果就不一样,所以程序的每次运行构成不同的进程.所以进程和程序之间的联系;一个程序可以对应多个进程,而一个进程可以被多个程序调用

  • 区别
    进程是动态的,他是为了描述程序一次运行的过程,程序是静态的代码段.

进程是暂时的,程序是永久的,进程的状态会变化,而程序永久不变

组成不一样,进程包括程序,数据和PCB(进程控制块)

下面结合清华大学的一位教授的例子理解
在这里插入图片描述

食谱:一条条指令==程序,科学家:要做这件事情
做蛋糕:这个过程是一个进程

从上面这个例子可以总结出

进程的特征
  • 动态性:进程可以从一个状态,切换到另一个状态
  • 并发性:同一段时间,能有多个进程进行
  • 独立性:不同的进程工作不受影响,一个进程不可能对破坏另一个进程的数据,使另一个进程的正确性得到破坏
  • 制约性:一个进程需要等待另一个进程的执行,操作系统要根据进程之间的特点去进行协调去执行.
  • 异步性:异步-进程会独立的,以不可预料的速度执行.
    正是因为这个原因,才有了并发的不可再现的特征.
PCB

为了管理进程,用一个数据结构来控制进行,也就PCB
PCB是识别进程的唯一标识,创建进程,就生成一个PCB,终止进程,PCB也被回收,PCB描述进程的情况和变化情况.

进程的状态

在这里插入图片描述

线程

为什么会产生线程??
因为几个进程独立的运行,他所操作的资源对应不同的地址空间,就不能对一个资源进行操作,所以引入了一个实体,线程,可以并发执行,但是可以共享相同的地址空间,可以对一个资源进行操作.

线程和进程的联系

进程:之前的概念:进程是具有独立功能的程序在一个数据集合上的一次动态的执行.所以说进程就是程序的运行的一次过程.那我们把进程拆解一下,进程是有两部分组成:1.资源管理2.线程.所以线程是在共享资源平台上的一次执行流程.
线程有自己的控制块,叫线程控制块(TCB)

在这里插入图片描述

线程 = 进程 - 共享的资源
就好像做蛋糕是一个进程,而里面有很多小步骤,比如说切蛋糕,发奶油,凹造型,都是共享了你做蛋糕的食材的. 这里的小步骤就是线程.

线程的优点和缺点
优点:1.可以并发执行2.一个进程可以有多个线程3.多个线程可以共享资源
缺点:不安全,因为多个线程可以共享资源,如果一个线程崩溃,破坏了共享的资源,会导致所有的线程崩溃,所以说很不安全.

进程同步

为什么要进程同步,进程同步是什么?
进程同步就是对多个进程在执行次序之间的协调,使多个进程能按照一定的次序共享资源.为什么要线程同步,为了让运行结果有可再现性

基本概念

互斥:只能有一个线程访问
临界资源:一次仅允许一个进程使用的资源称为临界资源
临界区:进程访问临界资源的那段代码

临界区的特征:
1.互斥
2.在临界区之外等待的进程一定会进入临界区
3.有限等待:进程在外面等待的时间一定是有限的

同步机制

为了使进程同步,以下有三种方法(机制)

  • 硬件中断
    原理:使正在运行的进程强制中断,转换为另一个进程
    方法:使进入临界区的时候关中断,离开临界区的时候打开中断.这样就可以使临界区只有一个进程在进行.
    缺点:
    1.中断机制是为了响应某种事件的,就跟家里的总闸一样,你只想关客厅的灯,没想到全部灯都关了.中断也是一样,你关了整个系统可以说都没法响应,可能导致其他的进程会等着,处于饥饿状态
    2.如果临界区很长怎么办?可能导致系统的其他功能出现问题

类比:
比如一个装着很多蛋糕的房间,里面有个记着蛋糕数量的计数器,如果蛋糕不够厨师会加蛋糕.现在房间只让一个人进去,也就是互斥.有什么办法只让一个进去呢?在每个人吃蛋糕的时候把门锁住,吃完再把门打开.把门关上和打开就相当于关中断开中断.这样可以实现互斥访问,可以实现process.但是在这个过程中,这个房间不能外部事件,厨师看见蛋糕少了要添加,但是进不了房间.这就是不能响应外部的时间.还如果一个同学吃的很多,把蛋糕给吃完了,这就相当于临界区很长怎么办??

  • 软件方法
    1.Dekker皮特算法
    地位:第一个针对双线程列子的正确解决途径

设有进程P0和P1,两者谁要访问临界区,就让对应的flag=true(例如P0要访问临界区,就让flag[0]=true),相当于“举手示意我要访问”。初始值为0表示一开始没人要访问
turn用于标识当前允许谁进入,turn=0则P0可进入,turn=1则P1可进入。

1)P0的逻辑
do{
flag[0] = true;// 首先P0举手示意我要访问
while(flag[1]) {// 看看P1是否也举手了
if(turn==1){// 如果P1也举手了,那么就看看到底轮到谁
flag[0]=false;// 如果确实轮到P1,那么P0先把手放下(让P1先)
while(turn==1);// 只要还是P1的时间,P0就不举手,一直等
flag[0]=true;// 等到P1用完了(轮到P0了),P0再举手
}
flag[1] = false; // 只要可以跳出循环,说明P1用完了,应该跳出最外圈的while
}
visit();// 访问临界区
turn = 1;// P0访问完了,把轮次交给P1,让P1可以访问
flag[0]=false;// P0放下手
2)P1的逻辑
do{
flag[1] = true;// 先P1举手示意我要访问
while(flag[0]) {// 如果P0是否也举手了
if(turn==0){// 如果P0也举手了,那么久看看到底轮到谁
flag[1]=false;// 如果确实轮到P0,那么P1先把手放下(让P0先)
while(turn==0);// 只要还是P0的时间,P1就不举手,一直等
flag[0]=true;// 等到P0用完了(轮到P1了),P1再举手
}
}
visit();// 访问临界区
turn = 0;// P1访问完了,把轮次交给P0,让P0可以访问
flag[1]=false;// P1放下手

这是一种谦让又不过分谦让的方法

2.Bakery算法
在这里插入图片描述
类比银行叫号服务

缺点:都使用的while循环让进程忙等待

  • 原子操作模式
    最多使用的,通常需要一定的硬件支持

他与软件的区别是:其中有两个方法,他们是原子性的,不支持在交换,赋值的时候进程中断.
所以完全可以轻松实现多个进程的互斥
在这里插入图片描述
其中test-and-set(value)是原子性的

其中还抽象了一个类:Lock锁,当value值为0的时候表示锁没有锁上,一个进程正在访问
有两张情况:
在这里插入图片描述

无忙等待实现的方法:将正在运行的进程挂起到队列中,把CPU让出来,使这个进程睡眠

优点:简单,开销很小
缺点:
1.忙等待耗费cpu开销
2.进入临界区,会做一个判断,判断哪些进程获得这个锁,这个是随机性的,可能会导致某一些进程抢不到锁,会发生饥饿现象.
3…可能会发生死锁:如果一个低优先级抢到锁,高优先级在忙等,使得低优先级不能释放这个锁

信号量

为了解决多个进程对同一资源的访问 才有了信号量

  • 整型信号量
    整型信号量就是一个整型数,只有0和1,当值为1 的时候进程可以进去临界区,出临界区把值修改成0.

为了实现同步机制:引入记录型信号量
这个信号量的根本功能就是记录和通知;

  • 为了更好的理解下面引入一个生产者和消费者的问题
    问题需求:1.可以有多个生产者同时生产,生产后只能有一个消费者消费
    2.在一个生产者对资源进行生产的时候,消费者不能消费

实现特性:互斥和同步

代码如下
在这里插入图片描述
为什么这个代码可以解决这个问题

首先我们思考一个实际情况,一群蛋糕师现在要做饼干,盒子里面只能放20个,旁边有人可以吃,但是只能有一个人,那么我们实际在做的时候,是不是要问吃的你吃了多少,里面现在的饼干=盒子能装的个数-吃的个数.那么信号量也是这样里面的fullBuffers就是里面还剩的小饼干,empty
Buffers就是里面吃了的小饼干.

有这些信号量才会让做饼干的人好操作,实际上进程也是这样有了这些量,才会分工明确.

对于这个问题分三种情况
我们假设这个可被分配的最大资源数n为5
1.正常情况
对于生产者
有5个进程,相当于调用了Deposit©这个方法5次,生产了5个资源,那么这个empty-n信号量从5变成了0,代表资源数满了,空的位置有0个;fullBuffers进行5次V操作也就是加加,代表着里面有5个资源.
对于消费者来说
只能一次消费一个资源,现在调用1到5次,每消费一个资源fullBuffers要减1,emptyBuffers要加1.
可以实现

2.生产者没有生产,消费者调用
empty-n信号为5,fullBuffers信号为0
如果现在消耗一个资源,执行fullBuffers会执行P操作-1,变成-1,当P操作里面的值<0的时候,会把消费者进程给阻塞.只有这个时候在运行生产者进程的时候,执行到fullBuffers的V操作加上1的时候才会把消费者进程换醒.

3.生产者生产过多,消费者没有调用
如果进程数变为了7个,那么执行Deposit©会执行7次,前5次不会挂起,后两次会挂到信号量上面.empty-n会变成-2,后两个生产者进程会阻塞,直到消费者执行到emptyBuffersV操作才能唤醒生产者.

对于mutex()的P操作可以和Buffers的P操作进行交换吗?
不可以,因为当生产者过多,会被挂起,但是你的生产者还在临界区内,消费者运行到这里他进不了临界区也就运行不了后面的代码.自然生产者就不会释放进程.所以程序就会死锁.


  • 读者写者问题(读优先)
    问题需求:
    有一个数据文件,允许多个进程同时读,因为读不会使数据混乱,但不允许一个Writer进程和Read进程或者Writer进程同时访问
    重点:原则有多个读操作,会先把读操作进程执行完,然后再执行写操作,所以读者的里面的进程顺序可能会变化,但是写者总是等读者进程结束,在执行的,所以可以用Readcount== 0来做判断
    分析:读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。

所以写者实现很简单,它和任何进程互斥,用互斥信号量的P操作、V操作即可解决。
读者的问题比较复杂,它必须实现与写者互斥的同时还要实现与其他读者的同步,因此,仅仅简单的一对P操作、V操作是无法解决的。那么,在这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者的时候写者是无法写文件的,此时读者会一直占用文件,当没有读者的时候写者才可以写文件。同时这里不同读者对计数器的访问也应该是互斥的。

semaphore Wmutex, Rmutex = 1;

int Rcount = 0;

void reader() /*读者进程*/

{

      while(true)

      {

               P(Rmutex);

               if(Rcount == 0) P(wmutex);

               Rcount= Rcount + 1;

               V(Rmutex);

               ⋯⋯;

               read;/* 执行读操作 */

               ⋯⋯;

               P(Rmutex);

               Rcount= Rcount - 1;

               if(Rcount == 0) V(wmutex);

               V(Rmutex);

      }

}

void writer() /*写者进程*/

{

      while(true)

      {

               P(Wmutex);

               ⋯⋯;

               write;/* 执行写操作 */

               ⋯⋯;

               P(Wmutex);

      }

}

writer()好实现,直接套上一个互斥的P操作和V操作,
Reader()进程在读之前是不是要考虑有没有写操作,如果有写操作把读操作给阻塞掉,如果没有向下执行.所以我们做一个判断,在这个读进程之前有没有读进程,如果没有读进程,他做一个互斥的P操作,就可以实现互斥. 现在我们来看就这样写这个程序就能满足我们的需求吗?那我们看看来实现,假设现在有两个读者,第一个读者执到read oprtion,另一个就进来了,另一个跑到i++停住了,第一个继续执行,i–,现在i=1,他没有等于0,所以跳不出临界区,也释放不了这个进程,当有读者产生的时候,也进不了临界区,会导致一个死锁现象.

所以我们要对这个readCount进行一个互斥管理.使他在一段时间只能有一个进程执行readCount操作.

缺点:读者优先会导致比如说 现有2个读进程,1个写进程,2个读进程,如果有一个进程读的可能比较慢,那么他不会进行ReadCount–操作,但是下一个写进程他和读操作是互斥的,执行不了,他执行不了,后的读操作可以执行,可能等后面的读操作执行完了,那个运行特别长的进程才能结束,把临界区让出来,才能继续写操作.

写者优先:

如果希望写进程优先,即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等待到已在共享文件的读进程执行完毕则立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并且在上面的程序中 writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。


int count = 0; //用于记录当前的读者数量



semaphore mutex = 1; //用于保护更新count变量时的互斥



semaphore rw=1; //用于保证读者和写者互斥地访问文件



semaphore w=1; //用于实现“写优先”







writer(){



while(1){



P(w); //在无写进程请求时进入



P(rw); //互斥访问共享文件



writing; //写入



V(rw); // 释放共享文件



V(w) ; //恢复对共享支件的访问



}



}







reader () { //读者进程



while (1){



P (w) ; // 在无写进程请求时进入



P (mutex); // 互斥访问count变量







if (count==0) //当第一个读进程读共享文件时



P(rw); //阻止写进程写





count++; //读者计数器加1



V (mutex) ; //释放互斥变量count



V(w); //恢复对共享文件的访问



reading; //读取



P (mutex) ; //互斥访问count变量



count--; //读者计数器减1







if (count==0) //当最后一个读进程读完共享文件



V(rw); //允许写进程写







V (mutex); //释放互斥变量count



}



}





进程通信

什么叫进程通信?
在进程之间交换信息(传输数据).

为什么要进行进程通信?
因为现在对于一个程序都是多进程的,每个进程拥有各自的进程映像(PCB,程序,数据组成),由于不同的进程运行在各自不同的内存空间中,对于一个进程修改变量的值,其他进程无法感知,所以不能够通过修改变量和数据结构来进行进程的信息传输,只能用进程间通信(进程进行数据传输的方法)

管程

为了实现语言的互斥,他是比信号量机制抽象程度更高级的一种机制.由数据结构和在数据结构上的一组操作组成.


相关概念:

enter过程:进程在进入管程之前会申请管程,
一般由管程提供一个外部过程–enter过程,enter()过程就是排序,找到优先级最高的进程进入管程
leave过程:当一个进程离开管程时,如果条件变量上面的队列不空,那么它就必须负责唤醒紧急队列中的一个进程,此时也由管程提供一个外部过程—leave过程,如Monitor.leave()表示进程调用管程Monitor外部过程leave离开管程。
条件变量:如果一个函数被一个进程所调用,其他的进程会阻塞挂起,就会挂到条件变量上,他会会管理并发访问共享数据.他有两个操作Wait()—释放锁,使线程挂起.Signal()操作唤醒等待,c实际上是一个指针,它指向一个等待该条件的PCB队列。如notfull表示缓冲区不满,如果缓冲区已满,那么将要在缓冲区写入数据的进程就要等待notfull,即wait(notfull)。相应的,如果一个进程在缓冲区读数据,当它读完一个数据后,要执行signal(notempty),表示已经释放了一个缓冲区单元。

lock(锁):相当于互斥量,有两个操作:Acquire()—等待直到锁可以用,抢占锁,
Release()----释放锁,唤醒其他的进程,实现了进程之间的互斥.

在这里插入图片描述

问题:为什么是先释放锁,在创建锁呢?
因为这个进程在进入函数体之前,(看下方的生产者进程,在wait()之前,有lock->Acquire)就获得锁,必须释放掉这个锁,如果不释放,所有等待进入管程的线程都会堵着,程序就会停止.

这里num waitng 是等待的进程数,这跟型号量的资源数不一样.

为什么single()里面的num waiting不一定减1.
因为等待队列没有进程,num waitng为0,则single()里面的操作啥也不做.与信号量不同,型号量的V操作调用信号量值一定会加1.

管程实现生产者消费者

count 是buffer的状态,当为0,buffer为空
在这里插入图片描述
与信号量机制互斥的实现不同,信号量互斥是紧靠着buffer操作,但是管程是在方法的开头和结尾,为什么?管程的定义决定的,线程进入这个管程的时候,只有一个线程才能执行这个函数,为了确保唯一性,一进入这个函数就是互斥的

如果buffer为空,消费者睡眠,如果buffer为满,生产者睡眠
在这里插入图片描述

思路:和信号量一致,但细节不同.

对于线程在管程中执行的时候,线程要针对某个变量的唤醒操作的时候,是要 执行那个挂在变量上的线程1,还是发出唤醒操作的线程2?
在这里插入图片描述

  • 左边(Hansen):线程2先执行,执行完release()操作后,才把控制权交给进程1去执行.

  • 右边(Hoare):线程1先执行,线程2去睡眠,直到线程2执行到release之后,线程1才能继续执行

Hoare的更好实现.

个人想法,如有错误,欢迎大家指正.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值