linux进程调度与管理(三)
来源:互联网 发布:种族灭绝政策 知乎 编辑:程序博客网 时间:2024/05/29 18:39
转载请标明出处floater的csdn blog,http://blog.csdn.net/flaoter
本节内容介绍用于普通进程调度的完全公平调度类(Completely Fair Scheduler),它的基本原理是这样的:设定一个调度周期(sched_latency_ns),目标是让每个进程在这个周期内至少有机会运行一次,换一种说法就是每个进程等待CPU的时间最长不超过这个调度周期;然后根据进程的数量,大家平分这个调度周期内的CPU使用权,由于进程的优先级即nice值不同,分割调度周期的时候要加权;每个进程的累计运行时间保存在自己的虚拟时间(vruntime)字段里,哪个进程的vruntime最小就获得本轮运行的权利。
下面先对引入的两个概念虚拟时间和调度周期等进行介绍,然后再对调度器类进行说明。
1 虚拟时间
CFS算法引入的虚拟时间概念,用于度量等待进程在完全公平系统中能得到的CPU时间,与实际运行时间和上一节提到的负荷权重相关,关系如下,
delta_vruntime = (now - curr->exec_start)* NICE_0_LOAD/se->load
task->vruntime += delta_vruntime
虚拟运行时间增量与实际运行时间(now - curr->exec_start)成正比,与权重成反比。虚拟运行时间是虚拟运行时间增量的累加和。
内核会根据vruntime的值决定进程在红黑树排序中的位置,vruntime越小进程越靠近红黑树的最左端,意味着更早地调度。
vruntime的计算代码如下,
static void update_curr(struct cfs_rq *cfs_rq){ struct sched_entity *curr = cfs_rq->curr; //当前进程 u64 now = rq_clock_task(rq_of(cfs_rq)); //rq的clock_task u64 delta_exec; if (unlikely(!curr)) return; delta_exec = now - curr->exec_start; //计算当前时间与上一次update_curr的时间差 if (unlikely((s64)delta_exec <= 0)) return; curr->exec_start = now; //更新exec_start,供下次使用 schedstat_set(curr->statistics.exec_max, max(delta_exec, curr->statistics.exec_max)); curr->sum_exec_runtime += delta_exec; //当前进程总的运行时间 schedstat_add(cfs_rq, exec_clock, delta_exec); curr->vruntime += calc_delta_fair(delta_exec, curr); //公式计算 update_min_vruntime(cfs_rq); //更新cfs_rq的最小vruntime ...}
vruntime的计算是在update_curr函数中进行的,主要工作是
1. 计算vruntime,如代码中注释
2. 更新cfs_rq的min_vruntime。先在当前进程或就绪队列最左子节点进程中找出最小vruntime,再与队列的min_vruntime比较,如果某个节点的vruntime超出了队列的min_vruntime就进行更新。内核可确保min_vruntime只能增加,不能减少。
static void update_min_vruntime(struct cfs_rq *cfs_rq){ u64 vruntime = cfs_rq->min_vruntime; if (cfs_rq->curr) vruntime = cfs_rq->curr->vruntime; if (cfs_rq->rb_leftmost) { struct sched_entity *se = rb_entry(cfs_rq->rb_leftmost, struct sched_entity, run_node); if (!cfs_rq->curr) vruntime = se->vruntime; else vruntime = min_vruntime(vruntime, se->vruntime); } /* ensure we never gain time by being placed backwards. */ cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);}
红黑树的排序是依赖进程的键值vruntime与cfs_rq->min_vruntime的差值决定的,键值越小的节点,排序位置越靠左。
下面是《深入理解Linux内核架构》中的一段话,
(1)在进程运行时, 其vruntime稳定地增加, 它在红黑树中总是向右移动的.
因为越重要的进程vruntime增加的越慢, 因此它们向右移动的速度也越慢, 这样其被调度的机会要大于次要进程, 这刚好是我们需要的
(2) 如果进程进入睡眠, 则其vruntime保持不变. 因为每个队列min_vruntime同时会单调增加(回想一下,它是单调的!), 那么当进程从睡眠中苏醒, 在红黑树中的位置会更靠左, 因为其键值相对来说变得更小了.
2 理想运行时间
调度延迟 sched_latency_ns:cfs调度保证在一个延迟周期内每个可运行的进程都应该至少运行一次。延迟周期可通过/proc/sys/kernel/sched_latency_ns控制, 默认值为20000000纳秒, 即20毫秒。
延迟周期中处理的最大活动进程数目 sched_nr_latency,sched_nr_latency可以通过sysctl_sched_min_granularity间接的控制, 后者可通过/procsys/kernel/sched_min_granularity_ns设置. 默认值是4000000纳秒, 即4毫秒, 每次sysctl_sched_latency/sysctl_sched_min_granularity之一改变时, 都会重新计算sched_nr_latency.
延迟周期 __sched_period = sysctl_sched_latency * nr_running / sched_nr_latency
每个进程的 理想运行时间 ideal_runtime值的大小与它的权重weight成正比。比如: 如果period=20ms,当前系统中只有A、B、C、D四个进程,它们的weight分别为:1、2、3、4。那么A的ideal_runtime = 2ms,B,C,D的ideal_runtime依次为4ms,6ms, 8ms。
ideal_runtime的计算方法如下,
task[i]->ideal_runtime = task[i]->weight * period / sun_weight(task,N) ;
理想运行时间的计算是通过sched_slice函数实现的, 代码实现与计算方法一致。
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se){ u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq); for_each_sched_entity(se) { struct load_weight *load; struct load_weight lw; cfs_rq = cfs_rq_of(se); load = &cfs_rq->load; if (unlikely(!se->on_rq)) { lw = cfs_rq->load; update_load_add(&lw, se->load.weight); load = &lw; } slice = __calc_delta(slice, se->load.weight, load); } return slice;}
3 完全公平调度类
定义如下,
const struct sched_class fair_sched_class = { .next = &idle_sched_class, .enqueue_task = enqueue_task_fair, .dequeue_task = dequeue_task_fair, .yield_task = yield_task_fair, .yield_to_task = yield_to_task_fair, .check_preempt_curr = check_preempt_wakeup, .pick_next_task = pick_next_task_fair, .put_prev_task = put_prev_task_fair,#ifdef CONFIG_SMP .select_task_rq = select_task_rq_fair, .migrate_task_rq = migrate_task_rq_fair, .rq_online = rq_online_fair, .rq_offline = rq_offline_fair, .task_waking = task_waking_fair, .task_dead = task_dead_fair, .set_cpus_allowed = set_cpus_allowed_common,#endif .set_curr_task = set_curr_task_fair, .task_tick = task_tick_fair, .task_fork = task_fork_fair, .prio_changed = prio_changed_fair, .switched_from = switched_from_fair, .switched_to = switched_to_fair, .get_rr_interval = get_rr_interval_fair, .update_curr = update_curr_fair,#ifdef CONFIG_FAIR_GROUP_SCHED .task_move_group = task_move_group_fair,#endif};
常用的几个成员函数描述如下,
enqueue_task:当某个任务进入可运行状态时,该函数将得到调用。它将调度实体(进程)放入红黑树中,并对 nr_running 变量加 1。
dequeue_task:当某个任务退出可运行状态时调用该函数,它将从红黑树中去掉对应的调度实体,并从 nr_running 变量中减 1。
yield_task:在 compat_yield sysctl 关闭的情况下,该函数实际上执行先出队后入队;在这种情况下,它将调度实体放在红黑树的最右端。
check_preempt_curr:该函数将检查当前运行的任务是否被抢占。在实际抢占正在运行的任务之前,CFS 调度程序模块将执行公平性测试。这将驱动唤醒式(wakeup)抢占。
pick_next_task:该函数选择接下来要运行的最合适的进程。
set_curr_task:当任务修改其调度类或修改其任务组时,将调用这个函数。
task_tick:该函数通常调用自 time tick 函数;它可能引起进程切换。这将驱动运行时(running)抢占。
task_new:内核调度程序为调度模块提供了管理新任务启动的机会。CFS 调度模块使用它进行组调度,而用于实时任务的调度模块则不会使用这个函数。
本文将对pick_next_task_fair和task_tick_fair成员进行注释说明。
pick_next_task_fair的实现主要在pick_next_entity函数中实现,
static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr){ struct sched_entity *left = __pick_first_entity(cfs_rq); //取红黑树最左子节点 struct sched_entity *se; /* * If curr is set we have to see if its left of the leftmost entity * still in the tree, provided there was anything in the tree at all. */ if (!left || (curr && entity_before(curr, left))) //left为空或者curr进程比left进程的vruntime更小 left = curr; //将curr代替最左子节点 se = left; /* ideally we run the leftmost entity */ /* * Avoid running the skip buddy, if running something else can * be done without getting too unfair. */ if (cfs_rq->skip == se) { //cfs_rq->skip与得到的se相同的情况下,需要重新选择次优进程 struct sched_entity *second; //定义次优进程 if (se == curr) { //之前选择的进程是curr进程 second = __pick_first_entity(cfs_rq); //使用最左节点作为次优进程 } else { //之前选择的最左子节点 second = __pick_next_entity(se); //选择红黑树上下一节点 if (!second || (curr && entity_before(curr, second))) //重复比较second进程和curr进程 second = curr; } if (second && wakeup_preempt_entity(second, left) < 1) se = second; }//为了提高缓存利用率,last和net进程要与选出来的进程做比较 /* * Prefer last buddy, try to return the CPU to a preempted task. */ if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1) se = cfs_rq->last; /* * Someone really wants this to run. If it's not unfair, run it. */ if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1) se = cfs_rq->next; clear_buddies(cfs_rq, se); return se;}
task_tick_fair主要通过entity_tick函数实现,entity_tick除了更新统计信息外,会通过check_preempt_tick检查是否需要调度,
static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr){ ... ideal_runtime = sched_slice(cfs_rq, curr); //ideal_runtime是理论上的处理器运行时间片 delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime; //该进程本轮调度累计运行时间 if (delta_exec > ideal_runtime) { // 假如实际运行超过调度器分配的时间,就标记调度标志 resched_task(rq_of(cfs_rq)->curr); clear_buddies(cfs_rq, curr); return; } ...}
当该进程运行时间超过实际分配的时间片,resched_task(rq_of(cfs_rq)->curr)就设置need_resched标志 ,否则本进程继续执行。
- linux进程调度与管理(三)
- Linux进程ID号--Linux进程的管理与调度(三)
- Linux进程的管理与调度(三) -- Linux进程ID号
- linux进程管理与调度(一)
- linux进程调度与管理(二)
- linux进程管理与调度
- linux进程管理与调度
- Linux进程管理与调度
- linux进程管理与调度
- linux进程管理与调度
- linux进程管理与调度
- linux进程管理与调度
- Linux进程调度器概述--Linux进程的管理与调度(十五)
- Linux进程调度策略的发展和演变--Linux进程的管理与调度(十六)
- Linux进程调度器的设计--Linux进程的管理与调度(十七)
- Linux进程调度策略的发展和演变--Linux进程的管理与调度(十六)
- linux进程管理之调度与切换
- Linux 进程管理与调度剖析
- Android四大组件之Service简介
- android + jni + ndk 遇到的各种错误
- 并发(八):无法检查的中断状态
- 曲面绘制Mesh
- Android入门学习笔记整理(三)
- linux进程调度与管理(三)
- POJ 1845 Sumdiv
- unique和unique_copy用法
- 数据结构之哈希表
- 深度学习之Helloworld
- HDU
- 数据库帮助类
- 如何运行一个pre-train过的神经网络——以VGG16为例
- 菱形继承