Linux 3.2 current_thread_info 函數
前言
current_thread_info, 這個函數在內核中, 經常被用於訪問當前CPU正在運行的任務, 那麼它的底層是怎麼實現的呢?
這是我閲讀 LKD 遇到的第一個難點, 也是我第一次體會到 "紙上得來終覺淺, 絕知此事要躬行" 的點.
關於 Linux 3.2 進程模型, 在 copy_process 中已有記載.
1.讓我們來看看, LKD 對此是怎麼寫的
LKD對此的描述如下

對, 不就是獲取RSP, 然後去掉13位嗎? 這有什麼難的, 那不是隻需要 rsp & ~(8192-1) 不就好了嗎?
帶着這個思路, 我打開了 thread_info.h...
2.但是, Linux 3.2 的源代碼呢?
然而, 在Linux 3.2中, 代碼是這樣寫的
static inline struct thread_info *current_thread_info(void) {
struct thread_info *ti;
ti = (void *)(percpu_read_stable(kernel_stack) + KERNEL_STACK_OFFSET - THREAD_SIZE);
return ti;
}
相信不止是我有這樣的感受吧:
這什麼鬼? 這percpu又是什麼鬼? 為什麼還要加加減減的? 和我在書上看到的完全不一樣啊!
別急, 我們先拆分一下這段代碼, 讓它更清晰易懂:
static inline struct thread_info *current_thread_info(void) {
void* kstack = (void *)percpu_read_stable(kernel_stack);
struct thread_info *ti;
ti = kstack + KERNEL_STACK_OFFSET - THREAD_SIZE;
return ti;
}
3. percpu 機制
3.1 percpu 含義
percpu, 顧名思義, 每個cpu.
眾所周知, 現代的 CPU 其實就是一個大公司, 每個核心相當於每個牛馬, 操作系統相當於主管.
那我們這些在玩黑公司: 打工的牛馬, 也有自己的隱私, 也就是説, 一個牛馬不能訪問其他牛馬獨有的資料和文件, 保證數據安全. 同時, 公司也有一些數據是公共的, 每個人都可以訪問.
對, cpu核心也是一樣的, cpu核心也有屬於自己的數據, 和每個核心都能訪問到的公共數據.
那問題來了, cpu核心怎麼知道哪些數據是自己的, 哪些數據是公共的呢? 這些數據存儲在哪? 如何保證隔離?
3.2 x86_64的分段模式
在 x86 中, 段寄存器存儲的是段選擇子. 那你可能會想, x86_64 就是 x86 的擴展嘛. 那分段也總和x86一樣吧.
N O!
x86_64的長模式, 可謂是差不多快把分段這玩意給廢了, 主要用的是平坦模型+分頁模式.
更具體的來説, x86_64強制CS, DS, ES三個段寄存器的值為0(當然, 還有一種情況不是0, 那就是 x86 兼容模式. 向下兼容這塊沒得説).
FS GS 存儲的值仍然是段選擇子(當然, 允許是0), 但是在長模式下, 段選擇子僅僅用於檢查特權級, 它的基址字段是不起作用的.
那麼, 在長模式下, CPU 怎麼計算地址呢?
段寄存器是CS DS ES的情況下, 計算地址的時候直接忽略掉這些段寄存器. 然而 FS GS 寄存器的情況有些不同.
在 每個CPU核心 (注意每個, 下面要考) 中有個區域叫 MSR, 這個區域中有兩個字段分別叫做 MSR_GS_BASE 和 MSR_FS_BASE, CPU 在計算基址的時候, 會加上這兩個字段存儲的值, 也就是説假設有如下代碼
mov ecx, 0xC0000101
mov eax,0x10
mov edx,0x0
wrmsr
;以上是操作 MSR 寄存器的彙編代碼, 將 MSR_GS_BASE 的值設置為 0x00000010.
mov rax,qword gs:[0x1234]
那 CPU 會從 0x00001244 處獲取數據.
3.3 percpu_read_stable 的含義
OK, 現在讓我們看看這個函數. 這個函數的作用就是讀取每個CPU獨有的變量.
讓我們看看 percpu_read_stable 宏展開時候的樣子
({ typeof(kernel_stack) pfo_ret__; switch (sizeof(kernel_stack)) { case 1: asm("mov" "b ""%%""gs"":" "%P" "1"",%0" : "=q" (pfo_ret__) : "p" (&(kernel_stack))); break; case 2: asm("mov" "w ""%%""gs"":" "%P" "1"",%0" : "=r" (pfo_ret__) : "p" (&(kernel_stack))); break; case 4: asm("mov" "l ""%%""gs"":" "%P" "1"",%0" : "=r" (pfo_ret__) : "p" (&(kernel_stack))); break; case 8: asm("mov" "q ""%%""gs"":" "%P" "1"",%0" : "=r" (pfo_ret__) : "p" (&(kernel_stack))); break; default: __bad_percpu_size(); } pfo_ret__; })
嚇哭了, 然而, 實際上, 翻譯成人話, 這段代碼就在幹這件事:
mov rax,qword gs:[var]
對, 發現了嗎? 它實際上就是引用gs寄存器上的數據. 那麼根據上面講的, 引用gs寄存器, 實際上是讀取了對應CPU的MSR_GS_BASE, 然後加上offset.
誒, 對應CPU? 那也就是説... 每個CPU的MSR_GS_BASE是可以不同的?
BINGO!
所以, 我們把每個 CPU 核心的 MSR_GS_BASE 都設置成不同的值, 設立不同的 GS 基址, 讓不同的CPU訪問不同的內存, 那豈不是就可以做到每個CPU的數據隔離了嗎?
對, Linux 就是這樣乾的. offset就是變量偏移. 這就是 percpu_read_stable 的原理.
回到這段代碼, 因為每個 CPU 都需要執行內核任務, 所以 Linux 為每個 CPU 核心都分配了一個內核棧, 這個棧屬於 CPU 的私有數據.
CPU要是想知道當前的運行任務的話, 只需要獲取內核棧頂的 thread_info 儲存的值就可以.
在 Linux 中, kernel_stack記錄該cpu的內核棧起始點的位置(具體見下文), 所以, percpu_read_stable(kernel_stack) 其實就是獲取它:
static inline struct thread_info *current_thread_info(void) {
//...
void* kstack = MSR_GS_OFFSET + kernel_stack;
//...
}
4.後續的操作呢?
4.1 x86_64 的特權級壓棧機制
在 x86_64 中, 要是進行特權級切換, 那麼就必須往內核棧壓入 5 個寄存器 SS,RSP,RFLAGS,CS,RIP, 用於保存當前 CPU 狀態.
所以, 棧底其實還預留了 40 字節, 用於保存切換特權級前的CPU狀態的, 而並不是直接存儲的 thread_info.
由此, 我們可以構造出棧模型了
高地址 (棧底) +----------------------------+ <--- kernel_stack (TSS 中記錄的值)
| SS (8 bytes) |
| RSP (8 bytes) |
| RFLAGS (8 bytes) |
| CS (8 bytes) |
| RIP (8 bytes) |
+----------------------------+ <--- 棧起始點(kernel_stack變量)
| |
| 內核運行時的棧空間 |
| (向下增長) |
| | |
| v |
| |
+----------------------------+ <--- thread_info (ti) 放在最底部
低地址 (棧頂) +----------------------------+ <--- (kernel_stack - THREAD_SIZE)
4.2 後面的那加加減減
#define KERNEL_STACK_OFFSET (5*8)
//現在知道5*8怎麼來了吧
#define THREAD_SIZE 8192
//內核棧大小
static inline struct thread_info *current_thread_info(void) {
//...
ti = kstack + KERNEL_STACK_OFFSET - THREAD_SIZE;
//...
}
kstack 是棧起始點的位置, THREAD_SIZE 是棧大小, 所以我們先通過 kstack - THREAD_SIZE 獲取棧底的位置.
然後, 我們再加上KERNEL_STACK_OFFSET, 就是棧頂的位置了, 也是 thread_info 的位置.
The End
所以, 總體的代碼是這樣的
static inline struct thread_info *current_thread_info(void) {
void* kstack = (void *)percpu_read_stable(kernel_stack);
struct thread_info *ti;
ti = kstack + KERNEL_STACK_OFFSET - THREAD_SIZE;
return ti;
}
本期隨筆寫到這, 感謝大家的觀看哦~萌新初涉 Linux 內核, 有錯誤也請多多指正~
版權聲明: 本文采用 CC BY-NC-SA 4.0 許可協議。轉載請註明出處!
作者: Sudo-su-Bash (Alien-Bash)
發佈時間: 2026-02-18
原文鏈接: https://www.cnblogs.com/SudosuBash/p/19622204