手機微網站開發(fā)教程怎樣優(yōu)化網站關鍵詞排名靠前
目錄標題
- 信號的一些概念
- 信號的保存
- pending表
- block表
- handler表
- 信號的捕捉
- 內核態(tài)和用戶態(tài)
- 信號的捕捉
信號的一些概念
1.進程會收到各種各樣的信號,那么程序對該信號進行實際處理的動作叫做信號的遞達。
2.我們之前說過當進程收到信號的時候可能并不會立即處理這個信號,而是先將信號進行保存,那么我們把信號從產生到抵達之間的狀態(tài)稱為信號未決。
3.進程可以阻塞某個信號,這樣即使發(fā)送信號給對應的進程進程也不會做出任何的處理。
4.被阻塞的信號會一直保持在未決的狀態(tài),只有當進程解除了對此信號的阻塞時才會執(zhí)行該信號的動作。
5.阻塞和忽略是兩個不同的東西,一個信號要是被阻塞說明即使收到了該信號也不會執(zhí)行該信號的動作,而忽略是收到該信號之后根據(jù)該信號執(zhí)行的一個動作,只不過該動作是忽略也就是什么都不做而已。
信號的保存
之前的學習中我們知道操作系統(tǒng)是要給每個進程保存收到的信號的,而保存的位置就是task_struct中的一個位圖,這是我們當時說的保存方法,但是信號存在三種狀態(tài)比如說是否收到了信號,是否阻塞了信號,以及信號與之對應的動作以及動作是否被自定義修改了,所以內核使用一張圖來保存信號肯定是不夠用的,所以在實際的應用中使用了三張表一起來保存了信號,這三個表分別為:pending表,block表和handler表,那么接下來我們來一一介紹這三個表的功能。
pending表
pinding表本質上就是位圖,我們之前說進程在運行的任何時候都會收到信號,這個信號并不會被立即處理,所以進程得保存這個信號,保存信號的方式是位圖,那么這個位圖就是pending表,位圖的比特位位置表示哪個信號,比特位的值表示是否收到了該信號,如果收到了信號就將對應位置的值修改為1,如果沒有收到信號對應位置的值就是0.
block表
block表也是一個位圖,位圖的位置表示信號編號,位圖的值表示是否阻塞了對應的信號,被阻塞的信號不會被遞達,只有當阻塞被解除了信號才會被遞達,即使沒有收到對應的信號我們也是可以阻塞一些信號的,這里的邏輯可以用下面的偽代碼來表示:
if(1<<(signo)&pcb->block))
{//信號是被阻塞的,不遞達
}
else
{if((1<<(signo-1))&pcb->pending){//遞達該信號}
}
那這里就可以這么來理解,操作系統(tǒng)會掃描PCB中的位圖來判斷是否有信號需要被處理,如果信號對應在block上的值為1就直接跳過,如果對應在block上的值為0就接續(xù)查看對應在panding位圖上的值,如果值為1的活就執(zhí)行的對應的方法,那么這就是block表的功能。
handler表
handler表是一個函數(shù)指針數(shù)組,每個元素都是一個函數(shù)指針,數(shù)組的位置(下標)表示信號的編號,數(shù)組下標對應的內容就表示對應信號的處理方法,signal函數(shù)可以修改信號對應的方法,那么這個本質上就是修改handler表對應元素的指向,當信號被遞達時就可以根據(jù)信號的值來找到handler表中處理信號的方法并執(zhí)行該方法,那么這里的圖片就是下面這樣:
所以看到這里我們可以得出三個結論:如果一個信號沒有產生的話,并不妨礙他可以先被阻塞,因為我們只需修改block表中的值跟記錄是否收到信號的pending表沒有關系,如果阻塞的過程中收到了信號,那么我們就修改pending位圖上對應的值并不會接著去找handler表中的方法 2.進程為什么能夠識別信號,是因為內核中存在三個表這三個表能讓進程認識信號并處理對應的信號。 3.如果SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數(shù)sighandler。如果在進程解除對某信號的阻塞之前這種信號產生過多次,將如何處理?POSIX.1允許系統(tǒng)遞送該信號一次或多次。Linux是這樣實現(xiàn)的:常規(guī)信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個隊列里。
信號的捕捉
信號產生的時候不會立即被處理,而是在合適的時候被處理,那這個合適的時間是什么時候呢?并且我們上面說到操作系統(tǒng)會在某些時刻檢查block表和pending表,那這個時刻又是指的什么時候呢?那么要想解決這些問題我們就得談談什么是內核太什么是用戶態(tài)。
內核態(tài)和用戶態(tài)
我們自己寫的代碼經過編譯和運行之后都是運行在用戶態(tài)上的,但是在我們在編寫代碼的時候難免會訪問到操作系統(tǒng)的資源(getpid,waitpid)和硬件資源(printf,read,write),當我們訪問操作系統(tǒng)的資源或者硬件資源的時候,我們都得直接或者間接的訪問系統(tǒng)提供的借口,通過這些接口來訪問操作系統(tǒng)的資源,我們把這些接口稱為系統(tǒng)調用
調用系統(tǒng)調用的時候,普通用戶不能以自己的身份來調用系統(tǒng)調用,必須將自己的身份變?yōu)閮群藨B(tài),
這就好比當我們是一個公司的基層時副總裁的辦公室我們幾乎不能直接進出,但是當我們的身份變成了公司的CEO時,副總裁的辦公室我們就可以隨意的進出,所以當我們的身份變成了內核態(tài)時我們才能夠有資格使用或者訪問內核態(tài)提供的代碼,實際執(zhí)行系統(tǒng)調用的人是進程,但是身份是內核態(tài),因為用戶態(tài)調用內核態(tài)的時候要發(fā)生身份的轉換,所以系統(tǒng)調用往往比較費時間一些,所以要盡量避免頻繁的調用系統(tǒng)調用
但是這里存在一個問題,你說進程只是那一個進程,但是卻存在兩個身份,那該操作系統(tǒng)是如何來實現(xiàn)這一點的呢?那么這里我們就得提到寄存器這個東西
cpu中存在大量的寄存器,寄存器分為兩種:1.可見寄存器,2.不可見寄存器,雖然寄存器分為兩種但是只要和當前進程強相關的寄存器我們都將其稱為上下文數(shù)據(jù),比如說cpu中存在一個寄存器專門用來指向當前進程的pcb這樣就可以快速的知道當前運行的進程是哪個,再比如說有個寄存器中存儲的是當前頁表的起始地址,這樣就可以快速的找到當前進程的頁表,
cpu中還存在一個名為CR3的寄存器,這個寄存器中的內容表征當前進程的運行級別,0表示內核態(tài),3表示用戶態(tài),所以想要判斷當前的進程是用戶態(tài)還是內核態(tài)就只需要查看CR3里面的內容就行,這里存在一個問題:我是一個進程,進程是怎么跑到內核中執(zhí)行對應的方法呢?之前我們提到的進程地址空間往往指的是用戶空間,我們寫的代碼存放或者申請空間都是通過用戶級地址空間+頁表來進行映射,這個空間的大小為3GB
這里的頁表是用戶級頁表該頁表每個進程都有一份,但是地址空間的總大小是4GB,用戶級空間只占了3GB那剩下的1GB空間用來干什么呢?我們把這個1GB的空間稱為內核級空間,這一塊空間也對應了一個頁表,這個頁表就叫內核級頁表,這個頁表的作用就是將操作系統(tǒng)級別的代碼從內核虛擬地址空間映射到內存上面,
因為操作系統(tǒng)的代碼只有一份,在機器啟動的時候會將這份代碼加載到內存上,并且每個進程都要使用操作系統(tǒng)代碼,所以內核級頁表只有一份,每個進程都通過這個頁表來映射找到內存上的操作系統(tǒng)的代碼,每一個進程都有自己的地址空間(用戶空間獨占),內核空間(被映射到了每個進程的地址空間的3-4G),所以進程要想訪問操作系統(tǒng)的接口,其實只需要在自己的地址空間上進行跳轉即可,由用戶區(qū)跳轉到內核區(qū)然后再由用戶級頁表跳轉訪問內存上的內核代碼即可,每一個進程的進程地址空間都有3-4GB的內容,都會共享一個內核級頁表,所以無論進程如何切換都不會修改進程地址空間中3-4GB的內容。那用戶憑什么能夠執(zhí)行并訪問內核的接口或者數(shù)據(jù)呢?原因是當操作系統(tǒng)發(fā)現(xiàn)你要訪問內核的數(shù)據(jù)和代碼時,會幫進程修改CR3寄存器的內容,這樣就從用戶態(tài)變成了內核態(tài)那么這時就有權利訪問內核的數(shù)據(jù),那操作系統(tǒng)為什么能知道我們要訪問內核的數(shù)據(jù)和代碼呢?答案是當我們要訪問操作系統(tǒng)的接口時最開始還是用戶態(tài)執(zhí)行的,當以用戶態(tài)執(zhí)行系統(tǒng)調用接口時,系統(tǒng)調用接口做的最多的事情還是講寄存器中的3號狀態(tài)修改成為0號狀態(tài)也就是將用戶態(tài)改成內核態(tài),然后才跳轉區(qū)域訪問內核的數(shù)據(jù)和代碼,那么這就是用戶態(tài)和內核態(tài)的概念
信號的捕捉
我們說操作系統(tǒng)會在合適的時候處理信號,那么這個合適的時候就是從內核態(tài)返回用戶態(tài)的時候對信號進行處理,那么這就說明我們之前一定先進入了內核態(tài),
當執(zhí)行系統(tǒng)調用和進程切換(一個進程沒有執(zhí)行完,那一定是放到運行隊列或者等待隊列,那放進隊列的時候一定是以操作系統(tǒng)的身份把我放了進去,所以得先變成內核態(tài) 就會變成內核態(tài))
當操作系統(tǒng)進入內核態(tài)并且馬上要返回普通狀態(tài)的時候還會做一件事就是檢查內核中的三張表,
如果block中的信號為0,pending中的信號也為0就會接著檢查下一個信號,當block中的值為1就算pending中的值為1的話也不會執(zhí)行該信號,當block的值為0并且penging的值為1的話就會執(zhí)行該信號,然后就去handler中查找對應的方法,處理的方法有默認,忽略,自定義,默認的方法中有70%是終止進程,因為當前的身份是內核態(tài)所以可以很輕松的終止當前進程,忽略就是要處理該信號,但是處理的動作是什么都不做,所以直接將pending對應位置的信號設置為0就行,自定義的方法的實現(xiàn)在用戶態(tài),那這里就存在一個問題:我們能不能以內核態(tài)的身份來執(zhí)行用戶的代碼呢?從技術的角度上我們可以以內核態(tài)的身份訪問用戶的代碼,但是從設計的角度上來看是不能的,因為操作系統(tǒng)不相信任何人,handler方法中可能會有對系統(tǒng)造成危險的操作,而這些操作操作系統(tǒng)是無法識別是安全還是有害的,所以當我們以內核態(tài)的身份執(zhí)行用戶的代碼的話,這部分代碼可能會被惡意分子利用,然后進行一部分越權的非法動作,所以即使從技術上的角度能這么干,操作系統(tǒng)也不讓你這么干,所以當要執(zhí)行自定義代碼的時候操作系統(tǒng)得通過特定的調用,將自己的身份從內核態(tài)轉換稱為用戶態(tài),然后再執(zhí)行自定義代碼,那么這個時候自定方法出現(xiàn)了問題就是用戶自己的問題了,這個時候想干什么壞事情就會收到操作系統(tǒng)的管制了
執(zhí)行完自定義方法之后也不能直接返回之前調用方法的地方,因為我們無法知道當初是從哪里進行跳轉的,不能從用戶態(tài)的一個地方跳轉到另外一個地方,這里必須得有操作系統(tǒng)的支持,因為在從用戶態(tài)跳轉到內核態(tài)的時候操作系統(tǒng)保存了你的上下文信息,所以執(zhí)行完自定義方法后得從用戶態(tài)再跳轉回內核,再通過特定的系統(tǒng)調用由內核態(tài)回到用戶態(tài)上次的地方再繼續(xù)向后執(zhí)行
那么這就是信號捕捉的全部流程希望大家能夠理解。