一文讀懂進程怎么綁定 CPU(值得收藏)
首先,我們先來了解下將進程與 CPU 進行綁定的好處。
進程綁定 CPU 的好處:在多核 CPU 結構中,每個核心有各自的L1、L2緩存,而L3緩存是共用的。如果一個進程在核心間來回切換,各個核心的緩存命中率就會受到影響。相反如果進程不管如何調度,都始終可以在一個核心上執(zhí)行,那么其數據的L1、L2 緩存的命中率可以顯著提高。
所以,將進程與 CPU 進行綁定可以提高 CPU 緩存的命中率,從而提高性能。而進程與 CPU 綁定被稱為:CPU 親和性。
設置進程的 CPU 親和性
前面介紹了進程與 CPU 綁定的好處后,現(xiàn)在來介紹一下在 Linux 系統(tǒng)下怎么將進程與 CPU 進行綁定的(也就是設置進程的 CPU 親和性)。
Linux 系統(tǒng)提供了一個名為 sched_setaffinity 的系統(tǒng)調用,此系統(tǒng)調用可以設置進程的 CPU 親和性。我們來看看 sched_setaffinity 系統(tǒng)調用的原型:
下面介紹一下 sched_setaffinity 系統(tǒng)調用各個參數的作用:
pid:進程ID,也就是要進行綁定 CPU 的進程ID。
cpusetsize:mask 參數所指向的 CPU 集合的大小。
mask:與進程進行綁定的 CPU 集合(由于一個進程可以綁定到多個 CPU 上運行)。
參數 mask 的類型為 cpu_set_t,而 cpu_set_t 是一個位圖,位圖的每個位表示一個 CPU,如下圖所示:

例如,將 cpu_set_t 的第0位設置為1,表示將進程綁定到 CPU0 上運行,當然我們可以將進程綁定到多個 CPU 上運行。
我們通過一個例子來介紹怎么通過 sched_setaffinity 系統(tǒng)調用來設置進程的 CPU 親和性:
【文章福利】小編推薦自己的Linux內核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進群領取,額外贈送一份價值699的內核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)?

CPU 親和性實現(xiàn)
知道怎么設置進程的 CPU 親和性后,現(xiàn)在我們來分析一下 Linux 內核是怎樣實現(xiàn) CPU 親和性功能的。
本文使用的 Linux 內核版本為 2.6.23
Linux 內核為每個 CPU 定義了一個類型為 struct rq 的 可運行的進程隊列,也就是說,每個 CPU 都擁有一個獨立的可運行進程隊列。
一般來說,CPU 只會從屬于自己的可運行進程隊列中選擇一個進程來運行。也就是說,CPU0 只會從屬于 CPU0 的可運行隊列中選擇一個進程來運行,而絕不會從 CPU1 的可運行隊列中獲取。
所以,從上面的信息中可以分析出,要將進程綁定到某個 CPU 上運行,只需要將進程放置到其所屬的 可運行進程隊列 中即可。
下面我們來分析一下 sched_setaffinity 系統(tǒng)調用的實現(xiàn),sched_setaffinity 系統(tǒng)調用的調用鏈如下:
從上面的調用鏈可以看出,sched_setaffinity 系統(tǒng)調用最終會調用 migrate_task 函數來完成進程與 CPU 進行綁定的工作,我們來分析一下 migrate_task 函數的實現(xiàn):
我們先來介紹一下 migrate_task 函數各個參數的意義:
p:要設置 CPU 親和性的進程描述符。
dest_cpu:綁定的 CPU 編號。
req:進程遷移請求對象(下面會介紹)。
所以,migrate_task 函數的作用就是將進程描述符為 p 的進程綁定到編號為 dest_cpu 的目標 CPU 上。
migrate_task 函數主要分兩種情況來將進程綁定到某個 CPU 上:
情況1:如果進程還沒有在任何 CPU 的可運行隊列中(不可運行狀態(tài)),那么只需要將進程描述符的 cpu 字段設置為 dest_cpu 即可。當進程變?yōu)榭蛇\行時,會根據進程描述符的 cpu 字段來自動放置到對應的 CPU 可運行隊列中。
情況2:如果進程已經在某個 CPU 的可運行隊列中,那么需要將進程從之前的 CPU 可運行隊列中遷移到新的 CPU 可運行隊列中。遷移過程由 migration_thread 內核線程完成,migrate_task 函數只是構建一個進程遷移請求,并通知 migration_thread 內核線程有新的遷移請求需要處理。
而進程遷移過程由 __migrate_task 函數完成,我們來看看 __migrate_task 函數的實現(xiàn):
__migrate_task 函數主要完成以下兩個工作:
把進程從原來的可運行隊列中刪除。
把進程放置到目標可運行隊列中。
其工作過程如下圖所示(將進程從 CPU0 的可運行隊列遷移到 CPU3 的可運行隊列中):

如上圖所示,進程原本在 CPU0 的可運行隊列中,但由于重新將進程綁定到 CPU3,所以需要將進程從 CPU0 的可運行隊列遷移到 CPU3 的可運行中。
遷移過程首先將進程從 CPU0 的可運行隊列中刪除,然后再將進程插入到 CPU3 的可運行隊列中。
當 CPU 要運行進程時,首先從它所屬的可運行隊列中挑選一個進程,并將此進程調度到 CPU 中運行。
總結
從上面的分析可知,其實將進程綁定到某個 CPU 只是將進程放置到 CPU 的可運行隊列中。
由于每個 CPU 都有一個可運行隊列,所以就有可能會出現(xiàn) CPU 間可運行隊列負載不均衡問題。如 CPU0 可運行隊列中的進程比 CPU1 可運行隊列多非常多,從而導致 CPU0 的負載非常高,而 CPU1 負載非常低的情況。
當出現(xiàn)上述情況時,就需要對 CPU 間的可運行隊列進行重平衡操作,有興趣的可以自行閱讀源碼或參考相關資料。
