如何將進程/線程綁定到固定的CPU核

概念

什麼是CPU AffinityAffinity是進程的一個屬性,這個屬性指明瞭進程調度器能夠把這個進程調度到哪些CPU上。

Linux中,我們可以利用CPU affinity 把一個或多個進程綁定到一個或多個CPU上。CPU Affinity分爲2種,soft affinityhard affinitysoft affinity僅是一個建議,如果不可避免,調度器還是會把進程調度到其它的CPU上。hard affinity是調度器必須遵守的規則。

爲什麼需要CPU綁定?

增加CPU緩存的命中率

CPU之間是不共享緩存的,如果進程頻繁的在各個CPU間進行切換,需要不斷的使舊CPUcache失效。如果進程只在某個CPU上執行,則不會出現失效的情況。

增加CPU緩存的命中率

在多個線程操作的是相同的數據的情況下,如果把這些線程調度到一個處理器上,大大的增加了CPU緩存的命中率。但是可能會導致併發性能的降低。如果這些線程是串行的,則沒有這個影響。

適合time-sensitive應用

real-timetime-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節。

綁定線程和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的例子。       

進程獨佔CPU

如何實現一個或多個進程獨佔一個或多個CPU 即調度器只能把指定的進程調度至指定的CPU。最簡單的方法是利用fork()的繼承特性,子進程繼承父進程的affinity。這種方法無需修改和編譯內核代碼。

init進程是所有進程的祖先,我們可以設置init進程的affinity來達到設置所有進程的affinity的目地,然後把我們自己的進程綁定到目地CPU上。這樣就到達了在指定CPU上只運行指定的的進程的目地。那麼,如何修改init進程的affinity?我們只需在/etc/rc.d/rc.sysinit/etc/rc.sysinit中,起始處增加如下兩行,其中bind6.1小節編譯生成的可執行文件,rc.sysinit文件是init進程運行的第一個腳本。

/bin/bind 1 1  #綁定init進程至處理器0
/bin/bind $$ 1  #綁定當前進程至處理器0

線程獨佔CPU

通過內核參數isolcpus 來指示系統保留CPU。然後把目標線程綁定至保留的CPU

源代碼

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;
}

參考

[1] http://www.linuxjournal.com/article/6799?page=0,0

[2] http://blog.chinaunix.net/uid-26739406-id-3181199.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章