【OS】从调度到死锁基本概念

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_41374099/article/details/99652365

前言

不知道一篇能写成什么样子。第一篇是随便写,当时写完打算第二篇应该认真写才是,这一拖就是半个月。
操作系统也就进程、调度、内存、io与文件这几个部分。如果有机会我还想早点学到嵌入式系统与分布式系统的特点。


处理机调度

调度就是按某种算法选择一个进程将处理机资源给它(处理机资源即单核CPU)

处理机调度分为高中低三种调度,也有按长中短分的,也有把IO调度单独拿出来的,我只写前者。

  • 高级调度是作业调度。将作业从外存调入内存,然后给它建立一个PCB,作为一个进程等待被执行。
  • 低级调度是进程调度、切换。给进程处理机,让它被执行。一是对原来运行进程各种数据的保存。二是对新的进程各种数据的恢复。
  • 中级调度是内存调度。内存中部分没有被执行的进程可以先调回外存,等到有处理机后再调入内存。

   \;

调度原则

  • 空闲让进。就绪队列没有进程时,此时来了一个进程,一定能让它被执行。
  • 忙则等待。如果有其他进程在执行,并且新的进程的优先级也没有其他进程的高,那就只有等待了。
  • 有限等待。每个进程的等待时间不能是无穷的,不能有饥饿甚至饿死的情况。
  • 让权等待。当进程不能进入临界区时,应该立即释放处理机,防止进程忙等待。

什么是临界区、退出区…

/*
	进入区:检查进程是否进入临界区,如果可以,设置【正在访问临界资源】的标志
*/
/*
	临界区:访问临界区资源
*/
/*
	退出区:解除【正在访问临界资源】的标志
*/
/*
	剩余区:其他操作
*/

   \;

调度的时机

当前进程主动放弃CPU

  1. 进程正常终止
  2. 运行过程中发生异常而终止
  3. 进程主动请求阻塞

当前进程被动放弃CPU

  1. 分给进程的时间片用完
  2. 有更紧急的事件要处理
  3. 有优先级更高的进程进入就绪队列
       \;

不能进行调度、切换的情况

处理机中断的过程中。
中断处理过程复杂,与硬件密切相关,很难做到在中断处理过程中进行进程切换。

进程在操作系统内核程序临界区中。

在原子操作过程中。
原子操作/原语不可中断,比如修改PCB中的进程状态标志,并把PCB放入相应队列。
   \;

抢占式与非抢占式

非抢占式是只允许进程主动放弃处理机。在运行过程中即便有更紧迫任务到达,当前进程依然会继续使用处理机,直到该进程终止或主动要求进入阻塞态。

抢占式是当一个进程正在处理机上运行时,如果另一个更有意义、更紧迫的进程需要使用处理机,则立即暂停正在运行的进程。将处理机分配给更紧迫的。
   \;

进程七状态模型

进程的挂起态是暂时调用到外存等待的进程状态。还可以分为就绪挂起和阻塞挂起。如果只有一个挂起态的话,也可以构造六状态模型。

挂起和阻塞都不能获得CPU服务,区别是挂起态将进程映像调到外存了(PCB还在内存),而阻塞态下进程映像还在内存中。
在这里插入图片描述

   \;

进程两状态模型

进程是否在执行,根据这个可以构造最简单的进程状态模型——进程只有两个状态,运行与未运行态。
在这里插入图片描述    \;

调度算法的评价指标

CPU利用率:忙碌时间与总运行时间之比。

系统吞吐量:单位时间内完成作业的数量,即总共完成的作业数与总共花了多少时间之比。

周转时间:作业完成时刻减去作业提交时刻

带权周转时间:作业周转时间与作业实际运行时间之比

等待时间:进程被建立之后的等待时间之和,即作业上外存后备队列中等待时间加上进程的等待时间

响应时间:从提交请求到响应的时间之差

   \;
   \;

调度算法

先来先服务(First Come First Serve)

用于作业调度时,将作业按顺序加入后备队列。
用于进程调度时,将进程按顺序加入就绪队列。
按顺序一个一个地执行,公平、简单、不会有饥饿。
非抢占

   \;

短作业优先(Shortest Job First)

SJF是非抢占式的,还有一种Shortest Process First也是非抢占的。
短作业优先也有抢占式版本【Shortest Remaining Time Next】

对于非抢占式的来说,每次调度时,操作系统会选择当前已到达的且剩余运行时间最短的作业或进程。因为是非抢占式的,所以就算又有剩余运行时间更短的进程到达,也要等这个进程主动结束或放弃才行。

对于抢占式的来说,每当有进程加入就绪队列,就要重新进行调度,选择此时剩余运行时间最短的。

   \;

高响应比优先(Highest Response Ratio Next)

不会有饥饿,即可用于作业调度,也可用于进程调度。
只有当前运行的进程放弃CPU时才需要调度。调度时计算就绪队列的进程的响应比,选择响应比最高的进程,并给它分配CPU。
非抢占

= + 响应比 = \frac{等待时间 + 要求服务的时间}{要求服务的时间}

  • 要求服务的时间也就是剩余的运行时间。
       \;

时间片轮转调度(Round-Robin )

不会有饥饿,只用于进程调度。
公平,轮流为各个进程服务。按照抵达就绪队列的顺序,轮流让各个进程执行一个时间片。就算一个时间片内进程没执行完,也要被剥夺CPU,将进程重新放入就绪队列队尾重新排队。
抢占
   \;

优先级调度(Priority)

有抢占式,也有非抢占式。即可用于作业调度,也可用于进程调度。
按优先级来调度进程,有静态优先级与动态优先级。动态是可变的,比如如果某个进程等待时间太长,则可以适当提高优先级。

一般系统进程大于用于进程,IO型进程大于计算型进程,前台进程大于后台进程(比如手机,重要的是用户的交互,所以表面上的快更重要)。

   \;

多级反馈队列调度(Multi-level Feedback Queue )

抢占式,只用于进程调度,会导致饥饿。
多级反馈队列调度结合了其他几个调度算法的优点,拥有时间片、优先级等特点。

  • 设置多级队列,优先级从高到低,时间片大小从小到大。
  • 新的进程先进入第一级队列,按FCFS原则等待CPU。等到CPU并且自己的时间片用完后还没结束的话,就把这个进程放入第二级队列队尾。最后一级队列的进程被执行后被结束就放入最后一级队列队尾。
  • 当第k级队列为空时,为k+1级队列分配时间片,即CPU往下逐次执行。

在这里插入图片描述

   \;
   \;

同步与互斥

同步不是同时,相反同步是不同时。在某些做事步骤上必须由先后顺序,必须一前一后。这种直接的制约关系就是同步。

互斥的概念比同步大,可以说同步包含于互斥之中。互斥不要求两件事执行的先后顺序,只需要不同时就行。

  • 在研究多进程/线程问题时,需要分别考虑其互斥关系和同步关系。根据互斥关系构造互斥变量,根据先后关系构造同步变量。有时,可以省略互斥变量,单单用同步的就行了。

   \;

信号量

信号量是一个变量,用来表示系统中某种资源的数量。

信号量实现进程同步

  1. 分析什么地方需要同步(一前一后)
  2. 设置同步变量S,初始化为0
  3. 在前操作后V(S) —— P、V操作即加锁解锁或者阻塞释放。
  4. 在后操作前P(S)

用信号量解决生产者-消费者问题

有一个或者多个生产者生产某种类型的数据,并放置在缓冲区;有一个消费者从缓冲区取数据,每次取一个;系统避免对缓冲区的重复操作——在任何时候只能有一个生产者或者消费者访问缓冲区。
当缓冲区已满时,生产者不会继续向其中添加数据;当缓冲区为空时,消费者不会从其中取出数据。

/* 二元信号量的原语的定义 */
struct binary_semaphore{
	enum{zero ,one}value;
	queueType queue;
};
void semWaitB(binary_semaphore S){
	if(S.value==one)S.value=zero;
	else{
		/*把当前进程插入等待队列*/
		/*阻塞当前进程*/
	}
}
void semSignalB(semaphore S){
	if(S.queue is empty)S.value=one;
	else{
		/*把进程P从等待队列中移除*/
		/*把进程P插入就绪队列*/
	}
}
/* program producerconsumer */
int N;
binary_semaphore S=1,delay=0;
void producer(){
	while(1){
		produce();//生产数据
		semWaitB(S);//阻塞
		append();//放入数据
		if(++N==1)semSignalB(delay);
		semSignalB(S);//释放
	}
}
void consumer(){
	semWaitB(delay);
	while(1){
		semWait(S); //阻塞
		take();//取出数据
		semSignalB(S); //释放
		consumer();//消费数据
		if(--N==0)semWaitB(delay);
	}
}
void main(){
 	N=0;
 	parbegin(producer,consumer);//构造producer\consumer两个进程/线程
}

   \;

管程

管程是一种高效的同步机制,引入管程是因为信号量存在问题——编写程序困难、易出错。

管程是由一个或多个过程(方法),一个初始化序列和局部数据组成的软件模块。
局部数据变量只能被管程的过程访问,任何外部过程都不能访问
一个进程通过调用管程的一个过程进入管程
任何时刻只能有一个进程在管程中。调用管程的其他进程都被阻塞,等待被调用。

用管程解决生产者-消费者问题

管程模块boundedbuffer控制着用于保存和取回字符的缓存区,管程中有两个条件变量(使用结构cond声明)。当缓冲区中至少有增加一个字符的空间时,notfull为真;当缓冲区至少有一个字符时,notempty为真。

/*program producerconsumer */
monitor boundedbuffer;
char buffer[N];//分配N个数据空间
int nextin,nextout;//缓冲区指针(索引)
cond notfull,notempty;//同步变量

void append(char x){
	if(count==N) wait(notfull);   //如果缓冲区满了,就等待notfull,即等待消费者取出

	buffer[nextin]=x;  
	nextin=(nextin+1)%N;
	count++; //空间减少,数据增加
	
	signal(notempty);  //放入了,所以不为空了
}

void take(char x){
	if(count==0) wait(notempty);  //如果为空,就等待notempty,即等待生产者放入
 
    x=buffer[nextout];
    nextout=(nextout+1)%N;
    count--; //空间增加,数据减少
    
	signal(notfull); //拿出了,所以不为满了
}

{ //管程体
	nextin=nextout=count=0;//初始化缓冲区
}


//生产者,不断地生产-放入-生产-
void producer(){
	char x;
	while(1){
		produce(x);
		append(x);
	}
}
//消费者,不断地取出-消费-取出-
void consumer(){
	char x;
	while(1){
		take(x);
		consume(x);
	}
}

void main(){	
	parbegin(producer,consumer);
}

管程优于信号量是因为其所有的同步机制都被限制在管程内部,所以易于验证同步的正确性,而且容易检测出错误。此外,如果一个管程被正确地编写,则所有进程对受保护资源的访问都是正确的;而对信号量而言,只有当所有访问资源的进程都被正确地编写时,资源访问才是正确的。
   \;
   \;

死锁

死锁是一组相互竞争系统资源或进程通信的进程间的“永久”阻塞。

当一组进程中的每个进程都在等待某个事件(比如等待所请求的资源被释放),而只有这组进程中的其他被阻塞的进程才可以触发该事件,这时就称这组进程发生死锁。因为没有事件能够被触发,所以死锁是永久性的。与并发进程管理中的其他问题不同,死锁问题没有一种有效的通用解决方案。

死锁的三个必要条件

  • 互斥。一次只有一个进程可以使用一个资源,其他进程不能访问已分配给其他进程资源。
  • 占有且等待。当一个进程等待其他进程时,继续占有已分配的资源。
  • 不可抢占。不能强行抢占进程已占有的资源。

另外,构成死锁存在的充分条件还有

  • 循环等待。存在一个封闭的进程链,使得每个进程至少占有此链中下一个 进程所需要的一个资源。

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/99652365