1 概念
什麼是CPU Affinity?Affinity是進程的一個屬性,這個屬性指明瞭進程調度器能夠把這個進程調度到哪些CPU上。
在Linux中,我們可以利用CPU affinity 把一個或多個進程綁定到一個或多個CPU上。CPU Affinity分爲2種,soft affinity和hard affinity。soft affinity僅是一個建議,如果不可避免,調度器還是會把進程調度到其它的CPU上。hard affinity是調度器必須遵守的規則。
爲什麼需要CPU綁定?
●增加CPU緩存的命中率
CPU之間是不共享緩存的,如果進程頻繁的在各個CPU間進行切換,需要不斷的使舊CPU的cache失效。如果進程只在某個CPU上執行,則不會出現失效的情況。
●增加CPU緩存的命中率
在多個線程操作的是相同的數據的情況下,如果把這些線程調度到一個處理器上,大大的增加了CPU緩存的命中率。但是可能會導致併發性能的降低。如果這些線程是串行的,則沒有這個影響。
●適合time-sensitive應用
在real-time或time-sensitive應用中,我們可以把系統進程綁定到某些CPU上,把應用進程綁定到剩餘的CPU上。典型的設置是,把應用綁定到某個CPU上,把其它所有的進程綁定到其它的CPU上。
2綁定進程和CPU的編碼實現
進程親和性的設置和獲取主要通過下面兩個函數來實現:
#define _GNU_SOURCE
#include <sched.h>
long sched_setaffinity(pid_t pid, unsigned int len,
unsigned long *user_mask_ptr);
long sched_getaffinity(pid_t pid, unsigned int len,
unsigned long *user_mask_ptr);
從函數名以及參數名都很明瞭,唯一需要解釋的是第三個參數, 這個參數select中的fd_set比較類似,每個bit代表一個CPU。
//設置affinity的例子
unsigned long mask = 7; /* processors 0, 1, and 2 */
unsigned int len = sizeof(mask);
sched_setaffinity(0, len, &mask);
//設置獲取affinity的例子
unsigned long mask;
unsigned int len = sizeof(mask);
sched_getaffinity(0, len, &mask);
printf("my affinity mask is: %08lx\n", mask);
我們也可以不修改源代碼來綁定CPU,前提是你必須是root用戶或者是進程的owner。參見第6.1節。
3 綁定線程和CPU的編碼實現
與進程的情況相似,線程親和性的設置和獲取主要通過下面兩個函數來實現:
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);
從函數名以及參數名都很明瞭,唯一需要點解釋下的可能就是cpu_set_t這個結構體了。這個結構體的理解類似於select中的fd_set,可以理解爲cpu集,也是通過約定好的宏來進行清除、設置以及判斷:
//初始化,設爲空
void CPU_ZERO (cpu_set_t *set);
//將某個cpu加入cpu集中
void CPU_SET (int cpu, cpu_set_t *set);
//將某個cpu從cpu集中移出
void CPU_CLR (int cpu, cpu_set_t *set);
//判斷某個cpu是否已在cpu集中設置了
int CPU_ISSET (int cpu, const cpu_set_t *set);
cpu集可以認爲是一個掩碼,每個設置的位都對應一個可以合法調度的 cpu,而未設置的位則對應一個不可調度的 CPU。換而言之,線程都被綁定了,只能在那些對應位被設置了的處理器上運行。通常,掩碼中的所有位都被置位了,也就是可以在所有的cpu中調度。
6.2小節是線程綁定CPU的例子。
4 進程獨佔CPU
如何實現一個或多個進程獨佔一個或多個CPU? 即調度器只能把指定的進程調度至指定的CPU。最簡單的方法是利用fork()的繼承特性,子進程繼承父進程的affinity。這種方法無需修改和編譯內核代碼。
init進程是所有進程的祖先,我們可以設置init進程的affinity來達到設置所有進程的affinity的目地,然後把我們自己的進程綁定到目地CPU上。這樣就到達了在指定CPU上只運行指定的的進程的目地。那麼,如何修改init進程的affinity?我們只需在/etc/rc.d/rc.sysinit或/etc/rc.sysinit中,起始處增加如下兩行,其中bind是6.1小節編譯生成的可執行文件,rc.sysinit文件是init進程運行的第一個腳本。
/bin/bind 1 1 #綁定init進程至處理器0
/bin/bind $$ 1 #綁定當前進程至處理器0
5 線程獨佔CPU
通過內核參數isolcpus 來指示系統保留CPU。然後把目標線程綁定至保留的CPU。
6 源代碼
6.1 綁定進程
/* bind - simple command-line tool to set CPU
* affinity of a given task
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <sched.h>
int main(int argc, char *argv[])
{
unsigned long new_mask;
unsigned long cur_mask;
unsigned int len = sizeof(new_mask);
pid_t pid;
if (argc != 3) {
fprintf(stderr,
"usage: %s [pid] [cpu_mask]\n",
argv[0]);
return -1;
}
pid = atol(argv[1]);
sscanf(argv[2], "%08lx", &new_mask);
if (sched_getaffinity(pid, len,
&cur_mask) < 0) {
perror("sched_getaffinity");
return -1;
}
printf("pid %d's old affinity: %08lx\n",
pid, cur_mask);
if (sched_setaffinity(pid, len, &new_mask)) {
perror("sched_setaffinity");
return -1;
}
if (sched_getaffinity(pid, len,
&cur_mask) < 0) {
perror("sched_getaffinity");
return -1;
}
printf(" pid %d's new affinity: %08lx\n",
pid, cur_mask);
return 0;
}
6.2 綁定線程
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
void *myfun(void *arg)
{
cpu_set_t mask;
cpu_set_t get;
char buf[256];
int i;
int j;
int num = sysconf(_SC_NPROCESSORS_CONF);
printf("system has %d processor(s)\n", num);
for (i = 0; i < num; i++) {
CPU_ZERO(&mask);
CPU_SET(i, &mask);
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) {
fprintf(stderr, "set thread affinity failed\n");
}
CPU_ZERO(&get);
if (pthread_getaffinity_np(pthread_self(), sizeof(get), &get) < 0) {
fprintf(stderr, "get thread affinity failed\n");
}
for (j = 0; j < num; j++) {
if (CPU_ISSET(j, &get)) {
printf("thread %d is running in processor %d\n", (int)pthread_self(), j);
}
}
j = 0;
while (j++ < 100000000) {
memset(buf, 0, sizeof(buf));
}
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, (void *)myfun, NULL) != 0) {
fprintf(stderr, "thread create failed\n");
return -1;
}
pthread_join(tid, NULL);
return 0;
}