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