「技术干货」一文搞懂Linux内核实时调度类及SMP
haoteby 2025-01-11 13:24 2 浏览
实时调度类源码分析
Linux 实时进程与普通进程的根本不同之处,系统中有一个实时进程且可运行,调度器总是会选择它,除非另有一个优先级更高的实时进程。
SCHED_FIFO:没有时间片,在调度器被选择之后,可以运行任意长时间;
SCHED_RR:有时间片,其值在进程运行时会减少。
实时调度实体sched_rt_entity数据结构及操作
进程的插入、选择、删除三种基本操作。
//实体
struct sched_rt_entity {
struct list_head run_list;
unsigned long timeout; // watchdog计数器 主要用于判断当前进程时间是否超过RLIMIT_RTIME
unsigned long watchdog_stamp;
unsigned int time_slice; // 针对RR调度策略的调度时隙
struct sched_rt_entity *back; // dequeue_rt_stack() 中作为临时变量
#ifdef CONFIG_RT_GROUP_SCHED
struct sched_rt_entity *parent; // 指向上层调度实体
/* rq on which this entity is (to be) queued: */
struct rt_rq *rt_rq; //当前实时调度实体所在的就绪队列
/* rq "owned" by this entity/group: */
struct rt_rq *my_q; //当前实时调度实体的子调度实体所在的就绪队列
#endif
};
//类
const struct sched_class rt_sched_class = {
.next = &fair_sched_class,
.enqueue_task = enqueue_task_rt, //将一个task放入到就绪队列头部或者尾部
.dequeue_task = dequeue_task_rt, // 将一个task从就绪队列末尾删除
.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, //核心调度器给任务选定CPU 将任务分发到不同的CPU上执行
.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,
};
//进程插入操作
/*
* Adding/removing a task to/from a priority 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;
// 实际工作
// 将当前实时调度实体添加到对应优先级链表上面,添加到头部还是尾部取决于flags是否包含ENQUEUE_HEAD来判断
enqueue_rt_entity(rt_se, flags & ENQUEUE_HEAD);
if (!task_current(rq, p) && p->nr_cpus_allowed > 1)
enqueue_pushable_task(rq, p); //添加到hash表中
}
// 进程选择操作
// 实时调度会选择最高优先级的实时进程来运行。
static struct task_struct *_pick_next_task_rt(struct rq *rq)
{
struct sched_rt_entity *rt_se;
struct task_struct *p;
struct rt_rq *rt_rq = &rq->rt;
do { //遍历组调度中的每一个进程
rt_se = pick_next_rt_entity(rq, rt_rq);
BUG_ON(!rt_se);
rt_rq = group_rt_rq(rt_se);
} while (rt_rq);
p = rt_task_of(rt_se);
// 更新执行域
p->se.exec_start = rq_clock_task(rq);
return p;
}
static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq,
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;
next = list_entry(queue->next, struct sched_rt_entity, run_list);
// 返回找到运行实体
return next;
}
// 进程删除操作
// 从优先级队列中删除实时进程,并更新调度信息,然后把这个进程添加到队尾。
static void dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
struct sched_rt_entity *rt_se = &p->rt;
// 更新调度数据信息
update_curr_rt(rq);
// 实际工作,将rt_se从运行队列中删除,然后添加到队尾
dequeue_rt_entity(rt_se);
// 从hash表中删除
dequeue_pushable_task(rq, p);
}
/*
* Update the current task's runtime statistics. Skip current tasks that
* are not in our scheduling class.
*/
static void update_curr_rt(struct rq *rq)
{
struct task_struct *curr = rq->curr;
struct sched_rt_entity *rt_se = &curr->rt;
u64 delta_exec;
// 判断是否有实时调度进程
if (curr->sched_class != &rt_sched_class)
return;
// 执行时间
delta_exec = rq_clock_task(rq) - curr->se.exec_start;
if (unlikely((s64)delta_exec <= 0))
return;
schedstat_set(curr->se.statistics.exec_max,
max(curr->se.statistics.exec_max, delta_exec));
// 更新当前进程总执行时间
curr->se.sum_exec_runtime += delta_exec;
account_group_exec_runtime(curr, delta_exec);
// 更新执行的开始时间
curr->se.exec_start = rq_clock_task(rq);
cpuacct_charge(curr, delta_exec);
sched_rt_avg_update(rq, delta_exec);
if (!rt_bandwidth_enabled())
return;
for_each_sched_rt_entity(rt_se) {
struct rt_rq *rt_rq = rt_rq_of_se(rt_se);
if (sched_rt_runtime(rt_rq) != RUNTIME_INF) {
raw_spin_lock(&rt_rq->rt_runtime_lock);
rt_rq->rt_time += delta_exec;
if (sched_rt_runtime_exceeded(rt_rq))
resched_curr(rq);
raw_spin_unlock(&rt_rq->rt_runtime_lock);
}
}
}
static void dequeue_rt_entity(struct sched_rt_entity *rt_se)
{
struct rq *rq = rq_of_rt_se(rt_se);
dequeue_rt_stack(rt_se); // 从运行队列中删除
for_each_sched_rt_entity(rt_se) {
struct rt_rq *rt_rq = group_rt_rq(rt_se);
if (rt_rq && rt_rq->rt_nr_running)
__enqueue_rt_entity(rt_se, false);
}
enqueue_top_rt_rq(&rq->rt);
}
更多Linux内核视频教程文档资料免费领取后台私信【内核】自行获取。
对称多处理器SMP
多处理器系统的工作方式分为非对称多处理(asym-metrical mulit-processing)和对称多处理(symmetrical mulit-processing,SMP)两种。
在对称多处理器系统中,所有处理器的地位都是相同的,所有的资源,特别是存储器、中断及I/O空间,都具有相同的可访问性,消除结构上的障碍。
多处理器系统上,内核必须考虑几个额外的问题,以确保良好的调度。
- CPU负荷必须尽可能公平地在所有的处理器上共享。
- 进程与系统中某些处理器的亲合性(affinity)必须是可设置的。
- 内核必须能够将进程从一个CPU迁移到另一个。
linux SMP调度就是将进程安排/迁移到合适的CPU中去,保持各CPU负载均衡的过程。
SMP优点
- 增加吞吐时的一种划算方法;
- 由于操作系统由所有处理器共享,它们提供了一个单独的系统映像(容易管理);
- 对一个单独的问题应用多处理器(并行编程);
- 负载均衡由操作系统实现;
- 单处理器(UP)编程模型可用于一个SMP中;
- 对于共享数据来说,可伸缩;
- 所有数据可由所有处理器寻址,并且由硬件监视逻辑保持连续性;
- 由于通信经由全局共享内存执行,在处理器之间通信不必使用消息传送库;
SMP局限性
- 由于告诉缓存相关性、锁定机制、共享对象和其它问题,可伸缩性受限制;
- 需要新技术来利用多处理器,例如:线程编程和设备驱动程序编程等。
CPU域初始化
Linux内核中有一个数据结构struct sched_domain_topology_level用来描述CPU的层次关系。
内核对CPU的管理是通过bitmap来管理,并且定义possible、present、online、active这4种状态。
struct sched_domain_topology_level {
sched_domain_mask_f mask; //函数指针 用于指定某个SDTL层级的cpumask位置
sched_domain_flags_f sd_flags; //函数指针 用于指定某个SDTL层级的标志位
int flags;
int numa_level;
struct sd_data data;
#ifdef CONFIG_SCHED_DEBUG
char *name;
#endif
};
// 表示系统中有多少个可以运行的CPU核心
const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits);
EXPORT_SYMBOL(cpu_possible_mask);
// 表示系统中有多少个正处于运行状态的CPU核心
static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_online_mask = to_cpumask(cpu_online_bits);
EXPORT_SYMBOL(cpu_online_mask);
// 表示系统中有多少个具备online条件的CPU核心,它们不一定都处于online状态,有的CPU核心可能被热插拔。
static DECLARE_BITMAP(cpu_present_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_present_mask = to_cpumask(cpu_present_bits);
EXPORT_SYMBOL(cpu_present_mask);
//表示系统中有多少个活跃的CPU核心
static DECLARE_BITMAP(cpu_active_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_active_mask = to_cpumask(cpu_active_bits);
EXPORT_SYMBOL(cpu_active_mask);
//以上4个变量都是bitmap类型变量。
SMP负载均衡
SMP负载均衡机制从注册软中断开始,每次系统处理调度tick时会检查当前是否需要处 理SMP负载均衡。
负载均衡时机
- 周期性调用进程调度程序scheduler_tick()->trigger_load_balance()中,通过软中断触发负载均衡。
- 某个CPU上无可运行进程,__schedule()准备调度idle进程前,会尝试从其它CPU上拉一批进程过来。
两路4核8核心CPU,CPU调度域逻辑关系
分层角度分析
所有CPU一共分成撒个层次:SMT、MC、NUMA,每层都包含所有CPU,但是划分粒度不同。根据Cache和内存的相关性划分调度域,调度域内的CPU又划分一次调度组。越往下层调度域越小,越往上层调度域越大。进程负载均衡会尽可以在底层调度域内部解决,这样Cache利用率最优。
周期性负载均衡:CPU对应的运行队列数据结构记录下一次周期性负载均衡时间,当超过这个时间点后,将触发SCHED_SOFIRQ软中断来进行负载均衡。
用到SMP负载均衡模型的时机
内核运行中,还有部分情况需要用掉SMP负载均衡模型来确定最佳运行CPU:
- 进程A唤醒进程B时,try_to_wake_up()中会考虑进程B将在哪个CPU上运行;
- 进程调用execve()系统调用时;
- fork出子进程,子进程第一次被调度运行。
Linux运行时调优:
Linux引入重要sysctls来在运行时对调度程序进行调优(单位ns)
sched_child_runs_first: child在fork之后进行调度,为默认设备。如果设置为0,则先调度parent。
sched_min_granularity_ns:针对CPU密集型任务执行最低级别抢占粒度。
sched_latency_ns:针对CPU密集型任务进行目标抢占延迟。
sched_stat_granularity_ns:收集调度程序统计信息的粒度。
总结
本文主要介绍了实时调度类源码分析,包括实时调度数据结构及其相关操作(插入、选择、删除等);SMP的优缺点,负载均衡机制,CPU分层角度分析,linux运行时调优等相关参数介绍等。
相关推荐
- 用户界面干货盘点
-
为了解决大家找资源难的问题,EVGET特别开辟每周盘点用户界面干货的专栏,一网打尽热门的界面资讯、Demo示例、版本升级及下载、移动Web开发,以及各种UI神器推荐。更多资源及工具也可以在用户界面专题...
- 不仅仅是创意,26款科技小玩意
-
新科技不断在卖场出现,总是吸引着消费者的眼球。许多很棒的科技小玩意儿被发明,手机、平板、手提电脑、游戏主机、甚至是3D打印都适用。现在的初创公司已经发正在让21世纪打破各种科技壁垒障碍。本文收集26...
- FastReport.Net报表设计器如何连接到SQLCe
-
MicrosoftSQLServerCompactEdition是一个简单的本地关系数据库,不需要安装,并且已与数据库文件建立连接。您不需要管理员权限即可使用基础功能。您也只能“密码”基础功能...
- 2015年最值得关注的8款用户界面新品
-
软件界面开发解决方案这一块一直以来是慧都控件(EVGET)的强项,我们有400多款用户界面产品,250多款图表报表产品,此外还提供专业的软件界面定制开发服务,其中DevExpress定制开发、甘特图定...
- 小贴士:安装TBarCode office的注意事项和相关资源
-
TBarCodeoffice是一款适用于MicrosoftWord2007、2010等版本,具有强大功能的条码插件。在这里我们介绍一下安装TBarCodeoffice的注意事项和相关资源。安装...
- 初学者不容错过的修复Bug小技巧
-
Bug的发生,我想这是每个开发人员几乎每天都要面对的问题,包括历史上非常有名的编程人员,他们依旧要面对Bug。成为一个熟练的程序员并不意味着永远不会犯错误,而是擅于发现错误并能很好地修正错误。当你刚开...
- 【推荐】一款基于 .NET 开源的支持多厂区、多项目级的MOM/MES系统
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍tmom是一款基于.NET开源、通用的生产制造系统,支持多厂区/多项目级的MOM/MES系统,计划排程...
- 你不可不知的10个Github功能
-
Github让全世界的开发人员、设计人员可以在一起工作交流。Github不仅提供大量开源项目、编程语言代码,他也发布过Windows和OSX桌面应用,可以让我们在工作中无缝集成Github。...
- Fastreport.Net用户手册(十四):文本编辑
-
编辑对象的文本,只需双击文本内容,然后会弹出一个文本编辑器。在编辑器右方有一个可以添加至文本中的数据树组件。可以通过鼠标拖拽该组件到需要的地方。在文本中嵌入该组件的另一个方法是双击该组件,然后该组件将...
- 火狐浏览器开发者专版上手体验
-
当Mozilla宣布FirefoxDeveloperEdition,我想不少开发者都很高兴,因为第一个大型开发者专用浏览器诞生了。既然是开发者专用版,那么和普通版本肯定是不一样的。早已经迫不及待...
- FastReport.Net 2015.3.3 优化了报表解析器
-
FastReport.Net2015.3.3于近日正式发布。点击FastReport.Net2015.3.3下载试用FastReport.Net最新版本。[Core][Exports]重写保存在...
- 改变上网体验:10个超赞的Google Chrome扩展
-
你使用谷歌浏览器浏览网页吗?其实,全世界数以百万的用户都喜欢使用GoogleChrome浏览网页,这也促使其成为全球使用量第二大的Web浏览器。GoogleChrome浏览器具有快速、干净的页面,...
- 如何在 FastReport Online Designer 中处理报表的 5 个函数
-
FastReports产品的时代并没有停滞不前。每个月都会添加新的函数和对象,并改进和优化当前的代码。FastReportOnlineDesigner...
- Winform应用界面开发技术特点图解
-
整理一下自己之前的Winform开发要点,以图文的方式展示一些关键性的技术特点,总结一下。...
- 跨平台的可视化Web报表设计器-FastReport Online Designer
-
好消息!FastReportOnlineDesigner现在作为一个独立的应用程序发布啦!此前作为FastReport.Net的专业版的一部分的在线设计测试版,现在可以单独或作为FastRepor...