Linux2.6内核--进程调度理论

从1991年Linux的第1版到后来的2.4内核系列,Linux的调度程序都相当简陋,设计近乎原始,见0.11版内核进程调度。当然它很容易理解,但是它在众多可运行进程或者多处理器的环境下都难以胜任。

正因为如此,在Linux2.5开发系列的内核中,调度程序做了大手术。开始采用了一种叫做O(1)调度程序的新调度程序——它是因为其算法的行为而得名的。它解决了先前版本Linux调度程序的许多不足,引入了许多强大的新特性和性能特征。O(1)调度程序虽然对于大服务器的工作负载很理想,但是在有很多交互程序要运行的桌面系统上则表现不佳,因为其缺少交互进程。

自2.6内核系统开发初期,开发人员为了提高对交互程序的调度性能引入了新的进程调度算法。其中最为著名的是“反转楼梯最后期限调度算法”(RSDL),该算法吸取了队列理论,将公平调度的概念引入了Linux调度程序。并且最终在2.6.23内核版本中替代了O(1)调度算法,它此刻被称为“完全公平调度算法”,或者简称CFS。

1.策略

策略决定调度程序在何时让什么进程运行。调度器的策略往往就决定系统的整体印象,并且,还要负责优化使用处理器时间。无论从哪个方面来看,它都是至关重要的。

进程可以被分为I/O消耗型和处理器消耗型。

1.1.进程优先级

调度算法中最基本的一类就是基于优先级的调度。Linux采用了2种不同的优先级范围。第一种是用nice值,它的范围是从-20到+19,默认值为0,越大的nice值意味着更低的优先级;第二种范围是实时优先级,其值是可配置的,默认情况下它的变化范围是从0到99(包括0和99),与nice值意义相反,越高的实时优先级数值意味着进程优先级越高。

1.2.时间片

时间片是一个数值,它表明进程在被抢占前所能持续运行的时间。I/O消耗型不需要长的时间片,而处理器消耗型的进程则希望越长越好(比如这样可以让它们的高速缓存命中率更高)。Linux的CFS调度器并没有直接分配时间片到进程,它是将处理器的使用比划分给了进程。这样一来,进程所获得的处理器时间其实是和系统负载密切相关的。

在多数操作系统中,是否要将一个进程立刻投入运行(也就是抢占当前进程),是完全由进程优先级和是否有时间片决定的。而在Linux中使用新的CFS调度器,其抢占时机取决于新的可运行程序消耗了多少处理器使用比。如果消耗的使用比比当前进程小,则新进程立刻投入运行,抢占当前进程。否则,将推迟其运行。

2.Linux调度算法

在前面内容中,我们抽象地讨论了进程调度原理。

2.1.调度器类

Linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性地选择调度算法。

这种模块化结构被称为调度器类,它允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程。每个调度器都有一个优先级,基础的调度器代码定义在kernel/sched.c文件中,它会按照优先级顺序遍历调度类,拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面要执行的那一个程序。

完全公平调度(CFS)是一个针对普通进程的调度类。CFS采用的方法是对时间片分配方式进行根本性的重新设计(就进程调度器而言):完全摒弃时间片而是分配给进程一个处理器使用比重。通过这种方式,CFS确保了进程调度中能有恒定的公平性,而将切换频率置于不断变动中。

2.2.公平调度

CFS的做法是允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,而不再采用分配给每个进程时间片的做法了,CFS在所有可运行进程总数基础上计算出一个进程应该运行多久,而不是依靠nice值来计算时间片。

nice值在CFS中被作为进程获得的处理器运行比的权重:越高的nice值(越低的优先级)进程获得更低的处理器使用权重,这是相对默认nice值进程的进程而言的;相反,更低的nice值(越高的优先级)的进程获得更高的处理器使用权重。

绝对的nice值不再影响调度决策:只有相对值才会影响处理器时间的分配比例。

总结一下,任何进程所获得的处理器时间是由它自己和其他所有可运行进程nice值的相对差值决定的。nice值对时间片的作用不再是算数加权,而是几何加权。任何nice值对应的绝对时间不再是一个绝对值,而是处理器的使用比。CFS称为公平调度器是因为它确保给每个进程公平的处理器使用比。CFS不是完美的公平,它只是近乎完美的多任务。

3.Linux调度的实现

● 时间记账

● 进程选择

● 调度器入口

● 睡眠和唤醒

3.1.时间记账

CFS不再有时间片的概念,但是它也必须维护每个进程运行的时间记账,因为它需要确保每个进程只在公平分配给它的处理器时间内运行。CFS使用调度器实体结构(<linux/sched.h>的struct_sched_entiry中)来追踪进程运行记账。

vruntime变量存放进程的虚拟运行时间,该运行时间(花在运行上的时间和)的计算是经过了所有可运行进程总数的标准化(或者说是被加权的)。虚拟时间是以ns为单位的,所以vruntime和定时器节拍不再相关。CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它还应该再运行多久。

3.2.进程选择

在前面谈到若存在一个完美的多任务处理器,所有可运行进程的vruntime值将一致。但事实上我们没有找到完美的多任务处理器,因此CFS试图利用一个简单的规则去均衡进程的虚拟运行时间:当CFS需要选择下一个运行进程时,它会挑一个具有最小vruntime的进程。这其实就是CFS调度算法的核心:选择具有最小vruntime的任务。

CFS使用红黑树来组织可运行进程队列,并利用其迅速找到最小vruntime值的进程。

我们先假设,有那么一个红黑树存储了系统中所有的可运行进程,其中节点的键值便是可运行进程的虚拟运行时间。CFS调度器选取待运行的下一个进程,是所有进程中vruntime最小的那个,它对应的便是树中最左侧的叶子节点。CFS的进程选择算法可简单总结为“运行rbtree树中最左边叶子节点所代表的那个进程”。实现这一过程的函数是_pick_next_entity(),它定义在kernel/sched_fair.c中。

3.3.调度器入口

进程调度的主要入口点是函数schedule(),定在kernel/sched.c中。该函数中唯一重要的事情是,它会调用pick_next_task(),被调用的这个函数会以优先级为序,从高到低,依次检查每一个调度类,并且从最高优先级的调度类中,选择最高优先级的进程。

3.4.睡眠和唤醒

休眠的进程处于一个特殊的不可执行状态。这点非常重要,如果没有这种特殊状态的话,调度程序就可能选出一个本不愿意被执行的进程。休眠的一个常见原因就是文件I/O。

无论哪种情况,内核的操作都相同:进程把自己标记成休眠状态,从可执行红黑树中移出,放入等待队列,然后调用schedule()选择和执行一个其他进程。唤醒的过程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行红黑树中。

4.抢占和上下文切换

上下文切换,也就是从一个可执行进程切换到另一个可执行进程,由定义在kernel/sched.c中的context_switch()函数负责处理。每当一个新的进程被选出来准备投入运行的时候,schedule()就会调用该函数。它完成了两项基本的工作:

● 调用声明在<asm/mmu_context.h>中的switch_mm(),该函数负责把虚拟内存从上一个进程映射切换到新进程中。

● 调用声明在<asm/system.h>中的switch_to(),该函数负责从上一个进程的处理器状态切换到新进程的处理器状态等。

4.1.用户抢占

内核即将返回用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,此时就会发生用户抢占。用户抢占在以下情况时产生:

● 从系统调用返回用户空间时。

● 从中断处理程序返回用户空间时。

4.2.内核抢占

与其他大部分的Unix变体和其他大部分的操作系统不同,Linux完整地支持内核抢占。在不支持内核抢占的内核中,内核代码可以一直执行,到它完成为止。也就是说,调度程序没有办法在一个内核级的任务正在执行的时候重新调度。

如果没有持有锁,正在执行的代码就是可重新导入的,也就是可以抢占的。

每个进程的thread_info引入preempt_count计数器,初始值为0,每当使用锁的时候数值加1,释放锁的时候数值减1.当数值为0的时候,内核就可执行抢占。从中断返回内核空间的时候,内核会检查need_resched和preempt_count的值。如果need_resched被设置,并且preempt_count为0的话,这说明有一个更为重要的任务需要执行并且可以安全地抢占,此时,调度程序就会被调用。如果preempt_count不为0,说明当前任务持有锁,所以抢占是不安全的。这时,内核就会像通常那样直接从中断返回当前执行进程。

时间: 2024-12-23 10:06:24

Linux2.6内核--进程调度理论的相关文章

Linux2.6内核进程调度系列--scheduler_tick()函数2.更新实时进程的时间片

RT /** * 递减当前进程的时间片计数器,并检查是否已经用完时间片. * 由于进程的调度类型不同,函数所执行的操作也有很大差别. */ /* 如果是实时进程,就进一步根据是FIFO还是RR类型的实时进程 */ if (rt_task(p)) { /** * 对SCHED_RR类型(时间片轮转)的实时进程,需要递减它的时间片. * 对SCHED_FIFO类型(先进先出)的实时进程,什么都不做, * 退出.在这种情况下, * current进程不可能被比其优先级低或其优先级相等的进程所抢占, *

Linux内核编程:Linux2.6内核源码解析_进程遍历 &nbsp; &nbsp; &nbsp; &nbsp;

/*     *File    : test.c   *Author  : DavidLin        *Date    : 2014-12-07pm        *Email   : [email protected] or [email protected]        *world   : the city of SZ, in China        *Ver     : 000.000.001        *history :     editor      time    

Linux2.6内核实现的是NPTL

NPTL是一个1×1的线程模型,即一个线程对于一个操作系统的调度进程,优点是非常简单.而其他一些操作系统比如Solaris则是MxN的,M对应创建的线程数,N对应操作系统可以运行的实体.(N<M),优点是线程切换快,但实现稍复杂 转 Linux2.6内核实现的是NPTL线程模型,依然是用进程来模拟线程,但新引入了线程组(进程组)的概念,使得实现效率更好. 在2.4内核中,不存在线程组的概念,当运行一个多线程得程序时,使用ps命令,可以看到有许多个进程,在ps命令看来,线程基本上是等同于进程,在信

Linux2.6 内核的 Initrd 机制解析

Linux 的 initrd 技术是一个非常普遍使用的机制,linux2.6 内核的 initrd 的文件格式由原来的文件系统镜像文件转变成了 cpio 格式,变化不仅反映在文件格式上, linux 内核对这两种格式的 initrd 的处理有着截然的不同.本文首先介绍了什么是 initrd 技术,然后分别介绍了 Linux2.4 内核和 2.6 内核的 initrd 的处理流程.最后通过对 Linux2.6 内核的 initrd 处理部分代码的分析,使读者可以对 initrd 技术有一个全面的认

mini2440 官方linux-2.6内核文件zImage编译

官方linux-2.6内核文件zImage编译 by HYH | 2018 年 1 月 1 日 下午 5:34 一.说明 1.编译linux内核需要make和arm的交叉编译工具链(gcc),由于linux-2.6较老,采用友善之臂官方的gcc即可,不要采用最新的gcc. 2.编译过程中需要ncurses和zlib的支持库.对于Debian系的linux用一下指令即可: apt-get install ncurses-dev zlib1g-dev 3.编译过程中还可能用到tar和任意一种文本编辑

Linux0.11内核--进程调度分析之2.调度

上一篇说到进程调度归根结底是调用timer_interrupt函数,在system_call.s中: #### int32 -- (int 0x20) 时钟中断处理程序.中断频率被设置为100Hz(include/linux/sched.h,5), # 定时芯片8253/8254 是在(kernel/sched.c,406)处初始化的.因此这里jiffies 每10 毫秒加1. # 这段代码将jiffies 增1,发送结束中断指令给8259 控制器,然后用当前特权级作为参数调用 # C 函数do

linux2.4内核调度

进程调度需要兼顾3种进程:交互进程,批处理进程,实时进程,在设计一个进程调度机制时需要考虑具体问题 (1)调度时机? 答:进程在用户空间可以pause()或者让内核设置进程为睡眠状态,以此调度,调度还可以强制性的发生在从系统调用返回前夕,以此每次从中断或异常处理返回到用户空间前夕(用户空间表示,只有cpu在用户空间运行时,发生异常或者中断才会调度),如果发生在内核的异常或者中断不会引起调度 缺陷:在实时进程中,内核中发生了中断,而且这个中断处理时间很长,并且内核中断不会调度.那就可能将调度过分延

【转载】linux2.6内核initrd机制解析

题记 很久之前就分析过这部分内容,但是那个时候不够深入,姑且知道这么个东西存在,到底怎么用,来龙去脉咋回事就不知道了.前段时间工作上遇到了一个initrd的问题,没办法只能再去研究研究,还好,有点眉目,索性整理了一下. 网络上流传着很多关于ramdisk.initrd的各种版本的分析,我的这篇源于对他们的理解,非常感谢那些前辈的无私奉献,要不然我们这些晚辈学起东 西来该是多么艰难呀.在这里需要特别声明的是如果文中有引用了您的思想而没有给出参考文献,请您原谅我的疏忽.晚辈就是需要站在像您这种巨人的

Linux内核进程调度的时机和进程切换

陈铁+ 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 对于现代操作系统,多任务是必备的,在linux系统下,进程会不断的被内核调度,从X进程切换为Y进程,以实现用户所见到的多任务状态,下面我们就看一看这样的过程,分析一下内核如何对进程调度,以及进程间如何切换. 内核使用schedule()函数实现进程的调度,而通常的用户进程要无法主动调度这个函数,只能通过中断处理过程(包括时钟中断