RT调度器源码简析

        实时调度器,对于不同优先级任务,优先级更高的任务始终获得CPU资源,直到其退出可运行状态,才会运行更低优先级的任务。对于相同优先级的任务,分为SCHED_FIFO和SCHED_RR两种调度策略。SCHED_FIFO先入先出,先入队的任务始终获得CPU资源,直到其退出可运行状态,才会运行同优先级下一个任务。SCHED_RR调度策略,同优先级的任务轮流获得CPU资源。

1.总体框架

        每个CPU都对应一个运行队列,用于管理运行在该CPU上的任务,内核将运行队列抽象为结构体struct rq,运行队列中用于管理实时任务的成员是struct rt_rq rt。结构体struct rt_rq保存运行队列的实时任务相关信息,可运行状态的任务保存在其成员struct rt_prio_array active中。

struct rq {
    ......................................
	struct cfs_rq		cfs;   //CFS任务相关
	struct rt_rq		rt;    //RT任务相关
	struct dl_rq		dl;    //DEADLINE任务相关
    ......................................
}
struct rt_rq {
    //active管理处于可运行状态的任务
	struct rt_prio_array	active;
	unsigned int		rt_nr_running;
	unsigned int		rr_nr_running;
    ......................................
};

        结构体struct rt_prio_array有两个成员,struct list_head queue[MAX_RT_PRIO]是长度为MAX_RT_PRIO的内核链表数组,一个实时优先级对应数组中一个内核链表,用于保存该优先级的调度实体。bitmap成员是一个位图,当某一优先级对应内核链表中有调度实体时,该优先级在位图中对应比特位置位。

#define MAX_USER_RT_PRIO	100
#define MAX_RT_PRIO		MAX_USER_RT_PRIO
struct rt_prio_array {
	DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1);
	struct list_head queue[MAX_RT_PRIO];
};

        RT调度器对应的调度实体抽象为结构体struct sched_rt_entity,调度器中的任务都有属于自己的调度实体,例如struct task_struct结构体中就包含成员struct sched_rt_entity rt。

struct task_struct {
    ...............................................
	struct sched_entity		se;
	struct sched_rt_entity		rt;
#ifdef CONFIG_CGROUP_SCHED
	struct task_group		*sched_task_group;
#endif
	struct sched_dl_entity		dl;
	................................................
}

        RT调度器相关结构体的关系如下图所示:

2.任务入队

        调度器入队接口enqueue_task,在RT调度器中对应的函数是enqueue_task_rt()。根据任务的优先级,在struct rt_prio_array中找到优先级对应的内核链表,并把任务加入到内核链表中,然后置位该优先级在位图中对应比特位。

static void enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
	struct sched_rt_entity *rt_se = &p->rt;//获取进程调度实体

	if (flags & ENQUEUE_WAKEUP)
		rt_se->timeout = 0;

	enqueue_rt_entity(rt_se, flags);

	if (!task_current(rq, p) && p->nr_cpus_allowed > 1)
		enqueue_pushable_task(rq, p);
}
static void enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
	struct rq *rq = rq_of_rt_se(rt_se);

	dequeue_rt_stack(rt_se, flags);
	for_each_sched_rt_entity(rt_se)
		__enqueue_rt_entity(rt_se, flags);
	enqueue_top_rt_rq(&rq->rt);
}
static void __enqueue_rt_entity(struct sched_rt_entity *rt_se, unsigned int flags)
{
	struct rt_rq *rt_rq = rt_rq_of_se(rt_se);
	struct rt_prio_array *array = &rt_rq->active;
	struct rt_rq *group_rq = group_rt_rq(rt_se);
	struct list_head *queue = array->queue + rt_se_prio(rt_se);//优先级对应内核链表
    ..............................................................................
	if (move_entity(flags)) {
		WARN_ON_ONCE(rt_se->on_list);
		// 加入优先级对应内核链表
		if (flags & ENQUEUE_HEAD)
			list_add(&rt_se->run_list, queue);
		else
			list_add_tail(&rt_se->run_list, queue);
                  //置位优先级在位图中对应比特位
		__set_bit(rt_se_prio(rt_se), array->bitmap);
		rt_se->on_list = 1;
	}
	rt_se->on_rq = 1;

	inc_rt_tasks(rt_se, rt_rq);
}

3.选择下一个运行任务

        当有任务退出可运行状态,或者有任务被唤醒时,会调用调度器pick_next_task接口,选择下一个运行的任务。RT调度器pick_next_task接口对应的函数是pick_next_task_rt(),首先通过位图找到有可运行状态任务的最高优先级,然后通过优先级找到对应的内核链表,最后选择内核链表第一个成员作为接下来运行的任务。

static struct task_struct *pick_next_task_rt(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
	struct task_struct *p;

	WARN_ON_ONCE(prev || rf);

	if (!sched_rt_runnable(rq))
		return NULL;

	p = _pick_next_task_rt(rq);
	set_next_task_rt(rq, p, true);
	return p;
}
static struct task_struct *_pick_next_task_rt(struct rq *rq)
{
	struct sched_rt_entity *rt_se;
	struct rt_rq *rt_rq  = &rq->rt;

	do {
		rt_se = pick_next_rt_entity(rt_rq);
		if (unlikely(!rt_se))
			return NULL;
		rt_rq = group_rt_rq(rt_se);
	} while (rt_rq);

	return rt_task_of(rt_se);//由调度实体找到进程
}
static struct sched_rt_entity *pick_next_rt_entity(struct rt_rq *rt_rq)
{
	struct rt_prio_array *array = &rt_rq->active;
	struct sched_rt_entity *next = NULL;
	struct list_head *queue;
	int idx;
    //通过位图找到优先级
	idx = sched_find_first_bit(array->bitmap);
	BUG_ON(idx >= MAX_RT_PRIO);
    //通过优先级找到内核链表
	queue = array->queue + idx;
	if (SCHED_WARN_ON(list_empty(queue)))
		return NULL;
	next = list_entry(queue->next, struct sched_rt_entity, run_list);

	return next;
}

4.周期调度

        每次时钟节拍到来时,在函数scheduler_tick ()中会调用当前任务所属调度器的task_tick接口,以驱动调度器判断是继续运行当前任务,还是选择新任务运行。RT调度器中,只有RR调度策略的任务支持task_tick,FIFO调度策略不支持该接口(直接返回)。RR调度策略的任务都有时间片time_slice,task_tick接口中时间片减一,当时间片用完后重置时间片,选择同优先级中下一个任务运行,如此往复,已到达同优先级任务轮流执行的目的。

static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{
	struct sched_rt_entity *rt_se = &p->rt;

	update_curr_rt(rq);
	update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);

	watchdog(rq, p);
    //不是RR调度策略,则直接返回
	if (p->policy != SCHED_RR)
		return;
    //时间片减一,如果没用完,继续运行当前任务
	if (--p->rt.time_slice)
		return;
    //如果用完,重置时间片
	p->rt.time_slice = sched_rr_timeslice;
    //如果用完,选择同优先级下一个任务运行
	for_each_sched_rt_entity(rt_se) {
		if (rt_se->run_list.prev != rt_se->run_list.next) {
			requeue_task_rt(rq, p, 0);
			resched_curr(rq);
			return;
		}
	}
}

5.SCHED_FIFO和SCHED_RR对比

        SCHED_FIFO不支持task_tick,故只有更高优先级的任务入队,或者当前任务主动让出CPU,才会选择同优先级内核链表中下一个任务运行。SCHED_RR支持task_tick,为任务分配时间片,同优先级的每个任务轮流运行time_slice长度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux内核中的进程调度被称为“调度”,其源代码位于`kernel/sched/`目录下。调度是内核的一部分,它控制着在多个进程之间分配CPU时间片的方式。在这个过程中,调度需要考虑多个因素,例如进程优先级、进程所需的资源、进程等待的时间以及系统负载等。 下面是对Linux内核进程调度的源代码进行简要的解析: 1. 调度初始化 调度初始化函数为`sched_init()`,它负责初始化调度的各种数据结构和变量。在这里,调度将创建一个名为`init_task`的内核线程,该线程是系统中的第一个进程。调度还会初始化各种调度相关的数据结构和变量,例如CPU负载平衡、进程优先级队列等。 2. 进程调度 进程调度函数为`sched_schedule()`,它被调用以选择下一个要运行的进程。调度使用了一种多级反馈队列调度算法,其中进程根据它们的优先级被分配到不同的队列中。调度将首先从优先级最高的队列中选择下一个要运行的进程。如果该队列为空,则调度将继续选择下一个队列,直到找到非空队列为止。如果所有队列都为空,则调度将选择一个空闲的CPU,并将当前进程移到该CPU上。 3. 进程优先级 进程的优先级是一个重要的因素,它决定了进程在被分配CPU时间片时的顺序。Linux内核中,进程的优先级范围从0到139,其中0是最高优先级,139是最低优先级。优先级为0的进程是实时进程,可以通过`sched_setscheduler()`函数设置。其他进程的优先级由调度动态调整,通常根据进程的历史运行时间、进程类型、进程等待时间等因素进行计算。 4. CPU负载平衡 当系统中有多个CPU时,调度还需要考虑如何平衡CPU的负载。为此,调度将在不同的CPU之间移动进程,以确保每个CPU的负载尽可能相等。调度会定期扫描系统中的所有CPU,查找负载最轻的CPU,并将某些进程从其他CPU移动到该CPU上。 5. 调度策略 调度还支持不同的调度策略,例如完全公平调度(CFS)、实时调度RT)、轮转调度(RR)等。不同的调度策略适用于不同的场景,例如CFS适用于普通的任务,而RT适用于实时任务。调度策略可以通过`sched_setscheduler()`函数进行设置。 以上是对Linux内核进程调度的简要介绍和源代码解析。由于调度的复杂性,这里仅介绍了一些基本概念和代码实现。如果您对此感兴趣,建议仔细阅读相关文档和源代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值