【原创】(六)Linux进程调度-实时调度器

背景

  • Read the fucking source code! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:

  1. Kernel版本:4.14
  2. ARM64处理器,Contex-A53,双核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

在Linux内核中,实时进程总是比普通进程的优先级要高,实时进程的调度是由Real Time Scheduler(RT调度器)来管理,而普通进程由CFS调度器来管理。

实时进程支持的调度策略为:SCHED_FIFOSCHED_RR

前边的系列文章都是针对CFS调度器来分析的,包括了CPU负载组调度Bandwidth控制等,本文的RT调度器也会从这些角度来分析,如果看过之前的系列文章,那么这篇文章理解起来就会更容易点了。

前戏不多,直奔主题。

2. 数据结构

有必要把关键的结构体罗列一下了:

  • struct rq:运行队列,每个CPU都对应一个;
  • struct rt_rq:实时运行队列,用于管理实时任务的调度实体;
  • struct sched_rt_entity:实时调度实体,用于参与调度,功能与struct sched_entity类似;
  • struct task_group:组调度结构体;
  • struct rt_bandwidth:带宽控制结构体;

老规矩,先上张图,捋捋这些结构之间的关系吧:

  • 从图中的结构组织关系看,与CFS调度器基本一致,区别在与CFS调度器是通过红黑树来组织调度实体,而RT调度器使用的是优先级队列来组织实时调度实体;
  • rt_rq运行队列,维护了100个优先级的队列(链表),优先级0-99,从高到底;
  • 调度器管理的对象是调度实体,任务task_struct和任务组task_group都是通过内嵌调度实体的数据结构,来最终参与调度管理的;
  • task_group任务组调度,自身为每个CPU维护rt_rq,用于存放自己的子任务或者子任务组,子任务组又能往下级联,因此可以构造成树;

上述结构体中,struct rqstruct task_group,在前文中都分析过。

下边针对RT运行队列相关的关键结构体,简单注释下吧:

struct sched_rt_entity {
	struct list_head		run_list;   //用于加入到优先级队列中
	unsigned long			timeout;    //设置的时间超时
	unsigned long			watchdog_stamp;     //用于记录jiffies值
	unsigned int			time_slice;     //时间片,100ms,
	unsigned short			on_rq;
	unsigned short			on_list;

	struct sched_rt_entity		*back;  //临时用于从上往下连接RT调度实体时使用
#ifdef CONFIG_RT_GROUP_SCHED
	struct sched_rt_entity		*parent;    //指向父RT调度实体
	/* rq on which this entity is (to be) queued: */
	struct rt_rq			*rt_rq;     //RT调度实体所属的实时运行队列,被调度
	/* rq "owned" by this entity/group: */
	struct rt_rq			*my_q;  //RT调度实体所拥有的实时运行队列,用于管理子任务或子组任务
#endif
} __randomize_layout;

/* Real-Time classes‘ related field in a runqueue: */
struct rt_rq {
	struct rt_prio_array active;    //优先级队列,100个优先级的链表,并定义了位图,用于快速查询
	unsigned int rt_nr_running; //在RT运行队列中所有活动的任务数
	unsigned int rr_nr_running;
#if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED
	struct {
		int curr; /* highest queued rt task prio */     //当前RT任务的最高优先级
#ifdef CONFIG_SMP
		int next; /* next highest */        //下一个要运行的RT任务的优先级,如果两个任务都有最高优先级,则curr == next
#endif
	} highest_prio;
#endif
#ifdef CONFIG_SMP
	unsigned long rt_nr_migratory;      //任务没有绑定在某个CPU上时,这个值会增减,用于任务迁移
	unsigned long rt_nr_total;      //用于overload检查
	int overloaded;     //RT运行队列过载,则将任务推送到其他CPU
	struct plist_head pushable_tasks;   //优先级列表,用于推送过载任务
#endif /* CONFIG_SMP */
	int rt_queued;  //表示RT运行队列已经加入rq队列

	int rt_throttled;   //用于限流操作
	u64 rt_time;    //累加的运行时,超出了本地rt_runtime时,则进行限制
	u64 rt_runtime;     //分配给本地池的运行时
	/* Nests inside the rq lock: */
	raw_spinlock_t rt_runtime_lock;

#ifdef CONFIG_RT_GROUP_SCHED
	unsigned long rt_nr_boosted;    //用于优先级翻转问题解决

	struct rq *rq;      //指向运行队列
	struct task_group *tg;  //指向任务组
#endif
};

struct rt_bandwidth {
	/* nests inside the rq lock: */
	raw_spinlock_t		rt_runtime_lock;
	ktime_t			rt_period;      //时间周期
	u64			rt_runtime;     //一个时间周期内的运行时间,超过则限流,默认值为0.95ms
	struct hrtimer		rt_period_timer;    //时间周期定时器
	unsigned int		rt_period_active;
};

3. 流程分析

3.1 运行时统计数据

运行时的统计数据更新,是在update_curr_rt函数中完成的:

  • update_curr_rt函数功能,主要包括两部分:

    1. 运行时间的统计更新处理;
    2. 如果运行时间超出了分配时间,进行时间均衡处理,并且判断是否需要进行限流,进行了限流则需要将RT队列出队,并重新进行调度;

为了更直观的理解,下边还是来两张图片说明一下:

sched_rt_avg_update更新示意如下:

  • rq->age_stamp:在CPU启动后运行队列首次运行时设置起始时间,后续周期性进行更新;
  • rt_avg:累计的RT平均运行时间,每0.5秒减半处理,用于计算CFS负载减去RT在CFS负载平衡中使用的时间百分比;

3.2 组调度

RT调度器CFS调度器的组调度基本类似,CFS调度器的组调度请参考(四)Linux进程调度-组调度及带宽控制

看一下RT调度器组调度的组织关系图吧:

  • 系统为每个CPU都分配了RT运行队列,以及RT调度实体,任务组通过它包含的RT调度实体来参与调度;
  • 任务组task_group的RT队列,用于存放归属于该组的任务或子任务组,从而形成级联的结构;

看一下实际的组织示意图:

3.3 带宽控制

请先参考(四)Linux进程调度-组调度及带宽控制

RT调度器在带宽控制中,调度时间周期设置的为1s,运行时间设置为0.95s:

/*
 * period over which we measure -rt task CPU usage in us.
 * default: 1s
 */
unsigned int sysctl_sched_rt_period = 1000000;

/*
 * part of the period that we allow rt tasks to run in us.
 * default: 0.95s
 */
int sysctl_sched_rt_runtime = 950000;

这两个值可以在用户态通过/sys/fs/cgroup/cpu/rt_runtime_us/sys/fs/cgroup/cpu/rt_period_us来进行设置。

看看函数调用流程:

  • init_rt_bandwidth函数在创建分配RT任务组的时候调用,完成的工作是将rt_bandwidth结构体的相关字段进行初始化:设置好时间周期rt_period和运行时间限制rt_runtime,都设置成默认值;
  • 可以从用户态通过操作/sys/fs/cgroup/cpu下对应的节点进行设置rt_periodrt_runtime,最终调用的函数是tg_set_rt_bandwidth,在该函数中会从下往上的遍历任务组进行设置时间周期和限制的运行时间;
  • enqueue_rt_entity将RT调度实体入列时,最终触发start_rt_bandwidth函数执行,当高精度定时器到期时调用do_sched_rt_period_timer函数;
  • do_sched_rt_period_timer函数,会去判断该RT运行队列的累计运行时间rt_time与设置的限制运行时间rt_runtime之间的大小关系,以确定是否限流的操作。在这个函数中,如果已经进行了限流操作,会调用balance_time来在多个CPU之间进行时间均衡处理,简单点说,就是从其他CPU的rt_rq队列中匀出一部分时间增加到当前CPU的rt_rq队列中,也就是将当前rt_rt运行队列的限制运行时间rt_runtime增加一部分,其他CPU的rt_rq运行队列限制运行时间减少一部分。

来一张效果示意图:

3.4 调度器函数分析

来一张前文的图:

看一下RT调度器实例的代码:

const struct sched_class rt_sched_class = {
	.next			= &fair_sched_class,
	.enqueue_task		= enqueue_task_rt,
	.dequeue_task		= dequeue_task_rt,
	.yield_task		= yield_task_rt,

	.check_preempt_curr	= check_preempt_curr_rt,

	.pick_next_task		= pick_next_task_rt,
	.put_prev_task		= put_prev_task_rt,

#ifdef CONFIG_SMP
	.select_task_rq		= select_task_rq_rt,

	.set_cpus_allowed       = set_cpus_allowed_common,
	.rq_online              = rq_online_rt,
	.rq_offline             = rq_offline_rt,
	.task_woken		= task_woken_rt,
	.switched_from		= switched_from_rt,
#endif

	.set_curr_task          = set_curr_task_rt,
	.task_tick		= task_tick_rt,

	.get_rr_interval	= get_rr_interval_rt,

	.prio_changed		= prio_changed_rt,
	.switched_to		= switched_to_rt,

	.update_curr		= update_curr_rt,
};

3.4.1 pick_next_task_rt

pick_next_task_rt函数是调度器用于选择下一个执行任务。流程如下:

  • CFS调度器不同,RT调度器会在多个CPU组成的domain中,对任务进行pull/push处理,也就是说,如果当前CPU的运行队列中任务优先级都不高,那么会考虑去其他CPU运行队列中找一个更高优先级的任务来执行,以确保按照优先级处理,此外当前CPU也会把任务推送到其他更低优先级的CPU运行队列上。
  • _pick_next_task_rt的处理逻辑比较简单,如果实时调度实体是task,则直接查找优先级队列的位图中,找到优先级最高的任务,而如果实时调度实体是task_group,则还需要继续往下进行遍历查找;

关于任务的pull/push,linux提供了struct plist,基于优先级的双链表,其中任务的组织关系如下图:

pull_rt_task的大概示意图如下:

  • 当前CPU上的优先级任务不高,从另一个CPU的pushable_tasks链表中找优先级更高的任务来执行;

3.4.2 enqueue_task_rt/dequeue_task_rt

当RT任务进行出队入队时,通过enqueue_task_rt/dequeue_task_rt两个接口来完成,调用流程如下:

  • enqueue_task_rtdequeue_task_rt都会调用dequeue_rt_stack接口,当请求的rt_se对应的是任务组时,会从顶部到请求的rt_se将调度实体出列;
  • 任务添加到rt运行队列时,如果存在多个任务可以分配给多个CPU,设置overload,用于任务的迁移;

有点累了,收工了。

原文地址:https://www.cnblogs.com/LoyenWang/p/12584345.html

时间: 2024-08-25 10:11:32

【原创】(六)Linux进程调度-实时调度器的相关文章

Linux之企业级调度器LVS

1,实验拓扑图:VS/NAT 1,全部基于Ansible主机操作 安装ipvsadm 基于光盘yum源 # yum install ipvsadm # ipvsadm -A -t 172.22.145.146:80 添加集权主节点,VIP:PORT # ipvsadm -Ln IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forwar

Linux进程调度器的设计--Linux进程的管理与调度(十七)

日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度 前景回顾 进程调度 内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来. 调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 内核必须提供一种方法, 在各个进程之间尽可能公平地

Linux进程调度器概述--Linux进程的管理与调度(十五)

日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度 内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来. 调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 什么是调度器 通常来说,操作系统是应用程序和可用资源之间的媒介. 典型

linux调度器源码研究 - 概述(一)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 调度器作为操作系统的核心部件,具有非常重要的意义,其随着linux内核的更新也不断进行着更新.本系列文章通过linux-3.18.3源码进行调度器的学习和分析,一步一步将linux现有的调度器原原本本的展现出来.此篇文章作为开篇,主要介绍调度器的原理及重要数据结构. 调度器介绍 随着时代的发展,linux也从其初始版本稳步发展到今天,从2.4的非抢占内核发展到今天的可抢占内核,调度器无论从代码结构还是设

Linux调度器 - deadline调度器

一.概述 实时系统是这样的一种计算系统:当事件发生后,它必须在确定的时间范围内做出响应.在实时系统中,产生正确的结果不仅依赖于系统正确的逻辑动作,而且依赖于逻辑动作的时序.换句话说,当系统收到某个请求,会做出相应的动作以响应该请求,想要保证正确地响应该请求,一方面逻辑结果要正确,更重要的是需要在最后期限(deadline)内作出响应.如果系统未能在最后期限内进行响应,那么该系统就会产生错误或者缺陷.在多任务操作系统中(如Linux),实时调度器(realtime scheduler)负责协调实时

Linux进程管理 (7)实时调度

关键词:RT.preempt_count.RT patch. 除了CFS调度器之外,还包括重要的实时调度器,有两种RR和FIFO调度策略.本章只是一个简单的介绍. 更详细的介绍参考<Linux进程管理 (9)实时调度类分析,以及FIFO和RR对比实验>. 同时为了提高Linux的实时性,Linux社区还维护了realtime相关的补丁.这些补丁的介绍在<Linux实时补丁及其分析>. 1. 抢占内核 如果Linux内核不支持抢占,那么进程要么主动要求调度,如schedule()或者

Linux kernel源码阅读笔记2-2.6版本调度器sched.c功能

来自:http://www.ibm.com/developerworks/cn/linux/l-scheduler/ 2.6 版本调度器的源代码都很好地封装到了 /usr/src/linux/kernel/sched.c 文件中.我们在表 1 中对在这个文件中可以找到的一些有用的函数进行了总结. 表 1. Linux 2.6 调度器的功能 函数名 函数说明 schedule 调度器主函数.调度优先级最高的任务执行. load_balance 检查 CPU,查看是否存在不均衡的情况,如果不均衡,就

朴素的UNIX之-调度器细节

0.多进程调度的本质 我们都知道UNIX上有一个著名的nice调用,何谓nice,当然是"好"了,常规的想法是nice值越大越好,实际上,nice值越好,自己的优先级越低,那么为何不用badness呢? 事实上,如果我们理解了操作系统多进程调度系统是一个"利他"系统,这个问题就不是个问题了.nice当然还是好,不是对自己好,而是对别人好.利他系统 是一个人人为我我为人人的系统,类似还有TCP流量控制和拥塞控制,人类的宗教社会组织等等,利他系统都有一个负反馈机制,让波

Linux进程调度机制(1)

进程调度负责决定哪个进程投入运行,何时运行以及运行多长时间. 进程调度:非抢占式和抢占式 Linux这么酷的系统当然是抢占式的喽. 进程在被抢占之前可以运行的时间是预先设定好的,叫做时间片.有效管理时间片能使调度程序从系统全局角度作出调度决定,避免个别进程独占系统资源. I/O消耗型/处理器消耗型 I/O消耗型:进程的大部分时间用来提交I/O请求或是等待I/O请求,这样的进程经常处于可运行状态,但通常只运行很短时间,在等待I/O时会阻塞. 处理器消耗型:进程大部分时间都在执行代码,除非被抢占,否