Linux-0.11操作系统实验5理论-信号量与临界区

L14 CPU调度策略

FIFO:谁先进入,谁先调度

Priority:任务短可以适当优先调度

周转时间: 从开始申请执行任务,到执行任务完成

响应时间: 从开始申请执行任务到开始执行任务

相互影响和矛盾:响应时间小 ------> 切换次数多 ------> 系统内耗大 ------> 吞吐量小

调度需要折中和综合

FCFS:先来先服务 first come first serverd 公平

SJF:短作业优先,周转时间最小,但是响应时间长

RR: round robin 按照时间片轮转调度,保证响应时间,适用于前台程序,IO操作多的

  • 时间片大:响应时间太长;时间片小:吞吐量小

直观想法:定义前台任务和后台任务,前台RR,后台SJF ,只有前台任务没有时才调度后台任务。

前台优先级高于后台优先级,若只按照优先级固定调度,可能会产生饥饿,某些任务调度不到

L15 schedule()分析

/*
 * 'schedule()'是调度函数。这是个很好的代码!没有任何理由对它进行修改,因为它可以在所有的
 * 环境下工作(比如能够对IO-边界处理很好的响应等)。只有一件事值得留意,那就是这里的信号
 * 处理代码。
 * 注意!!任务0 是个闲置('idle')任务,只有当没有其它任务可以运行时才调用它。它不能被杀
 * 死,也不能睡眠。任务0 中的状态信息'state'是从来不用的。
 */
void
schedule (void)
{
  int i, next, c;
  struct task_struct **p;	// 任务结构指针的指针。

  /* check alarm, wake up any interruptible tasks that have got a signal */
  /* 检测alarm(进程的报警定时值),唤醒任何已得到信号的可中断任务 */

  // 从任务数组中最后一个任务开始检测alarm。
  for (p = &LAST_TASK; p > &FIRST_TASK; --p)
    if (*p)
      {
	// 如果任务的alarm 时间已经过期(alarm<jiffies),则在信号位图中置SIGALRM 信号,然后清alarm。
	// jiffies 是系统从开机开始算起的滴答数(10ms/滴答)。定义在sched.h 第139 行。
	if ((*p)->alarm && (*p)->alarm < jiffies)
	  {
	    (*p)->signal |= (1 << (SIGALRM - 1));
	    (*p)->alarm = 0;
	  }
	// 如果信号位图中除被阻塞的信号外还有其它信号,并且任务处于可中断状态,则置任务为就绪状态。
	// 其中'~(_BLOCKABLE & (*p)->blocked)'用于忽略被阻塞的信号,但SIGKILL 和SIGSTOP 不能被阻塞。
	if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
	    (*p)->state == TASK_INTERRUPTIBLE)
	  (*p)->state = TASK_RUNNING;	//置为就绪(可执行)状态。
      }

  /* this is the scheduler proper: */
  /* 这里是调度程序的主要部分 */

  while (1)
    {
      c = -1;
      next = 0;
      i = NR_TASKS;//设置末尾,从后往前移动
      p = &task[NR_TASKS];
      // 这段代码也是从任务数组的最后一个任务开始循环处理,并跳过不含任务的数组槽。比较每个就绪
      // 状态任务的counter(任务运行时间的递减滴答计数)值,哪一个值大,运行时间还不长,next 就
      // 指向哪个的任务号。
      while (--i)
	{
	  if (!*--p)
	    continue;
	  if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
	    c = (*p)->counter, next = i;//找到最大的counter时间片
	}
      // 如果比较得出有counter 值大于0 的结果,则退出124 行开始的循环,执行任务切换(141 行)。
      if (c)
	break;
      // 否则就根据每个任务的优先权值,更新每一个任务的counter 值,然后回到125 行重新比较。
      // counter 值的计算方式为counter = counter /2 + priority。[右边counter=0??]
      for (p = &LAST_TASK; p > &FIRST_TASK; --p)
	if (*p)
	  (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;//修改counter,恢复时间片
    }
  switch_to (next);		// 切换到任务号为next 的任务,并运行之。
}

counter即承担着时间片作用,也承担着优先级的作用,counter代表的优先级可以动态调整,I/O正是前台进程的特征,前台进程优先,counter的操作((*p)->counter >> 1)保证了响应时间的界。

L16 进程同步与信号量

合理有序的推进多进程,共同完成一个任务

核心:阻塞,唤醒

生产者消费者模型:counter 反应缓冲区中空闲的个数,还需要知道有多少个进程睡眠(信号量)

P (semaphore s); //消费资源,睡眠 -1

V (semaphore s); //生产资源,唤醒 +1

L17 信号量临界区保护

临界区保护信号量,信号量实现进程间的同步

临界区:一次只允许一个进程进入

为什么需要临界区?

  • 两个生产者因竞争资源会产生随机的执行顺序,造成信号量的修改不当,信号量语义错误,所以需要保护信号量。

临界区代码的保护原则:

  1. 互斥进入:如果一个进程在临界区中执行,则其他进程不允许进入(基本原则)
  2. 有空让进:若干进程要求进入空闲临界区时,应尽快使一个进程进入临界区
  3. 有限等待:从进程发出进入请求到允许进入,不能无限等待

软件方法:

1)轮换法 有空让进效果不好!!!

2)标记法 可能会造成无限制空转等待

3)非对称标记 结合了标记和轮转两种思想

两个进程:Peterson算法

多个进程:面包店算法

硬件方法:

1)单cpu实现临界区保护的方法:开关中断
在这里插入图片描述

cli = clear interuption ; //关中断
sti = set interuption;    //开中断

原理是,在进入区阻止调度schedule的发生,而调度是因为中断,所以就是关闭中断
但在多CPU上不行,因为无法控制关闭所有cpu上的中断。

2)硬件原子指令

其实是用 mutex锁信号量 来保护信号量,为了解决mutex仍然需要保护的问题
使用硬件级原子操作,不能被打断不能切出去进行调度

L18 进入睡眠的方式

  • 用 if 方式进入睡眠:
    在这里插入图片描述
  • 用 while 方式进入睡眠:
    在这里插入图片描述
    开关中断做信号量的保护,根据信号量的值是否睡眠,若不睡眠,加锁修改信号量
    在这里插入图片描述
    睡眠的实质,就是把自己写到阻塞队列上+改变自己的状态(也就是圈圈里的内容) ,然后再调用schedule切出去执行其他进程。
    事实上,圈圈里的内容形成了一个睡眠队列:
    在这里插入图片描述
    tmp是局部变量,存储在当前进程的内核栈中,所以可以通过当前的task_struct找到内核栈中的tmp;tmp指向的是下一个task_struct,又可以通过下一个task_struct找到tmp,指向下下一个……由此形成队列。实际上把自己作为了队首,然后用tmp记录了队列里的下一个pcb,便于在唤醒的时候能够唤醒队列里的下一个进程。

利用中断唤醒wake_up阻塞队列,进程被唤醒后要从当时进入sleep的后面一句继续执行:
在这里插入图片描述
也就是if(tmp) tmp->state=0;
因为tmp指向睡眠队列中的下一个进程,所以会继续唤醒队列中下一个进程。

while的好处:

  • while把所有阻塞进程一次性全部唤醒,让schedule来决定switch_to谁,这样可以不按照先来先后,可以按优先级。if的话只是将一个进程唤醒。
  • 并且优先级最高的执行后,其他被唤醒的进程会继续上锁,不会一连串执行:
  • 被唤醒后还要判断一遍while(),schedule切换到优先级最高的进程,执行bh->b_lock=1,而其他被唤醒的在进行while(bh->b_lock)判断时,就执行sleep_on了。
    在这里插入图片描述

L19 死锁处理

死锁:多个进程由于互相等待对方持有的资源而造成谁都无法执行的情况

  • 必要条件(怎么样造成死锁:形成了资源等待环路!!!)
    在这里插入图片描述
  • 死锁处理方法
    在这里插入图片描述
  1. 死锁预防:破坏死锁出现的条件
    一次性申请所有需要资源,不会占有资源后再去申请其他资源
    对资源进行排序,按照顺序申请资源,不会出现环路等待
    缺点:资源浪费,利用率低。

  2. 死锁避免 :检测每个资源请求,假装分配,看看进程组是否会造成死锁,如果造成死锁就拒绝如果找到了安全序列,就可以这样分配。
    银行家算法
    但是这样的算法时间复杂度太高,每次请求资源都算一次,效率太低

  3. 死锁检测+恢复:发现问题再处理
    等出现问题了,有一些进程因为死锁而停住了,再处理,选择一个进程进行回滚,然后再用银行家算法来算是否能找到安全序列,如果不行,再回滚,直到所有程序都能执行。
    但是回滚是个大问题!!!已经写入磁盘,还得退回来,那就很麻烦了。

  4. 死锁忽略:
    windows,linux个人版都不做死锁处理,直接忽略,大不了重启就好了,小概率事件,代价可以接受

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值