怎麼理解linux內核棧?

關於linux內核棧,一直很困惑,找不到詳細資料。

1. linux內核棧是所有進程共享的嗎,每個進程都有一個單獨的內核棧?

2. 從內核模塊編程的角度看(不涉及用戶態進程),內核棧該怎麼理解?和用戶進程進行系統調用使用的棧空間有什麼不同?

3. 怎麼理解linux內核棧空間只有4KB或8KB,linux內核編程中的堆(heap)和棧(stack)有什麼區別?


1. 讀Linux內核以及相關的資料的時候,時刻要清醒地認識到它說的是內核態還是用戶態的東西。

2. 一個用戶態進程/線程在內核中都是用一個task_struct的實例描述的,這個有點類似設計模式裡面的橋接模式(handle-body), 用戶態看到的進程PID,線程TID都是handle, task_struct是body。

3. C語言書裡面講的堆、棧大部分都是用戶態的概念,用戶態的堆、棧對應用戶進程虛擬地址空間里的一個區域,棧向下增長,堆用malloc分配,向上增長。

4. 用戶空間的堆棧,在task_struct-&>mm-&>vm_area裡面描述,都是屬於進程虛擬地址空間的一個區域。

5.而內核態的棧在tsak_struct-&>stack裡面描述,其底部是thread_info對象,thread_info可以用來快速獲取task_struct對象。整個stack區域一般只有一個內存頁(可配置),32位機器也就是4KB。

6. 所以說,一個進程的內核棧,也是進程私有的,只是在task_struct-&>stack裡面獲取。

7. 內核態沒有進程堆的概念,用kmalloc()分配內存,實際上是Linux內核統一管理的,一般用slab分配器,也就是一個內存緩存池,管理所有可以kmalloc()分配的內存。所以從原理上看,在Linux內核態,kmalloc分配的所有的內存,都是可以被所有運行在Linux內核態的task訪問到的。


1. Linux 內核中使用 `task_struct` 作為進程描述符,該結構定義在&文件中:

struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, &>0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
/* ...... */
};

可以發現 `task_struct` 中有一個 `stack` 成員,而 `stack` 正好用於保存內核棧地址。內核棧在進程創建時綁定在 `stack` 上。可以觀察 `fork` 流程:Linux 通過 `clone()` 系統調用實現 `fork()`,然後由 `fork()` 去調用 `do_fork()`。定義在&中的 `do_fork()` 負責完成進程創建的大部分工作,它通過調用 `copy_process()` 函數,然後讓進程運行起來。`copy_process()` 完成了許多工作,這裡重點看內核棧相關部分。`copy_process()` 調用 `dup_task_struct` 來創建內核棧、`thread_info` 和 `task_struct`:

static struct task_struct *dup_task_struct(struct task_struct *orig) {
struct task_struct *tsk;
struct thread_info *ti;
unsigned long *stackend;
int err; prepare_to_copy(orig);
tsk = alloc_task_struct();
if (!tsk) return NULL;
ti = alloc_thread_info(tsk);
if (!ti) {
free_task_struct(tsk);
return NULL;
}
err = arch_dup_task_struct(tsk, orig);
if (err) goto out;
tsk-&>stack = ti;
err = prop_local_init_single(tsk-&>dirties);
if (err) goto out;
setup_thread_stack(tsk, orig);
stackend = end_of_stack(tsk);
*stackend = STACK_END_MAGIC;
/* for overflow detection */
#ifdef CONFIG_CC_STACKPROTECTOR
tsk-&>stack_canary = get_random_int();
#endif
/* One for us, one for whoever does the "release_task()"
(usually parent) */
atomic_set(tsk-&>usage,2);
atomic_set(tsk-&>fs_excl, 0);
#ifdef CONFIG_BLK_DEV_IO_TRACE
tsk-&>btrace_seq = 0;
#endif
tsk-&>splice_pipe = NULL;
account_kernel_stack(ti, 1);
return tsk;
out:
free_thread_info(ti);
free_task_struct(tsk);
return NULL;
}

其中重點是下面部分:

tsk = alloc_task_struct();
if (!tsk) return NULL;
ti = alloc_thread_info(tsk);
if (!ti) {
free_task_struct(tsk);
return NULL;
}
err = arch_dup_task_struct(tsk, orig);
if (err) goto out;
tsk-&>stack = ti;

這裡可以看到內核棧的創建過程。可能會疑惑為何 `stack` 指向了 `thread_info`,那是因為在2.6以前的內核中,各個進程的 `task_struct` 存放在內核棧的尾端,這樣做是為了在寄存器較少的體系結構中直接使用棧指針加偏移就可以算出它的位置。2.6以後使用slab分配器動態分配 `task_struct` ,所以只需要在棧頂創建一個 `thread_info` 記錄 `task_struct` 的地址。

所以這裡回答了第一個問題, 每個進程都有一個單獨的內核棧。

2.

從內核模塊編程的角度看(不涉及用戶態進程),內核棧該怎麼理解?和用戶進程進行系統調用使用的棧空間有什麼不同?

每個進程運行時都持有上下文,用於保證並行性。為了保證內核和用戶態隔離,陷入內核不影響用戶態,所以使用了不同的棧。內核棧只是對內核態上下文中的棧的稱謂。

為了方便管理用戶程序,限制用戶程序許可權,所以區分了內核態和用戶態。內核態中擁有高特權級,能夠執行io等特權指令,而用戶態程序想要執行特權級指令則必須陷入內核態。從用戶程序角度來看,內核更類似與庫文件的存在。

內核通過虛擬地址訪問許可權來限制用戶程序訪問內存地址,比如內核空間的代碼和數據不應該被用戶程序訪問到。因此內核運行時使用的棧不應該能被用戶態代碼訪問到,否則用戶態代碼完全可以通過構造特定的數據控制內核(參考ret2libc)。因此,用戶態使用的棧空間和內核棧並無本質區別,它們均處於同一塊頁表映射中,內核棧處於高特權級訪問限制的虛擬地址中,防止用戶態代碼訪問內核數據。

3.

怎麼理解linux內核棧空間只有4KB或8KB,linux內核編程中的堆(heap)和棧(stack)有什麼區別?

內核中的資源是非常寶貴的,而一個比較大的棧空間多數時間是浪費了。那為何不設計小一點,然後保證內核調用層次低、局部變數小,做到不溢出?

而內核編程中的堆和棧並非通常寫程序時所說的堆和棧有嚴格的區分。

內核中的堆和棧沒有嚴格的地址區分,只是程序角度的不同解釋而已。


內核棧有不同含義。一是內核線程使用的棧,比如初始化線程,idle,kthread,這些僅在內核空間運行,只有內核棧,沒有用戶態以及用戶空間棧。

還有就是用戶線程發生中斷,系統調用進入內核態時候使用的棧。由於中斷處理很簡單,而且不允許中斷重入,使用的內核棧很少,4k/8k就夠了。還有是異常棧,專門處理異常,跟中斷棧分開,否則在中斷處理髮生異常就導致重入了。系統調用內核棧也是分開的。

但有時候中斷處理,系統調用實際上要處理很多東西的,但內核不是直接在中斷棧處理所有事情,而是處理最簡單的部分,複雜的交給其他內核線程/軟中斷完成。

中斷/異常處理/系統調用內核棧是所有進程共享的。但不是說只有一份,而是每個虛擬cpu核心一份。

那如果同一個cpu多個線程切換怎麼辦?比如系統調用過程中發生時鐘硬體中斷,這個時候發現需要切換線程。如果在內核棧處理一半過程中發生切換,那就會出現問題。為了避免這個問題,線程切換都發生在中斷/系統調用返回的時候,即內核棧恢復到棧頂的時候發生任務切換。即本來硬體中斷返回原來線程,如果發生切換,返回的時候發回b線程。對於需要長時間處理的系統調用,中斷處理,會有內核線程進行處理,同樣也是在中斷/系統調用返回的時候切換到內核線程。


1、每個進程被創建的時候,在生成進程描述符task_struct的同時,會生成兩個棧,一個是用戶棧,位於用戶地址空間;一個是內核棧,位於內核空間。當進程在用戶地址空間中執行的時候,使用的是用戶棧,CPU堆棧指針寄存器中存的是用戶棧的地址;同理,當進程在內核空間執行時,CPU堆棧指針寄存器中放的是內核棧的地址。

2、當位於用戶空間的進程進行系統調用時,它會陷入內核,讓內核代其執行。此時,進程用戶棧的地址會被存進內核棧中,CPU堆棧指針寄存器中的內容也會變為內核棧的地址。當系統調用執行完畢,進程從內核棧找到用戶棧的地址,繼續在用戶空間中執行,此時CPU堆棧指針寄存器就變為了用戶棧的地址。

3、我的理解是,因為一個進程就對應著一個內核棧,而系統一般默認可同時存在的進程數目是32768,如果按每個內核棧空間4KB計算,32768個進程就已經佔用了128MB內存,而且單個內存棧過大也容易造成內存空間浪費的結果。


代碼從cpl3跳轉到cpl0的時候必然存在一個棧的轉換,每一級代碼都對應一個棧空間,這是cpu的實現機制。也是為了防止用戶進程訪問內核數據,進程結構中棧信息只有棧段,用cpu機制切棧棧空間就會被清空,除非你在代碼中提供額外的控制。所以就給每個用戶空間綁定一個核心棧。和用戶棧並沒有什麼不同的,數據段是內核而已。


我不懂linux 我只知道我的lmos是每個線程一個內核棧,大小8KB,lmos內核沒有堆 但是所有的內核數據結構都是動態分配的


這個問題我看了好久了。我也是初學linux,嘗試著答一答,拋磚引玉,也希望各位的指正。

1. linux內核棧是所有進程共享的嗎,每個進程都有一個單獨的內核棧?

不是進程共享的,每個進程有單獨的內核棧。

```

union thread_union{

struct thread_info thread_info;

unsigned long stack[THREAD_SIZE/sizeof(long)];

}

```

線程描述符thread_info中有一個指向進程描述符的指針,這裡也可以看出是進程私有的。
2. 從內核模塊編程的角度看(不涉及用戶態進程),內核棧該怎麼理解?和用戶進程進行系統調用使用的棧空間有什麼不同?

不牽扯用戶態進程的內核棧,我的理解是內核線程擁有的。內核線程沒有獨立的地址空間,只在內核空間運行。系統調用的時候,用的是內核棧,內核運行與進程上下文中。
之前疑惑我的地方是,把linux單內核,理解成了所有的只有一個內核棧了。單內核的含義是所有內核服務都在一個大內核地址空間上運行。多個內核線程,還有進程的內核棧,系統運行時是有多個內核棧的。

還有一點就是linux內核設計與實現第18頁上有一句話:每個處理器都有自己的內核棧。這句我感覺好奇怪,內核棧是和線程相關的,怎麼和處理器綁定了?英文原文是Each process receives its own stack。這裡receives翻譯成擁有真的好嗎,應該是get something的意思。


推薦閱讀:

win8+每月一次對ssd執行碎片整理是有利的么?
初三馬上畢業了,復讀還是職高?
如何解密被wannacry軟體加密的文件?
為什麼在知乎出沒的卡內基梅隆大學計算機系博士研究生,數量遠多於其它四大 CS 牛校?
請教我該如何取捨課程,然後如何學習成為一個程序員?

TAG:Linux | 嵌入式系統 | 計算機科學 | CC | Linux內核 |