多工功能
| 多工處理 |
|---|
| 程序 |
| 執行緒或任務 |
| 同步 |
| 排程程式 |
| 中斷核心 |
| CPU 特定 |
Linux 核心是一個搶佔式 多工 作業系統。作為一個多工作業系統,它允許多個程序共享處理器 (CPU) 和其他系統資源。每個 CPU 每次執行一個任務。但是,多工處理允許每個處理器在執行的任務之間切換,而無需等待每個任務完成。為此,核心可以在任何時間暫時中斷處理器正在執行的任務,並用另一個任務替換它,該任務可以是新的任務,也可以是先前掛起的任務。涉及執行任務交換的操作稱為上下文切換。
程序是執行中的使用者空間程式。核心在函式 run_init_process id 中使用 kernel_execve id 啟動第一個程序 /sbin/init。程序佔用系統資源,例如記憶體、CPU 時間。系統呼叫 sys_fork id 和 sys_execve id 用於從使用者空間建立新程序。程序使用 sys_exit id 系統呼叫退出。
Linux 從 Unix 繼承了其基本程序管理系統呼叫 (⚲ API ↪ ⚙️ 實現)
man 2 fork ↪ kernel_clone id 透過 複製 呼叫它的程序來建立一個新程序。
man 2 _exit ↪ do_exit id “立即”終止呼叫程序。屬於該程序的任何開啟的檔案描述符都將關閉。
man 2 wait ↪ kernel_waitid id 掛起呼叫程序的執行,直到其子程序之一終止。
man 2 execve ↪ do_execve id 在當前程序的上下文中執行可執行檔案,替換先前的可執行檔案。此係統呼叫由 libc man 3 exec 函式族使用。
Linux 使用其自己的系統呼叫 man 2 clone 來增強傳統的 Unix 程序 API。克隆建立一個子程序,該子程序可以與父程序共享其執行上下文的一部分。它通常用於實現執行緒(儘管程式設計師通常會使用更高級別的介面,例如 man 7 pthreads,它是在克隆之上實現的)。
PID - 程序識別符號 定義為 pid_t id 是唯一的順序編號。 man 1 ps -A 列出當前程序。系統呼叫 man 2 getpid ↪ task_tgid_vnr id 返回當前程序的 PID,在內部稱為 TGID - 執行緒組 ID。一個程序可以包含多個執行緒。 man 2 gettid ↪ task_pid_vnr id 返回執行緒 ID。在內部歷史上稱為 PID。⚠️ 警告:混淆。使用者空間 PID ≠ 核心空間 PID。 man 1 ps -AF 列出當前程序和執行緒作為 LWP。對於單執行緒程序,所有這些 ID 都是相等的。
⚲ API
⚙️ 內部
- task_structid
- pid_typeid
- kernel/fork.csrc
- syscalls
- man 2 set_tid_address – 設定指向執行緒 ID 的指標
- man 2 fork – 建立子程序
- man 2 vfork – 建立子程序並阻塞父程序
- man 2 clone – 建立子程序
- man 2 unshare – 取消關聯程序執行上下文的部分
- kernel/sys.csrc
- syscalls
- man 2 prctl – 對程序或執行緒的操作
- kernel/pid.csrc
- syscalls
- man 2 pidfd_open – 獲取指向程序的檔案描述符
- man 2 pidfd_getfd – 獲取另一個程序的檔案描述符的副本
- syscalls
- man 2 pidfd_open – 獲取指向程序的檔案描述符
- man 2 pidfd_getfd – 獲取另一個程序的檔案描述符的副本
- kernel/exit.csrc
- syscalls
- man 2 exit – 終止呼叫程序
- man 2 exit_group – 退出程序中的所有執行緒
- man 2 waitid – 等待程序狀態改變
- man 2 waitpid – 等待程序狀態改變
📖 參考資料
程序間通訊
[edit | edit source]程序間通訊 (IPC) 特指作業系統提供的機制,用於允許其管理的程序共享資料。實現 IPC 的方法分為幾類,它們根據軟體需求(如效能和模組化需求)以及系統環境而有所不同。Linux 從 Unix 繼承了以下 IPC 機制
訊號 (⚲ API ↪ ⚙️ 實現)
- man 2 kill 向程序傳送訊號
- man 2 tgkill ↪ do_tkill id 向執行緒傳送訊號
- man 2 process_vm_readv ↪ process_vm_rw id - 程序地址空間之間零複製資料傳輸
🔧 TODO: man 2 sigaction man 2 signal man 2 sigaltstack man 2 sigpending man 2 sigprocmask man 2 sigsuspend man 2 sigwaitinfo man 2 sigtimedwait
- 匿名管道 和命名管道 (FIFO) man 2 mknod ↪ do_mknodat id S_IFIFO id
- 快速資料路徑 PF_XDP id
- Unix 域套接字 PF_UNIX id
- 記憶體對映檔案 man 2 mmap ⤑ ksys_mmap_pgoff id
- Sys V IPC
- 訊息佇列
- 訊號量
- 共享記憶體: man 2 shmget, man 2 shmctl, man 2 shmat, man 2 shmdt
📖 參考資料
執行緒或任務
[edit | edit source]在 Linux 核心中,“執行緒”和“任務”幾乎是同義詞。
💾 歷史:直到 2.6.39,核心模式只有一個執行緒,由 大核心鎖 保護。
⚲ API
- linux/sched.h inc - 主要排程程式 API
- arch/x86/include/asm/current.hsrc
- current id 和 get_current id () 返回當前 task_struct id
- uapi/linux/taskstats.h inc 每個任務的統計資訊
- linux/thread_info.hinc
- 函式 current_thread_info id() 返回 thread_info id
- linux/sched/task.h inc - 排程程式和各種任務生命週期 (fork()/exit()) 功能之間的介面
- linux/kthread.h inc - 用於建立和停止核心執行緒的簡單介面,無需麻煩。
- kthread_run id 建立並喚醒執行緒
- kthread_createid
⚙️ 內部
排程程式
[edit | edit source]排程程式 是作業系統中決定在特定時間點執行哪個程序的部分。它通常能夠暫停正在執行的程序,將其移到執行佇列的末尾,並啟動一個新程序。
活動程序被放置在一個稱為執行佇列 的陣列中,或執行佇列 - rq id。執行佇列可能包含每個程序的優先順序值,排程程式將使用這些值來確定下一個執行哪個程序。為了確保每個程式都能公平地共享資源,每個程式都會執行一段時間(時間片),然後暫停並放回執行佇列中。當一個程式停止讓另一個程式執行時,執行佇列中優先順序最高的程式將被允許執行。當程序請求睡眠、等待資源可用或被終止時,它們也會從執行佇列中移除。
Linux 使用 完全公平排程程式 (CFS),這是第一個在通用作業系統中廣泛使用的公平佇列程序排程程式的實現。CFS 使用一種經過充分研究的經典排程演算法,稱為“公平佇列”,最初是為分組網路發明的。CFS 排程程式的排程複雜度為 O(log N),其中 N 是執行佇列中的任務數。選擇任務可以在恆定時間內完成,但是任務執行後重新插入需要 O(log N) 操作,因為執行佇列是用 紅黑樹 實現的。
與之前的 O(1) 排程程式 相比,CFS 排程程式的實現不是基於執行佇列。相反,紅黑樹實現了一個“時間線”,用於描述未來任務的執行。此外,排程程式使用納秒級粒度記賬,這是分配給單個程序的 CPU 使用份額的原子單位(因此使得之前的時間片概念變得多餘)。這種精確的知識也意味著不需要特定的啟發式方法來確定程序的互動性,例如。
與舊的 O(1) 排程程式一樣,CFS 使用一個稱為“睡眠公平”的概念,它將睡眠或等待的任務視為與執行佇列中的任務等效。這意味著,當互動式任務需要 CPU 時間時,它們可以獲得與在使用者輸入或其他事件等待期間花費的大部分時間相似的 CPU 時間份額。
用於排程演算法的資料結構是紅黑樹,其中節點是特定於排程程式的結構,稱為 sched_entity id。 這些是從通用task_struct程序描述符派生的,並添加了排程程式元素。 這些節點按納秒級的處理器執行時間索引。 每個程序還計算出最大執行時間。 此時間基於這樣一種想法,即“理想處理器”將在所有程序之間平等地共享處理能力。 因此,最大執行時間是程序等待執行的時間除以程序總數,或者換句話說,最大執行時間是程序在“理想處理器”上預期執行的時間。
當呼叫排程程式來執行新程序時,排程程式的操作如下:
- 選擇排程樹的最左節點(因為它將具有最低的已消耗執行時間),並將其傳送以執行。
- 如果程序簡單地完成執行,它將從系統和排程樹中刪除。
- 如果程序達到其最大執行時間或以其他方式停止(自願或透過中斷),則它將根據其新的已消耗執行時間重新插入到排程樹中。
- 然後將從樹中選擇新的最左節點,重複迭代。
如果程序將其大部分時間花費在休眠上,則其已消耗時間值很低,並且當它最終需要時,它會自動獲得優先順序提升。 因此,此類任務不會比不斷執行的任務獲得更少的處理器時間。
CFS 的替代方法是 Con Kolivas 建立的 Brain Fuck Scheduler (BFS)。 與其他排程程式相比,BFS 的目標是提供一個具有更簡單演算法的排程程式,該演算法不需要調整啟發式演算法或調整引數來使效能適應特定型別的計算工作負載。
Con Kolivas 還維護著 CFS 的另一個替代方案,即 MuQSS 排程程式。[1]
Linux 核心包含不同的排程程式類(或策略)。 目前預設使用的完全公平排程程式是 SCHED_NORMAL id 排程程式類,也稱為 SCHED_OTHER。 核心還包含另外兩個類 SCHED_BATCH id 和 SCHED_IDLE id,以及另外兩個即時排程程式類,名為 SCHED_FIFO id(即時先入先出)和 SCHED_RR id(即時迴圈排程),以及第三個即時排程策略,稱為 SCHED_DEADLINE id,它實現了 最早截止期限優先演算法 (EDF),該演算法稍後新增。 任何即時排程程式類都優先於任何“正常”(即非即時)類。 排程程式類是透過 man 2 sched_setscheduler ↪ do_sched_setscheduler id 系統呼叫選擇和配置的。
在排程程式中適當地平衡延遲、吞吐量和公平性是一個開放性問題。[1]
⚲ API
- man 1 renice – 執行程序的優先順序
- man 1 nice – 以修改後的排程優先順序執行程式
- man 1 chrt – 操作程序的即時屬性
- man 2 sched_getattr ↪ sys_sched_getattr id – 獲取排程策略和屬性
- linux/sched.h inc – 主要排程程式 API
- man 2 getpriority,man 2 setpriority
- man 2 sched_setscheduler,man 2 sched_getscheduler
⚙️ 內部
- sched_init id 從 start_kernel id 呼叫
- __schedule id 是主要的排程程式函式。
- runqueues id,this_rq id
- kernel/schedsrc
- kernel/sched/core.csrc
- kernel/sched/fair.c src 實現 SCHED_NORMAL id,SCHED_BATCH id,SCHED_IDLE id
- sched_setscheduler id,sched_getscheduler id
- task_struct id::rt_priority id 以及其他具有不太獨特識別符號的成員
🛠️ 工具
📖 參考資料
- man 7 sched
- 排程 doc
- CFS
- 完全公平排程程式 LWN
- 截止日期任務排程程式 doc
- sched ltp
- sched_setparam ltp
- sched_getscheduler ltp
- sched_setscheduler ltp
📚 關於排程程式的進一步閱讀
搶佔
[edit | edit source]搶佔是指系統中斷正在執行的任務以切換到另一個任務的能力。 這是確保高優先順序任務獲得必要的 CPU 時間並提高系統響應能力所必需的。 在 Linux 中,搶佔模型定義了核心如何以及何時搶佔任務。 不同的模型在系統響應能力和吞吐量之間提供了不同的權衡。
📖 參考資料
- kernel/Kconfig.preemptsrc
- CONFIG_PREEMPT_NONE id – 伺服器沒有強制搶佔
- CONFIG_PREEMPT_VOLUNTARY id – 桌面自願搶佔
- CONFIG_PREEMPT id – 除關鍵部分外,低延遲桌面可搶佔
- CONFIG_PREEMPT_RT id – 即時搶佔,用於 高度響應的應用程式
- CONFIG_PREEMPT_DYNAMIC id,請參見 /sys/kernel/debug/sched/preempt
等待佇列
[edit | edit source]核心中的等待佇列是一種資料結構,它允許一個或多個程序等待(休眠),直到發生感興趣的事情。它們在整個核心中被用來等待可用記憶體、I/O 完成、訊息到達以及許多其他事情。在 Linux 的早期,等待佇列只是一個簡單的等待程序列表,但各種可擴充套件性問題(包括驚群問題)導致從那時起添加了相當多的複雜性。
⚲ API
wait_queue_head id 由 wait_queue_entry id 的雙向連結串列和一個自旋鎖組成。
等待簡單事件
- 使用兩種方法之一來初始化 wait_queue_head id
- init_waitqueue_head id 在函式上下文中初始化 wait_queue_head id
- DECLARE_WAIT_QUEUE_HEAD id - 實際上在全域性上下文中定義 wait_queue_head id
- 等待替代方案
- wait_event_interruptible id - 首選等待
- wait_event_interruptible_timeoutid
- wait_event id - 不可中斷等待。可能導致死鎖 ⚠
- wake_up id 等
👁 例如,請參閱對唯一的 suspend_queue id 的引用。
在複雜情況下,顯式使用 add_wait_queue 而不是簡單的 wait_event
- DECLARE_WAITQUEUE id 實際上使用 default_wake_function id 定義 wait_queue_entry
- add_wait_queue id 將程序插入等待佇列的第一個位置
- remove_wait_queueid
⚙️ 內部
📖 參考資料
同步
[edit | edit source]執行緒同步被定義為一種機制,它確保兩個或多個併發程序或執行緒不會同時執行某個特定的程式段,稱為互斥(互斥)。當一個執行緒開始執行臨界區(程式的序列化段)時,另一個執行緒應該等待,直到第一個執行緒完成。如果沒有應用適當的同步技術,它可能會導致競爭條件,其中變數的值可能是不可預測的,並且會根據程序或執行緒的上下文切換時間而變化。
使用者空間同步
[edit | edit source]Futex
[edit | edit source]一個 man 2 futex ↪ do_futex id(“快速使用者空間互斥體”的縮寫)是一個核心系統呼叫,程式設計師可以使用它來實現基本鎖定,或作為更高級別鎖定抽象的構建塊,例如訊號量和 POSIX 互斥體或條件變數。
Futex 由一個核心空間等待佇列組成,該佇列附加到使用者空間中的對齊整數。多個程序或執行緒完全在使用者空間中操作該整數(使用原子操作以避免相互干擾),並且僅在請求等待佇列上的操作(例如喚醒等待程序或將當前程序放入等待佇列)時才訴諸於相對昂貴的系統呼叫。經過正確程式設計的基於 futex 的鎖將不會使用系統呼叫,除非鎖存在爭用;由於大多數操作不需要程序之間的仲裁,因此在大多數情況下不會發生這種情況。
futex 的基本操作僅基於兩個核心操作 futex_wait id 和 futex_wake id,儘管實現對於更多專門情況有更多操作。
- WAIT(addr,val)檢查儲存在地址addr處的 value 是否為val,如果是,則將當前執行緒置於休眠狀態。
- WAKE(addr,val)喚醒等待地址addr的val個執行緒。
⚲ API
⚙️ 內部結構:kernel/futex.c src
📖 參考資料
檔案鎖定
[edit | edit source]⚲ API:man 2 flock
訊號量
[edit | edit source]💾 歷史:訊號量是 System V IPC man 7 sysvipc 的一部分
⚲ API
⚙️ 內部結構:ipc/sem.c src
核心空間同步
[edit | edit source]對於核心模式同步,Linux 提供了三類鎖定原語:休眠、每個 CPU 本地鎖和自旋鎖。
休眠鎖
[edit | edit source]讀-複製-更新
[edit | edit source]解決讀者-寫者問題的常用機制是讀-複製-更新(RCU)演算法。讀-複製-更新實現了一種對讀者無等待(非阻塞)的互斥,允許非常低的開銷。但是,RCU 更新可能很昂貴,因為它們必須將資料結構的舊版本保留在適當的位置,以適應現有的讀者。
💾 歷史:RCU 於 2002 年 10 月新增到 Linux。從那時起,核心中存在著數千種 RCU API 的使用,包括網路協議棧和記憶體管理系統。Linux 核心 2.6 版本中 RCU 的實現是最知名的 RCU 實現之一。
⚲ linux/rcupdate.h inc 中的核心 API 很小
- rcu_read_lock id 標記受 RCU 保護的資料結構,以便在該臨界區持續期間不會回收它。
- rcu_read_unlock id 用於讀者告知回收器讀者已退出 RCU 讀取端臨界區。請注意,RCU 讀取端臨界區可以巢狀和/或重疊。
- synchronize_rcu id 會阻塞,直到所有 CPU 上所有現有的 RCU 讀取端臨界區都已完成。請注意,
synchronize_rcu不會必然等待任何後續的 RCU 讀取端臨界區完成。
👁 例如,考慮以下事件序列
CPU 0 CPU 1 CPU 2 ----------------- ------------------------- --------------- 1. rcu_read_lock() 2. enters synchronize_rcu() 3. rcu_read_lock() 4. rcu_read_unlock() 5. exits synchronize_rcu() 6. rcu_read_unlock()

- 由於
synchronize_rcu是必須弄清楚讀者何時完成的 API,因此它的實現是 RCU 的關鍵。為了使 RCU 在除最密集讀取的情況之外的所有情況下都變得有用,synchronize_rcu的開銷也必須非常小。
- 或者,同步_rcu 可以註冊一個回撥函式,在所有正在進行的 RCU 讀取側關鍵區段完成後呼叫,而不是阻塞。這種回撥變體在 Linux 核心中被稱為 call_rcu id。
- rcu_assign_pointer id - 更新器使用此函式將新值分配給由 RCU 保護的指標,以便安全地將值的更改從更新器傳達給讀取器。此函式返回新值,並執行給定 CPU 架構所需的任何 記憶體屏障 指令。也許更重要的是,它用於記錄哪些指標受 RCU 保護。
- rcu_dereference id - 讀取器使用此函式獲取由 RCU 保護的指標,它返回一個值,然後可以安全地取消引用。它還執行編譯器或 CPU 所需的任何指令,例如,gcc 的易失性強制轉換、C/C++11 的 memory_order_consume 載入或舊 DEC Alpha CPU 所需的記憶體屏障指令。
rcu_dereference返回的值僅在包含的 RCU 讀取側關鍵區段內有效。與rcu_assign_pointer一樣,rcu_dereference的一個重要功能是記錄哪些指標受 RCU 保護。
RCU 基礎設施觀察 rcu_read_lock、rcu_read_unlock、synchronize_rcu 和 call_rcu呼叫的時間順序,以便確定何時 (1) synchronize_rcu 呼叫可以返回給呼叫者,以及 (2) call_rcu 回撥可以被呼叫。RCU 基礎設施的高效實現大量使用批處理,以便將開銷分攤到相應 API 的多次使用上。
⚙️ 內部
📖 參考資料
互斥量
[edit | edit source]⚲ API
- linux/mutex.hinc
- linux/completion.hinc
- mutex id 擁有者和使用約束,比訊號量更容易除錯
- rt_mutex id 帶有優先順序繼承 (PI) 支援的阻塞互斥鎖
- ww_mutex id 傷口/等待互斥量:帶有死鎖避免功能的阻塞互斥鎖
- rw_semaphore id 讀寫訊號量
- percpu_rw_semaphoreid
- completion id - 使用完成來同步具有 ISR 和任務或兩個任務的任務。
💾 歷史
- semaphore id - 如果可能,使用互斥量代替訊號量
- linux/semaphore.hinc
- linux/rwsem.hinc
📖 參考資料
每個 CPU 本地鎖
[edit | edit source]
在正常的可搶佔核心中,local_lock 呼叫 preempt_disable id。在 RT 可搶佔核心中,local_lock 呼叫 migrate_disable id 和 spin_lock id。
⚲ API
📖 參考資料
💾 歷史:在核心版本 2.6 之前,Linux 停用中斷來實現短關鍵區段。從 2.6 及更高版本開始,Linux 是完全可搶佔的。
自旋鎖
[edit | edit source]自旋鎖
[edit | edit source]自旋鎖是一種鎖,它會導致試圖獲取它的執行緒簡單地在迴圈中等待 (“自旋”),同時反覆檢查鎖是否可用。由於執行緒保持活動狀態但沒有執行有用的任務,因此使用這種鎖是一種忙等待。一旦獲取,自旋鎖通常會一直保持,直到被顯式釋放,儘管在某些實現中,如果等待的執行緒 (持有鎖的執行緒) 阻塞或“進入睡眠”,它們可能會被自動釋放。
自旋鎖通常在核心中使用,因為如果執行緒可能只被阻塞很短時間,它們效率很高。但是,如果自旋鎖保持較長時間,它們就會變得浪費,因為它們可能會阻止其他執行緒執行並需要重新排程。👁 例如 kobj_kset_join id 使用自旋鎖來保護對連結串列的訪問。
核心搶佔的啟用和停用取代了單處理器系統上的自旋鎖 (停用 CONFIG_SMP id)。大多數自旋鎖在 CONFIG_PREEMPT_RT id 核心中成為睡眠鎖。
📖 參考資料
順序鎖
[edit | edit source]順序鎖 (順序鎖的簡稱) 是一種特殊的鎖定機制,用於在 Linux 中支援兩個並行作業系統例程之間共享變數的快速寫入。當寫入器數量很少時,它是讀者-寫入器問題的特殊解決方案。
它是一種讀寫一致的機制,可以避免寫入器飢餓問題。一個 seqlock_t id 包含用於儲存序列計數器 seqcount_t id/seqcount_spinlock_t 的儲存,以及一個鎖。該鎖用於支援兩個寫入器之間的同步,計數器用於指示讀取器的一致性。除了更新共享資料外,寫入器還會在獲取鎖後和釋放鎖前遞增序列計數器。讀取器在讀取共享資料之前和之後讀取序列計數器。如果序列計數器在任一時刻都是奇數,則寫入器已獲取鎖,而資料正在被讀取,它可能已更改。如果序列計數器不同,則寫入器已更改資料,而它正在被讀取。在這兩種情況下,讀取器只需重試 (使用迴圈) 直到它們在之前和之後讀取到相同的偶數序列計數器。
💾 歷史:語義在 2.5.59 版本中穩定下來,並且存在於 2.6.x 穩定核心系列中。順序鎖由 Stephen Hemminger 開發,最初稱為 frlocks,基於 Andrea Arcangeli 的早期工作。第一個實現是在 x86-64 時間程式碼中,在那裡需要與使用者空間同步,而無法使用真正的鎖。
⚲ API
👁 示例:mount_lock id,定義於 fs/namespace.c src
📖 參考資料
自旋鎖或睡眠鎖
[edit | edit source]在伺服器上 在搶佔式即時系統上 spinlock_t, raw_spinlock_t rt_mutex_base, rt_spin_lock, 睡眠 rwlock_t 自旋 睡眠 local_lock preempt_disable migrate_disable, rt_spin_lock, 睡眠
低階
[edit | edit source]編譯器可能會最佳化掉或重新排序對變數的寫操作,從而導致多個執行緒併發訪問變數時出現意外行為。
⚲ API
- asm-generic/rwonce.h inc – 阻止編譯器合併或重新獲取讀寫操作。
- linux/compiler.hinc
- barrier id – 阻止編譯器重新排序屏障周圍的指令
- asm-generic/barrier.h inc – 通用屏障定義
- arch/x86/include/asm/barrier.h src – 強制嚴格的 CPU 排序
- mb id – 確保屏障之前的記憶體操作在屏障之後的任何記憶體操作開始之前完成
📚 進一步閱讀
時間
[edit | edit source]⚲ UAPI
- uapi/linux/time.hinc
- timespec id — 納秒級解析度
- timeval id — 微秒級解析度
- 時區id
- ...
- uapi/linux/time_types.hinc
- __kernel_timespec id — 納秒級解析度,用於系統呼叫
- ...
⚲ API
- linux/time.hinc
- tmid
- get_timespec64id
- ...
- linux/ktime.hinc
- ktime_t id — 用於核心時間值的納秒標量表示
- ktime_subid
- ...
- linux/timekeeping.hinc
- linux/time64.hinc
- uapi/linux/rtc.hinc
- linux/jiffies.hinc
⚙️ 內部
📖 參考資料
...
[edit | edit source]⚙️ 鎖定內部機制
📚 鎖定引用
- 鎖定 doc
- 鎖型別及其規則 doc
- 睡眠鎖 doc
- mutex id, rt_mutex id, semaphore id, rw_semaphore id, ww_mutex id, percpu_rw_semaphore id
- 在搶佔式即時系統上:local_lock, spinlock_t, rwlock_t
- 自旋鎖 doc:
- raw_spinlock_t, 位自旋鎖
- 在非搶佔式即時系統上:spinlock_t, rwlock_t
- 睡眠鎖 doc
- 鎖型別及其規則 doc
- 鎖定不可靠指南 doc
- 同步(計算機科學)
- 同步原語
- 無滴答(全動態滴答), CONFIG_NO_HZ_FULL id
中斷
[edit | edit source]中斷是指由硬體或軟體發出的訊號,通知處理器需要立即處理的事件。中斷提醒處理器發生了需要立即處理的緊急情況,因此需要中斷處理器當前正在執行的程式碼。處理器會響應中斷,暫停當前活動,儲存狀態,並執行一個稱為中斷處理程式(或中斷服務例程,ISR)的函式來處理事件。中斷是暫時的,中斷處理程式完成後,處理器將恢復正常活動。
中斷有兩種型別:硬體中斷和軟體中斷。硬體中斷由裝置使用,用於通知作業系統它們需要處理。例如,按下鍵盤上的鍵或移動滑鼠會觸發硬體中斷,導致處理器讀取按鍵或滑鼠位置。與軟體中斷不同,硬體中斷是非同步的,可能在指令執行過程中發生,因此在程式設計時需要格外注意。啟動硬體中斷的動作稱為中斷請求 - IRQ(⚙️ do_IRQ id)。
軟體中斷是由處理器本身的異常情況或指令集中導致中斷的特殊指令執行引起的。前者通常稱為*陷阱*(⚙️ do_trap id)或*異常*,用於處理程式執行期間發生的錯誤或事件,這些事件足夠異常,無法在程式本身內處理。例如,如果處理器的算術邏輯單元被命令將一個數字除以零,則此不可能的要求會導致*除零異常*(⚙️ X86_TRAP_DE id),可能導致計算機放棄計算或顯示錯誤訊息。軟體中斷指令的功能類似於子程式呼叫,並用於各種目的,例如請求低階系統軟體(如裝置驅動程式)的服務。例如,計算機通常使用軟體中斷指令與磁碟控制器通訊,以請求讀取或寫入磁碟上的資料。
每個中斷都有自己的中斷處理程式。硬體中斷的數量受處理器中斷請求 (IRQ) 線的數量限制,但可能存在數百種不同的軟體中斷。
⚲ API
- /proc/interrupts
- man 1 irqtop – 用於顯示核心中斷資訊的實用程式
- irqbalance – 在多處理器系統上將硬體中斷分配到各個處理器
- 有許多方法可以請求 ISR,其中兩種方法
- devm_request_threaded_irq id – 為具有執行緒化 ISR 的受管裝置分配中斷線的首選函式
- request_irq id,free_irq id – 用於新增和刪除中斷線處理程式的舊的常用函式
- linux/interrupt.h inc – 主要的中斷支援標頭檔案
- irqaction id – 包含處理程式函式
- linux/irq.hinc
- include/linux/irqflags.hinc
- linux/irqdesc.hinc
- linux/irqdomain.hinc
- irq_domain id – 硬體中斷編號轉換物件
- irq_domain_get_irq_dataid
- linux/msi.h inc – 訊息訊號中斷
- 結構體的結構
- irq_desc id 是以下內容的容器
⚙️ 內部
- kernel/irq/settings.hsrc
- kernel/irqsrc
- ls /sys/kernel/debug/irq/domains/
- irq_chipid
📖 參考資料
👁 示例
- dummy_irq_chip id – 虛擬中斷晶片實現
- lib/locking-selftest.csrc
IRQ 親和性
[edit | edit source]⚲ API
- /proc/irq/default_smp_affinity
- /proc/irq/*/smp_affinity 和 /proc/irq/*/smp_affinity_list
常用型別和函式
- struct irq_affinity id – 用於自動 irq 親和性分配的描述,參見 devm_platform_get_irqs_affinity id
- struct irq_affinity_desc id – 中斷親和性描述符,參見 irq_update_affinity_desc id,irq_create_affinity_masks id
- irq_set_affinityid
- irq_get_affinity_maskid
- irq_can_set_affinityid
- irq_set_affinity_hintid
- irqd_affinity_is_managedid
- irq_data_get_affinity_maskid
- irq_data_get_effective_affinity_maskid
- irq_data_update_effective_affinityid
- irq_set_affinity_notifierid
- irq_affinity_notifyid
- irq_chip_set_affinity_parentid
- irq_set_vcpu_affinityid
🛠️ 工具
- irqbalance – 將硬體中斷分配到各個 CPU
...
[edit | edit source]📖 參考資料
📚 進一步閱讀
- IDT – 中斷描述符表
延遲工作
[edit | edit source]排程程式上下文
[edit | edit source]執行緒化 IRQ
[edit | edit source]⚲ API
devm_request_threaded_irq id,request_threaded_irq id
ISR 應返回 IRQ_WAKE_THREAD 以執行執行緒函式
⚙️ 內部
📖 參考資料
工作
[edit | edit source]工作是 workqueue 的包裝器
⚲ API
- linux/workqueue.hinc
- work_struct id,INIT_WORK id,schedule_work id,
- delayed_work id,INIT_DELAYED_WORK id,schedule_delayed_work id,cancel_delayed_work_sync id
👁 示例用法 samples/ftrace/sample-trace-array.c src
⚙️ 內部機制:system_wq id
工作佇列
[edit | edit source]⚲ API
⚙️ 內部
📖 參考資料
中斷上下文
[edit | edit source]- linux/irq_work.h inc – 從硬中斷上下文中將回調排隊和執行的框架
定時器
[edit | edit source]軟中斷定時器
[edit | edit source]此定時器是用於具有節拍解析度的定期任務的軟中斷
⚲ API
- linux/timer.hinc
- timer_list id, DEFINE_TIMER id, timer_setup id
- mod_timer id — 以節拍設定過期時間。
- del_timerid
⚙️ 內部
👁 示例
高解析度定時器
[edit | edit source]⚲ API
- linux/hrtimer.hinc
- hrtimer id, hrtimer.function — 回撥
- hrtimer_init id, hrtimer_cancel id
- hrtimer_start id 啟動一個具有納秒級解析度的定時器
👁 示例 watchdog_enable id
⚙️ 內部
📚 HR 定時器參考
...
[edit | edit source]📚 定時器參考
任務
[edit | edit source]tasklet 是一個軟中斷,用於時間關鍵操作
⚲ API 已棄用,有利於執行緒化中斷:devm_request_threaded_irq id
⚙️ 內部實現:tasklet_action_common id HI_SOFTIRQ, TASKLET_SOFTIRQ
軟中斷
[edit | edit source]軟中斷是內部系統工具,不應直接使用。使用 tasklet 或執行緒化中斷
⚲ API
- cat /proc/softirqs
- open_softirq id 註冊 softirq_action id
⚙️ 內部
⚲ API
📖 參考資料
CPU 特定
[edit | edit source]🖱️ GUI
- tuna – 用於調整執行程序的程式
⚲ API
- cat /proc/cpuinfo
- /sys/devices/system/cpu/
- /sys/cpu/
- /sys/fs/cgroup/cpu/
- grep -i cpu /proc/self/status
- rdmsr – 用於讀取 CPU 機器特定暫存器 (MSR) 的工具
- man 1 lscpu – 顯示有關 CPU 架構的資訊
- linux/arch_topology.h inc – 架構特定的 CPU 拓撲資訊
- linux/cpu.h inc – 通用 CPU 定義
- linux/cpu_cooling.hinc
- linux/cpu_pm.hinc
- linux/cpufeature.hinc
- linux/cpufreq.hinc
- linux/cpuhotplug.h inc – CPU 熱插拔狀態
- linux/cpuidle.h inc – 用於 CPU 空閒功耗管理的通用框架
- linux/peci-cpu.hinc
- linux/sched/cpufreq.h inc – cpufreq 驅動程式和排程程式之間的介面
- linux/sched/cputime.h inc – cputime 計費 API
⚙️ 內部
快取
[edit | edit source]
⚙️ 內部
📖 參考資料
SMP
[edit | edit source]本章介紹 Linux 核心的多處理和多核方面。
Linux SMP 的關鍵概念和功能包括
- 對稱性:在 SMP 系統中,所有處理器都被認為是相同的,沒有硬體層次結構,與使用協處理器相反。
- 負載均衡:Linux 核心採用負載均衡機制將任務均勻地分配到可用 CPU 核心。這樣可以防止任何一個核心不堪重負,而其他核心卻閒置。
- 並行性:SMP 允許並行處理,其中多個執行緒或程序可以在不同的 CPU 核心上同時執行。這可以顯著提高為利用多個執行緒而設計的應用程式的執行速度。
- 執行緒排程:Linux 核心排程程式負責確定哪些執行緒或程序在哪些 CPU 核心上執行以及執行多長時間。它旨在透過最大限度地減少競爭和最大限度地利用 CPU 來最佳化效能。
- 共享記憶體:在 SMP 系統中,所有 CPU 核心通常共享相同的物理記憶體空間。這允許在不同核心上執行的程序和執行緒更有效地通訊和共享資料。
- NUMA – 非一致記憶體訪問: 在較大的 SMP 系統中,由於記憶體庫和處理器的物理排列,記憶體訪問時間可能不一致。Linux 具有有效處理 NUMA 架構的機制,允許程序排程到更靠近其相關記憶體的 CPU 上。
- 快取一致性: SMP 系統需要機制來確保所有 CPU 核心對記憶體的一致檢視。快取一致性協議確保對共享記憶體位置的更改正確傳播到所有核心。
- 可擴充套件性: SMP 系統可以擴充套件到包含更多 CPU 核心,從而增強系統的整體計算能力。但是,隨著核心數量的增加,可能會出現與記憶體訪問、爭用和核心之間通訊相關的挑戰。
- 核心和使用者空間: 在使用者空間執行的 Linux 應用程式可以利用 SMP,而無需瞭解底層硬體細節。核心處理 CPU 核心的管理和資源分配。
🗝️ 關鍵術語
- 親和性是指將程序或執行緒分配到特定 CPU 核心。這有助於控制哪些 CPU 執行任務,透過減少核心之間的資料移動來提高效能。可以使用系統呼叫或命令來管理。親和性可以用 CPU 位掩碼錶示: cpumask_t id 或 CPU 親和性列表: cpulist_parse id.
⚲ API
ps -PLe– 列出執行緒以及執行緒上次執行所在的處理器(第三列 PSR)。- man 1 taskset – 設定或檢索程序的 CPU 親和性
- man 2 getcpu – 確定呼叫執行緒正在執行的 CPU 和 NUMA 節點
- man 7 cpuset – 將程序限制在處理器和記憶體節點子集
- man 8 chcpu – 配置 CPU
- man 3 CPU_SET – 用於操作 CPU 集的宏
- grep Cpus_allowed /proc/self/status
- man 2 sched_setaffinity man 2 sched_getaffinity – 設定和獲取執行緒的 CPU 親和性掩碼
- set_cpus_allowed_ptr id – 更改任務親和性掩碼的常用核心函式
- linux/smp.hinc
- linux/cpu.hinc
- linux/group_cpus.h inc: group_cpus_evenly id – 根據 NUMA/CPU 區域性性均勻地對所有 CPU 進行分組
- linux/cpuset.h inc – cpuset 介面
- linux/cpu_rmap.h inc – CPU 親和性反向對映支援
- linux/cpumask_types.hinc
- struct cpumask, cpumask_t id – CPU 點陣圖,可能非常大
- cpumask_var_t id – 本地 cpumask 變數的型別,請參見 alloc_cpumask_var id, free_cpumask_var id.
- linux/cpumask.h inc – Cpumasks 提供一個位圖,適合表示系統中的 CPU 集,每個 CPU 編號一個位位置
- asm-generic/percpu.hinc
- linux/percpu-defs.h inc – 每個 CPU 區域的基本定義
- linux/percpu.hinc
- linux/percpu-refcount.hinc
- linux/percpu-rwsem.hinc
- linux/preempt.hinc
- /sys/bus/cpu
- 每個 CPU local_lock
⚙️ 內部
- boot_cpu_init id 啟用第一個 CPU
- smp_prepare_cpus id 在啟動期間初始化其餘 CPU
- cpuset_initid
- cpu_numberid
- cpus_mask id – task_struct id 的親和性
- CONFIG_SMPid
- trace/events/percpu.hinc
- IPI – 處理器間中斷
- trace/events/ipi.hinc
- kernel/irq/ipi.csrc
- ipi_send_single id, ipi_send_mask id ...
- drivers/base/cpu.c src – CPU 驅動程式模型子系統支援
- kernel/cpu.csrc
- smpboot
- lib/group_cpus.csrc
🛠️ 工具
- irqbalance – 將硬體中斷分配到各個 CPU
- man 8 numactl – 控制程序或共享記憶體的 NUMA 策略
📖 參考資料
- cgroup v1 的 CPUSETS doc
- 命令列引數中的 CPU 列表 doc
- nohz_full 清理維護操作。cpumasks id 用於 housekeeping_nohz_full_setup id 中的 tick、wq、timer、rcu、misc 和 kthread
- isolcpus 清理維護操作。cpumasks id 用於 housekeeping_isolcpus_setup id 中的 tick、domain 和 managed_irq
📚 進一步閱讀
- CPU 隔離技術現狀,LPC'23
- CPU 分割槽
- 排程程式域 doc – 排程程式在排程程式域內平衡 CPU(排程組)
- CPU 隔離
- isolcpus @LKML
- nohz_full @LKML
- TuneD 外掛的功能
CPU 熱插拔
[edit | edit source]⚲ API
- linux/cpuhotplug.h inc – CPU 熱插拔狀態
⚙️ 內部
📖 參考資料
📚 進一步閱讀
記憶體屏障
[edit | edit source]記憶體屏障 (MB) 是用於確保 SMP 環境中記憶體操作正確排序的同步機制。它們在維護不同 CPU 核心或處理器之間共享資料的 consistency 和正確性方面起著至關重要的作用。MB 阻止編譯器或 CPU 對記憶體訪問指令進行意外和潛在的 harmful 重排序,這會導致併發軟體系統中的資料損壞和競爭條件。
⚲ API
⚙️ 內部
📖 參考資料
架構
[edit | edit source]Linux CPU 架構指的是與 Linux 作業系統相容的各種中央處理器 (CPU) 型別。Linux 被設計為可在各種 CPU 架構上執行,使其能夠在各種裝置上使用,從智慧手機到伺服器和超級計算機。每種架構都有其獨特的特性、優點和設計注意事項。
架構按系列(例如 x86、ARM)、字 或 長整型 大小(例如 CONFIG_32BIT id、CONFIG_64BIT id)進行分類。
一些針對不同 CPU 架構有不同實現的函式
- do_boot_cpu id > start_secondary id > cpu_init id
- setup_arch id、start_thread id、get_current id、current id
⚲ API
⚙️ 架構內部
📖 參考資料
📚 關於多工、排程和 CPU 的進一步閱讀
- ↑ a b Malte Skarupke。 "測量互斥鎖、自旋鎖以及 Linux 排程程式到底有多糟糕".