Linux一直坚持采用对称多处理模式,这意味着,与其他CPU相比,内核不对一个CPU有任何偏向,但是,多处理器机器具有很多不同的风格,而且调度程序的实现随硬件特征的不同而有所不同,我们将特别关注下面三种不同类型的多处理器机器:
(1)标准的多处理器体系结构
直到最近,这是多处理器机器最普通的体系结构。这些机器所共有的RAM芯片集被所有CPU共享。
(2)超线程
超线程芯片是一个立刻执行几个执行线程的微处理器;它包括几个内部寄存器的拷贝,并快速在它们之间切换。这种由Intel发明的技术,使得当前线程在访问内存的间隙,处理器可以使用它的机器周期去执行另外一个线程。一个超线程的物理CPU可以被Linux看作几个不同的逻辑CPU。
(3)NUMA
把CPU和RAM以本地“结点”为单位分组,(通常一个结点包括一个CPU和几个RAM芯片)。内存仲裁器(一个使系统中的CPU以串型方式访问RAM的专用电路)是典型的多处理器系统的瓶颈。在NUMA体系结构中,当CPU访问与它同在一个结点中的“本地”RAM芯片时,几乎没有竞争,因此访问通常是非常快的。另一方面,访问它所属结点外的“远程”RAM芯片就非常慢。
这些基本的多处理器系统类型经常被组合使用。例如,内核把一个包括两个不同超线程CPU的主板看作四个逻辑CPU。
正如我们在上一节所看到的,schedule( )函数从本地CPU的运行队列挑选新进程运行。因此,一个指定的CPU只能执行它相应的运行队列中的可运行进程。另外,一个可运行进程总是存放在某一个运行队列中:任何一个可运行进程都不可能同时出现在两个或多个运行队列中。因此,一个保持可运行状态的进程通常被限制在一个固定的 CPU上。
这种设计通常对系统性能是有益的,因为,运行队列中的可运行进程所拥有的数据可能填满每个CPU的硬件高速缓存。但是,在有些情况下,把可运行进程限制在一个指定的CPU上可能引起严重的性能损失。例如,考虑频繁使用CPU的大量批处理进程:如果他们绝大多数都在同一个运行队列中,那么系统中一个CPU将会超负荷,而其他一些CPU几乎处于空闲状态。
因此,内核周期性地检查运行队列的工作量是否平衡,并在需要的时候,把一些进程从一个运行队列迁移到另一个运行队列。但是,为了从多处理器系统获得最佳性能,负载平衡算法应该考虑系统中CPU的拓扑结构。从内核2.6.7版本开始,Linux提出一种基于“调度域”概念的复杂的运行队列平衡算法。正是有了调度域这一概念,使得这种算法能够很容易适应各种已有的多处理器体系结构(甚至诸如那些基于“多核”微处理器的新近出现的体系结构)。
1 调度域
调度域实际上是一个CPU集合,他们的工作量应当由内核保持平衡。一般来说,调度域采取分层的组织形式:最上层的调度域(通常包括系统中的所有CPU)包括多个子调度域,每个子调度域包括一个CPU子集。正是调度域的这种分层结构,使工作量的平衡能以如下有效方式来实现:
每个调度域被依次划分成一个或多个组,每个组代表调度域的一个CPU子集。工作量的平衡总是在调度域的组之间来完成。换而言之,只有在一些调度域的某些组的总工作量远远低于同一个调度域的另一个组的工作量时,才把进程从一个CPU迁移到另一个CPU。
下图说明三个调度域分层实例,对应三种主要的多处理器机器体系结构:
图中(a)表示具有两CPU的标准多处理器体系结构中由单个调度域组成的一个层次结构,该调度域包括两个组,每个组有一个CPU。
图中(b)表示一个两层的层次结构,用在使用超线程技术、有两CPU的多处理器结构中。最上层的调度域包括了系统中所有四个逻辑CPU,它由两个组构成。上层域的每个组对应一个子调度域并包括一个物理CPU。底层的调度域(也被称为基本调度域)包括两个组,每个组一个逻辑CPU。
最后,图中(c)表示有两个结点,每个结点有四个CPU的8-CPUNUMA体系结构上的两层层次结构。最上层的域由两个组构成,每个组对应一个不同的结点。每个基本调度域包括一个结点内的CPU,包括四个组,每个组包括一个CPU。
每个调度域由一个 sched_domain描述符表示,而调度域中的每个组由sched_group 描述符表示。每个sched_domain 描述符包括一个groups字段,它指向组描述符链表中的第一个元素。此外,sched_domain 结构的parent字段指向父调度域的描述符(如果有的话)。
struct sched_domain {
/* These fields must be setup */
struct sched_domain *parent; /* top domain must be null terminated */
struct sched_group *groups; /* the balancing groups of the domain */
cpumask_t span; /* span of all CPUs in this domain */
unsigned long min_interval; /* Minimum balance interval ms */
unsigned long max_interval; /* Maximum balance interval ms */
unsigned int busy_factor; /* less balancing by factor if busy */
unsigned int imbalance_pct; /* No balance until over watermark */
unsigned long long cache_hot_time; /* Task considered cache hot (ns) */
unsigned int cache_nice_tries; /* Leave cache hot tasks for # tries */
unsigned int per_cpu_gain; /* CPU % gained by adding domain cpus */
unsigned int busy_idx;
unsigned int idle_idx;
unsigned int newidle_idx;
unsigned int wake_idx;
unsigned int forkexec_idx;
int flags; /* See SD_* */
/* Runtime fields. */
unsigned long last_balance; /* init to jiffies. units in jiffies */
unsigned int balance_interval; /* initialise to 1. units in ms. */
unsigned int nr_balance_failed; /* initialise to 0 */
#ifdef CONFIG_SCHEDSTATS
/* load_balance