通過IRQL看NT內核
linux強調的是進程自主性,windows則是對象自主性,其中線程本身也是一個對象,進程也是,所以一個進程可以操作另外一個進程的地址空間也就不足為奇了,windows的通信實際上是對象間通信,而linux因為一切圍著進程轉,最新的內核中斷也被線程化了,因此通信就是進程間通信,linux 中進程作為超級容器的意義要比windows的更大些,windows中進程是一個容器,也是一個對象,某種意義上它作為容器的意義是容納別的對象。 windows的模塊化思想更加鮮明。
windows中將缺頁,調度等概念從線程,進程中分離,專門安排一個irql級別來處理之,而linux下相應的概念則永遠和進程相綁定,看看缺頁中斷的處理代碼,考慮的一直都是進程的概念,在windows中,考慮的就是對象的概念了,比如文件對象等等,但是缺頁本質上真的是和進程相關的,所以在 windows中沒有進程/線程上下文的的執行緒當然就不能缺頁了,那么誰沒有進程/線程上下文呢(也就是ms說的“任意上下文”)?答案是 dispatch_level之上(包括dispatch)的沒有進程/線程上下文,所以,相應級別的執行中就不能訪問分頁內存(可能被換出而導致缺頁的內存)了。windows下進程/線程是和其他對象并列的概念,沒有什么特殊之處,但是在linux(unix)中,它們卻是終極重要的概念,貫穿整個系 統。windows為了實現完全異步和為了配合可移植性,將一切抽象成了“中斷”,所以我們沒有必要特別說什么上下文的概念,上下文只是一個進程/線程的 容器,沒有又如何,代碼照樣執行,之所以強調什么時候要有上下文什么時候沒有必要完全是為了配合進程/線程的,為了使進程/線程好管理。
我比較喜歡將一大堆的結果歸結為一個原因,就像一切宏觀運動都是牛頓定律的結果,一切微觀運動都是量子力學的結果,一切宇觀運動都是相對論的結果一樣,從這一點上看,研究windows是一個好的選擇。
我們看一下兩個操作系統的缺頁,先說linux。
在linux中,發生缺頁后執行缺頁中斷處理,當時的上下文還是引起缺頁的進程線程上下文,除非當前在中斷或軟中斷中,然后內核掛起當前線程,執行磁盤 io,將頁面取回或者分配一個匿名頁面,實際上這里掛起當前線程的意思是執行的缺頁中斷處理并不屬于當前線程和在磁盤io或分配匿名頁面的過程中可能要等 待,等待意味著線程切換,一直到線程實際切換,上下文一直是當前線程上下文。 在前面說的中斷或軟中斷中出現缺頁的話就不能隨便進行線程掛起了,因為此時是linux唯一可能在任意上下文的情況,即使在內核發生缺頁也沒有關系,缺頁 處理只認上下文,不管在什么態(因為內核數據結構常常在中斷中被訪問,所以,一般還是別在內核用分頁內存了,所以linux又規定,內核數據結構從 slab或類似的連續區分配分配,并且常駐內存,你完全可以修改內核,在內核分配一個頁面,映射到內核空間或用戶空間,然后修改缺頁處理程序,使得可以處理映射到內核導致的缺頁,最后保證你永遠不在中斷中訪問這個地址,很麻煩,但可能!),所以linux硬性規定:中斷不能引起缺頁(原因僅僅一個,上下文不確定)。(附:本質意義上,內核空間并不屬于任何進程的地址空間,只是一個所有進程和資源的管理空間,所以內核空間大家公用,所以linux很是巧妙的將物理內存線性映射到內核地址空間,內核數據結構大部分在此分配,一旦滿足不了要求,可以從vmalloc區域分配,并且只更新0號進程頁表,由缺頁中斷 來解決別的進程頁表和0號進程頁表同步問題,所以linux內核空間地址的中斷并不需要真正分配內存頁面,只需要修改個頁表就行。linux內核空間地址不許缺頁并沒有實質性的規定,只是為了內核開發的方便,所以linux僅僅解決用戶空間的缺頁和內核vmalloc頁表缺頁。)回到前面正常情況,在有上 下文的情況下進行磁盤io或分配匿名頁以及可能引起的當前線程睡眠切換有問題嗎?一點問題也沒有,因為linux/unix盡量使所有執行緒都有特定上下 文,不允許的操作作為稀有的特例已經被硬性明文禁止了,這樣的弊端在于,留給程序員的只有好好把這個原理研究透或者把那些規定背熟了。在windows中 的情況呢?上下文沒有那么特殊了,一切都是異步的中斷,很多執行緒沒有特定上下文,那么發生缺頁后呢?可能缺頁發生在用戶線程,而用戶線程運行在被動中斷請求級別,這是沒有任何問題的,所有別的級別的執行緒都可以中斷它,最重要的,io開始例程在dispatch級別運行,缺頁處理運行在 dispatch,調度也運行在dispatch。首先,為何調度在dispatch運行呢?因為在dispatch或之上的執行緒就是任意上下文環境 了,它們是不允許發生線程切換的,這怎么保證呢?如果在dirql級別的要切換,那么就要將irql提升到dispatch,因為當前dirql比 dispatch高,于是藍乎,如果在dispatch的dpc要調度,那么它要提升irql到dispatch,因為它已經在了,于是亦藍乎,這就是原 因,調度運行在dispatch實際上是為了保證沒有上下文的執行緒不能發生線程切換(類比linux,linux中也是沒有上下文的不能切換);那么 io開始為何也在dispatch呢?這是因為windows是異步的,一個線程發起一個io不一定在當前上下文就可以執行,可能在一個硬件中斷發生后有 了執行的條件(考慮irp排隊),然后此中斷派發一個dpc開始io動作,中斷沒有確定上下文,它派發的dpc同樣沒有上下文,所以io開始只能在沒有上 下文的dispatch執行;io分派例程比如read,write之類的必須在有上下文的被動級別運行,因為它們可能要睡眠,而且不能打擾io開始和完成例程;現在考慮缺頁,看看它能在哪里執行,咱們從低irql到高考慮,我們假設被動irql也參與到中斷模擬,如果缺頁處理運行在最低的被動irql, 那么就完了,試想一個同樣在被動irql的線程發生缺頁,缺頁運行前必先提升irql到被動級別,但是不巧,已經是被動級別了,出錯,返回,藍!但是事實上,被動級別并不參加中斷模擬,這說明缺頁處理運行在被動級別是可以的,但是考慮apc級別,它可使參與中斷模擬了,apc比被動級別高,于是如果缺頁處 理在被動級別,那么apc就不能缺頁了,這說明apc不能用分頁內存,這很影響大家伙的信心,windows怎么能這么限制程序員呢;那么我們就將缺頁處理運行級別往上提高一級,到apc級別,還是不行,apc還是不能缺頁,因為它就是在apc級別的,虛擬中斷只能被irql比它高的中斷掉;于是再往上 走,到dispatch吧,這下好了,apc可以使用分頁內存了,但是提升到這個級別僅僅為了讓apc使用分頁內存,如果又引起別的問題,那么還是別讓 apc使用分頁內存了。考慮缺頁需要哪些操作,無非就是分配內存頁面,或者從磁盤讀取頁文件,考慮后者,肯定要開始一個磁盤io過程,而磁盤io就是在 dispatch進行的,這可以,考慮前者,分配頁面可能導致線程睡眠,現在缺頁正在dispatch運行,想掛起一個線程也沒有問題,正好調度也是在這個級別運行,仍然沒有問題,現在我們可以說缺頁處理可以在這個級別了,但是,人是貪心的,一些限制總是使人能有所創新,只可惜,這次的創新在 windows完美主義下失敗了,我們不甘心dispatch以及之上的內存不能使用分頁內存,于是再往上提一級,提到哪級姑且不考慮,如果 dispatch這種沒有上下文的級別產生缺頁就有可能睡眠或請頁,但是io開始和調度都是確定性的在下面的級別運行,于是不能再往上了,就這樣了,缺頁中斷處理程序只能在dispatch級別運行。
這下有意思吧,windows的大框架已經定死,不需要硬性規定,一兩條原則就可以決定這么多事情:1.中斷虛擬化的irql機制使windows成為完全異步os內核;2.對象化模塊化使windows不過分依賴于進程/線程的概念,使得1的實現簡單化。網上很多人比如赫赫有名的毛德操教授說 windows的進程可以操作別的進程空間是windows不安全的原因之一,我認為這句話沒錯,但是毛教授似乎沒有理解windows為何這么做,它不安全是因為細節沒有把握好,而大的框架我很欣賞,也很堅信,沒有錯!linux的軟中斷和任務隊列就是windows的借鑒,只可惜,linux是善變 的,發現工作隊列后毅然拋棄了任務隊列。
linux更像是一件藝術作品而windows更像一件工業產品。舉個例子:一個高大的全鋼架結構建筑矗立在廣場,看似永遠都沒有完過工,巨大的鋼架抬頭 可見,耗資巨大,周圍只有鋼架,沒有封頂,人在里面難免風吹雨淋,人們會在底樓租一間蓋好的屋子,但難免還會被漏下來的建筑石料將屋頂砸破,頭破血流,目前開發商大勢已去,真不知能否完工,但是框架結構絕對一流,缺的不是技術,而是激情;在郊外的草地上,幾個滿懷激情的人在自己修建一座別墅,沒有開發商, 沒有人觀望,只有他們自己,別墅異常豪華,沒有什么框架,因為不需要,他們有技術,有激情,缺的是大家的支持。前者是windows,后者是linux, 而unix可能就是金字塔吧。
現在的硬件也在提供更多的功能,從而使一些軟件策略更加容易實現,比如apic就實現了軟中斷功能,這就更加使得windows的irql如魚得水了,我認為linux馬上也將用最新的apic軟中斷功能來實現softirq,這一向是linux的作風:絕不落伍!
感嘆:現在的硬件也在越來越快的發展了,快趕超軟件發展速度了,看,什么都在封裝,以前用引腳傳遞硬件信息,現在用消息了(看看pci和pcie吧),硬 件和軟件都在兩極分化,一幫人搞的東西越來越簡單,越傻瓜,另一幫人卻越來越高深,越深不見底!嗚呼,我,何去何從?!