執行人: irenesu2000
請描述你閱讀過程中的疑惑並討論
為什麼 kernel stack 無法擴張
1.記憶體碎片化:由於記憶體配置需要連續配置,若擴大則會導致 memory fragmentation ,同時增加記憶體管理的複雜性
2.為了避免 kernel stack overflow 和其他相關的安全問題,核心會對 stack 進行保護和限制,以確保 stack 不會超出其分配的固定大小。
為什麼 linux kernel 是使用 doubly linked list
1.不用考慮邊緣問題
2.高效刪除
3.方便走訪全部(順序逆序)
4.穩定性
為什麼 kernel 中不區分 process 和 thread
Linux核心將行程表示為task_struct,每個task_struct都包含了一個行程或執行緒的上下文資訊、調度資訊、資源分配資訊等,它們在核心中都被表示為任務控制區塊。同時在使用者空間,進程和執行緒可以通過不同的ID進行區分,但在核心它們被視為相同的實體。這種設計簡化了核心的實現和維護,提高了核心的性能和效率。
為什麼Completely Fair Scheduler 中是使用virtual runtime來決定排程
使用 vruntime 而不是使用實際運行時間來決定排程順序的好處為可以避免長時間運行之 process 佔據過多 CPU時間導致其他行程舞法公平競爭,以實現公平時間分配
為什麼Completely Fair Scheduler 中是使用紅黑樹來儲存任務
CFS使用紅黑樹來儲存任務是為了實現高效的調度和管理,能夠在動態環境下快速、公平地調度任務
1.快速的插入和刪除:由於紅黑樹的平衡性和快速插入/刪除的特性,CFS能夠高效地處理任務的插入和刪除操作,能夠很好的處理動態調度
2.良好的平衡性:紅黑樹的自平衡性確保了樹的相對平衡,避免樹的高度過高的問題導致過高的時間複雜度,即使在面對大量任務時也能保持較低的時間複雜度
3.有序性:可以快速找到虛擬運行時間最小的任務。這樣,CFS可以輕鬆地選擇下一個要執行的任務,以保持公平的時間片分配
研讀《Demystifying the Linux CPU Scheduler》,紀錄閱讀過程中的認知和相關疑惑,授課教師撰寫此書的目的,是希望能讓大部分學過電腦科學基礎科目的學員都可藉此窺見 Linux 核心之美,學員的意見回饋很重要。
比照 Demystifying the Linux CPU Scheduler 閱讀筆記 及 CPU 排程器研究,紀錄閱讀過程中的問題,連同自己的推論彙整在本頁面,適時與授課教師討論。
提及頁碼時,要附上電子書最後更新日期,範例:
P.16 @ March 21
至少要涵蓋第一章到第五章,採用 5 月 23 日 (或更新) 的書稿。
注意書寫規範:
P.5 @ March 21
除了呼叫 system call 來與 kernel 溝通,還可以透過 virtual filesystem (VFS) , kernel 將其視為用戶與 file system 之間的互動介面,有利於用戶透過檔案操作來存取系統資源,所謂檔案操作如 open(), read(), write() 等等,為了使 file system 可以被作業系統使用 , file system 會被掛載到指定的掛載點 ,從掛載點與 file system 連接。
mount -t <fstype> <device> $MOUNT_POINT
每個系統中的行程皆有兩個 stacks ,分別為 user stack 與 kernel stack,當權限模式更換時, stack 之指標會透過 syscall**** 來自動切換模式。
Why are kernel stack so small?
kernel threads
主要的差別為有多少的服務需要在kernel上執行
核心內部將其分為數個子系統,如下圖
P.14 @ MAY 30
每個任務由 task_struct 的結構來表示,並使用雙向環狀鏈結串列,藉由 PID 在 hash table 中來搜尋特定任務
基於 time sharing 的手法,Linux 藉由將 CPU 時間切成多個時間片段來實作多個行程的並行執行效果。一個時間片段只能執行一個行程,除了時間到期導致自動結束運行也會出現被動結束,如搶佔,因此以何種方式順序來安排,所以後續也出現多種排程的演算法,如同 FIFO, RR, EDF, SJF,演算法須滿足:
任務皆有一個生命週期 P.30 @ MAY 30
sched_yield()
系統呼叫或是被搶佔 (preempt),狀態會從 running state 至 runnable state,最後 process 會透過執行 exit() 來進行終止。CPU 排程器的主要目標之一是通過減少系統的能源消耗來最大化功耗效率, 排程器達到此目標的方法之一是識別和優先執行需要較少功耗的任務,如空閒任務或優先權較低的任務,而不是資源消耗較大的任務。
P.35 @ MAY 30
early scheduler scheduler scheduler CFS
排程器在處理少量任務時運行良好,但在處理多任務時會出現性能問題,因此引入 排程器,可以在恆定時間內排程任務,無論系統中運行了多少個行程 。
同時 排程器在 SMP 系統中也有下列問題:
注意用語:
詳見 資訊科技詞彙翻譯 及 詞彙對照表。另外,中英文間用一個半形空白或者標點符號區隔。
RSDL 是基於排名以及時間片段來確保公平性和互動性,行程首先會根據其靜態優先權獲得一個時間片段,每個行程會根據其原始優先權逐一插入佇列中,排程器會挑選最高排名之行程來執行,當行程執行完時間片段後會以較低的名次重新加入陣列中,每當執行完其排名會越來越低,如同下樓梯,透過這樣重複執行至佇列的底部,此外樓梯中每個排名級別皆有執行時間限制,若達到限制該級別的所有行程皆會下降一個級別每個優先級的時間限制保證結束時間,使行程不會餓死。
CFS 與 RSDL 相似,皆無固定的時間片段,由於理想的公平排程會導致過多的上下文切換,進而影響到效能,這樣的方法為 。
CFS 透過給定一個 virtual runtime(vrutime)代表在理想的多工處理器上的執行時間,其目標為希望每個任務的 vrutime 是接近的,因此每次會選擇最小的 vrutime 來執行。
CFS 給定每個行程 timeslice,當過期時會排程最小的 vrutime,執行完的任務會將 vrutime 加上 (依據優先權)。
一個任務 p 所得到的 CPU 分配對應於下面公式,W 為對應權重:
Weight function
Assigned time
我們定義了一個函數來為每個nice等級分配權重。一個任務在CPU上運行的時間量由以下4個值決定:
Virtual runtime
表示一个行程在搶佔之前必须運行的最小時間,每個任務的絕對運行時間是根據前面討論的權重值加權的,加權後的時間被稱為虛擬運行時間。
每個任務皆是以 task_struct 來表示,其中包含下列資訊:
此結構表示可排程實體,構成sched_entity包括:
這個結構包含了特定於排程類別的排程程序。排程器透過連結器腳本按照優先順序走訪每個排程類別,從高優先順序類別開始,直到找到可排程的任務為止。sched_class 收集了排程類別的所有方法,形成了一個表,task_struct中的指標指向該表。在不同的排程類別中,有一些常用的函數:
->Linux CPU排程器具有模組化的結構,並且支援多種排程類別。每個排程類別都以自己的方式實作排程函式,並運用 sched_class
進行管理和存取。這種設計使得開發人員可以靈活地擴展和切換不同的排程算法,並且能夠根據優先級選擇最適合的任務進行排程。
可運行的任務儲存在一個紅黑樹 (rbtree) 中,這些行程根據執行時間的線性順序被插入其中,紅黑樹是一種自平衡的 AVL tree 。
紅黑樹具有以下特性:
這些特性確保了紅黑樹的平衡性,存取最左邊的節點的時間複雜度為,紅黑樹在 Linux 核心中被廣泛應用,用於管理可運行的任務,並確保按照執行時間的順序進行排程。它提供了高效的插入、搜索和刪除操作,並能夠快速找到具有最小虛擬運行時間的任務進行排程。
核心需要追踪時間來進行時間追蹤和週期性操作,如排程、系統正常運行時間等。為此,核心依賴硬體計時器,它以固定的頻率發送中斷信號。因此定義了:
改變計時器中斷的頻率對系統行為有很大影響,因此較大或較小的HZ值都有優缺點。
是系統啟動以來發生的滴答數,每當中斷處理程序執行時,jiffies的值就會增加一個單位。這意味著根據HZ的值和jiffies的值,我們可以通過計算秒數乘以HZ來將秒轉換為jiffies。
它返回系統的運行,時間排程器使用此函數來確定當前任務的絕對、非加權運行時間(se->sum_exec_runtime)。
定時器中斷處理程序調用其他函數,最重要的兩個函數是do_timer()和update_process_times()
有兩種方式可以觸發重新排程:
一個任務可以在兩種情況下被添加到運行佇列中:當它被創建時和當它被喚醒時,如果新添加的進程的權重小於正在運行的任務,則會進行重新排程,當一個行程調用fork()時,它的task_struct被複製,並開始一個新的行程,父行程狀態的數據結構被複製以創建子行程也會繼承其父進程的排程策略。
調用wake_up_new_task(),將新創建的task_struct作為參數,此函數將任務的狀態設置為TASK_RUNNING,並在調用activate_task()之前初始化,在任務被激活並放入運行佇列之後,wake_up_new_task()觸發sched_wakeup_new事件。最後,此函數調用check_preempt_curr(),如果有其他更應該運行的任務,則會搶佔當前任務。
假設T是這個新創建的任務,ST是其關聯的調度類別(例如,SCHED_NORMAL和SCHED_FIFO)。初始喚醒算法如下:
介紹了一些關注改善響應性的CFS替代方案
在BORE中,CPU繁重任務的虛擬運行時間高於CFS的虛擬運行時間,這使得互動任務更容易被排程器選中,因為虛擬運行時間的差異增加了。因此,在BORE中,較不貪婪的任務(互動工作負載)獲得更多的時間片和喚醒搶佔侵略性,而更貪婪的任務(CPU繁重工作負載)權重較低(不太可能被調度),BORE的主要實現位於排程器的update_curr函數中。
由於 CFS 其目標為在所有任務之間公平分配CPU時間,但它並未考慮到任務所屬的行程或使用者導致,在多使用者系統中,無法公平地將CPU時間分配給不同的使用者或行程,因此引入了組排程的概念。
為實現組排程功能,引入了可排程實體(schedulable entity)的概念,任務或一組任務都可以由一個實體表示,在基本的CFS實現中,所有可運行的任務都維護在一個運行佇列(runqueue)中。調度器總是選擇最有價值的任務並執行。然而,在使用組排程時,並不將每個實體都放入運行佇列中,而是創建一個層次結構。每個對應於組的實體都有自己的運行佇列,其中包含所有子實體,每個運行佇列的行為與正常的CFS運行佇列完全相同。
P.135 @ MAY 30
圖中se(1) and se(3) 為獨自的任務,se(2) 代表 group task se(A) 及 se(B) 為其小孩
在多處理器系統中,屬於同一組的任務可以在不同的CPU上同時運行。為了允許任務的遷移,每個CPU都為每個組維護自己的調度實體。屬於同一組的任務只能在這些調度實體的運行隊列之間進行遷移。組的所有不同實體都存儲在一個專用的結構中。
Linux具有一個稱為組帶寬限制(group bandwidth control)的功能,可以對組的CPU使用量進行限制。這個功能是基於CONFIG_FAIR_GROUP_SCHED的擴展,需要在核心編譯時啟用CONFIG_CFS_BANDWIDTH標誌。
組帶寬限制通過兩個參數來控制CPU限制:
在多處理器系統中,對於群組限制 CPU 用量變得更加複雜,為了確保群組不超過其配額,不同處理器需要進行通信,全局追蹤器用於跟踪群組的剩餘配額。
控制群組(cgroups)是一種核心機制,用於建立和控制任務的分組。
Linux排程器沒有一個通用的理想標準。許多對於好的效能的定義常常彼此矛盾,改善調度器的某一方面可能會損害其他方面的效能。這意味著使用者可以自訂調度器,以適應他們特定的使用需求。
我們也可以使用chrt實用工具來更改行程的優先權,使用chrt -p選項來更改正在運行的行程的排程策略。
與排程相關的系統呼叫:sched_setscheduler()和sched_getscheduler()用於設定和獲取特定行程的排程參數,這些系統呼叫能夠在sched_get_priority_min()和sched_get_priority_max()所給定的範圍內設定rt_priority,以適應自身的排程策略。
與處理器 Affinity 相關的系統呼叫:sched_setaffinity()和sched_getaffinity(),通過設定Affinity ,可以限制任務運行的CPU子集,從而減少進程遷移的開銷和快取失效的概率,從而提高性能。
CFS提供了一些可配置的參數。這些參數允許我們在啟動時或系統運行時調整調度器的行為。
要更改這些參數,我們可以:
sched_tunable_scaling
sched_min_granularity_ns
不要用機器翻譯!尊重台灣科技詞彙經典用語。