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

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


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


此時,我們再次使用top命令,來查看當(dāng)前的CPU 狀態(tài),如下圖:

可以看到,此時循環(huán)代碼這個進(jìn)程正在后臺不停運行,此時的CPU占用率相比開始,非常的高。
記住當(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)前的測試進(jìn)程)的親和力CPU為0-3,即它同時運行在了0,1,2,3這四個CPU上面。
現(xiàn)在我們更改pid 為13265的進(jìn)程(即當(dāng)前的測試進(jìn)程)的親和力,將其改成只在0,1這兩個CPU上面運行。如下圖:


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

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

以本機(jī)中虛擬機(jī)為例,有4個CPU(分別為:CPU0, CPU1, CPU2, CPU3),每個CPU有1個核。
也可以使用系統(tǒng)調(diào)用sysconf獲取CPU核心數(shù):
#include <unistd.h> int sysconf(_SC_NPROCESSORS_CONF);/* 返回系統(tǒng)可以使用的核數(shù),但是其值會包括系統(tǒng)中禁用的核的數(shù)目,因 此該值并不代表當(dāng)前系統(tǒng)中可用的核數(shù) */ int sysconf(_SC_NPROCESSORS_ONLN);/* 返回值真正的代表了系統(tǒng)當(dāng)前可用的核數(shù) */ /* 以下兩個函數(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)前運行在哪個CPU上:

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

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

- 啟動程序時綁定CPU:
例如啟動時綁定到第二個CPU上,即CPU1:

使用sched_setaffinity系統(tǒng)調(diào)用
通過系統(tǒng)調(diào)用sched_setaffinity進(jìn)行綁定,通過sched_getaffinity獲取綁定關(guān)系。注意這對方法是進(jìn)程級別的綁定。代碼中指定cpu0和cpu3,我們可以通過top查看,兩個CPU使用達(dá)到了100%,其他的CPU均不會(正常場景)。
sched_setaffinity可以將某個進(jìn)程綁定到一個特定的CPU。
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include <sched.h> /* 設(shè)置進(jìn)程號為pid的進(jìn)程運行在mask所設(shè)定的CPU上 * 第二個參數(shù)cpusetsize是mask所指定的數(shù)的長度 * 通常設(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核心上運行。
*程序首先創(chuàng)建了兩個線程,然后使用sched_setaffinity函數(shù)將線程1綁定到CPU 0上,將線程2綁定到CPU 3上。
*運行時,可以通過查看輸出的pid來確定程序的進(jìn)程ID。
*然后,程序?qū)PU_ZERO宏應(yīng)用于一個cpu_set_t類型的變量mask,以將其初始化為空集。
*接下來,程序?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,用于存儲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)建失敗,打印錯誤信息
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)建失敗,打印錯誤信息
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)度線程在各個核上運行可以獲得更高的性能。
- 在多線程編程中,每個線程處理的任務(wù)優(yōu)先級是不一樣的,對于要求實時性比較高的線程或者是主線程,對于這種線程我們可以在創(chuàng)建線程時指定其綁定到某個CPU核上,以后這個核就專門處理該線程。
- 這樣可以使得該線程的任務(wù)可以得到較快的處理,特別是和用戶直接交互的任務(wù),較短的響應(yīng)時間可以提升用戶的體驗感。
幾個重要的宏操作:
一個線程的CPU親合力掩碼用一個cpu_set_t結(jié)構(gòu)體來表示一個CPU集合,下面的幾個宏分別對這個掩碼集進(jìn)行操作:
CPU_ZERO() 清空一個集合 CPU_SET()與CPU_CLR()分別對將一個給定的CPU號加到一個集合或者從一個集合中去掉 CPU_ISSET()檢查一個CPU號是否在這個集合中
設(shè)置獲取線程CPU親和力狀態(tài):
sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
該函數(shù)設(shè)置線程為pid的這個線程,讓它運行在mask所設(shè)定的CPU上。
如果pid的值為0,則表示指定的是當(dāng)前線程,使當(dāng)前線程運行在mask所設(shè)定的那些CPU上。
第二個參數(shù)cpusetsize是mask所指定的數(shù)的長度。通常設(shè)定為sizeof(cpu_set_t)。
如果當(dāng)前pid所指定的線程此時沒有運行在mask所指定的任意一個CPU上,則該指定的線程會從其它CPU上遷移到mask的指定的一個CPU上運行。
sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
該函數(shù)獲得pid所指示的線程的CPU位掩碼,并將該掩碼返回到mask所指向的結(jié)構(gòu)中。即獲得指定pid當(dāng)前可以運行在哪些CPU上。同樣,如果pid的值為0。也表示的是當(dāng)前進(jìn)程。
簡單的實例:
// 此代碼不完整,只是幫助理解綁定過程 // 在創(chuàng)建線程時添加以下代碼,可以將該線程綁定到1核 cpu_set_t mask; // 將掩碼清零 CPU_ZERO(&mask); // 將1添加到掩碼中 CPU_SET(1, &mask); // #將本線程綁定到1核 sched_setaffinity(0, sizeof(cpu_set_t), &mask);
查看線程是否運行在指定的核上:
實際工作中,為了方便查看線程的情況,會在創(chuàng)建線程時將相關(guān)信息保存到一個文件中,需要時用cat命令查看,內(nèi)容包括創(chuàng)建了哪些線程、線程名稱、線程id和pid、綁定的CPU核、優(yōu)先級、調(diào)度方式等。
使用“top”命令查看:
- top -d 2:查看線程的運行情況和CPU狀態(tài)
- 按’h’ 和 1:在上一句的基礎(chǔ)上可以查看更詳細(xì)的信息。
從文件中得到線程pid和ppid,通過top命令,查看線程在哪個CPU核上運行,驗證核綁定的核是否一樣。
對于線程綁定,我們需要借助pthread庫,通過函數(shù)pthread_setaffinity_np來設(shè)置綁定cpu關(guān)系。我們通過top查看,會發(fā)現(xiàn)cpu0和cpu3使用率達(dá)到100%。
代碼實例:
/*
*該程序演示了如何使用pthread_setaffinity_np函數(shù)將線程綁定到特定的CPU核心上運行。
*程序首先創(chuàng)建了兩個線程,然后使用pthread_setaffinity_np函數(shù)將線程1綁定到CPU 0上,將線程2綁定到CPU 3上。
*運行時,可以通過查看輸出的pid來確定程序的進(jìn)程ID。
*然后,程序?qū)PU_ZERO宏應(yīng)用于一個cpu_set_t類型的變量mask,以將其初始化為空集。
*接下來,程序使用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)計數(shù)器為3
while(i) { // 進(jìn)入循環(huán),條件為i非零
sleep(5); // 休眠5秒
printf("tid=%d,cpu=%d\n",pthread_self(), sched_getcpu()); // 打印線程ID和CPU編號
i--; // 計數(shù)器減一
}
while(1); // 進(jìn)入無限循環(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)建失敗,打印錯誤信息
return -1;
}
pthread_t tid2; // 定義線程tid2
if (pthread_create(&tid2, NULL, (void *)testfunc, NULL) != 0)
{
fprintf(stderr, "thread create failed\n"); // 線程創(chuàng)建失敗,打印錯誤信息
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é)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux網(wǎng)絡(luò)啟動問題:Device does not seem to be present解決辦法
這篇文章主要介紹了Linux網(wǎng)絡(luò)啟動問題:Device does not seem to be present解決辦法的相關(guān)資料,希望通過本文能幫助到大家解決這樣的問題,需要的朋友可以參考下2017-10-10
Linux內(nèi)核設(shè)備驅(qū)動之proc文件系統(tǒng)筆記整理
今天小編就為大家分享一篇關(guān)于Linux內(nèi)核設(shè)備驅(qū)動之proc文件系統(tǒng)筆記整理,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12

