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