Linux进程O(1)调度算法,面试必考哦

进程调度有很多方法,这里只讨论Linux下的进程调度,先说下,这个是高端面试必考题,既然我发文了,大家最好看看,而且目前看到的写得最好的文章,推荐给大家。

====

Linux是一个支持多任务的操作系统,而多个任务之间的切换是通过 调度器 来完成,调度器 使用不同的调度算法会有不同的效果。

Linux2.4版本使用的调度算法的时间复杂度为O(n),其主要原理是通过轮询所有可运行任务列表,然后挑选一个最合适的任务运行,所以其时间复杂度与可运行任务队列的长度成正比。

而Linux2.6开始替换成名为 O(1)调度算法,顾名思义,其时间复杂度为O(1)。虽然在后面的版本开始使用 CFS调度算法(完全公平调度算法),但了解 O(1)调度算法 对学习Linux调度器还是有很大帮助的,所以本文主要介绍 O(1)调度算法 的原理与实现。

由于在 Linux 内核中,任务和进程是相同的概念,所以在本文混用了任务和进程这两个名词。

O(1)调度算法原理

prio_array 结构

O(1)调度算法 通过优先级来对任务进行分组,可分为140个优先级(0 ~ 139,数值越小优先级越高),每个优先级的任务由一个队列来维护。prio_array 结构就是用来维护这些任务队列,如下代码:

#define MAX_USER_RT_PRIO    100
#define MAX_RT_PRIO         MAX_USER_RT_PRIO
#define MAX_PRIO            (MAX_RT_PRIO + 40)#define BITMAP_SIZE ((((MAX_PRIO+1+7)/8)+sizeof(long)-1)/sizeof(long))struct prio_array {int nr_active;unsigned long bitmap[BITMAP_SIZE];struct list_head queue[MAX_PRIO];
};

下面介绍 prio_array 结构各个字段的作用:

  1. nr_active: 所有优先级队列中的总任务数。

  2. bitmap: 位图,每个位对应一个优先级的任务队列,用于记录哪个任务队列不为空,能通过 bitmap 够快速找到不为空的任务队列。

  3. queue: 优先级队列数组,每个元素维护一个优先级队列,比如索引为0的元素维护着优先级为0的任务队列。

下图更直观地展示了 prio_array 结构各个字段的关系:

如上图所述,bitmap 的第2位和第6位为1(红色代表为1,白色代表为0),表示优先级为2和6的任务队列不为空,也就是说 queue 数组的第2个元素和第6个元素的队列不为空。

runqueue 结构

另外,为了减少多核CPU之间的竞争,所以每个CPU都需要维护一份本地的优先队列。因为如果使用全局的优先队列,那么多核CPU就需要对全局优先队列进行上锁,从而导致性能下降。

每个CPU都需要维护一个 runqueue 结构,runqueue 结构主要维护任务调度相关的信息,比如优先队列、调度次数、CPU负载信息等。其定义如下:

struct runqueue {spinlock_t lock;unsigned long nr_running,nr_switches,expired_timestamp,nr_uninterruptible;task_t *curr, *idle;struct mm_struct *prev_mm;prio_array_t *active, *expired, arrays[2];int prev_cpu_load[NR_CPUS];task_t *migration_thread;struct list_head migration_queue;atomic_t nr_iowait;
};

runqueue 结构有两个重要的字段:active 和 expired,这两个字段在 O(1)调度算法 中起着至关重要的作用。我们先来了解一下 O(1)调度算法 的大概原理。

我们注意到 active 和 expired 字段的类型为 prio_array,指向任务优先队列。active 代表可以调度的任务队列,而 expired 字段代表时间片已经用完的任务队列。active 和 expired 会进行以下两个过程:

  1. 当 active 中的任务时间片用完,那么就会被移动到 expired 中。

  2. 当 active 中已经没有任务可以运行,就把 expired 与 active 交换,从而 expired 中的任务可以重新被调度。

如下图所示:

O(1)调度算法 把140个优先级的前100个(0 ~ 99)作为 实时进程优先级,而后40个(100 ~ 139)作为 普通进程优先级。实时进程被放置到实时进程优先级的队列中,而普通进程放置到普通进程优先级的队列中。

实时进程调度

实时进程分为 FIFO(先进先出) 和 RR(时间轮询) 两种,其调度算法比较简单,如下:

  1. 先进先出的实时进程调度:如果调度器在执行某个先进先出的实时进程,那么调度器会一直运行这个进程,直至其主动放弃运行权(退出进程或者sleep等)。

  2. 时间轮询的实时进程调度:如果调度器在执行某个时间轮询的实时进程,那么调度器会判断当前进程的时间片是否用完,如果用完的话,那么重新分配时间片给它,并且重新放置回 active 队列中,然后调度到其他同优先级或者优先级更高的实时进程进行运行。

普通进程调度

每个进程都要一个动态优先级和静态优先级,静态优先级不会变化(进程创建时被设置),而动态优先级会随着进程的睡眠时间而发生变化。动态优先级可以通过以下公式进行计算:

动态优先级 = max(100, min(静态优先级 – bonus + 5), 139))

上面公式的 bonus(奖励或惩罚) 是通过进程的睡眠时间计算出来,进程的睡眠时间越大,bonus 的值就越大,那么动态优先级就越高(前面说过优先级的值越小,优先级越高)。

另外要说明一下,实时进程的动态优先级与静态优先级相同。

当一个普通进程被添加到运行队列时,会先计算其动态优先级,然后按照动态优先级的值来添加到对应优先级的队列中。而调度器调度进程时,会先选择优先级最高的任务队列中的进程进行调度运行。

运行时间片计算

当进程的时间用完后,就需要重新进行计算。进程的运行时间片与静态优先级有关,可以通过以下公式进行计算:

静态优先级 < 120,运行时间片 = max((140-静态优先级)*20, MIN_TIMESLICE)
静态优先级 >= 120,运行时间片 = max((140-静态优先级)*5, MIN_TIMESLICE)

O(1)调度算法实现

接下来我们分析一下 O(1)调度算法 在内核中的实现。

时钟中断

时钟中断是由硬件触发的,可以通过编程来设置其频率,Linux内核一般设置为每秒产生100 ~ 1000次。时钟中断会触发调用 scheduler_tick() 内核函数,其主要工作是:减少进程的可运行时间片,如果时间片用完,那么把进程从 active 队列移动到 expired 队列中。代码如下:

void scheduler_tick(int user_ticks, int sys_ticks)
{runqueue_t *rq = this_rq();task_t *p = current;...// 处理普通进程if (!--p->time_slice) {                // 减少时间片, 如果时间片用完dequeue_task(p, rq->active);       // 把进程从运行队列中删除set_tsk_need_resched(p);           // 设置要重新调度标志p->prio = effective_prio(p);       // 重新计算动态优先级p->time_slice = task_timeslice(p); // 重新计算时间片p->first_time_slice = 0;if (!rq->expired_timestamp)rq->expired_timestamp = jiffies;// 如果不是交互进程或者没有出来饥饿状态if (!TASK_INTERACTIVE(p) || EXPIRED_STARVING(rq)) {enqueue_task(p, rq->expired); // 移动到expired队列} elseenqueue_task(p, rq->active);  // 重新放置到active队列}...
}

上面代码主要完成以下几个工作:

  1. 减少进程的时间片,并且判断时间片是否已经使用完。

  2. 如果时间片使用完,那么把进程从 active 队列中删除。

  3. 调用 set_tsk_need_resched() 函数设 TIF_NEED_RESCHED 标志,表示当前进程需要重新调度。

  4. 调用 effective_prio() 函数重新计算进程的动态优先级。

  5. 调用 task_timeslice() 函数重新计算进程的可运行时间片。

  6. 如果当前进程是交互进程或者出来饥饿状态,那么重新加入到 active 队列。

  7. 否则把今天移动到 expired 队列。

任务调度

如果进程设置了 TIF_NEED_RESCHED 标志,那么当从时钟中断返回到用户空间时,会调用 schedule() 函数进行任务调度。schedule() 函数代码如下:

void schedule(void)
{...prev = current;  // 当前需要被调度的进程rq = this_rq();  // 获取当前CPU的runqueuearray = rq->active; // active队列// 如果active队列中没有进程, 那么替换成expired队列if (unlikely(!array->nr_active)) {rq->active = rq->expired;rq->expired = array;array = rq->active;rq->expired_timestamp = 0;}idx = sched_find_first_bit(array->bitmap); // 找到最高优先级的任务队列queue = array->queue + idx;next = list_entry(queue->next, task_t, run_list); // 获取到下一个将要运行的进程...prev->sleep_avg -= run_time; // 减少当前进程的睡眠时间...if (likely(prev != next)) {...prev = context_switch(rq, prev, next); // 切换到next进程进行运行...}...
}

上面代码主要完成以下几个步骤:

  1. 如果当前 runqueue 的 active 队列为空,那么把 active 队列与 expired 队列进行交换。

  2. 调用 sched_find_first_bit() 函数在 bitmap 中找到优先级最高并且不为空的任务队列索引。

  3. 减少当前进程的睡眠时间。

  4. 调用 context_switch() 函数切换到next进程进行运行。


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/466047.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

让Visual Studio 也支持JS代码折叠 —— 续 [ Visual Studio | Js | ScriptOutline | SmallOutline ]...

前言 上文让JS代码折叠的功能能用了&#xff0c;本文将对代码继续改进以期更好用、更实用&#xff0c;随后有介绍Visual Studio JS方面的几个插件。 文章 1. VS2003折叠代码的Micro 2. MSDN 3. Document Outline for Client Script in Visual Studio 2005 正文 …

嵌入式OS入门笔记-以RTX为案例:二.快速移植到RTX

嵌入式OS入门笔记-以RTX为案例&#xff1a;二.快速移植到RTX本篇笔记将简单介绍RTX&#xff0c;包括基本架构&#xff0c;如何在Keil中配置。需要安装ARM-MDK和一块硬件板&#xff0c;笔记以STM32F4Discovery为例子。1.为什么要用RTOS&#xff1f;尽管把所有程序放在一个大的循…

再解析下内核自旋锁和优先级翻转问题

[内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼&#xff1f;Linux内核自旋锁之前写的自旋锁的文章&#xff0c;现在再加一篇&#xff0c;可能单纯的一两次说明不能把问题说清楚。所以再写一篇文…

ios 逆向编程(环境搭建)

首先如果你想要逆向其他的APP 动态的查看 或者修改人家APP里面的东西 1&#xff0c; 首先要有一台越狱的手机 最好是9.1以下的&#xff0c;因为9.2以上&#xff08;包括9.2&#xff09;就不能完美越狱了 2&#xff0c;手机也要5s以上的&#xff08;因为从5S开始支持arm64架构&…

最大、最小堆的实现

最大最小堆 堆是一种经过排序的完全二叉树&#xff0c;其中任一非终端节点的数据值均不大于&#xff08;或不小于&#xff09;其左子节点和右子节点的值。 最大堆和最小堆是二叉堆的两种形式。 最大堆&#xff1a;根结点的键值是所有堆结点键值中最大者。 最小堆&#xff1a;根…

嵌入式OS入门笔记-以RTX为案例:四.简单的时间管理

嵌入式OS入门笔记-以RTX为案例&#xff1a;四.简单的时间管理 上一节简单记录了进程task。有了进程以后&#xff0c;我们需要关心怎么样分配CPU资源&#xff08;或者运行时间&#xff09;给每个进程。那么就要引入排程&#xff08;scheduling&#xff09;的概念。排程一般都是O…

我等这个含蓄的技术男当上了CEO

大家好&#xff0c;祝大家五一节日快乐&#xff01;今天没有写技术文章&#xff0c;今天想吹一个人&#xff0c;他是我的朋友&#xff0c;他做公众号很久了&#xff0c;技术文章写的也不错&#xff0c;但是阅读和关注量一直没有上来&#xff0c;我之前好几次在公众号上转发了他…

广东总冠军

lets go tiger 看这篇文章之前&#xff0c;先看看我们看球的视频 恭喜广东拿下总冠军&#xff01; 恭喜胡明轩夺得FMVP&#xff01; 我当时预测的是周鹏或者胡明轩拿下FMVP&#xff0c;最后是胡明轩&#xff0c;广东后场三条枪表现都非常亮眼。如果是上一场广东夺冠&#xff0c…

每日一题(1) —— 数组计算

判断下面代码是否可执行&#xff1f;如果可执行&#xff0c;执行结果是多少&#xff1f; #include <stdio.h>int main(void) {int array[10] {0, 2, 3, 4, 5, 6, 7, 8, 9, 10};0[array] 1;printf("%d\n", (-1)[array 5]);return 0; } 分析&#xff1a; C语…

内存文章汇总,并剖析mmap

在看这篇文章之前&#xff0c;可以先看看下面这几篇文章Linux内存&#xff0c;先看这篇文章Linux内存寻址方式Linux虚拟内存TLBLinux物理内存初始化Linux io内存存在的意义~修改cmdline 把内存改成512MB用mtrace定位内存泄漏什么是内存泄漏&#xff1f;Linux内存管理slub分配器…

[综述泛读] A survey on web services composition (IJWGS, 2005)

Time: 2.5 hours Dustdar S, Schreiner W. "A survey on web services composition." International Journal of Web and Grid Services: 1-30. 2005 (30 pages, 单栏) (gs:169) Schahram Dustdar (维也纳技术大学, full prof) Dusdar是Distributed Systems Group的老…

韦老师的开发板和嵌入式书籍赠送

大家五一快乐&#xff01;我知道这个时候大家都没有什么心思学习&#xff0c;所以找了联合了几个朋友一起给大家送点东西。这几个技术号主都非常用心的给大家分享技术文章&#xff0c;我相信&#xff0c;跟他们一起&#xff0c;你们也能变得更加优秀。奖品包括&#xff1a;1. 韦…

每日一题(2)—— -2与2的比较

分析下面的代码&#xff0c;求运行结果。 #include <stdio.h>int main(void) {if(-2 > 2){printf("11111\r\n");}else{printf("22222\r\n");}return 0; }分析&#xff1a; -2和2都没有声明存储类型&#xff0c;编译器默认按int存储&#xff0c;所…

truffle unbox react 出坑指南

最近几天差点就被这鬼东西给逼疯了&#xff0c;truffle init 、truffle unbox webpack 不管我怎么运行都是对的&#xff0c;唯独truffle unbox react 不管在哪个windows都会报错&#xff0c;换了好几台电脑&#xff0c;心都累完了&#xff0c;还好我坚持了下来&#xff0c;找了…

单片机6年想转嵌入式Linux ,不知如何下手?

刷知乎看到下面这个提问。单片机6年想转嵌入式Linux &#xff0c;不知如何下手&#xff1f;现在挺尴尬&#xff0c;做的单片机产品总是感觉重复重复再重复&#xff0c;想学习点新东西&#xff0c;不知道如何转。说实话&#xff0c;这个问题自己关注了很久。今天就借题主这个问题…

每日一题(3)—— -2与2的比较(二)

分析下面的代码&#xff0c;求运行结果。 #include <stdio.h>int main(void) {if(-2L > 2UL){printf("11111\r\n");}else{printf("22222\r\n");}return 0; }分析&#xff1a; 常量后面接L表示long型存储&#xff0c;UL表示unsigned long型存储&a…

嵌入式技术面试时的10大潜规则

编排 | strongerHuang微信公众号 | 嵌入式专栏找工作也是一门技能&#xff0c;有的人很快就找到自己喜欢的工作&#xff0c;有的人找了很久也没找到合适的工作。下面给大家分享几点找工作过程中存在的“潜规则”内容。嵌入式专栏1面试的本质不是考试&#xff0c;而是告诉面试官…

每日一题(4)—— (a ^ b 2)

分析下面的代码&#xff0c;求运算结果。 #include <stdio.h>int main(void) {int a 6, b 4;printf("%d\n", a ^ b << 2);return 0; }分析&#xff1a; 主要是考的是“优先级”和按位“异或”&#xff0c; 左移运算符优先级高于按位异或运算符&#…

轻轻的你来了,悄悄的你走了,邓总没有带走一个bug

写这篇文章的时候&#xff0c;比较晚&#xff0c;所以思绪是清晰的&#xff0c;这个时候刚好是邓总来公司跟我交接离职的最后一天。给邓总制作的告别MV&#xff1a;我跟邓总2017年12月在恒大认识&#xff0c;我们入职时间相差一周&#xff0c;我入职的时候就开始注意到旁边的这…

神奇的css3(2)动画

四、Css3 2D动画 1、2D 转换方法 函数 描述 matrix(n,n,n,n,n,n) 定义 2D 转换&#xff0c;使用六个值的矩阵。 translate(x,y) 定义 2D 转换&#xff0c;沿着 X 和 Y 轴移动元素。 translateX(n) 定义 2D 转换&#xff0c;沿着 X 轴移动元素。 translateY(n) 定义 2D…