进程组调度机制

又碰到一个神奇的进程调度问题,在系统重启过程中,发现系统挂住了,过了30s后才重新复位,真正系统复位的原因是硬件看门狗重启的系统,而非原来正常的reboot流程。硬件狗记录的复位时间,将不喂狗的时间向前推30s分析串口记录日志,当时的日志就打印了一句话:“sched: RT throttling activated”。
从linux-3.0.101-0.7.17版本内核代码中可以看出,sched_rt_runtime_exceeded打印了这句话。在内核进程组调度过程中,实时进程调度受rt_rq->rt_throttled 的限制,下面便具体说一下涉及到的linux中进程组调度机制。

进程组调度机制

组调度是cgroup里面的概念,指将N个进程视为一个整体,参与系统中的调度过程,具体体现在示例中:A任务有8个进程或线程,B任务有2个进程或线程,仍然有其他的进程或线程存在,就需要控制A任务的CPU占用率不高于40%,B任务的CPU占用率不高于40%,其他任务占用率不少于20%,那么就有对cgroup阀值的设置,cgroup A设置为200,cgroup B设置为200,其他任务默认为100,如此便实现了CPU控制的功能。
在内核中,进程组由task_group进行管理,其中涉及的内容很多都是cgroup控制机制 ,另外开辟单元在写,此处指重点描述组调度的部分,具体见如下注释。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

struct task_group {

    struct cgroup_subsys_state css;

 

//下面是普通进程调度使用

#ifdef CONFIG_FAIR_GROUP_SCHED

    /* schedulable entities of this group on each cpu */

//普通进程调度单元,之所以用调度单元,因为被调度的可能是一个进程,也可能是一组进程

    struct sched_entity **se;

    /* runqueue "owned" by this group on each cpu */

//公平调度队列

    struct cfs_rq **cfs_rq;

//下面就是如上示例的控制阀值

    unsigned long shares;

    atomic_t load_weight;

#endif

 

#ifdef CONFIG_RT_GROUP_SCHED

//实时进程调度单元

    struct sched_rt_entity **rt_se;

//实时进程调度队列

    struct rt_rq **rt_rq;

//实时进程占用CPU时间的带宽(或者说比例)

    struct rt_bandwidth rt_bandwidth;

#endif

 

    struct rcu_head rcu;

    struct list_head list;

//task_group呈树状结构组织,有父节点,兄弟链表,孩子链表,内核里面的根节点是root_task_group

    struct task_group *parent;

    struct list_head siblings;

    struct list_head children;

 

#ifdef CONFIG_SCHED_AUTOGROUP

    struct autogroup *autogroup;

#endif

 

    struct cfs_bandwidth cfs_bandwidth;

};

调度单元有两种,即普通调度单元和实时进程调度单元。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

struct sched_entity {

    struct load_weight    load;        /* for load-balancing */

    struct rb_node        run_node;

    struct list_head    group_node;

    unsigned int        on_rq;

 

    u64            exec_start;

    u64            sum_exec_runtime;

    u64            vruntime;

    u64            prev_sum_exec_runtime;

 

    u64            nr_migrations;

 

#ifdef CONFIG_SCHEDSTATS

    struct sched_statistics statistics;

#endif

 

#ifdef CONFIG_FAIR_GROUP_SCHED

//当前调度单元归属于某个父调度单元

    struct sched_entity    *parent;

    /* rq on which this entity is (to be) queued: */

//当前调度单元归属的父调度单元的调度队列,即当前调度单元插入的队列

    struct cfs_rq        *cfs_rq;

    /* rq "owned" by this entity/group: */

//当前调度单元的调度队列,即管理子调度单元的队列,如果调度单元是task_group,my_q才会有值

//如果当前调度单元是task,那么my_q自然为NULL

    struct cfs_rq        *my_q;

#endif

    void *suse_kabi_padding;

};

 

struct sched_rt_entity {

    struct list_head run_list;

    unsigned long timeout;

    unsigned int time_slice;

    int nr_cpus_allowed;

 

    struct sched_rt_entity *back;

#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

};

下面看一下调度队列,因为实时调度和普通调度队列需要说明的选项差不多,以实时队列为例:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

struct rt_rq {

    struct rt_prio_array active;

    unsigned long rt_nr_running;

#if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED

    struct {

        int curr; /* highest queued rt task prio */

#ifdef CONFIG_SMP

        int next; /* next highest */

#endif

    } highest_prio;

#endif

#ifdef CONFIG_SMP

    unsigned long rt_nr_migratory;

    unsigned long rt_nr_total;

    int overloaded;

    struct plist_head pushable_tasks;

#endif

//当前队列的实时调度是否受限

    int rt_throttled;

//当前队列的累计运行时间

    u64 rt_time;

//当前队列的最大运行时间

    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 list_head leaf_rt_rq_list;

//当前实时调度队列归属的调度单元

    struct task_group *tg;

#endif

};

通过以上3个结构体分析,可以得到下图(点击看大图):

task_group

task_group

从图上可以看出,调度单元和调度队列组合一个树节点,又是另一种单独树结构存在,只是需要注意的是,只有调度单元里面有TASK_RUNNING的进程时,调度单元才会被放到调度队列中。
另外一点是,在没有组调度前,每个CPU上只有一个调度队列,当时可以理解成所有的进程在一个调度组里面,现在则是每个调度组在每个CPU上都有调度队列。在调度过程中,原来是系统选择一个进程运行,当前则是选择一个调度单元运行,调度发生时,schedule进程从root_task_group开始寻找由调度策略决定的调度单元,当调度单元是task_group,则进入task_group的运行队列选择一个合适的调度单元,最终找一个合适的task调度单元。整个过程就是树的遍历,拥有TASK_RUNNING进程的task_group是树的节点,task调度单元则是树的叶子。

组进程调度策略

组进程调度要实现的目的和原来没有区别,就是完成实时进程调度和普通进

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值