CPUSETS¶
版權 (C) 2004 BULL SA。
部分版權 (c) 2004-2006 Silicon Graphics, Inc.
修改者:Paul Jackson <pj@sgi.com>
修改者:Christoph Lameter <cl@gentwo.org>
修改者:Paul Menage <menage@google.com>
修改者:Hidetoshi Seto <seto.hidetoshi@jp.fujitsu.com>
1. Cpusets¶
1.1 什麼是 cpusets?¶
Cpusets 提供了一種機制,用於將一組 CPU 和記憶體節點分配給一組任務。在本文件中,“記憶體節點”指的是包含記憶體的線上節點。
Cpusets 將任務的 CPU 和記憶體放置限制在任務當前 cpuset 中的資源內。 它們形成一個巢狀層次結構,可在虛擬檔案系統中看到。 除了已經存在的之外,這些是管理大型系統上動態作業放置所需的基本鉤子。
Cpusets 使用 控制組 中描述的通用 cgroup 子系統。
任務透過 sched_setaffinity(2) 系統呼叫請求將其 CPU 親和性掩碼中包含 CPU,以及透過 mbind(2) 和 set_mempolicy(2) 系統呼叫請求在其記憶體策略中包含記憶體節點,這些請求都會透過該任務的 cpuset 進行過濾,濾除該 cpuset 中不存在的任何 CPU 或記憶體節點。 排程程式不會在 cpus_allowed 向量中不允許的 CPU 上排程任務,並且核心頁面分配器不會在請求任務的 mems_allowed 向量中不允許的節點上分配頁面。
使用者級程式碼可以透過 cgroup 虛擬檔案系統按名稱建立和銷燬 cpusets,管理這些 cpusets 的屬性和許可權,以及分配給每個 cpuset 的 CPU 和記憶體節點,指定和查詢任務分配給哪個 cpuset,並列出分配給 cpuset 的任務 pid。
1.2 為什麼需要 cpusets?¶
大型計算機系統的管理,這些系統具有許多處理器(CPU)、複雜的記憶體快取層次結構和具有非均勻訪問時間 (NUMA) 的多個記憶體節點,對程序的有效排程和記憶體放置提出了額外的挑戰。
通常,規模較小的系統可以透過簡單地讓作業系統在請求的任務之間自動共享可用的 CPU 和記憶體資源來以足夠的效率執行。
但是,較大的系統可以透過仔細的處理器和記憶體放置來減少記憶體訪問時間和爭用,從而更多地受益,並且通常代表客戶的更大投資,可以從將作業顯式放置在系統適當大小的子集上受益。
這在以下情況下尤其有價值:
運行同一 Web 應用程式的多個例項的 Web 伺服器,
執行不同應用程式的伺服器(例如,Web 伺服器和資料庫),或者
執行具有嚴格效能要求的大型 HPC 應用程式的 NUMA 系統。
這些子集或“軟分割槽”必須能夠動態調整,隨著作業組合的變化,而不會影響其他併發執行的作業。 當記憶體位置發生更改時,執行的作業頁面的位置也可能會移動。
核心 cpuset 補丁提供了有效實現此類子集所需的最基本核心機制。 它利用 Linux 核心中現有的 CPU 和記憶體放置工具,避免對關鍵排程程式或記憶體分配器程式碼產生任何額外影響。
1.3 cpusets 如何實現?¶
Cpusets 提供了一種 Linux 核心機制來限制程序或程序集使用的 CPU 和記憶體節點。
Linux 核心已經有一對機制來指定任務可以排程在哪些 CPU 上 (sched_setaffinity) 以及它可以從哪些記憶體節點獲取記憶體 (mbind, set_mempolicy)。
Cpusets 按如下方式擴充套件了這兩種機制
Cpusets 是核心已知的一組允許的 CPU 和記憶體節點。
系統中的每個任務都透過任務結構中指向引用計數的 cgroup 結構的指標附加到 cpuset。
對 sched_setaffinity 的呼叫僅過濾為該任務的 cpuset 中允許的那些 CPU。
對 mbind 和 set_mempolicy 的呼叫僅過濾為該任務的 cpuset 中允許的那些記憶體節點。
根 cpuset 包含所有系統的 CPU 和記憶體節點。
對於任何 cpuset,都可以定義包含父 CPU 和記憶體節點資源子集的子 cpuset。
cpuset 的層次結構可以掛載在 /dev/cpuset 上,以便從使用者空間瀏覽和操作。
可以將 cpuset 標記為獨佔,這可確保沒有其他 cpuset(直接祖先和後代除外)可以包含任何重疊的 CPU 或記憶體節點。
您可以列出附加到任何 cpuset 的所有任務(按 pid)。
cpuset 的實現需要一些簡單的鉤子進入核心的其餘部分,沒有一個位於效能關鍵路徑中
在 init/main.c 中,在系統啟動時初始化根 cpuset。
在 fork 和 exit 中,將任務附加到其 cpuset 並從中分離。
在 sched_setaffinity 中,透過該任務的 cpuset 中允許的內容來遮蔽請求的 CPU。
在 sched.c migrate_live_tasks() 中,儘可能將遷移任務保留在其 cpuset 允許的 CPU 內。
在 mbind 和 set_mempolicy 系統呼叫中,透過該任務的 cpuset 中允許的內容來遮蔽請求的記憶體節點。
在 page_alloc.c 中,將記憶體限制為允許的節點。
在 vmscan.c 中,將頁面回收限制為當前 cpuset。
您應該掛載“cgroup”檔案系統型別,以便能夠瀏覽和修改核心當前已知的 cpusets。 沒有為 cpusets 新增新的系統呼叫 - 對查詢和修改 cpusets 的所有支援都透過此 cpuset 檔案系統進行。
每個任務的 /proc/<pid>/status 檔案都有四個新增的行,顯示任務的 cpus_allowed(可以排程在哪些 CPU 上)和 mems_allowed(可以從中獲取記憶體的記憶體節點),採用以下示例中看到的兩種格式
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-127
Mems_allowed: ffffffff,ffffffff
Mems_allowed_list: 0-63
每個 cpuset 由 cgroup 檔案系統中的一個目錄表示,該目錄包含(在標準 cgroup 檔案之上)以下描述該 cpuset 的檔案
cpuset.cpus:該 cpuset 中的 CPU 列表
cpuset.mems:該 cpuset 中的記憶體節點列表
cpuset.memory_migrate 標誌:如果設定,則將頁面移動到 cpuset 節點
cpuset.cpu_exclusive 標誌:CPU 放置是否獨佔?
cpuset.mem_exclusive 標誌:記憶體放置是否獨佔?
cpuset.mem_hardwall 標誌:記憶體分配是否硬隔離
cpuset.memory_pressure:衡量 cpuset 中分頁壓力的程度
cpuset.memory_spread_page 標誌:如果設定,則在允許的節點上均勻分佈頁面快取
cpuset.memory_spread_slab 標誌:已過時。 沒有任何功能。
cpuset.sched_load_balance 標誌:如果設定,則在該 cpuset 上的 CPU 內進行負載平衡
cpuset.sched_relax_domain_level:遷移任務時的搜尋範圍
此外,只有根 cpuset 具有以下檔案
cpuset.memory_pressure_enabled 標誌:計算 memory_pressure 嗎?
使用 mkdir 系統呼叫或 shell 命令建立新的 cpusets。 透過寫入該 cpusets 目錄中的適當檔案來修改 cpuset 的屬性,例如其標誌、允許的 CPU 和記憶體節點以及附加的任務,如上所列。
巢狀 cpusets 的命名分層結構允許將大型系統劃分為巢狀的、可動態更改的“軟分割槽”。
每個任務的附加(由該任務的任何子任務在 fork 時自動繼承)到 cpuset 允許將系統上的工作負載組織成相關的任務集,以便每個集都被限制為使用特定 cpuset 的 CPU 和記憶體節點。 如果必要的 cpuset 檔案系統目錄上的許可權允許,可以將任務重新附加到任何其他 cpuset。
這種對系統“大局”的管理與使用 sched_setaffinity、mbind 和 set_mempolicy 系統呼叫對單個任務和記憶體區域進行的詳細放置無縫整合。
以下規則適用於每個 cpuset
其 CPU 和記憶體節點必須是其父級的子集。
除非其父級是獨佔的,否則不能將其標記為獨佔。
如果其 CPU 或記憶體是獨佔的,則它們可能不會與任何同級重疊。
這些規則以及 cpusets 的自然層次結構,可以有效地強制執行獨佔保證,而無需每次更改其中任何一個時都掃描所有 cpusets,以確保沒有任何內容與獨佔 cpuset 重疊。 此外,使用 Linux 虛擬檔案系統 (vfs) 來表示 cpuset 層次結構為 cpusets 提供了熟悉的許可權和名稱空間,並具有最少的額外核心程式碼。
根 (top_cpuset) cpuset 中的 cpus 和 mems 檔案是隻讀的。 cpus 檔案使用 CPU 熱插拔通知器自動跟蹤 cpu_online_mask 的值,mems 檔案使用 cpuset_track_online_nodes() 鉤子自動跟蹤 node_states[N_MEMORY] 的值,即具有記憶體的節點。
cpuset.effective_cpus 和 cpuset.effective_mems 檔案通常分別是 cpuset.cpus 和 cpuset.mems 檔案的只讀副本。 如果 cpuset cgroup 檔案系統以特殊的“cpuset_v2_mode”選項掛載,這些檔案的行為將變得類似於 cpuset v2 中的相應檔案。 換句話說,熱插拔事件不會更改 cpuset.cpus 和 cpuset.mems。 這些事件只會影響 cpuset.effective_cpus 和 cpuset.effective_mems,它們顯示此 cpuset 當前正在使用的實際 CPU 和記憶體節點。 有關 cpuset v2 行為的更多資訊,請參閱 控制組 v2。
1.4 什麼是獨佔 cpusets?¶
如果 cpuset 是 CPU 或記憶體獨佔的,則除了直接祖先或後代之外,沒有其他 cpuset 可以共享任何相同的 CPU 或記憶體節點。
cpuset.mem_exclusive *或* cpuset.mem_hardwall 的 cpuset 是“硬隔離的”,即它限制了核心對頁面、緩衝區和其他核心通常在多個使用者之間共享的資料的分配。 所有 cpusets,無論是否硬隔離,都限制了使用者空間的記憶體分配。 這使得可以配置一個系統,以便多個獨立的作業可以共享常見的核心資料(例如,檔案系統頁面),同時將每個作業的使用者分配隔離在其自己的 cpuset 中。 為此,構造一個大型 mem_exclusive cpuset 來容納所有作業,併為每個單獨的作業構造子級、非 mem_exclusive cpusets。 即使是 mem_exclusive cpuset,也只允許在外部獲取少量的典型核心記憶體,例如來自中斷處理程式的請求。
1.5 什麼是 memory_pressure?¶
cpuset 的 memory_pressure 提供了一個簡單的每 cpuset 指標,用於衡量 cpuset 中的任務嘗試釋放 cpuset 節點上的使用中記憶體以滿足其他記憶體請求的速率。
這使批處理管理器可以監控在專用 cpusets 中執行的作業,以有效地檢測該作業導致的記憶體壓力級別。
這在嚴格管理的系統上執行各種提交的作業混合時很有用,這些系統可以選擇終止或重新確定優先順序,以便作業嘗試使用的記憶體超過分配給它們的節點上的允許記憶體,並且在緊密耦合的、長時間執行的大規模並行科學計算作業中,如果它們開始使用的記憶體超過分配給它們的記憶體,將無法達到所需的效能目標。
這種機制為批處理管理器提供了一種非常經濟的方式來監控 cpuset 的記憶體壓力跡象。 由批處理管理器或其他使用者程式碼決定如何處理它並採取行動。
- ==>
除非透過將“1”寫入特殊檔案 /dev/cpuset/memory_pressure_enabled 來啟用此功能,否則 __alloc_pages() 的重新平衡程式碼中的此指標的鉤子會簡化為僅注意到 cpuset_memory_pressure_enabled 標誌為零。 因此,只有啟用此功能的系統才會計算該指標。
為什麼是每 cpuset 的執行平均值
因為此儀表是每 cpuset 的,而不是每任務或 mm 的,因此批次排程程式監控此指標所施加的系統負載在大型系統上會急劇減少,因為可以避免每次查詢時掃描任務列表。
因為此儀表是執行平均值,而不是累積計數器,所以批次排程程式可以透過一次讀取來檢測記憶體壓力,而不是必須讀取和累積一段時間的結果。
因為此儀表是每 cpuset 的,而不是每任務或 mm 的,所以批次排程程式可以透過一次讀取獲得關鍵資訊(cpuset 中的記憶體壓力),而不是必須查詢和累積 cpuset 中所有(動態變化的)任務集的結果。
如果附加到該 cpuset 的任何任務進入同步(直接)頁面回收程式碼,則會保留一個每 cpuset 的簡單數字過濾器(每個 cpuset 需要一個自旋鎖和 3 個數據字),並由該 cpuset 更新。
每 cpuset 檔案提供一個整數,表示由 cpuset 中的任務引起的最近(半衰期為 10 秒)的直接頁面回收速率,以每秒嘗試的回收次數(乘以 1000)為單位。
1.6 什麼是記憶體擴散?¶
每個 cpuset 有兩個布林標誌檔案,用於控制核心在何處為檔案系統緩衝區和相關的核心資料結構分配頁面。 它們被稱為“cpuset.memory_spread_page”和“cpuset.memory_spread_slab”。
如果每 cpuset 的布林標誌檔案“cpuset.memory_spread_page”已設定,則核心會將檔案系統緩衝區(頁面快取)均勻地分佈在錯誤任務允許使用的所有節點上,而不是傾向於將這些頁面放在任務正在執行的節點上。
如果每 cpuset 的布林標誌檔案“cpuset.memory_spread_slab”已設定,則核心會將一些檔案系統相關的 slab 快取(例如 inodes 和 dentries)均勻地分佈在錯誤任務允許使用的所有節點上,而不是傾向於將這些頁面放在任務正在執行的節點上。
這些標誌的設定不會影響任務的匿名資料段或堆疊段頁面。
預設情況下,兩種記憶體擴散都已關閉,並且記憶體頁面分配在任務正在執行的本地節點上,除非受到任務的 NUMA 記憶體策略或 cpuset 配置的修改,只要有足夠的可用空閒記憶體頁面即可。
建立新的 cpusets 時,它們會繼承其父級的記憶體擴散設定。
設定記憶體擴散會導致受影響的頁面或 slab 快取的分配忽略任務的 NUMA 記憶體策略並改為擴散。 使用 mbind() 或 set_mempolicy() 呼叫來設定 NUMA 記憶體策略的任務不會注意到這些呼叫由於其包含任務的記憶體擴散設定而發生的任何更改。 如果記憶體擴散已關閉,則當前指定的 NUMA 記憶體策略再次適用於記憶體頁面分配。
“cpuset.memory_spread_page”和“cpuset.memory_spread_slab”都是布林標誌檔案。 預設情況下,它們包含“0”,這意味著該功能對於該 cpuset 已關閉。 如果將“1”寫入該檔案,則會開啟命名的功能。
實現很簡單。
設定標誌“cpuset.memory_spread_page”會為位於該 cpuset 中或隨後加入該 cpuset 的每個任務開啟每程序標誌 PFA_SPREAD_PAGE。 修改頁面快取的頁面分配呼叫以執行此 PFA_SPREAD_PAGE 任務標誌的內聯檢查,如果設定,則對新例程 cpuset_mem_spread_node() 的呼叫會返回首選的分配節點。
類似地,設定“cpuset.memory_spread_slab”會開啟標誌 PFA_SPREAD_SLAB,並且適當標記的 slab 快取將從 cpuset_mem_spread_node() 返回的節點分配頁面。
cpuset_mem_spread_node() 例程也很簡單。 它使用每任務轉子 cpuset_mem_spread_rotor 的值來選擇當前任務的 mems_allowed 中的下一個首選分配節點。
這種記憶體放置策略也稱為(在其他上下文中)輪詢或交錯。
此策略可以為需要在相應節點上放置執行緒本地資料的作業提供顯著改進,但需要訪問需要在作業 cpuset 中的幾個節點上分佈的大型檔案系統資料集才能適應。 如果沒有此策略,特別是對於可能有一個執行緒讀入資料集的作業,則作業 cpuset 中跨節點的記憶體分配可能會變得非常不均勻。
1.7 什麼是 sched_load_balance?¶
核心排程程式 (kernel/sched/core.c) 會自動負載平衡任務。 如果一個 CPU 未充分利用,則在該 CPU 上執行的核心程式碼將尋找其他過載的 CPU 上的任務,並將這些任務移動到自身,但這受 cpuset 和 sched_setaffinity 等放置機制的約束。
負載平衡的演算法成本及其對關鍵共享核心資料結構(例如任務列表)的影響會隨著要平衡的 CPU 數量呈非線性增長。 因此,排程程式支援將系統 CPU 分割槽為多個排程域,以便它僅在每個排程域內進行負載平衡。 每個排程域覆蓋系統 CPU 的某個子集; 沒有兩個排程域重疊; 有些 CPU 可能不在任何排程域中,因此不會進行負載平衡。
簡而言之,在兩個較小的排程域之間進行平衡比在一個大的排程域中進行平衡成本更低,但這樣做意味著兩個域之一中的過載不會負載平衡到另一個域。
預設情況下,有一個排程域覆蓋所有 CPU,包括使用核心啟動時“isolcpus=”引數標記為隔離的 CPU。 但是,隔離的 CPU 不會參與負載平衡,並且除非明確分配,否則不會在其上執行任務。
預設的跨所有 CPU 的負載平衡不適用於以下兩種情況
在大型系統上,跨多個 CPU 進行負載平衡的成本很高。 如果系統使用 cpusets 管理以將獨立的作業放置在單獨的 CPU 集上,則無需完全負載平衡。
在某些 CPU 上支援即時的系統需要最大限度地減少這些 CPU 上的系統開銷,包括避免不需要的任務負載平衡。
當啟用每 cpuset 標誌“cpuset.sched_load_balance”(預設設定)時,它會請求該 cpusets 允許的“cpuset.cpus”中的所有 CPU 都包含在單個排程域中,從而確保負載平衡可以將任務(不由 sched_setaffinity 以其他方式固定)從該 cpuset 中的任何 CPU 移動到任何其他 CPU。
當停用每 cpuset 標誌“cpuset.sched_load_balance”時,排程程式將避免跨該 cpuset 中的 CPU 進行負載平衡,--除非--為了滿足某些重疊 cpuset 啟用了“sched_load_balance”這一需求。
因此,例如,如果頂部 cpuset 啟用了標誌“cpuset.sched_load_balance”,則排程程式將擁有一個覆蓋所有 CPU 的排程域,並且任何其他 cpusets 中“cpuset.sched_load_balance”標誌的設定都無關緊要,因為我們已經在進行完全負載平衡。
因此,在上述兩種情況下,應停用頂部 cpuset 標誌“cpuset.sched_load_balance”,並且只有一些較小的子 cpuset 啟用了此標誌。
執行此操作時,您通常不希望在頂部 cpuset 中留下任何可能使用大量 CPU 的未固定任務,因為此類任務可能會人為地限制為 CPU 的某個子集,具體取決於後代 cpusets 中此標誌設定的細節。 即使此類任務可以使用某些其他 CPU 中的空閒 CPU 週期,核心排程程式也可能不會考慮將該任務負載平衡到未充分利用的 CPU 的可能性。
當然,可以將固定到特定 CPU 的任務留在停用“cpuset.sched_load_balance”的 cpuset 中,因為這些任務無論如何都不會去其他地方。
在 cpusets 和排程域之間存在阻抗不匹配。 Cpusets 是分層的和巢狀的。 排程域是扁平的; 它們不重疊,並且每個 CPU 最多位於一個排程域中。
排程域必須是扁平的,因為跨部分重疊的 CPU 集進行負載平衡可能會導致不穩定的動態,這將超出我們的理解。 因此,如果兩個部分重疊的 cpusets 都啟用了標誌“cpuset.sched_load_balance”,則我們形成一個單個排程域,該域是兩者的超集。 我們不會將任務移動到其 cpuset 之外的 CPU,但排程程式負載平衡程式碼可能會浪費一些計算週期來考慮這種可能性。
這種不匹配是為什麼在哪些 cpusets 啟用了標誌“cpuset.sched_load_balance”與排程域配置之間沒有簡單的一對一關係。 如果 cpuset 啟用了該標誌,它將在其所有 CPU 上進行平衡,但如果它停用了該標誌,則只有在沒有其他重疊的 cpuset 啟用該標誌時,才能確保沒有負載平衡。
如果兩個 cpusets 具有部分重疊的“cpuset.cpus”允許的 CPU,並且只有其中一個啟用了此標誌,則另一個可能會發現其任務僅部分負載平衡,僅在重疊的 CPU 上。 這只是上面給出的一些段落的 top_cpuset 示例的常見情況。 在一般情況下,就像在頂部 cpuset 情況下一樣,不要將可能使用大量 CPU 的任務留在這種部分負載平衡的 cpusets 中,因為由於缺少到其他 CPU 的負載平衡,它們可能會人為地限制為允許它們使用的 CPU 的某個子集。
“cpuset.isolcpus”中的 CPU 已透過 isolcpus= 核心引導選項從負載平衡中排除,並且無論任何 cpuset 中的“cpuset.sched_load_balance”的值如何,都永遠不會進行負載平衡。
1.7.1 sched_load_balance 實現細節。¶
每 cpuset 標誌“cpuset.sched_load_balance”預設為啟用(與大多數 cpuset 標誌相反)。 啟用 cpuset 時,核心將確保它可以跨該 cpuset 中的所有 CPU 進行負載平衡(確保該 cpuset 的 cpus_allowed 中的所有 CPU 都位於同一排程域中)。
如果兩個重疊的 cpusets 都啟用了“cpuset.sched_load_balance”,則它們將(必須)位於同一排程域中。
如果頂部 cpuset(如預設情況)啟用了“cpuset.sched_load_balance”,則根據上述內容,這意味著存在一個覆蓋整個系統的排程域,而與任何其他 cpuset 設定無關。
核心承諾使用者空間,它將在儘可能的情況下避免負載平衡。 它將選擇排程域的最精細粒度分割槽,同時仍為允許具有“cpuset.sched_load_balance”的 cpuset 的任何 CPU 集提供負載平衡。
內部核心 cpuset 到排程程式的介面將系統中負載平衡的 CPU 的分割槽從 cpuset 程式碼傳遞到排程程式程式碼。 此分割槽是 CPU 的子集集(表示為 struct cpumask 陣列),成對不相交,覆蓋所有必須進行負載平衡的 CPU。
每當以下情況發生時,cpuset 程式碼都會構建一個新的此類分割槽並將其傳遞給排程程式排程域設定程式碼,以便根據需要重建排程域
具有非空 CPU 的 cpuset 的“cpuset.sched_load_balance”標誌發生更改,
或 CPU 進入或離開啟用了此標誌的 cpuset,
或具有非空 CPU 且啟用了此標誌的 cpuset 的“cpuset.sched_relax_domain_level”值發生更改,
或刪除了具有非空 CPU 且啟用了此標誌的 cpuset,
或 CPU 已離線/上線。
此分割槽精確定義了排程程式應設定的排程域 - 分割槽中每個元素(struct cpumask)一個排程域。
排程程式會記住當前活動的排程域分割槽。 當從 cpuset 程式碼呼叫排程程式例程 partition_sched_domains() 以更新這些排程域時,它會將請求的新分割槽與當前分割槽進行比較,並更新其排程域,刪除舊的並新增新的,以進行每次更改。
1.8 什麼是 sched_relax_domain_level?¶
在排程域中,排程程式以兩種方式遷移任務; 滴答聲上的定期負載平衡,以及在某些排程事件時。
喚醒任務時,排程程式會嘗試將任務移動到空閒 CPU 上。 例如,如果在 CPU X 上執行的任務 A 激活了同一 CPU X 上的另一個任務 B,並且如果 CPU Y 是 X 的同級並且正在執行空閒,則排程程式會將任務 B 遷移到 CPU Y,以便任務 B 可以在 CPU Y 上啟動,而無需等待 CPU X 上的任務 A。
並且如果 CPU 在其執行佇列中耗盡任務,則 CPU 會嘗試從其他繁忙的 CPU 拉取額外的任務以幫助它們,然後再進入空閒狀態。
當然,找到可移動的任務和/或空閒 CPU 需要一些搜尋成本,排程程式可能不會每次都搜尋域中的所有 CPU。 實際上,在某些體系結構中,事件上的搜尋範圍限制在 CPU 所在的同一套接字或節點中,而滴答聲上的負載平衡會搜尋所有 CPU。
例如,假設 CPU Z 離 CPU X 相對較遠。即使 CPU Z 處於空閒狀態,而 CPU X 和其兄弟 CPU 處於繁忙狀態,排程器也無法將喚醒的任務 B 從 X 遷移到 Z,因為它超出了其搜尋範圍。 結果,CPU X 上的任務 B 需要等待任務 A 完成,或者等待下一個時鐘節拍的負載均衡。 對於某些特殊情況下的應用程式,等待一個時鐘節拍可能太長。
‘cpuset.sched_relax_domain_level’ 檔案允許你根據需要請求更改此搜尋範圍。 該檔案接受整數值,該值指示搜尋範圍的大小,級別大致如下,否則初始值為 -1,表示 cpuset 沒有請求。
-1 |
沒有請求。 使用系統預設值或遵循其他請求。 |
0 |
沒有搜尋。 |
1 |
搜尋兄弟(核心中的超執行緒)。 |
2 |
搜尋包中的核心。 |
3 |
搜尋節點中的 CPU [= 在非 NUMA 系統上是系統範圍的] |
4 |
搜尋節點塊中的節點 [在 NUMA 系統上] |
5 |
系統範圍搜尋 [在 NUMA 系統上] |
並非所有級別都存在,並且值可能會因系統架構和核心配置而異。 檢查 /sys/kernel/debug/sched/domains/cpu*/domain*/ 獲取系統特定詳細資訊。
系統預設值取決於架構。 可以使用 relax_domain_level= 啟動引數更改系統預設值。
此檔案是每個 cpuset 的,並且會影響 cpuset 所屬的排程域。 因此,如果停用了 cpuset 的標誌 ‘cpuset.sched_load_balance’,則 ‘cpuset.sched_relax_domain_level’ 不起作用,因為沒有屬於該 cpuset 的排程域。
如果多個 cpusets 重疊,因此它們形成單個排程域,則使用其中最大的值。 請注意,如果一個請求 0,而其他請求 -1,則使用 0。
請注意,修改此檔案會產生好的和壞的影響,是否可以接受取決於你的情況。 如果不確定,請勿修改此檔案。
如果你的情況是:
由於你的特殊應用程式行為或對 CPU 快取的特殊硬體支援等原因,可以認為每個 CPU 之間的遷移成本非常小(對你而言)。
搜尋成本沒有影響(對你而言),或者你可以透過管理 cpuset 使其緊湊等方式來使搜尋成本足夠小。
即使犧牲快取命中率等,也需要延遲。那麼增加 ‘sched_relax_domain_level’ 會對你有利。
1.9 如何使用 cpusets?¶
為了儘量減少 cpusets 對關鍵核心程式碼(如排程程式)的影響,並且由於核心不支援一個任務直接更新另一個任務的記憶體放置,因此更改任務的 cpuset CPU 或記憶體節點放置,或更改任務附加到的 cpuset,對任務的影響是微妙的。
如果一個 cpuset 的記憶體節點被修改,那麼對於附加到該 cpuset 的每個任務,核心下次嘗試為該任務分配記憶體頁面時,核心將注意到任務 cpuset 中的更改,並更新其每個任務的記憶體放置,以保持在新 cpusets 記憶體放置範圍內。 如果任務正在使用 mempolicy MPOL_BIND,並且其繫結的節點與新的 cpuset 重疊,那麼該任務將繼續使用新的 cpuset 中仍然允許的 MPOL_BIND 節點的任何子集。 如果任務正在使用 MPOL_BIND,並且現在新的 cpuset 中不允許其任何 MPOL_BIND 節點,那麼該任務將基本上被視為 MPOL_BIND 繫結到新的 cpuset(即使其 NUMA 放置,如 get_mempolicy() 查詢的那樣,沒有更改)。 如果一個任務從一個 cpuset 移動到另一個 cpuset,那麼核心將調整任務的記憶體放置,如上所述,在核心下次嘗試為該任務分配記憶體頁面時。
如果一個 cpuset 的 ‘cpuset.cpus’ 被修改,那麼該 cpuset 中的每個任務都將立即更改其允許的 CPU 放置。 同樣,如果一個任務的 pid 被寫入另一個 cpuset 的 ‘tasks’ 檔案,那麼它允許的 CPU 放置也會立即更改。 如果此類任務已使用 sched_setaffinity() 呼叫繫結到其 cpuset 的某個子集,則該任務將被允許在新的 cpuset 中允許的任何 CPU 上執行,從而否定先前 sched_setaffinity() 呼叫的效果。
總而言之,在下次為任務分配頁面時,核心會更新更改了 cpuset 的任務的記憶體放置,並立即更新處理器放置。
通常,一旦分配了一個頁面(給定了主記憶體的物理頁面),那麼只要它保持分配狀態,該頁面就會保留在其分配的任何節點上,即使 cpusets 記憶體放置策略 ‘cpuset.mems’ 隨後發生更改。 如果 cpuset 標誌檔案 ‘cpuset.memory_migrate’ 設定為 true,那麼當任務附加到該 cpuset 時,該任務之前在其先前 cpuset 的節點上分配給它的任何頁面都會遷移到任務的新 cpuset。 如果可能,在這些遷移操作期間會保留頁面在 cpuset 中的相對位置。 例如,如果頁面位於先前 cpuset 的第二個有效節點上,那麼該頁面將放置在新 cpuset 的第二個有效節點上。
此外,如果 ‘cpuset.memory_migrate’ 設定為 true,那麼如果該 cpuset 的 ‘cpuset.mems’ 檔案被修改,分配給該 cpuset 中任務的頁面,這些頁面位於 ‘mems’ 的先前設定中的節點上,將被移動到 ‘mems’ 的新設定中的節點。 不在該任務的先前 cpuset 中,或在 cpuset 的先前 ‘cpuset.mems’ 設定中的頁面,將不會被移動。
以上存在一個例外。 如果使用熱插拔功能刪除當前分配給 cpuset 的所有 CPU,那麼該 cpuset 中的所有任務將被移動到具有非空 cpus 的最近祖先。 但是,如果 cpuset 與另一個 cgroup 子系統繫結,並且該子系統對任務附加有一些限制,那麼移動某些(或全部)任務可能會失敗。 在這種失敗的情況下,這些任務將保留在原始 cpuset 中,並且核心將自動更新其 cpus_allowed 以允許所有線上 CPU。 當用於刪除記憶體節點的記憶體熱插拔功能可用時,預計也會應用類似的例外。 一般來說,核心寧願違反 cpuset 放置,也不願讓所有允許的 CPU 或記憶體節點都已離線的任務捱餓。
以上存在第二個例外。 GFP_ATOMIC 請求是核心內部分配,必須立即滿足。 如果 GFP_ATOMIC 分配失敗,核心可能會刪除一些請求,在極少數情況下甚至會崩潰。 如果請求無法在當前任務的 cpuset 中得到滿足,那麼我們會放寬 cpuset,並在我們可以找到它的任何地方尋找記憶體。 違反 cpuset 比使核心承受壓力更好。
要啟動一個將包含在 cpuset 中的新作業,步驟如下:
mkdir /sys/fs/cgroup/cpuset
mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
透過在 /sys/fs/cgroup/cpuset 虛擬檔案系統中執行 mkdir 和 write(或 echo)來建立新的 cpuset。
啟動一個將成為新作業的“創始之父”的任務。
透過將其 pid 寫入該 cpuset 的 /sys/fs/cgroup/cpuset tasks 檔案,將該任務附加到新的 cpuset。
從這個創始之父任務 fork、exec 或 clone 作業任務。
例如,以下命令序列將設定一個名為 “Charlie” 的 cpuset,其中僅包含 CPU 2 和 3,以及記憶體節點 1,然後在該 cpuset 中啟動一個子 shell ‘sh’
mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
cd /sys/fs/cgroup/cpuset
mkdir Charlie
cd Charlie
/bin/echo 2-3 > cpuset.cpus
/bin/echo 1 > cpuset.mems
/bin/echo $$ > tasks
sh
# The subshell 'sh' is now running in cpuset Charlie
# The next line should display '/Charlie'
cat /proc/self/cpuset
有多種方法可以查詢或修改 cpusets
直接透過 cpuset 檔案系統,使用 shell 中的各種 cd、mkdir、echo、cat、rmdir 命令,或 C 中的等效命令。
透過 C 庫 libcpuset。
透過 C 庫 libcgroup。 (https://github.com/libcgroup/libcgroup/)
透過 python 應用程式 cset。 (http://code.google.com/p/cpuset/)
sched_setaffinity 呼叫也可以在 shell 提示符下完成,使用 SGI 的 runon 或 Robert Love 的 taskset。 mbind 和 set_mempolicy 呼叫可以在 shell 提示符下完成,使用 numactl 命令(Andi Kleen 的 numa 包的一部分)。
2. 用法示例和語法¶
2.1 基本用法¶
可以透過 cpuset 虛擬檔案系統建立、修改和使用 cpusets。
要掛載它,請鍵入:# mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset
然後在 /sys/fs/cgroup/cpuset 下,你可以找到一個樹,它對應於系統中 cpusets 的樹。 例如,/sys/fs/cgroup/cpuset 是儲存整個系統的 cpuset。
如果你想在 /sys/fs/cgroup/cpuset 下建立一個新的 cpuset
# cd /sys/fs/cgroup/cpuset
# mkdir my_cpuset
現在你想用這個 cpuset 做點什麼
# cd my_cpuset
在這個目錄中你可以找到幾個檔案
# ls
cgroup.clone_children cpuset.memory_pressure
cgroup.event_control cpuset.memory_spread_page
cgroup.procs cpuset.memory_spread_slab
cpuset.cpu_exclusive cpuset.mems
cpuset.cpus cpuset.sched_load_balance
cpuset.mem_exclusive cpuset.sched_relax_domain_level
cpuset.mem_hardwall notify_on_release
cpuset.memory_migrate tasks
讀取它們將為你提供有關此 cpuset 狀態的資訊:它可以使用的 CPU 和記憶體節點、正在使用它的程序及其屬性。 透過寫入這些檔案,你可以操作 cpuset。
設定一些標誌
# /bin/echo 1 > cpuset.cpu_exclusive
新增一些 cpus
# /bin/echo 0-7 > cpuset.cpus
新增一些 mems
# /bin/echo 0-7 > cpuset.mems
現在將你的 shell 附加到這個 cpuset
# /bin/echo $$ > tasks
你也可以透過在此目錄中使用 mkdir 在你的 cpuset 中建立 cpusets
# mkdir my_sub_cs
要刪除 cpuset,只需使用 rmdir
# rmdir my_sub_cs
如果 cpuset 正在使用中(內部有 cpusets,或者附加了程序),這將失敗。
請注意,由於歷史原因,“cpuset” 檔案系統作為 cgroup 檔案系統的包裝存在。
命令
mount -t cpuset X /sys/fs/cgroup/cpuset
相當於
mount -t cgroup -ocpuset,noprefix X /sys/fs/cgroup/cpuset
echo "/sbin/cpuset_release_agent" > /sys/fs/cgroup/cpuset/release_agent
2.2 新增/刪除 cpus¶
這是在 cpuset 目錄中的 cpus 或 mems 檔案中寫入時使用的語法
# /bin/echo 1-4 > cpuset.cpus -> set cpus list to cpus 1,2,3,4
# /bin/echo 1,2,3,4 > cpuset.cpus -> set cpus list to cpus 1,2,3,4
要將 CPU 新增到 cpuset,請寫入包含要新增的 CPU 的新 CPU 列表。 要將 6 新增到上面的 cpuset
# /bin/echo 1-4,6 > cpuset.cpus -> set cpus list to cpus 1,2,3,4,6
類似地,要從 cpuset 中刪除 CPU,請寫入不包含要刪除的 CPU 的新 CPU 列表。
要刪除所有 CPU
# /bin/echo "" > cpuset.cpus -> clear cpus list
2.3 設定標誌¶
語法非常簡單
# /bin/echo 1 > cpuset.cpu_exclusive -> set flag 'cpuset.cpu_exclusive'
# /bin/echo 0 > cpuset.cpu_exclusive -> unset flag 'cpuset.cpu_exclusive'
2.4 附加程序¶
# /bin/echo PID > tasks
請注意,它是 PID,而不是 PIDs。 你一次只能附加一個任務。 如果你有多個任務要附加,你必須一個接一個地完成
# /bin/echo PID1 > tasks
# /bin/echo PID2 > tasks
...
# /bin/echo PIDn > tasks
3. 問題¶
- Q
這個 ‘/bin/echo’ 是怎麼回事?
- A
bash 的內建 ‘echo’ 命令不會檢查對 write() 的呼叫是否存在錯誤。 如果你在 cpuset 檔案系統中使用它,你將無法判斷命令是成功還是失敗。
- Q
當我附加程序時,只有該行的第一個程序真正被附加!
- A
我們每次呼叫 write() 只能返回一個錯誤程式碼。 因此,你也應該只放一個 pid。