进程调度之--进程的组织

来源:互联网 发布:冒泡法排序c语言解释 编辑:程序博客网 时间:2024/05/20 18:41
(代码基于linux4.4.42)

    在探讨进程调度之前,我们想一下如下几个问题:
      进程创建后组织在哪里?
      何时去挑选进程来运行?
      cpu挑选谁作为下一个运行的进程?
  归纳起来,就是WHERE?WHEN? and WHO?

WHRER?

一. 数据结构

    要回答这个问题,我们先以公平调度为例来看看一些相关的数据结构:

task_struct代表一个任务(进程/线程):
struct task_struct{
。。。。
      const struct sched_class *sched_class;
      struct sched_entity se;
      struct sched_rt_entity rt;
。。。。
}

    在task_struct中的两个元素结构体sched_entity和sched_rt_entity是调度实体,顾名思义,它们是调度管理的主要实体:
struct sched_entity {
。。。。。。
      struct rb_node run_node;      /* 所有的sched_entity通过run_node组织起来 */
      struct list_head group_node;
。。。。。。
      #ifdef CONFIG_FAIR_GROUP_SCHED
      int depth;
      struct sched_entity*parent;
      /* rq on which this entity is (to be) queued: */
     struct cfs_rq*cfs_rq;
     /* rq "owned" by this entity/group: */
     struct cfs_rq*my_q;
     #endif
。。。。。。。
};

  关于cfs调度我们关注的另外一个结构叫cfs运行队列struct cfs_rq,其主要作用是将调度实体组织起来。而实际它用于组织的数据结构并非是队列,而是红黑树。


/* CFS-related fields in a runqueue */
struct cfs_rq {
。。。。。。
      unsigned int nr_running, h_nr_running;
。。。。。。
      struct rb_root tasks_timeline;          /* 红黑树的根 */
      struct rb_node *rb_leftmost;

    /*
    * 'curr' points to currently running entity on this cfs_rq.
    * It is set to NULL otherwise (i.e when none are currently running).
    */

      struct sched_entity *curr, *next, *last, *skip;
。。。。。。
};

    讲完来cfs,接着我们再来看看实时调度的两个相关结构,首先是rt调度实体:

struct sched_rt_entity {
      struct list_head run_list;    /* 调度实体通过此链挂到运行队列上 */
。。。。。。
};

  再看看组织实时任务的rt运行队列:
/* Real-Time classes' related field in a runqueue: */
实时运行队列。
struct rt_rq {
      struct rt_prio_array active;         /* 优先级数组 */
      unsigned int rt_nr_running;
。。。。。。
};
    最后,我们还要讲一个结构运行队列结构:/*
 * This is the main, per-CPU runqueue data structure.
 *
 * Locking rule: those places that want to lock multiple runqueues
 * (such as the load balancing or the thread migration code), lock
 * acquire operations must be ordered by ascending &runqueue.
 */

struct rq {
      struct cfs_rq cfs;
      struct rt_rq rt;
      struct dl_rq dl;

      #ifdef CONFIG_FAIR_GROUP_SCHED
      /* list of leaf cfs_rq on this cpu: */
      struct list_head leaf_cfs_rq_list;
      #endif /* CONFIG_FAIR_GROUP_SCHED */
};


二. 组织关系

    我们在上面列出了task_struct、调度实体(sched_entity, sched_rt_entity)、调度运行队列(cfs_rq,rt_rq) 和 rq四种结构体,这四中结构提就是进程调度的主要组织结构。

2.1 cfs调度

    上面的结构中,运行队列结构struct rq我们可以将其理解为所有进程的组织结构的"root"。 内核使用每cpu变量定义了struct rq结构实例runqueues:
DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues)
    在多核系统中我们可以将runqueues理解为struct rq runqueues[cpus](虽然这种理解不够准确),每个cpu对应着一个runqueues的一个元素。在内核中可以通过宏“rq = cpu_rq(N)”来获取cpuN对应的rq运行队列指针。
    下面来看看这些结构是如何把任务都组织起来的。

图1 cfs调度的组织情况

   这里只画出了cpu0的情况,其他cpu的情况和cpu0类似。
   整个结构的“根”在struct rq runqueues[0],它管理着三个“队列”:cfs运行队列、rt运行队列和dl运行队列。这三个“队列”分别代表着不同的调度类,struct cfs_rq cfs是用于组织公平调度相关的结构;struct rt_rq rt用于组织实时任务调度相关结构;而struct dl_rq dl主要用于deadline调度相关结构。不同的调度其组织结构也有所差异,我们这里以公平调度为例来进行说明。

   而cfs运行队列struct cfs_rq则主要包含了struct rb_root tasks_timeline红黑根节点结构和struct rb_node *rb_leftmost红黑节点指针。在cfs调度中所有的任务通过内置的成员组织成红黑树,每个cpu组织有一棵红黑树。   而struct rb_root tasks_timeline结构中的tasks_timeline.rb_node成员指向了本cpu上红黑树的树"根",红黑树中的每个红黑节点代表一个调度实体,在不考虑组调度的情况下,一个调度实体实际上就对应一个任务task_struct结构。

    cfs_rq的另外一个成员rb_leftmost指向本cpu红黑树中最左边的节点。在CFS调度中,位置越靠近左边的红黑树节点就越有得到调度的机会。而rb_leftmost所指向的红黑树节点就在下一次调度时发生时选择运行,这大大增加了任务调度选择的速度。

    整棵红黑树中的红黑节点也就只起到了连接器的作用,真正调度的主要结构还是调度实体struct sched_entity,这个结构里面嵌入了struct rb_node run_node红黑节点。这样就可以通过红黑节点将本cpu上的所有调度实体间接的组织成了一棵红黑树。内核在没有使能CONFIG_FAIR_GROUP_SCHED组调度的情况下,一个调度实体sched_entity对应着一个任务task_struct结构,就像我们图中所看到的那样。
    但是如果内核配置了CONFIG_FAIR_GROUP_SCHED组调度,则情况会复杂一些:sched_entity不一定再是老老实实对应一个任务task_struct,它有可能是一个“组”。这个时候sched_entity的成员struct cfs_rq *my_q又指向一个cfs_rq结构,即一个cfs运行队列,而随这个struct cfs_rq *my_q->tasks_timeline而来的又会是一颗长满了调度实体sched_entity的“子红黑树”。
    虽然在使能了CONFIG_FAIR_GROUP_SCHED的cfs组调度中,一个调度实体sched_entity不再一定对应着一个任务task_struct;但是一个任务task_struct始终对应着一个sched_entity,而这个与task_struct一一对应的调度实体,在组调度中就是红黑树的叶子节点。即组调度中红黑树的叶子节点才真正代表着一个实实在在的任务。

    有了上面的了解作为铺垫,我们就可以来大概了解一下内核是如何找到下一个将要运行的任务的。

    内核中主要通过sched_class.pick_next_task()函数来选择下一个将运行的任务,这里的sched_class是调度类。当前的内核中的定义4个调度类:dl_sched_class、rt_sched_class、fair_sched_class和idle_sched_class。
      1) 首先通过cpu = smp_processor_id()获取当前cpu;
     2) 在通过rq = cpu_rq(cpu)获取当前cpu对应的runqueues[cpu];
     3) 遍历系统中的所有调度类(前面提到的4个sched_class),依次调用各个sched_class.pick_next_task()函数(直到找到一个可运行的任务)。
     4) 不同的sched_class.pick_next_task()函数会找到struct rq runqueues[cpu]结构中各自的调度运行队列结构。如cfs调度会找到rq中的struct cfs_rq cfs,而rt调度则会找到这个rq中的struct rt_rq rt成员。

     5) 有了调度运行队列,如上面的cfs_rq,就可以根据各自的调度算法先找到最适合运行的调度实体sched_entiy(实时调度为sched_rt_entity),然后自然也就找到了对应的任务task_struct。剩下的任务切换工作就交给内核来完成。

2.2 rt调度

     rt调度的结构在《RT任务调度概览》中已经介绍过。为了保持本章的一致性和完整性,也简单介绍一下rt调度的组织结构。
     和cfs一样,struct rq runqueues依然是rt进程组织结构的“根”,但是只不过所有的实时任务组织在struct rt_rq rq.rt中。与cfs采用红黑树组织管理架构不同,实时任务的采用的是优先级数组方式来管理,这种优先级数组不是通用的数据结构,而是专为任务调度所设计,它定义在内核kernel/sched/sched.h文件中:
/* * This is the priority-queue data structure of the RT scheduling class: */struct rt_prio_array {    DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */    struct list_head queue[MAX_RT_PRIO];};
     其中rt_prio_arry的第一个成员是一个位图变量bitmap用以标记哪个优先级有就绪的任务;而链表数组queue[MAX_RT_PRIO]则为每个优先级的实时任务都准备了一个struct list_head链表,用以将各个优先级的任务组织起来。
     当bitmap中的某一位为1时,表示queue[MAX_RT_PRIO]对应的某个优先级的链表中挂有就绪的任务。
     在rt_rq中,优先级数组成员为:struct rt_prio_array active,整个rt调度的组织架构如下所示:
图2:rt任务的组织架构

    如上图所示,rt调度的组织也是通过rq/rt_rq/sched_rt_entity/task_struct的层级方式组织起来。当系统要从rt调度类中挑选一个任务时,流程如下:
     1) 首先通过cpu = smp_processor_id()获取当前cpu;
     2) 在通过rq = cpu_rq(cpu)获取当前cpu对应的runqueues[cpu];
     3) 遍历所有的调度类,找到rt_sched_class,接着找到rt对应的回调函数pick_next_task_rt,这个函数会找到requeues[cpuN]中的struct rt_rq rt成员;

     4) 再通过rt_rq.active获取到rt运行队列中的优先级数组。优先级数组中有两个元素,一个是含有MAX_RT_PRIO个struct list_head元素的数组queue[];另外一个是位图bitmap。

     5)  在没有指定组调度的情况下,优先级数组中的的两个元素就将本cpu上的所有的调度实体组织起来。其中queue[MAX_RT_PRIO]中的每个元素挂着某一个优先级的所有就绪的调度实体(也有可能某个优先级没有任何就绪调度实体);如果某一优先级中有就绪的调度实体,则bitmap中的对应的某一位就置1。

     6)  具有同一个优先级的所有就绪rt调度实体都通过sched_rt_entity.run_list 链接组织成一个链表,挂到queue[prio]链表头上。

     7) 和cfs调度类似sched_rt_entity是嵌入在任务task_struct中的。如果一个任务是实时任务,那么它就唯一对应一个rt调度实体sched_rt_entity;在没有定义实时组调度的情况下sched_rt_entity与task_struct是一一对应的;然而一旦定义了组调度,和cfs调度的情况类似,一个sched_rt_entity可能代表一个组,即实时调度实体可以通过struct rt_rq sched_rt_entity.my_q成员又组织成一个优先级数组。只有在sched_rt_entity.my_q为NULl时,这个实时调度实体才表示一个真正的任务。 

     内核中的调度的大概框架就讲到这里。为了了解整个组织原理,没有详细讲解里面的细节,以免“喧宾夺主”。有了框架概念,后续会更好的了解到具体的调度细节。

原创粉丝点击