內核在創(chuàng )建進(jìn)程的時(shí)候,在創(chuàng )建task_struct的同時(shí),會(huì )為進(jìn)程創(chuàng )建相應的堆棧。每個(gè)進(jìn)程會(huì )有兩個(gè)棧,一個(gè)用戶(hù)棧,
存在于用戶(hù)空間,一個(gè)內核棧,存在于內核空間。記住,進(jìn)程對應的用戶(hù)棧和內核棧都是進(jìn)程私有的。當進(jìn)程在用戶(hù)空間
運行時(shí),cpu堆棧指針寄存器里面的內容是用戶(hù)堆棧地址,使用用戶(hù)棧;當進(jìn)程在內核空間時(shí),cpu堆棧指針寄存器里面
的內容是內核??臻g地址,使用內核棧。
注:有些系統中專(zhuān)門(mén)為全局中斷處理提供了中斷棧,但是x86中并沒(méi)有中斷棧,中斷在當前進(jìn)程的內核棧中處理。
2、linux中有多少個(gè)內核棧
在/include/linux/sched.h中定義了如下一個(gè)聯(lián)合結構:
union task_union {
struct task_struct task;
unsigned long stack[2048];
};
每個(gè)進(jìn)程在創(chuàng )建的時(shí)候會(huì )在內核空間連續分配兩個(gè)page即8K的數據用來(lái)保存進(jìn)程結構(task_struct),這個(gè)進(jìn)程結
構大概有1K左右,剩下的7K用作該進(jìn)程的內核棧(寫(xiě)中斷程序的時(shí)候不要用什么遞歸,大的局部變量)。 也就是說(shuō),
除了每個(gè)進(jìn)程都有一個(gè)用戶(hù)棧之外,同時(shí)都有一個(gè)系統空間棧。
實(shí)際上,進(jìn)程的task_struct結構所占的內存是由內核動(dòng)態(tài)分配的,更確切地說(shuō),內核根本不給task_struct分配內
存,而僅僅給內核棧分配8K的內存,并把其中的一部分給task_struct使用。
2.1、首先要搞清楚linux的調度機制,在內核態(tài)時(shí)是不會(huì )發(fā)生調度的;
2.2、進(jìn)入內核態(tài)與返回用戶(hù)態(tài)對堆棧的使用是平衡的:
在進(jìn)程從用戶(hù)態(tài)轉到內核態(tài)的時(shí)候,進(jìn)程的內核??偸强盏?。這是因為,當進(jìn)程在用戶(hù)態(tài)運行時(shí),使用的是用戶(hù)
棧,當進(jìn)程陷入到內核態(tài)時(shí),內核棧保存進(jìn)程在內核態(tài)運行的相關(guān)信息,但是一旦進(jìn)程返回到用戶(hù)態(tài)后,內核棧中保存
的信息無(wú)效,會(huì )全部恢復,因此每次進(jìn)程從用戶(hù)態(tài)陷入內核的時(shí)候得到的內核棧都是空的。
2.3、linux把堆棧與task_struct放在一起,并用簡(jiǎn)單操作得到current指針(esp & 8191UL),這在共享堆棧情況下
是不允許的,也無(wú)法區分是哪一個(gè)進(jìn)程;
2.4、進(jìn)程的獨立性:如果一個(gè)進(jìn)程(中斷,調用)的時(shí)候由于某種原因(中斷處理程序寫(xiě)的不對)使堆棧不平衡了,那就會(huì )影
響整個(gè)系統,而如果堆棧是獨立的,那只會(huì )影響此進(jìn)程,大不了把它kill掉。這就像linux的設計:用戶(hù)與內核分的非常清楚,好
理解也更強壯,你死你的,不關(guān)別人的事。
3、進(jìn)程用戶(hù)棧和內核棧的切換
當進(jìn)程因為中斷或者系統調用而陷入內核態(tài)時(shí),進(jìn)程所使用的堆棧也要從用戶(hù)棧轉到內核棧。進(jìn)程陷入內核態(tài)后,首先把
用戶(hù)態(tài)的堆棧地址保存在內核堆棧中,然后設置堆棧指針寄存器的地址為內核棧地址(CPU從任務(wù)狀態(tài)段TSS中裝入內核棧指
針esp),這樣就完成了用戶(hù)棧向內核棧的轉換; 當進(jìn)程從內核態(tài)恢復到用戶(hù)態(tài)之行時(shí),在內核態(tài)之行的最后將保存在內核棧
里面的用戶(hù)棧的地址恢復到堆棧指針寄存器即可。這樣就實(shí)現了內核棧和用戶(hù)棧的互轉。
那么,我們知道從內核轉到用戶(hù)態(tài)時(shí)用戶(hù)棧的地址是在陷入內核的時(shí)候保存在內核棧里面的,但是在陷入內核的時(shí)候,我們
是如何知道內核棧的地址的呢?
關(guān)鍵在進(jìn)程從用戶(hù)態(tài)轉到內核態(tài)的時(shí)候,進(jìn)程的內核??偸强盏模ɡ碛梢?jiàn)上面的2.2)。所以在進(jìn)程陷入內核的時(shí)候,直接
把內核棧的棧頂地址給堆棧指針寄存器就可以了。
4、用戶(hù)態(tài)、內核態(tài)之間的共享
4.1、我們知道linux的虛擬地址空間是內核態(tài)使用3G以上的高地址空間,那么所有的用戶(hù)進(jìn)程是如何共享這一個(gè)內核空間的呢?
Linux系統中的init進(jìn)程(pid=1)是除了idle進(jìn)程(pid=0,也就是init_task)之外另一個(gè)比較特殊的進(jìn)程,它是Linux內核開(kāi)始
建立起進(jìn)程概念時(shí)第一個(gè)通過(guò)kernel_thread產(chǎn)生的進(jìn)程,其開(kāi)始在內核態(tài)執行,然后通過(guò)一個(gè)系統調用,開(kāi)始執行用戶(hù)空間的
/sbin/init程序,期間Linux內核也經(jīng)歷了從內核態(tài)到用戶(hù)態(tài)的特權級轉變,/sbin/init極有可能產(chǎn)生出了shell,然后所有的用戶(hù)
進(jìn)程都有該進(jìn)程派生出來(lái)。而linux采用2級頁(yè)表(1K x 1K x 4K),頁(yè)目錄的1/4(3G/4G)即256B是屬于內核的;所以創(chuàng )建用戶(hù)
進(jìn)程時(shí)會(huì )復制init進(jìn)程的這256B的頁(yè)目錄以及后面的一級、二級頁(yè)表,也即實(shí)現了內核空間的共享。
4.2、一個(gè)進(jìn)程在內核態(tài) 可以直接通過(guò)虛擬地址訪(fǎng)問(wèn)其他進(jìn)程內核態(tài)的數據,因為他們是一個(gè)頁(yè)表。
一個(gè)進(jìn)程在內核態(tài) 不可以直接通過(guò)虛擬地址訪(fǎng)問(wèn)其他進(jìn)程的用戶(hù)態(tài)的數據,因為他們不使用同一個(gè)頁(yè)表。 4.3、由于系統中只有一個(gè)內核實(shí)例在運行,因此所有進(jìn)程都映射到單一內核地址空間。內核中維護全局數據結構和每個(gè)進(jìn)程的
一些對象信息,后者包括的信息使得內核可以訪(fǎng)問(wèn)任何進(jìn)程的地址空間。通過(guò)地址轉換機制進(jìn)程可以直接訪(fǎng)問(wèn)當前進(jìn)程的地址空
間(通過(guò)MMU),而通過(guò)一些特殊的方法也可以訪(fǎng)問(wèn)到其它進(jìn)程的地址空間。
4.4、內核態(tài)與用戶(hù)態(tài)的交互
舉個(gè)特例:當系統調用的參數超過(guò)6個(gè)時(shí),將借助寄存器將所要傳遞給內核的參數包裝成一個(gè)結構體,并將結構體指針?lè )诺?/span>
指定寄存器。