您的位置:首页 > 运维架构 > Linux

内核源码阅读(八)进程调度器的实现

2017-08-20 12:26 393 查看
调度器的任务就是使程序之间共享CPU时间,创造并行执行的假象。其可分为两个方面:一是调度策略;二是上下文切换。

1.总览

一般原理:按所能分配的计算能力,向系统中每个进程提供最大的公正性。

调度器对于进程等待时间的记录如下图所示。所有的可运行程序都按时间在红黑树中排序。就绪队列装备了虚拟时钟,其精确速度依赖于当前等待调度器挑选的进程的数目,约为实时时钟速度的1/4。



2.数据结构

调度器子系统各组件概观如下图所示



激活调度方法:

1)直接由进程放弃CPU;

2)周期性机制,以固定频率运行,不时检测是否进行进程切换。

task_struct中与调度相关的成员:

<sched.h>
struct task_struct {
......
int prio, static_prio, normal_prio; unsigned int rt_priority;

struct list_head run_list;
const struct sched_class *sched_class; struct sched_entity se;

unsigned int policy; cpumask_t cpus_allowed; unsigned int time_slice;
.....
}

prio、normal_prio:进程动态优先级
static_prio:进程静态优先级,在进程启动时就被分配,可通过nice和sched_setscheduler函数进行修改。
rt_priority:表示实时进程优先级。最高优先级99,最低优先级0。
sched_class:表示该进程所属的调度类。
policy:保存进程调度策略,Linux可取值为:SCHED_NORMAL用于普通进程,SCHED_BATCH用于非交互、CPU使用密集的批处理进程,SCHED_IDLE权重相对较小,SCHED_FIFO使用先进先出机制,SCHED_RR使用循环方法,两个都用于软实时进程。
cpus_allow:位域,用来限制执行进程的CPU。
run_list:用于维护包含各进程的一个运行表
time_slice::指定进程可使用CPU的剩余时间段。


调度器类

其结构如下:

1.  struct sched_class {
2.    const struct sched_class *next;
3.  /* 向就绪队列添加新进程*/
4.    void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);   /* 从就绪队列中删除一个进程*/
5.    void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);   /* 进程自愿放弃对处理器的控制权时调用sched_yield->yield_task */
6.    void (*yield_task) (struct rq *rq);
7.  /* 用一个新唤醒的进程类抢占当前进程 */
8.    void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
9.  /*  选择下一个要运行的进程*/
10.    struct task_struct * (*pick_next_task) (struct rq *rq);
11. /* 用一个进程替代当前运行的进程 */
12.   void (*put_prev_task) (struct rq *rq, struct task_struct *p);
14.#ifdef CONFIG_SMP    /* 选择就绪队列*/
15.    int  (*select_task_rq)(struct rq *rq, struct task_struct *p,
16.                   int sd_flag, int flags);
17.
18.    void (*pre_schedule) (struct rq *this_rq, struct task_struct *task);
19.    void (*post_schedule) (struct rq *this_rq);
20.    void (*task_waking) (struct rq *this_rq, struct task_struct *task);
21.    void (*task_woken) (struct rq *this_rq, struct task_struct *task);
22.
23.    void (*set_cpus_allowed)(struct task_struct *p,
24.                 const struct cpumask *newmask);
25.
26.    void (*rq_online)(struct rq *rq);
27.    void (*rq_offline)(struct rq *rq);
28.#endif
29.  /* 设置当前执行的进程 */
30.    void (*set_curr_task) (struct rq *rq);
31.  /* 在每次激活周期性调度器时,由周期性调度器调用 */
32. void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
33.   /* 创建一个新进程*/
34.  void (*task_fork) (struct task_struct *p);
35.
36.    void (*switched_from) (struct rq *this_rq, struct task_struct *task,
37.                   int running);
38.    void (*switched_to) (struct rq *this_rq, struct task_struct *task,
39.                 int running);
40.    void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
41.                 int oldprio, int running);
42.
43.    unsigned int (*get_rr_interval) (struct rq *rq,
44.                     struct task_struct *task);
45.
46.#ifdef CONFIG_FAIR_GROUP_SCHED
47.    void (*task_move_group) (struct task_struct *p, int on_rq);
48.#endif
49.};
50.  /* 用于负载均衡* /
51.struct load_weight {
52.    unsigned long weight, inv_weight;
53.};


就绪队列

核心调度器用于管理活动进程的主要数据结构称之为就绪队列。每个CPU都有自己的就绪队列,一个进程不可能出现在多个就绪队列,即不能多个CPU不能运行一个进程。

就绪队列的结构如下所示。

kernel/sched.c
struct rq {
unsigned long nr_running;
#define CPU_LOAD_IDX_MAX 5
unsigned long cpu_load[CPU_LOAD_IDX_MAX];
......
struct load_weight load;

struct cfs_rq cfs; struct rt_rq rt;

struct task_struct *curr, *idle; u64 clock;
......
};
nr_running:制定了队列上可运行进程的数目。
load:提供了就绪队列当前负荷的度量。
cpu_load:跟踪当前的负荷状态。
cfs和rt:嵌入的子就绪队列,分别用于完全公平调度器和实时调度器。
curr:指向当前运行的进程实例。
idle:指向idle进程的task_struct实例。
clock和pre_raw_clock:用于实现就绪队列自身的时钟。


调度实体

调度器的操作实体结构如下:

<sched.h>
struct sched_entity {
struct load_weight load; /* 用于负载均衡 */
struct rb_node run_node; unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime; u64 vruntime;
u64 prev_sum_exec_runtime;
......
}
load:指定了权重,决定了各个实体占队列总负荷的比例。
run_node:标准的树结点,使得实体可以在红黑树上排序。
on_rq:表示该实体当前是否在就绪队列上接受调度。
exec_start:每次调用时,会计算当前时间和exec_start之间的差值,exec则更新到当前时间,差值则被加到sun_exec_time上。
vruntime:统计在进程执行期间虚拟时钟上流逝的时间数量。
pre_sun_exec_runtime:保存进程被撤销时的值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内核 源码 linux