ARM Linux系統(tǒng)中的用戶棧與內(nèi)核棧
用戶棧
用戶棧就是應(yīng)用程序直接使用的棧。如下圖所示,它位于應(yīng)用程序的用戶進(jìn)程空間的最頂端。
本文引用地址:http://m.butianyuan.cn/article/201611/317854.htm當(dāng)用戶程序逐級(jí)調(diào)用函數(shù)時(shí),用戶棧從高地址向低地址方向擴(kuò)展,每次增加一個(gè)棧幀,一個(gè)棧幀中存放的是函數(shù)的參數(shù)、返回地址和局部變量等,所以棧幀的長(zhǎng)度是不定的。
用戶棧的棧底靠近進(jìn)程空間的上邊緣,但一般不會(huì)剛好對(duì)齊到邊緣,出于安全考慮,會(huì)在棧底與進(jìn)程上邊緣之間插入一段隨機(jī)大小的隔離區(qū)。這樣,程序在每次運(yùn)行時(shí),棧的位置都不同,這樣黑客就不大容易利用基于棧的安全漏洞來實(shí)施攻擊。
用戶棧的伸縮對(duì)于應(yīng)用程序來說是透明的,應(yīng)用程序不需要自己去管理?xiàng)#@是操作系統(tǒng)提供的功能。應(yīng)用程序在剛剛啟動(dòng)的時(shí)候(由fork()系統(tǒng)調(diào)用復(fù)制出新的進(jìn)程),新的進(jìn)程其實(shí)并不占有任何棧的空間。當(dāng)應(yīng)用程序中調(diào)用了函數(shù)需要壓棧時(shí),會(huì)觸發(fā)一個(gè)page fault,內(nèi)核在處理這個(gè)異常里會(huì)發(fā)現(xiàn)進(jìn)程需要新的??臻g,于是建立新的VMA并映射內(nèi)存給用戶棧。
內(nèi)核棧
內(nèi)核棧對(duì)于應(yīng)用程序是不可見的,因?yàn)樗挥趦?nèi)核空間中。在應(yīng)用程序執(zhí)行過程中,如果發(fā)生異常、中斷或系統(tǒng)調(diào)用的話,應(yīng)用程序會(huì)被暫停,系統(tǒng)進(jìn)入內(nèi)核態(tài),轉(zhuǎn)去執(zhí)行異常響應(yīng)等代碼,這個(gè)時(shí)候所使用的棧就是內(nèi)核棧。
與用戶棧相比,內(nèi)核棧的尺寸要小得多。在32位Linux系統(tǒng)上,用戶棧最多可以擴(kuò)展到64M,但內(nèi)核棧最多也只有8K字節(jié),而且有時(shí)為了提高內(nèi)存利用率還常常把內(nèi)核棧配置成4K。其實(shí)即使是只有4K,在絕大多數(shù)情況下也仍然是夠用的,因?yàn)檫@里只是給內(nèi)核代碼使用的,棧不會(huì)很大。
每個(gè)進(jìn)程在內(nèi)核空間中都擁有一個(gè)對(duì)應(yīng)的內(nèi)核棧,而且這個(gè)棧是在進(jìn)程fork的時(shí)候就預(yù)留出來的。以下是創(chuàng)建內(nèi)核棧的代碼(Kernel 2.6.35 版本):
[c]static struct task_struct *dup_task_struct(struct task_struct *orig){struct task_struct *tsk;struct thread_info *ti;......ti = alloc_thread_info(tsk);......tsk->stack = ti;......}[/c]
內(nèi)核棧的結(jié)構(gòu)比較精巧,內(nèi)核使用一個(gè)聯(lián)合體來定義內(nèi)核棧:
[c]union thread_union {struct thread_info thread_info;unsigned long stack[THREAD_SIZE/sizeof(long)];};[/c]
其中thread_info中存放了進(jìn)程/線程(內(nèi)核不大區(qū)分進(jìn)程與線程)的一些數(shù)據(jù),其中包括指向task_struct結(jié)構(gòu)的指針。數(shù)組stack即內(nèi)核棧,stack占據(jù)8K/4K(依配置不同)空間,是這個(gè)聯(lián)合體的主要部分。
這樣,一個(gè)實(shí)際的內(nèi)核棧的結(jié)構(gòu)將如下圖所示。由于??偸怯筛叩刂废虻偷刂费由斓模詶5孜挥趖hread_union聯(lián)合體的最末端,而thread_info結(jié)構(gòu)則位于thread_union聯(lián)合體的開始處,而且所占用的空間比較少。只要不出現(xiàn)內(nèi)核棧特別大的極端情況,棧與thread_info可以互不干擾。
為什么要設(shè)計(jì)成這樣的結(jié)構(gòu)呢?原因就在于,使用這種結(jié)構(gòu)可以在系統(tǒng)進(jìn)入內(nèi)核態(tài)時(shí)很方便地取得當(dāng)前進(jìn)程的信息。如果不用這種方式的話,取得task_struct將是一個(gè)比較麻煩的事情。
不管系統(tǒng)因?yàn)槭裁丛蜻M(jìn)入內(nèi)核態(tài),最后都要切換到SVC模式做主要的異常處理。在進(jìn)入SVC模式時(shí),SP/R13寄存器所指向的位置就正好是當(dāng)前進(jìn)程的內(nèi)核棧。通過簡(jiǎn)單的對(duì)齊操作,就可以拿到thread_union即thread_info結(jié)構(gòu)的指針,從中又可以得到最重要的task_struct的指針,這個(gè)進(jìn)程的所有信息就都有了。
以下兩個(gè)函數(shù)即分別用于從SP寄存器取得當(dāng)前進(jìn)程的thread_info,以及進(jìn)一步取得task_struct結(jié)構(gòu)的內(nèi)容。
[c]static inline struct thread_info *current_thread_info(void){register unsigned long sp asm ("sp");return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));}static inline struct task_struct *get_current(void){return current_thread_info()->task;}[/c]
關(guān)于SP寄存器,這里有一個(gè)問題值得澄清一下,前面提到“在進(jìn)入SVC模式時(shí),SP/R13寄存器所指向的位置就正好是當(dāng)前進(jìn)程的內(nèi)核棧”,原因是什么呢?
在當(dāng)前進(jìn)程即當(dāng)前被異?;蛑袛嗨鶗和5倪@個(gè)進(jìn)程,是在上一次發(fā)生進(jìn)程調(diào)度(schedule())的時(shí)候被調(diào)入的,當(dāng)時(shí)在“上下文切換”(context_switch())完成的時(shí)候,當(dāng)前這個(gè)進(jìn)程可以說已經(jīng)被調(diào)入了CPU,系統(tǒng)當(dāng)時(shí)所處的模式也是SVC模式。當(dāng)進(jìn)程高度完成,CPU從SVC模式切換到USR模式時(shí)候,SVC模式下的SP寄存器已經(jīng)指向了當(dāng)前進(jìn)程的內(nèi)核棧。所以當(dāng)再次切換到SVC模式時(shí),進(jìn)程還是這個(gè)進(jìn)程,SP也還是指向這個(gè)內(nèi)核棧。
其實(shí)ARM處理器的每一種模式下都有自己獨(dú)立的SP/R13寄存器。當(dāng)CPU在不同的模式間切換的時(shí)候所看到的寄存器內(nèi)容都是不同的。Linux對(duì)于各種模式的使用策略是:SVC和USR兩種模式是可以穩(wěn)定工作的模式;在其它的模式下都是不穩(wěn)定的,會(huì)盡快切換到穩(wěn)定的模式去工作。在SVC模式下,SP寄存器總是指向內(nèi)核棧;在USR模式下,SP寄存器總是指向用戶棧;那么,其它模式下,SP又指向哪里呢?
其它模式下,Linux對(duì)于SP寄存器的維護(hù)很簡(jiǎn)單。在系統(tǒng)啟動(dòng)階段,cpu_init()函數(shù)會(huì)被調(diào)用,其中有對(duì)其它模式下SP寄存器的初始化操作:
[c]struct stack {u32 irq[3];u32 abt[3];u32 und[3];} ____cacheline_aligned;static struct stack stacks[NR_CPUS];void cpu_init(void){unsigned int cpu = smp_processor_id();struct stack *stk = &stacks[cpu];__asm__ ("msr cpsr_c, %1nt""add r14, %0, %2nt""mov sp, r14nt""msr cpsr_c, %3nt""add r14, %0, %4nt""mov sp, r14nt""msr cpsr_c, %5nt""add r14, %0, %6nt""mov sp, r14nt""msr cpsr_c, %7":: "r" (stk),PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),"I" (offsetof(struct stack, irq[0])),PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),"I" (offsetof(struct stack, abt[0])),PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),"I" (offsetof(struct stack, und[0])),PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE): "r14");}[/c]
可以看到,Linux為IRQABTUND3種模式的SP寄存器指定了相應(yīng)的棧,但是這個(gè)棧很小很小,只有12個(gè)字節(jié)。但這已經(jīng)足夠了,在中斷處理最初那部分相應(yīng)模式的代碼vector_XXX中,linux只保存r0, lr和spsr三個(gè)32位的數(shù)據(jù),這正好需要12個(gè)字節(jié)。
評(píng)論