內核中的VMAP_STACK特性

在Linux-4.14之前,Linux內核棧都是位於線性映射區,該區域對應的虛擬地址和物理地址具有一個固定的偏移,並且是在系統剛啓動是進行過pre-mapped,因此內核在使用時不需要另外做頁表映射。

在2016年的時候內核引入了vmap_stack機制,它是採用vmalloc申請的內存作爲內核棧的一種機制。只需要使能 CONFIG_VMAP_STACK 配置選項即可打開該功能。這個功能帶來了如下一些優勢:

1.利用vmalloc的guard page增強了棧溢出檢測能力
2.減少了內存碎片化

當然除了有這些優勢,同時也對系統中帶來了一些兼容性的問題,不過這些都是可以解決,比如:
使能了CONFIG_VMAP_STACK,那麼將使用vmalloc申請的內存作爲內核棧,這些內存在物理上可能是不連續的,這就會對一些驅動的DMA操作造成影響,因爲一些DMA設備要求數據傳輸時的物理地址是連續的,如果申請了棧上的內存作爲傳輸數據的緩衝區,那麼就會遇到問題。

棧溢出檢測

棧溢出檢測功能,我們可以利用guard page來判斷是否發生了棧溢出錯誤,但是對於傳統的內核棧,guard page就會佔用更多的物理內存,而對於vmap_stack則不然,因爲guard page對應的虛擬地址可以不做任何映射,這樣可以利用MMU的特性來檢測棧溢出,也能夠節省更多的物理內存。vmap_stack特性不用再特意實現guard page,因爲vmalloc本身就自帶這種guard page溢出檢測功能,vmap_stack利用vmalloc申請內存因此也就帶有了該功能。

反碎片

對於內核棧是每個進程都會有各自獨立的內核棧,當系統中不斷創建和銷燬進程時,如果內核棧存在於線性映射區,那麼內存也就是越來越趨於碎片化。而使用了vmalloc申請內存作爲內核棧,則可以在一定程度上減輕內存碎片化,因爲本身vmalloc就是把物理不連續的內存頁映射到虛擬地址連續的空間內。

thread_info和stack的關係

提到stack內核棧,很多人會想到它和thread_info結構體的關係,比如在ARM32平臺上有一個聯合體:

union thread_union {
#ifndef CONFIG_ARCH_TASK_STRUCT_ON_STACK
    struct task_struct task;
#endif
#ifndef CONFIG_THREAD_INFO_IN_TASK
    struct thread_info thread_info;
#endif
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

簡化之後爲:

union thread_union {
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

因此利用這個關係,我們可以通過sp寄存器獲取到對應的stack所在的地址,然後在轉換到對應的thread_info結構體,而該結構體中保存的有當前進程的task_struct結構體指針。這就是current的實現原理。

而對於ARM64來說,一般會使能 CONFIG_THREAD_INFO_IN_TASK 這個宏,該宏會導致這個聯合體結構發生變化,此時thread_info已經不和stack有什麼關係了。此時thread_info是存在於task_struct結構體中的:

struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
    /*
     * For reasons of header soup (see current_thread_info()), this
     * must be the first element of task_struct.
     */
    struct thread_info      thread_info;
#endif
    void                *stack;

那麼這時的current怎麼找到當前運行的進程呢?實際上這裏的實現已經和ARM32不一樣了,對於ARM64平臺,記錄當前進程的task_struct地址是利用sp0_el1寄存器,當內核執行進程切換時會把當前要運行的進程task_struct地址記錄到該寄存器中。因此我們current查找task_struct時也是很簡單了,不用通過sp和thread_info去定位了。

看到這裏,會想到另一個問題:task_struct這裏的stack和前面的thread_union中的stack有什麼不同,該怎麼理解呢?

task_struct結構體中一直都存在一個stack成員,它是用來記錄每個進程的內核棧起始地址的,內核爲每個進程都分配內核棧空間,並記錄在此。而每個進程的thread_union中的stack實際上也是同一個stack地址,只不過我們定義一個聯合體的目的,是爲了方便我們把進程的thread_info結構體保存進stack的最低地址處。當然對於使能了 CONFIG_THREAD_INFO_IN_TASK 的系統,我們完全可以不必關注這個聯合體。

說了這麼多,至於VMAP_STACK,和上述的唯一差異點就是使用了vmalloc來分配內存並賦值給stack成員,它和thread_info也沒有太大關係。


參考鏈接:
https://lwn.net/Articles/692208/
https://zhuanlan.zhihu.com/p/84591715

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章