Linux下進(jìn)程的CPU配置與線程綁定過(guò)程
1 基于進(jìn)程的CPU配置
基于進(jìn)程的CPU配置技術(shù)是指在Linux操作系統(tǒng)中,通過(guò)調(diào)整進(jìn)程的CPU使用率來(lái)實(shí)現(xiàn)對(duì)系統(tǒng)資源的合理分配和管理。這種技術(shù)可以用于限制特定進(jìn)程的CPU使用,防止其占用過(guò)多的CPU資源,從而保證系統(tǒng)的穩(wěn)定性和性能。
基于進(jìn)程的CPU配置是指將一個(gè)進(jìn)程綁定到特定的CPU核心或者CPU集合上運(yùn)行。這樣可以控制進(jìn)程在特定的CPU資源上執(zhí)行,以提高性能或?qū)崿F(xiàn)特定的調(diào)度策略。
1.1 對(duì)CPU親和力的配置
對(duì)于CentOS 8下的Linux系統(tǒng),首先可以使用top命令來(lái)查看當(dāng)前的CPU占用率,如下圖所示:
鍵盤(pán)上按數(shù)字1,可以以數(shù)據(jù)化的形式看到具體的使用情況,如下圖:

鍵盤(pán)上按字母t,可以以圖形化的形式看到具體的使用情況,如下圖:


可以看到,當(dāng)前的CPU基本是空閑的狀態(tài)。
這時(shí)候,我們寫(xiě)一個(gè)測(cè)試代碼,讓其掛在后臺(tái)運(yùn)行,例如下面的一個(gè)死循環(huán)函數(shù):
#include <iostream>
int main()
{
    while (true) {
        // 在這里編寫(xiě)你的代碼
        // 例如,輸出一條信息
        std::cout << "Hello, I am running in an infinite loop!" << std::endl;
    }
    return 0;
}
讓其掛在后臺(tái)運(yùn)行著,暫時(shí)不管。
如下圖:


此時(shí),我們?cè)俅问褂胻op命令,來(lái)查看當(dāng)前的CPU 狀態(tài),如下圖:

可以看到,此時(shí)循環(huán)代碼這個(gè)進(jìn)程正在后臺(tái)不停運(yùn)行,此時(shí)的CPU占用率相比開(kāi)始,非常的高。
記住當(dāng)前的進(jìn)程PID,然后使用taskset -c -p 13265 命令查看當(dāng)前進(jìn)程的CPU親和力。13265是當(dāng)前進(jìn)程的PID。

可以從輸出的信息看出,pid 為13265的進(jìn)程(即當(dāng)前的測(cè)試進(jìn)程)的親和力CPU為0-3,即它同時(shí)運(yùn)行在了0,1,2,3這四個(gè)CPU上面。
現(xiàn)在我們更改pid 為13265的進(jìn)程(即當(dāng)前的測(cè)試進(jìn)程)的親和力,將其改成只在0,1這兩個(gè)CPU上面運(yùn)行。如下圖:


這里由于我使用了ctrl + c暫停了剛才的進(jìn)程,然后重新啟動(dòng)該進(jìn)程時(shí),進(jìn)程的PID變成了13495,不過(guò)這沒(méi)有任何影響??梢钥吹?,當(dāng)前的進(jìn)程新的親和力列表為0,1,說(shuō)明設(shè)置新的CPU親和力成功。而pid 13495 的當(dāng)前親和力掩碼為3,同樣能說(shuō)明親和力設(shè)置成功。使用taskset命令來(lái)查看進(jìn)程運(yùn)行在哪個(gè)CPU上。
使用以下命令:
taskset -p < PID >
【請(qǐng)將< PID >替換為你要查看的進(jìn)程的實(shí)際PID。】
該命令將顯示進(jìn)程的當(dāng)前親和力掩碼,其中每個(gè)位表示一個(gè)CPU核心。例如,如果輸出為pid 's current affinity mask: 3,表示進(jìn)程當(dāng)前在CPU核心0和1上運(yùn)行,因?yàn)槎M(jìn)制表示為11。
再次實(shí)驗(yàn)一下,這次我們將CPU親和力設(shè)置在CPU 1,CPU2上,如下圖:

由當(dāng)前親和力掩碼可以看到,為6,說(shuō)明此時(shí)運(yùn)行在CPU1和CPU2上。(2^1 + 2^2 = 6)
1.2 綁定進(jìn)程到指定CPU核上運(yùn)行
查看CPU有幾個(gè)核
使用 cat /proc/cpuinfo 查看CPU信息,如下兩個(gè)信息:
- ·processor:指明第幾個(gè)cpu處理器
 - ·cpu cores:指明每個(gè)處理器的核心數(shù)
 

以本機(jī)中虛擬機(jī)為例,有4個(gè)CPU(分別為:CPU0, CPU1, CPU2, CPU3),每個(gè)CPU有1個(gè)核。
也可以使用系統(tǒng)調(diào)用sysconf獲取CPU核心數(shù):
#include <unistd.h> int sysconf(_SC_NPROCESSORS_CONF);/* 返回系統(tǒng)可以使用的核數(shù),但是其值會(huì)包括系統(tǒng)中禁用的核的數(shù)目,因 此該值并不代表當(dāng)前系統(tǒng)中可用的核數(shù) */ int sysconf(_SC_NPROCESSORS_ONLN);/* 返回值真正的代表了系統(tǒng)當(dāng)前可用的核數(shù) */ /* 以下兩個(gè)函數(shù)與上述類似 */ #include <sys/sysinfo.h> int get_nprocs_conf (void);/* 可用核數(shù) */ int get_nprocs (void);/* 真正的反映了當(dāng)前可用核數(shù) */
使用 taskset 指令
- 獲取進(jìn)程pid:
 

- 查看進(jìn)程當(dāng)前運(yùn)行在哪個(gè)CPU上:
 

顯示的十六進(jìn)制f轉(zhuǎn)換為二進(jìn)制為最低四個(gè)是1,每個(gè)1對(duì)應(yīng)一個(gè)CPU,所以進(jìn)程運(yùn)行在4個(gè)CPU上。
- 指定進(jìn)程10770運(yùn)行在CPU0上:
 

注意,CPU的標(biāo)號(hào)是從0開(kāi)始的,所以cpu0表示第1個(gè)CPU(第一個(gè)CPU的標(biāo)號(hào)是0)。
至此,就把應(yīng)用程序綁定到了CPU0上運(yùn)行,查看如下:

- 啟動(dòng)程序時(shí)綁定CPU:
 
例如啟動(dòng)時(shí)綁定到第二個(gè)CPU上,即CPU1:

使用sched_setaffinity系統(tǒng)調(diào)用
通過(guò)系統(tǒng)調(diào)用sched_setaffinity進(jìn)行綁定,通過(guò)sched_getaffinity獲取綁定關(guān)系。注意這對(duì)方法是進(jìn)程級(jí)別的綁定。代碼中指定cpu0和cpu3,我們可以通過(guò)top查看,兩個(gè)CPU使用達(dá)到了100%,其他的CPU均不會(huì)(正常場(chǎng)景)。
sched_setaffinity可以將某個(gè)進(jìn)程綁定到一個(gè)特定的CPU。
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <sched.h> /* 設(shè)置進(jìn)程號(hào)為pid的進(jìn)程運(yùn)行在mask所設(shè)定的CPU上 * 第二個(gè)參數(shù)cpusetsize是mask所指定的數(shù)的長(zhǎng)度 * 通常設(shè)定為sizeof(cpu_set_t) * 如果pid的值為0,則表示指定的是當(dāng)前進(jìn)程 */ int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);/* 獲得pid所指示的進(jìn)程的CPU位掩碼,并將該掩碼返回到mask所指向的結(jié)構(gòu)中 */
代碼示例:
/*
*該程序演示了如何使用sched_setaffinity函數(shù)將線程綁定到特定的CPU核心上運(yùn)行。
*程序首先創(chuàng)建了兩個(gè)線程,然后使用sched_setaffinity函數(shù)將線程1綁定到CPU 0上,將線程2綁定到CPU 3上。
*運(yùn)行時(shí),可以通過(guò)查看輸出的pid來(lái)確定程序的進(jìn)程ID。
*然后,程序?qū)PU_ZERO宏應(yīng)用于一個(gè)cpu_set_t類型的變量mask,以將其初始化為空集。
*接下來(lái),程序?qū)PU_SET宏應(yīng)用于mask,將CPU 0和CPU 3添加到集合中。
*最后,程序調(diào)用sched_setaffinity函數(shù)將mask應(yīng)用于當(dāng)前進(jìn)程,將線程1綁定到CPU 0上,將線程2綁定到CPU 3上。
*線程創(chuàng)建成功后,程序使用pthread_join函數(shù)等待線程1和線程2的結(jié)束。
*/
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
 
void* testfunc(void* t) {
  while(1);
  return NULL; 
}
 
int main()
{
  cpu_set_t mask; // 定義cpu_set_t類型的變量mask,用于存儲(chǔ)CPU集合
  printf("pid=%d\n", getpid()); // 打印進(jìn)程ID
  CPU_ZERO(&mask); // 將mask初始化為空集
  CPU_SET(0, &mask);//將cpu0綁定到mask中
  CPU_SET(3, &mask);//將cpu3綁定到mask中
  // 將mask應(yīng)用于當(dāng)前進(jìn)程,綁定線程到指定的CPU核心
  sched_setaffinity(0, sizeof(cpu_set_t), &mask) ;
  
  pthread_t tid1;//創(chuàng)建線程1
  if (pthread_create(&tid1, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 線程創(chuàng)建失敗,打印錯(cuò)誤信息
    return -1;   
  }
  pthread_t tid2;//創(chuàng)建線程2
  if (pthread_create(&tid2, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 線程創(chuàng)建失敗,打印錯(cuò)誤信息
    return -1;   
  } 
  pthread_join(tid1, NULL); // 等待線程1結(jié)束
  pthread_join(tid1, NULL); // 等待線程2結(jié)束
  return 0;
}
執(zhí)行結(jié)果如下圖所示:
- 執(zhí)行前:
 

- 執(zhí)行后:
 

2 基于線程的CPU配置
2.1 線程綁定:使用函數(shù)pthread_setaffinity_np
線程綁定CPU核心的意義:
- 在多核CPU中合理的調(diào)度線程在各個(gè)核上運(yùn)行可以獲得更高的性能。
 - 在多線程編程中,每個(gè)線程處理的任務(wù)優(yōu)先級(jí)是不一樣的,對(duì)于要求實(shí)時(shí)性比較高的線程或者是主線程,對(duì)于這種線程我們可以在創(chuàng)建線程時(shí)指定其綁定到某個(gè)CPU核上,以后這個(gè)核就專門(mén)處理該線程。
 - 這樣可以使得該線程的任務(wù)可以得到較快的處理,特別是和用戶直接交互的任務(wù),較短的響應(yīng)時(shí)間可以提升用戶的體驗(yàn)感。
 
幾個(gè)重要的宏操作:
一個(gè)線程的CPU親合力掩碼用一個(gè)cpu_set_t結(jié)構(gòu)體來(lái)表示一個(gè)CPU集合,下面的幾個(gè)宏分別對(duì)這個(gè)掩碼集進(jìn)行操作:
CPU_ZERO() 清空一個(gè)集合 CPU_SET()與CPU_CLR()分別對(duì)將一個(gè)給定的CPU號(hào)加到一個(gè)集合或者從一個(gè)集合中去掉 CPU_ISSET()檢查一個(gè)CPU號(hào)是否在這個(gè)集合中
設(shè)置獲取線程CPU親和力狀態(tài):
sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
該函數(shù)設(shè)置線程為pid的這個(gè)線程,讓它運(yùn)行在mask所設(shè)定的CPU上。
如果pid的值為0,則表示指定的是當(dāng)前線程,使當(dāng)前線程運(yùn)行在mask所設(shè)定的那些CPU上。
第二個(gè)參數(shù)cpusetsize是mask所指定的數(shù)的長(zhǎng)度。通常設(shè)定為sizeof(cpu_set_t)。
如果當(dāng)前pid所指定的線程此時(shí)沒(méi)有運(yùn)行在mask所指定的任意一個(gè)CPU上,則該指定的線程會(huì)從其它CPU上遷移到mask的指定的一個(gè)CPU上運(yùn)行。
sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
該函數(shù)獲得pid所指示的線程的CPU位掩碼,并將該掩碼返回到mask所指向的結(jié)構(gòu)中。即獲得指定pid當(dāng)前可以運(yùn)行在哪些CPU上。同樣,如果pid的值為0。也表示的是當(dāng)前進(jìn)程。
簡(jiǎn)單的實(shí)例:
// 此代碼不完整,只是幫助理解綁定過(guò)程 // 在創(chuàng)建線程時(shí)添加以下代碼,可以將該線程綁定到1核 cpu_set_t mask; // 將掩碼清零 CPU_ZERO(&mask); // 將1添加到掩碼中 CPU_SET(1, &mask); // #將本線程綁定到1核 sched_setaffinity(0, sizeof(cpu_set_t), &mask);
查看線程是否運(yùn)行在指定的核上:
實(shí)際工作中,為了方便查看線程的情況,會(huì)在創(chuàng)建線程時(shí)將相關(guān)信息保存到一個(gè)文件中,需要時(shí)用cat命令查看,內(nèi)容包括創(chuàng)建了哪些線程、線程名稱、線程id和pid、綁定的CPU核、優(yōu)先級(jí)、調(diào)度方式等。
使用“top”命令查看:
- top -d 2:查看線程的運(yùn)行情況和CPU狀態(tài)
 - 按’h’ 和 1:在上一句的基礎(chǔ)上可以查看更詳細(xì)的信息。
 
從文件中得到線程pid和ppid,通過(guò)top命令,查看線程在哪個(gè)CPU核上運(yùn)行,驗(yàn)證核綁定的核是否一樣。
對(duì)于線程綁定,我們需要借助pthread庫(kù),通過(guò)函數(shù)pthread_setaffinity_np來(lái)設(shè)置綁定cpu關(guān)系。我們通過(guò)top查看,會(huì)發(fā)現(xiàn)cpu0和cpu3使用率達(dá)到100%。
代碼實(shí)例:
/*
 *該程序演示了如何使用pthread_setaffinity_np函數(shù)將線程綁定到特定的CPU核心上運(yùn)行。
 *程序首先創(chuàng)建了兩個(gè)線程,然后使用pthread_setaffinity_np函數(shù)將線程1綁定到CPU 0上,將線程2綁定到CPU 3上。
 *運(yùn)行時(shí),可以通過(guò)查看輸出的pid來(lái)確定程序的進(jìn)程ID。
 *然后,程序?qū)PU_ZERO宏應(yīng)用于一個(gè)cpu_set_t類型的變量mask,以將其初始化為空集。
 *接下來(lái),程序使用pthread_create函數(shù)創(chuàng)建線程1和線程2,并檢查線程創(chuàng)建是否成功。
 *然后,程序打印出線程1和線程2的ID。
 *程序使用CPU_SET宏將CPU 0添加到mask中,并使用pthread_setaffinity_np函數(shù)將mask應(yīng)用于線程1,將線程1綁定到CPU 0上。
 *然后,程序清除之前設(shè)置的mask,并將CPU 3添加到mask中,并使用pthread_setaffinity_np函數(shù)將mask應(yīng)用于線程2,將線程2綁定到CPU   3上。
 *最后,程序使用pthread_join函數(shù)等待線程1和線程2的結(jié)束。
*/
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sched.h>
#include <pthread.h>
void* testfunc(void* t) {
  int i = 3; // 初始化循環(huán)計(jì)數(shù)器為3
  while(i) { // 進(jìn)入循環(huán),條件為i非零
     sleep(5); // 休眠5秒
     printf("tid=%d,cpu=%d\n",pthread_self(), sched_getcpu()); // 打印線程ID和CPU編號(hào)
     i--; // 計(jì)數(shù)器減一
  }
  while(1); // 進(jìn)入無(wú)限循環(huán)
  return NULL; 
}
int main()
{
  cpu_set_t mask; // 定義CPU集合
  printf("pid=%d\n", getpid()); // 打印進(jìn)程ID
  CPU_ZERO(&mask); // 清空CPU集合
  
  pthread_t tid1; // 定義線程tid1
  if (pthread_create(&tid1, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 線程創(chuàng)建失敗,打印錯(cuò)誤信息
    return -1;   
  }
  pthread_t tid2; // 定義線程tid2
  if (pthread_create(&tid2, NULL, (void *)testfunc, NULL) != 0) 
  {      
    fprintf(stderr, "thread create failed\n"); // 線程創(chuàng)建失敗,打印錯(cuò)誤信息
    return -1;   
  } 
  printf("tid1=%d,tid2=%d\n", tid1,tid2); // 打印線程tid1和tid2的值
 
  CPU_SET(0, &mask); // 將CPU0加入CPU集合
  pthread_setaffinity_np(tid1, sizeof(cpu_set_t), &mask) ; // 設(shè)置線程tid1的CPU親和性為CPU0
  
  // 清除之前設(shè)置,重新設(shè)置綁定cpu3
  CPU_ZERO(&mask); // 清空CPU集合
  CPU_SET(3, &mask); // 將CPU3加入CPU集合
  pthread_setaffinity_np(tid2, sizeof(cpu_set_t), &mask) ; // 設(shè)置線程tid2的CPU親和性為CPU3
  
  pthread_join(tid1, NULL); // 等待線程tid1結(jié)束
  pthread_join(tid1, NULL); // 等待線程tid2結(jié)束
  return 0;
}
- 執(zhí)行之后:
 

- 將其kill,恢復(fù):
 

建議:進(jìn)行配置之前先將虛擬機(jī)拍攝快照,以防配置不當(dāng)出現(xiàn)意外情況。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
 Linux提示網(wǎng)絡(luò)不可達(dá)問(wèn)題
這篇文章主要介紹了Linux提示網(wǎng)絡(luò)不可達(dá)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
 Linux網(wǎng)絡(luò)啟動(dòng)問(wèn)題:Device does not seem to be present解決辦法
這篇文章主要介紹了Linux網(wǎng)絡(luò)啟動(dòng)問(wèn)題:Device does not seem to be present解決辦法的相關(guān)資料,希望通過(guò)本文能幫助到大家解決這樣的問(wèn)題,需要的朋友可以參考下2017-10-10
 Linux內(nèi)核設(shè)備驅(qū)動(dòng)之proc文件系統(tǒng)筆記整理
今天小編就為大家分享一篇關(guān)于Linux內(nèi)核設(shè)備驅(qū)動(dòng)之proc文件系統(tǒng)筆記整理,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12

