Linux进程调度技术的前世今生之“今生”【干货整理】

本文详细探讨了Linux内核中的进程调度技术,从O(1)调度器的runqueue结构、时间片处理、优先级策略,到公平调度思想的引入和CFS调度器的模块化设计、动态时间片分配、虚拟时间概念。通过这些调度策略的演变,展示了如何提高系统响应时间和保证进程公平性。
摘要由CSDN通过智能技术生成

【推荐阅读】

路由选择协议——RIP协议

轻松学会linux下查看内存频率,内核函数,cpu频率

纯干货,linux内存管理——内存管理架构(建议收藏)

概述Linux内核驱动之GPIO子系统API接口

一篇长文叙述Linux内核虚拟地址空间的基本概括

2、O(1)调度器的软件功能划分
下图是一个O(1)调度器的软件框架:

O(n)调度器中只有一个全局的runqueue,严重影响了扩展性,因此在O(1)调度器中引入了per-CPU runqueue的概念。系统中所有的可运行状态的进程首先经过负载均衡模块挂入各个CPU的runqueue,然后由主调度器和tick调度器驱动该CPU上的调度行为。由于篇幅的原因,我们在本文中不讲解负载均衡模块,把重点放在单个CPU上的任务调度算法。

由于引入了per-CPU runqueue,O(1)调度器删除了全局runqueue的spin lock,而是把这个spin lock放入到per-CPU runqueue数据结构中(rq->lock),通过把一个大锁细分成小锁,可以大大降低调度延迟,提升系统响应时间。这种方法在内核中经常使用,是一个比较通用的性能优化方法。

通过上面的软件结构划分可以解决O(n)调度的SMP扩展性问题和CPU空转问题。此外,好的复杂均衡算法也可以解决O(n)调度器的task bouncing 问题。

3、O(1)调度器的runqueue结构

O(1)调度器的基本优化思路就是把原来runqueue上的单链表变成多个链表,即每一个优先级的进程被挂入不同链表中。相关的软件结构可以参考下面的图片:

在调度器中,runqueue是一个很重要的数据结构,它最重要的作用是管理那些处于可运行状态的进程。O(1)调度器引入了优先级队列的概念来管理task,具体由struct prio_array抽象:

struct prio_array { unsigned int nr_active; unsigned long bitmap[BITMAP_SIZE]; struct list_head queue[MAX_PRIO];};

由于支持140个优先级,因此queue成员中有140个分别表示各个优先级的链表头,不同优先级的进程挂入不同的链表中。bitmap是表示各个优先级进程链表是空还是非空。nr_active表示这个队列中有多少个task。在这些队列中,100~139是普通进程的优先级,其他的是实时进程的优先级。因此,在O(1)调度器中,RT进程和普通进程被区分开了,普通进程根本不会影响RT进程的调度。

Runqueue中有两个优先级队列(struct prio_array)分别用来管理active(即时间片还有剩余)和expired(时间片耗尽)的进程。Runqueue中有两个优先级队列的指针,分别指向这两个优先级队列。随着系统的运行,active队列的task一个个的耗尽其时间片,挂入到expired队列。当active队列的task为空的时候,切换active和expired队列,开始一轮新的调度过程。

虽然在O(1)调度器中task组织的形式发生了变化,但是其核心思想仍然和O(n)调度器一致的,都是把CPU资源分成一个个的时间片,分配给每一个runnable的进程。进程用完其额度后被抢占,等待下一个调度周期的到来。

4、核心调度算法

主调度器(就是schedule函数)的主要功能是从该CPU的runqueue找到一个最合适的进程调度执行。其基本的思路就是从active优先级队列中寻找,代码如下:

idx = sched_find_first_bit(array->bitmap); queue = array->queue + idx; next = list_entry(queue->next, task_t, run_list);

首先在当前active优先级队列的bitmap寻找第一个非空的进程链表,然后从该链表中找到的第一个节点就是最适合下一个调度执行的进程。由于没有遍历整个链表的操作,因此这个调度器的算法复杂度是一个常量,从而解决了O(n)算法复杂度的issue。

如果当前active优先级队列中“空无一人”(nr_active等于0),那么这时候就需要切换active和expired优先级队列了:

if (unlikely(!array->nr_active)) { rq->active = rq->expired; rq->expired = array; array = rq->active; }

切换很快,并没有一个遍历所有进程重新赋default时间片的操作(大大缩减了runqueue临界区的size)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值