多工功能
| 多工 |
|---|
| 程序 |
| 執行緒或任務 |
| 同步 |
| 排程器 |
| 中斷核心 |
| 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。Clone建立一個子程序,該子程序可能與父程序共享其執行上下文的一部分。它通常用於實現執行緒(儘管程式設計師通常會使用更高級別的介面,如man 7 pthreads,它是在clone之上實現的)。
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 – 等待程序狀態更改
📖 參考
程序間通訊 (IPC) 特指作業系統提供的一系列機制,允許其管理的程序之間共享資料。實現 IPC 的方法被劃分為不同的類別,這些類別根據軟體需求(例如效能和模組化需求)以及系統環境而有所不同。Linux 從 Unix 繼承了以下 IPC 機制:
訊號 (⚲ API ↪ ⚙️ 實現)
- man 2 kill 向程序傳送訊號
- man 2 tgkill ↪ do_tkill id 向執行緒傳送訊號
- man 2 process_vm_readv ↪ process_vm_rw id - 程序地址空間之間零複製資料傳輸
🔧 待補充: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 參見零複製
- Express 資料路徑 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
📖 參考
在 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
⚙️ 內部
排程器 是作業系統中決定在特定時間點執行哪個程序的部分。它通常具有暫停正在執行的程序、將其移至執行佇列末尾並啟動新程序的能力。
活動程序被放置在一個名為執行佇列或runqueue的陣列中 - rq id。執行佇列可能包含每個程序的優先順序值,排程器將使用這些值來確定接下來執行哪個程序。為了確保每個程式都能公平地共享資源,每個程式都會執行一段時間(時間片),然後暫停並放回執行佇列。當一個程式停止以讓另一個程式執行時,執行佇列中優先順序最高的程式將被允許執行。當程序請求睡眠、等待資源可用或被終止時,也會從執行佇列中移除。
Linux 使用 完全公平排程器 (CFS),這是第一個在通用作業系統中廣泛使用的公平排隊程序排程器的實現。CFS 使用一個經過充分研究的經典排程演算法,稱為“公平排隊”,最初是為資料包網路發明的。CFS 排程器的排程複雜度為 O(log N),其中 N 是執行佇列中任務的數量。選擇任務可以在恆定時間內完成,但任務執行結束後重新插入需要 O(log N) 次操作,因為執行佇列是使用紅黑樹實現的。
與之前的O(1) 排程器不同,CFS 排程器的實現不是基於執行佇列。相反,紅黑樹實現了未來任務執行的“時間線”。此外,排程器使用納秒級粒度的計量,這是為單個程序分配 CPU 時間的原子單位(因此使得之前的時間片概念變得多餘)。這種精確的知識也意味著不需要特定的啟發式方法來確定程序的互動性,例如。
與舊的 O(1) 排程器一樣,CFS 使用一個稱為“睡眠公平性”的概念,它將睡眠或等待的任務視為與執行佇列上的任務等效。這意味著大部分時間都在等待使用者輸入或其他事件的互動式任務,在需要時可以獲得同等比例的 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
📚 排程器相關進一步閱讀
搶佔是指系統中斷正在執行的任務以切換到另一個任務的能力。這對於確保高優先順序任務獲得必要的 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
核心中的等待佇列是一種資料結構,它允許一個或多個程序等待(休眠),直到發生感興趣的事情。它們在整個核心中被用於等待可用記憶體、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
⚙️ 內部
📖 參考
執行緒同步被定義為一種機制,它確保兩個或多個併發程序或執行緒不會同時執行某個特定的程式段,該程式段稱為互斥(互斥量)。當一個執行緒開始執行臨界區(程式的序列化段)時,另一個執行緒應該等待,直到第一個執行緒完成。如果未應用正確的同步技術,則可能導致競爭條件,其中變數的值可能不可預測,並且根據程序或執行緒的上下文切換時間而有所不同。
一個man 2 futex ↪ do_futex id(“快速使用者空間互斥量”的縮寫)是一個核心系統呼叫,程式設計師可以使用它來實現基本鎖定,或作為構建更高級別鎖定抽象(如訊號量和 POSIX 互斥量或條件變數)的構建塊。
Futex 由一個核心空間等待佇列組成,該佇列附加到使用者空間中的一個對齊整數。多個程序或執行緒完全在使用者空間中操作該整數(使用原子操作以避免相互干擾),並且僅在請求等待佇列上的操作時才訴諸相對昂貴的系統呼叫(例如,喚醒等待的程序,或將當前程序放入等待佇列)。正確程式設計的基於 futex 的鎖除了在鎖被爭用時之外不會使用系統呼叫;由於大多數操作不需要程序之間的仲裁,因此在大多數情況下不會發生這種情況。
Futex 的基本操作僅基於兩個核心操作futex_wait id 和futex_wake id,儘管實現具有更多操作以用於更專業的情況。
- WAIT(addr,val)檢查儲存在地址addr處的值是否為val,如果是,則使當前執行緒休眠。
- WAKE(addr,val)喚醒在地址addr處等待的val個執行緒。
⚲ API
⚙️ 內部實現:kernel/futex.c src
📖 參考
⚲ API:man 2 flock
💾 歷史:訊號量是 System V IPC 的一部分 man 7 sysvipc
⚲ API
⚙️ 內部實現:ipc/sem.c src
對於核心模式同步,Linux 提供了三類鎖定原語:睡眠鎖、每個 CPU 本地鎖和自旋鎖。
解決讀者-寫者問題的常用機制是讀-複製-更新 (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的開銷也必須非常小。
- 或者,
synchronize_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 的許多使用上的開銷。
⚙️ 內部
📖 參考
⚲ API
- linux/mutex.hinc
- linux/completion.hinc
- mutex id 具有所有者和使用約束,比訊號量更容易除錯
- rt_mutex id 帶有優先順序繼承 (PI) 支援的阻塞互斥鎖
- ww_mutex id 纏繞/等待互斥量:帶有死鎖避免的阻塞互斥鎖
- rw_semaphore id 讀者-寫者訊號量
- percpu_rw_semaphoreid
- completion id - 使用 completion 用於中斷服務程式和任務或兩個任務之間的同步。
💾 歷史
- semaphore id - 如果可能,請使用互斥量代替訊號量
- linux/semaphore.hinc
- linux/rwsem.hinc
📖 參考
在普通的可搶佔核心中,local_lock 呼叫preempt_disable id。在 RT 可搶佔核心中,local_lock 呼叫migrate_disable id 和 spin_lock id。
⚲ API
📖 參考
💾 歷史:在核心版本 2.6 之前,Linux 停用中斷以實現短關鍵段。從 2.6 及更高版本開始,Linux 是完全搶佔式的。
自旋鎖是一種鎖,它會導致嘗試獲取它的執行緒簡單地在迴圈中等待(“自旋”),同時重複檢查鎖是否可用。由於執行緒保持活動狀態但沒有執行有用的任務,因此使用此類鎖是一種忙等待。一旦獲取,自旋鎖通常會一直保持,直到顯式釋放,儘管在某些實現中,如果等待的執行緒(持有鎖的執行緒)阻塞或“進入睡眠”,它們可能會自動釋放。
自旋鎖通常在核心內部使用,因為如果執行緒可能僅被阻塞很短時間,它們效率很高。但是,如果持有自旋鎖的時間過長,它們就會變得浪費,因為它們可能會阻止其他執行緒執行並需要重新排程。👁 例如,kobj_kset_join id 使用自旋鎖來保護對連結列表的訪問。
啟用和停用核心搶佔替換了單處理器系統上的自旋鎖(停用CONFIG_SMP id)。大多數自旋鎖在CONFIG_PREEMPT_RT id 核心中變成了睡眠鎖。
📖 參考
seqlock(順序鎖的簡稱)是一種特殊的鎖定機制,用於 Linux 支援在兩個並行作業系統例程之間快速寫入共享變數。當寫者數量較少時,它是讀者-寫者問題的特殊解決方案。
這是一種讀寫一致性機制,避免了寫者飢餓的問題。一個seqlock_t id除了鎖之外,還包含用於儲存序列計數器seqcount_t id/seqcount_spinlock_t 的儲存空間。鎖用於支援兩個寫者之間的同步,而計數器則用於指示讀者的資料一致性。除了更新共享資料外,寫者還會在獲取鎖之後和釋放鎖之前遞增序列計數器。讀者在讀取共享資料之前和之後都會讀取序列計數器。如果計數器在任一時刻為奇數,則表示在讀取資料期間寫者獲取了鎖,並且資料可能已更改。如果序列計數器不同,則表示在讀取資料期間寫者已更改資料。在這兩種情況下,讀者都會簡單地重試(使用迴圈),直到他們在前後讀取到相同的偶數序列計數器為止。
💾 歷史:語義在 2.5.59 版本中穩定下來,並且存在於 2.6.x 穩定核心系列中。seqlocks 由 Stephen Hemminger 開發,最初稱為 frlocks,基於 Andrea Arcangeli 的早期工作。第一個實現是在 x86-64 時間程式碼中,在那裡需要與使用者空間同步,而使用者空間無法使用真正的鎖。
⚲ API
👁 示例:mount_lock id,定義在fs/namespace.c src
📖 參考
在伺服器上 在搶佔式 RT 上 spinlock_t, raw_spinlock_t rt_mutex_base, rt_spin_lock, 睡眠 rwlock_t 自旋 睡眠 local_lock preempt_disable migrate_disable, rt_spin_lock, 睡眠
編譯器可能會最佳化或重新排序對變數的寫入,從而導致多個執行緒併發訪問變數時出現意外行為。
⚲ 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 – 確保屏障之前的記憶體操作在屏障之後的任何記憶體操作開始之前完成
📚 進一步閱讀
⚲ 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
⚙️ 內部
📖 參考
⚙️ 鎖定內部
📚 鎖定參考
- 鎖定 doc
- 鎖型別及其規則 doc
- 睡眠鎖 doc
- mutex id, rt_mutex id, semaphore id, rw_semaphore id, ww_mutex id, percpu_rw_semaphore id
- 在搶佔式 RT 上:local_lock, spinlock_t, rwlock_t
- 自旋鎖 doc:
- raw_spinlock_t, 位自旋鎖
- 在非搶佔式 RT 上:spinlock_t, rwlock_t
- 睡眠鎖 doc
- 鎖型別及其規則 doc
- 不可靠的鎖定指南 doc
- 同步(計算機科學)
- 同步原語
- 無tick核心 (全dynticks), CONFIG_NO_HZ_FULL id
一個中斷 是由硬體或軟體發出的處理器訊號,指示需要立即關注的事件。中斷提醒處理器注意需要中斷處理器當前正在執行的程式碼的高優先順序條件。處理器透過暫停其當前活動、儲存其狀態並執行稱為中斷處理程式(或中斷服務例程,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
⚲ 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 上
📖 參考
📚 進一步閱讀
- IDT – 中斷描述符表
⚲ API
devm_request_threaded_irq id, request_threaded_irq id
ISR 應返回 IRQ_WAKE_THREAD 以執行執行緒函式
⚙️ 內部
📖 參考
work 是 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
⚲ API
⚙️ 內部
📖 參考
- linux/irq_work.h inc – 用於從硬中斷上下文中排隊和執行回撥的框架
此定時器是用於具有jiffies解析度的週期性任務的軟中斷。
⚲ API
- linux/timer.hinc
- 定時器相關的結構體和函式:timer_list id,DEFINE_TIMER id,timer_setup id
- mod_timer id — 設定以jiffies為單位的超時時間。
- del_timerid
⚙️ 內部
👁 示例
⚲ API
- linux/hrtimer.hinc
- hrtimer id,hrtimer.function — 回撥函式
- hrtimer_init id,hrtimer_cancel id
- hrtimer_start id 以納秒解析度啟動定時器
👁 示例 watchdog_enable id
⚙️ 內部
📚 高解析度定時器相關參考
📚 定時器相關參考
tasklet 是一種軟中斷,用於時間關鍵操作。
⚲ API 已被棄用,建議使用執行緒化中斷:devm_request_threaded_irq id
- tasklet 相關的結構體和函式:tasklet_struct id,tasklet_init id,tasklet_schedule id
⚙️ 內部實現:tasklet_action_common id HI_SOFTIRQ,TASKLET_SOFTIRQ
軟中斷是內部系統機制,不應直接使用。請使用 tasklet 或執行緒化中斷。
⚲ API
- cat /proc/softirqs
- open_softirq id 註冊 softirq_action id
⚙️ 內部
⚲ API
📖 參考
🖱️ 圖形介面
- 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
⚙️ 內部
⚙️ 內部
📖 參考
本章介紹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 – percpu區域的基本定義
- 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 – 處理器間中斷
- smpboot
- lib/group_cpus.csrc
🛠️ 工具
- irqbalance – 將硬體中斷分配到 CPU 上
- man 8 numactl – 控制程序或共享記憶體的NUMA策略
📖 參考
- cgroup v1的CPUSETS doc
- 命令列引數中的CPU列表 doc
- nohz_full 清除管家工作。cpumasks id 用於tick、wq、timer、rcu和kthread在housekeeping_nohz_full_setup id中。
- isolcpus 清除管家工作。cpumasks id 用於tick、domain和managed_irq在housekeeping_isolcpus_setup id中。
📚 進一步閱讀
- CPU隔離技術現狀,LPC'23
- CPU分割槽
- 排程程式域 doc – 排程程式在排程程式域內平衡CPU(排程組)
- CPU隔離
- LKML上的isolcpus
- LKML上的nohz_full
- 排程程式TuneD外掛的功能
⚲ API
- linux/cpuhotplug.h inc – CPU 熱插拔狀態
⚙️ 內部
📖 參考
📚 進一步閱讀
記憶體屏障 (MB) 是用於確保 SMP 環境中記憶體操作正確排序的同步機制。它們在維護不同 CPU 核心或處理器之間共享資料的完整性和正確性方面發揮著至關重要的作用。MB 可以防止編譯器或 CPU 對記憶體訪問指令進行意外且可能造成危害的重新排序,這可能導致併發軟體系統中的資料損壞和競爭條件。
⚲ API
⚙️ 內部
📖 參考
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 排程程式到底有多糟糕".