其他分享
首页 > 其他分享> > 实时调度类及SMP

实时调度类及SMP

作者:互联网

实时调度类源码分析

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);
}

对称多处理器SMP

多处理器系统的工作方式分为非对称多处理(asym-metrical mulit-processing)和对称多处理(symmetrical mulit-processing,SMP)两种。
在对称多处理器系统中,所有处理器的地位都是相同的,所有的资源,特别是存储器、中断及I/O空间,都具有相同的可访问性,消除结构上的障碍。
多处理器系统上,内核必须考虑几个额外的问题,以确保良好的调度。

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负载均衡。

负载均衡时机
两路4核8核心CPU,CPU调度域逻辑关系

img

分层角度分析

所有CPU一共分成撒个层次:SMT、MC、NUMA,每层都包含所有CPU,但是划分粒度不同。根据Cache和内存的相关性划分调度域,调度域内的CPU又划分一次调度组。越往下层调度域越小,越往上层调度域越大。进程负载均衡会尽可以在底层调度域内部解决,这样Cache利用率最优。
周期性负载均衡:CPU对应的运行队列数据结构记录下一次周期性负载均衡时间,当超过这个时间点后,将触发SCHED_SOFIRQ软中断来进行负载均衡。
用到SMP负载均衡模型的时机
内核运行中,还有部分情况需要用掉SMP负载均衡模型来确定最佳运行CPU:

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运行时调优等相关参数介绍等。

技术参考

https://ke.qq.com/webcourse/3294666/103425320#taid=11144559668118986&vid=5285890815288776379

标签:rt,task,sched,struct,调度,SMP,类及,rq
来源: https://www.cnblogs.com/myblogheyk/p/15760879.html