Linux内核分析(八)——进程的调度

禹晓博+ 原创作品转载请注明出处 + 欢迎加入《Linux内核分析》MOOC网易云课堂学习

一、进程调度简析

       我们知道现在的操作系统想要看起来很流程必须在后台进行快色的任务切换,才能达到表面上是哪个的多个进程同时执行的错觉。进程的切换我们实际上我之前的文章中已经有说过了,实际上进程的切换正式为了进程的调度做基础性的功能准备,这个不用说就能理解吧~调度自认就是要不停顿切换了。

       在典型的Unix操作系统的调度算繁重必须实现几个相互冲突的目标:那就是不但进程的响应时间要很快的同时,我们又要保证后台进程或者说人物执行的吞吐量要高,不要出现进程的饥饿现象(就是有些进程总也不能被执行),这就需要保证进程的优先级无乱高低又要尽可能的被公平的执行,这就是调度要解决的一系列问题。也就是我们常常说的进程调度策略。

       Linux的调度基于分时技术:所谓的分时就是指将时间划分成很小很小的片段,然后每个片段都相对公平的分给这一时刻需要执行的任务。这就我们所说的时间片调度。比如现在有是10个任务需要同时进行,每个任务都必须在1s内得到回馈,这样我么可以把1s分成10份,每过0.1s切换一个任务执行,这样宏观上看就是大家在同时的推进,当然实际上操作系统的时间片要小的多,比如1纳秒之类的。

       进入正题,我们知道进程调度是操作系统的核心功能。调度器只是是调度过程中的一部分,进程调度是非常复杂的过程,需要多个系统协同工作完成。本文所关注的仅为调度器,它的主要工作是在所有 RUNNING 进程中选择最合适的一个。作为一个通用操作系统,Linux 调度器将进程分为三类首先是交互式进程,此类进程有大量的人机交互,因此进程不断地处于睡眠状态,等待用户输入。典型的应用比如编辑器 vi。此类进程对系统响应时间要求比较高,否则用户会感觉系统反应迟缓。第二种就是批处理任务,此类进程不需要人机交互,在后台运行,需要占用大量的系统资源。但是能够忍受响应延迟。比如编译器。最后一种就是实时进程实时对调度延迟的要求最高,这些进程往往执行非常重要的操作,要求立即响应并执行。比如视频播放软件或飞机飞行控制系统,很明显这类程序不能容忍长时间的调度延迟,轻则影响电影放映效果,重则机毁人亡。根据进程的不同分类 Linux 采用不同的调度策略。对于实时进程,采用 FIFO 或者 Round Robin 的调度策略。对于普通进程,则需要区分交互式和批处理式的不同。传统 Linux 调度器提高交互式应用的优先级,使得它们能更快地被调度。而 CFS 和 RSDL 等新的调度器的核心思想“完全公平”。这个设计理念不仅大大简化了调度器的代码复杂度,还对各种调度需求的提供了更完美的支持。

1.1进程调度相关的数据结构

task_struct

       task_struct是进程在内核中对应的数据结构,它标识了进程的状态等各项信息。其中有一项thread_struct结构的变量thread,记录了CPU相关的进程状态信息,如内核控制的断点和栈指针等。在内核中获得当前进程task_struct结构使用宏current,该宏读取变量current_task得到指针。

thread_union   thread_info

       thread_union用于表示一个进程的内核态堆栈,当进程进入内核态时就会使用该进程对应的内核态堆栈。thread_union是一个联合体,由stack和thread_info两项组成。其中的stack用于直接访问内核态堆栈的各项,而thread_info表示了堆栈顶部(低地址部分)用于特殊用途的部分。thread_info结构定义在include/asm-x86/thread_info_32.h,定义了本进程task_struct结构的指针等在进入内核初期马上要访问的辅助数据。在内核中得到当前thread_info用current_thread_info函数得到,它通过对当前栈指针进行计算得到thread_info的指针。

sched_class

       sched_class是Linux2.6中调度算法对外的统一界面。Linux使用这个概念将进程调度的具体策略和进程切换的过程隔离开,使得组织有序且可以实现对不同类进程采用不同的调度策略。在Linux-2.6.26中sched_class有fair_sched_class,rt_sched_class和idle_sched_class三个实例,分别组织在kernel下的sched_fair.c、sched_rt.c、sched_idletask.c中。这三个实例在初始化时被串成了一个链表,依次为:rt,fair,idle

sched_entity   sched_rt_entity

    内核中对sched_entity的解释为“CFS stats for a schedulable entity (task, task-group etc)”,sched_rt_entity可类似解释。从这里可以看出这个结构的作用是存储一些调度算法相关的进程的状态。

rq

       rq是当前CPU上就绪进程所组织成的队列。这个结构体记录了每个队列的状态。rq结构体中有cfs_rq和rt_rq两个子结构,分别描述了该CPU上fair类型和rt类型进程的信息。

1.2 关于schedule函数的简析

       (其实应该放在实验里说的呢~强迫症犯了于是就放在1.2吧,看起来结构上好一些)schedule函数是进程调度的入口,在kernel/sched.c中。除去繁琐的检查、统计、上锁等操作,仔细观察,其主流如下:


       第一句中的prev在之前被赋值为rq->curr,因此是当前运行队列正在运行的进程。从字面看是将当前进程放回队列。第二句是从队列中取出下一个可运行的进程,叫next。接下来是进程的上下文切换工作。首先判断prev和next是否是同一个进程,若是,则不必切换。否则统计信息,接着设置rq->curr为next,然后调用context_switch来进行实际的上下文切换。schedule函数的简要分析结束。可见,理解进程的调度,核心是put_prev_task和pick _next_ task ;而理解进程的切换,核心是context_switch。下面就分两条线索,分别说明进程的切换和调度的流程。

二、实验过程

        这周的实验过程可能有点简陋啊,由于笔者这一整周都基本在面试某家集合了互联网商务金融云计算物流综合业务各种多的公司有木有,一面就要一个多小时,一天要好几面咩。啃啃,╮(╯▽╰)╭那位说了,面的啥啊?嘿嘿~不告诉你睡觉,所以不周到之处能望留言批评,我再补发。下周还要考试,真是任务紧啊~


       上面就是这几周一直在做的大小S啦,大家都懂的吧~调试跟踪内核启动么~。然后我们在进入一个新的终端控制台输入gdb开始调试啊,设置监视,加载文件符号表啊什么的,这都很简单的吧。


       按照惯例我们开始设置断点了当然就是要设置到那个schedule函数上了刚才分析过了。然后我们开到程序只想到可2866之后停了下来。然后我们看到在2867的地方有一个赋值过程就是讲当前的任务信息拷贝到任务结构体中,继续输入list我们就可以看到更多的代码了。


      继续向下我们看到有一个设置当先进程状态的语句在499那个地方。就是调度之后将进程状态设置为正在运行。


       然后我们又看到了自旋锁这个东西,这个不用说了吧,为什么要有呢。实际上在调度的过程中很多和上下文保存和切换的时候是不能被打断的,这个行为是非抢占的所以要有一些机制保证他不会被打断,同时这个过程有经常发生,所以我们不能用过于复杂的同步技术,自旋锁作为轻量级的同步工具这里就比较合适了(那位说了上次在之前的切换的文章里面你不就说要单说说自旋锁的咩,啃啃笔者是个说话算数的人,当时说的时候也强调了有机会~好么


       其实在2831之后也是这种赋值的语句用于给当前的任务做切换用,然后就有又会进入下一次的schedule了。

三、总结

       进程的调度少不了进程的切换,中做的关键操作是:切换地址空间、切换内核堆栈、切换内核控制流程,加上一些必要的寄存器保存和恢复。这里,除去地址空间的切换,其他操作要强调“内核”一词。这是因为,这些操作并非针对用户代码,切换完成后,也没有立即跑到next的用户空间中执行。用户上下文的保存和恢复是通过中断和异常机制,在内核态和用户态相互切换时才发生的。从这个意义上讲,切换地址空间才是本质上想要达到的“用户代码和数据的切换”,其余的切换不过是内核中不同的控制流程在“交接棒”而已。进程切换这里当初领会起来比较难,但是一旦理解,就会深深佩服这一系列过程的巧妙。特别是switch_to宏,几乎就是多一句嫌多,少一句嫌少。

       下面用几幅图总结一下switch_to的过程。

       首先是switch_to之前:


       切换堆栈之前:


       切换堆栈之后:


       push和jmp:


       switch_to返回:


       switch_to完成:


       看源代码我们知道schedule这个函数的中心环节是一个for循环,它遍历sched_class的每一个实例,并依次调用各个实例的pick_next_task函数。若返回非空,则将下一个进程设为它。由此可见,Linux调度系统采用的是操作系统理论中的多级队列调度,且上一个队列中进程的优先级恒比下一个队列中进程高。本文第一部分已述,sched_ class 链表依次为rt、fair、idle。因此,只要有rt类型进程就绪,调度时就一定会被选择,从而保证了rt类型进程的实时性。注释中还提到一点,idle队列中一定非空,因此在前两个类型的进程都没有就绪时,idle中的idle进程一定会被选中并调度,这保证了循环一定能终止。这里可以看到系统idle进程的重要性。









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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值