總覺得作業系統是每一個電腦人在專業技術領域的終極目標,雖然這種觀點有失偏頗,但是對於一個電腦科班出身的人來說,掌握作業系統至少是基本功之一。
於淵的這本《Orange‘S一個作業系統的實現》是一本很不錯的作業系統書籍,本科課堂上講的都是那種理論的東西,做做題還是可以的,但是太抽象的東西終歸是不好的,尤其對於工科來說,工科就是要實踐。於淵的這本書,是教大家從頭開始寫作業系統的,從彙編到C,步步深入作業系統的基本原理,雖然唯讀了前面幾章,但是反覆讀下來,以前的疑惑漸漸散去,基本的概念漸漸落地生根,這是種很奇妙的感覺,一個龐大軟體的大幕漸漸拉開。
閑話少說,馬上進入正題,今天談談書中的第三章,關於保護模式的東西。
保護模式是前朝遺留的產物,用於淵的話說,從實模式到保護模式是一次改朝換代的過程,很形象,因為改朝換代大部分時間意味著進步。實模式是dos時代的產物,16位寄存器,16位元據線,32位的地址線,但是時代在召喚,CPU終究要進化,程式也終究是要變大,因此就對這個作業系統提出了更高的要求。
第一要明白的一點是不是說有了32位的CPU,原來的16位的就要全盤否定,就像蘇聯不能一邊倒地去否定史達林的模式。於是在這裡引入了實模式和保護模式,也就是說,實模式和保護模式的同時出現,就是一個文化的革故鼎新、兼收並蓄的過程。
作業系統的啟動過程是首先進入實模式,然後進入保護模式的,保護模式的一節剛開始,於淵就給出了一段跳轉的代碼。這段代碼乍看很暈,暈是好事,暈說明自己還不懂,如果能夠克服對未知的恐懼,那麼我們就能夠有所長進了。
在16位的程式碼片段中,作者初始化了全域描述符,全域描述符是在許多系統級的書籍裡面都會提到的概念,在這裡先不深究,我們只要記住這個東西是用來提供段式儲存機制的就行。回到上面提到的16位的程式碼片段,這個段中,所有的指令都是老系統的,也就是16位的,我們要在這個16位的系統中為將來的保護模式下的32位系統做準備,準備什嗎?準備全域描述符(GDT)。這一點是很好理解的,因為在32位系統中,基於段的儲存機制中,我們必須要藉助GDT,也就是說GDT在你進入保護模式的時候必須是成型的,是好的,是直接可以拿來用的,而我們現在開機後必須要經過16位的系統只是曆史遺留的問題,那麼在這個16位的階段,我們應該幹點什麼呢?答案是把32位系統需要的一些東西給準備好。於是有了這段代碼:
movax, csshleax, 4addeax, LABEL_SEG_CODE32movword [LABEL_DESC_CODE32 + 2], axshreax, 16movbyte [LABEL_DESC_CODE32 + 4], almovbyte [LABEL_DESC_CODE32 + 7], ah
這些語句的主要作用就是GDT的初始化,即聲明了本程式的程式碼片段在記憶體中的地址。我們已經知道GDT就是為了段儲存機制而設定的一種資料結構,那麼GDT表項中一個很重要的內容就是該段的在整個程式空間中的地址。好了這裡終於引入地址兩個字了,終於到了那些課本上的東西了,終於和自己平常做的題有關了,對,這裡從GDT中得到的段地址加上程式中提供的位移地址就是以前我們學過的線性地址,線性地址不是我們腦中地址鏈的開頭,開頭應該是邏輯地址,邏輯地址是什嗎?邏輯地址就是你查GDT表的輸入。好了,現在我們有點頭緒了,為了給大家一個直觀的解釋,我們畫張圖來表示。
好了,邏輯地址和線性地址大已經家知道他們的關係了,但是邏輯地址的值到底是什麼,線性地址的值到底是什麼呢?先給出答案吧,邏輯地址裡面是【選擇子:位移地址】,線性地址是【段地址:位移地址】。選擇子又是什嗎?我們好像進入了一個DFS似的,但是不要緊,考點各個擊破嘛,不要怕陌生的概念。選擇子是一個16位的結構,其中高13位表示的是本段(記住,現在我們已經升級了,開始以段來管理程式了)在GDT中的位移量,低三位表示的優先順序等量的設定,暫時不要管它們了。好了,現在我們明白了,所謂的邏輯地址,就是給出了本段在通用描述元表(GDT)中的位移量,並給出了在本段中的位移。好了,現在給你1分鐘,自己根據以上提供的知識來推理一下邏輯地址到線性地址的轉換吧。
sleep(1000);
理清楚了嗎?邏輯地址提供的是選擇子和位移地址,然後根據位移我們能夠找到GDT中本段的表項,GDT中儲存的是什嗎?是本段在整個程式空間的起始地址,也就是段地址,有了段地址,有了邏輯地址上的位移,這兩個量相加就叫線性地址---【段地址:位移地址】。再聯絡一下中所示的過程,看看是不是清晰很多。
好了,在這個過程當中我們略去了很多的細節,比如GDT的基址儲存在寄存器cr0中等等。下面就看看作者在保護模式一節中,是怎樣通過編程,讓我們更明白這種轉換的吧。
下面這段代碼和之前貼出的代碼一致,為了方便說明,又貼出一遍。
movax, csshleax, 4addeax, LABEL_SEG_CODE32movword [LABEL_DESC_CODE32 + 2], axshreax, 16movbyte [LABEL_DESC_CODE32 + 4], almovbyte [LABEL_DESC_CODE32 + 7], ah
上面這段代碼,出自在實模式下對GDT表的初始化過程。先說明一下,代碼中出現的四條mov指令中的後三條是對GDT表項的填充,其他的一些細節可以先不管。好了,我們看一下具體填充的內容:cs寄存器的內容,左移四位,然後加上32位程式碼片段的起始地址,然後將得到的這個地址存入相應的表項中。就這麼簡單的幾句話,完成了對一個表項的填充,恍然大悟:在實模式條件下,地址線的定址空間是1MB,為了湊夠20位的地址,CPU的處理方式正好是,CS左移四位,得到所謂的“段”,然後加上位移,如此得到的地址我們可以稱作是在16位條件下的線性地址。然後把這個地址填入,程式碼片段GDT的地址中,作為了32位程式碼片段的入口地址,也即段地址。
好了,就這樣,我們的段的初始化描述結束。然後再看看具體的是怎樣通過邏輯地址得到線性地址。
movax, SelectorVideomovgs, axmovedi, (80 * 11 + 79) * 2;moval, 'P'mov[gs:edi], ax
上面一段程式功能是向顯存中寫了一個字元P。細節忽略,看第一行,我們已經知道邏輯地址提供的是【選擇子:位移地址】,因此第一、二兩行乾的活就是把顯存的選擇子載入到gs寄存器當中,然後在第三行確定了edi,也就是在位移地址,第四行是把字串P寫入了寄存器al,最後一行是有貨的一行,它是把al寄存器中的內容,寫進了【gs:edi】指向的記憶體中。我們可以認定在保護模式下,【gs;edi】在編譯的時候,完成了邏輯地址對線性地址的變換。也就是說,在保護模式下,CPU會通過gs中的內容,找到GDT中的表項,然後得到段基址,與位移地址相加得到線性地址。
好了,至此邏輯地址與線性地址解釋完畢,物理地址與線性地址的關係以及更多保護模式的內容會在接下來的日誌中一一講述。