高级进程管理之处理器亲和性

来源:互联网 发布:java xstream解析xml 编辑:程序博客网 时间:2024/05/19 03:25

Linux支持具有多个处理器的单一系统。引导过程除外,支持多个处理器的大量工作由进程调度程序负责。在对称多重处理(SMP)及其上,进程调度程序必须决定每个CPU上要运行哪些进程。有两项挑战源自此责任:调度程序必须想办法充分利用系统上的所有处理器,因为当有一个进程已就绪等待运行,却有一个CPU闲置一旁,这显然没有效率。

 

然而一个进程一旦被安排在某个CPU上运行,往后进程调度程序也会将它安排在相同的CPU上运行。这是有益的,因为将一个进程从一个处理器迁移到另一个处理器是要付出代价的。

 

这些代价中最大者与“迁移的缓存区效应”(cache effect)有关。由于现在SMP系统的设计,与每个处理器相关的缓存区是不同且独立的。也就是说,如果数据位于一个处理器的缓存区中,就不会位于另一个处理器的缓存区中。因此,如果一个进程被迁移往一个新的CPU,而且将新的数据写入内存,那么位于旧CPU缓存区中的数据就过时了。现在若使用该缓存区,则会导致数据被破坏。为了避免此现象,当一个缓存区中缓存了一个新的内存块时,这会让其他每个缓存区中的数据失效。因此任何时刻特定的一段数据只能出现在一个处理器的缓存区中(假设数据全部被缓存起来)。当有一个进程从一个处理器移往另一个处理器时会付出两种代价:被移动的进程无法访问被缓存起来的数据,而且位于原处理器缓存区的数据必须作废。因为必须付出这些代价,所以进程调度程序会尽可能为一个进程安排特定的CPU来运行它。

 

当然,进程调度程序的两项目标可能无法实现。如果一个处理器的进程负荷比另一个处理器大很多(或者,更糟糕的是,如果一个处理器处于忙碌状态,而另一个处理器却闲置一旁),明智的做法是对一些进程重新调度,让它们在比较不忙的CPU上运行。决定何时移动进程以响应此类不平衡的状态称为负载均衡(load balancing),这对SMP及其的性能而言非常重要。

 

处理器亲和性(processor affinity)是指一个进程被一致安排在同一个处理器上运行的可能性。软亲和性(soft affinity)是指调度程序继续安排一个进程在同一个处理器上运行的自然勤想。Linux调度程序会尽可能安排相同的进程在相同的处理器上运行,而且只会在负载极不平衡的情况下将一个进程从一个CPU移往另一个CPU。这让处理器可以最小化迁移的缓存区效应,但是仍能确保一个系统中所有处理器的负载时平衡的。

 

然而,有时用户或应用程序会想把进程与处理器结合在一起。这时因为进程具有强烈的缓存区敏感性,而且想停留在相同的处理器上。结合一个进程一个与一个特定的处理器,让内核按照此关系行事称为一个硬亲和性(har affinity)。

 

sched_getaffinity()与sched_setaffinity()

进程会继承其父进程的CPU亲和性,默认情况下,进程可以运行在任何CPU之上。Linux提供了两个系统调用可用于取得和设定一个进程的“硬亲和性”:

 

#define _GNU_SOURCE

#include <sched.h>

typedef struct cpu_set_t

size_t CPU_SET_SIZE

 

void CPU_SET(unsigned long cpu, cpu_set_t *set);

void CPU_CLR(unsigned long cpu, cpu_set_t *set);

int CPU_IISET(unsigned long cpu, cpu_set_t *set);

void CPU_ZERO(cpu_set_t *set);

 

int sched_setaffinity(pid_t pid, size_t setsize, const cpu_set_t *set);

int sched_getaffinity(pid_t pid, size_t setsize, cpu_set_t *set);

 

sched_getaffinity()可用于取得进程pid的CPU亲和性,而且会以特殊的cpu_set_t类型来存放它,这可通过特殊的宏来访问。如果pid为0,则此调用会取得当前进程的亲和性。setsize参数是cpu_set_t类型的大小,为了配合此类型大小未来的变化,glibc可能会用到此参数。执行成功时,sched_getaffinity()会返回0;执行失败是,它会返回-1并且设定errno。请看下面的例子:

 

 

 

进行调用之前,我们会使用CPU_ZERO将set中所有位都清零。然后我们会从0到CPU_SETSIZE迭代处理set。请处以,CPU_SETSIZE并不是set的大小,你绝对不应该把它传递给setsize,而是set所可能表示的处理器数目。因为当前实现是以单一位来表示每个处理器,所以CPU_SETSIZE会比sizeof(cpu_set_t)大很多。我们还会使用CPU_ISSET来检查系统中特定的处理器i,是否绑定或为绑定此进程。如果返回0,表示未绑定;如果返回非0值,表示绑定了。

 

系统中是有实体的处理器会被设定。因此,若在一个具有两个处理器的系统上运行此程序代码,将产生如下效果:

 

cpu=0 is set

cpu=1 is set

cpu=2 is unset

cpu=3 is unset

...

cpu=1023 is unset

 

我们所关心的只是CPU#0和CPU#1,因为它们是此系统上仅有的实体处理器。也许我们想确保我们的进程只会运作在CPU#0之上,而不会运作在CPU#1之上。下面程序代码可以完成此事:

 

 

一如往常,首先我们会使用CPU_ZERO将set清零。然后我们会使用CPU_SET设定CPU#0以及使用CPU_CLR清除CPU#1。CPU_CLR操作时多余的,因为我们刚刚将整个set清零,这么做只是为了完整性。

 

同样在这个具有两个处理器的系统上运行此程序会产生与之前稍微不同的输出:

 

cpu=0 is set

cpu=1 is unset

cpu=2 is unset

...

cpu=1023 is unset

 

现在CPU#1会被清除。此进程只会运行在CPU#0之上。

 

原创粉丝点击