《操作系統真象還原》讀書筆記 第0章

0x1 軟件訪問硬件的方法

軟硬件之間的訪問是依賴於各種硬件設備,也就是IO接口。接口就是生產硬件的標準,所有硬件必須按照這個標準才能讓軟件和硬件互通。
硬件在輸入輸出上分爲並行和串行兩種方式,相應接口也就是串行和並行接口。串行硬件通過串行接口與CPU通信,CPU通過串行接口與串行設備數據傳輸。並行同理,只有接口不同的差異。
訪問外部硬件的兩種方式:
1、將某個外設的內存映射到一定範圍的地址空間中,CPU通過地址總線訪問該區域時會自動轉移到外設的內存中,這種映射讓CPU訪問外設內存就如通訪問主板上的物理內存一樣。這種訪問外設的例子:顯卡,顯卡是顯示器的適配器,CPU不直接與其進行交互,它只與顯卡通信。顯卡上有片內存叫顯存,它被映射到主機物理內存上的低端1MB的0xB8000~0xBFFFF。CPU訪問這片內存就是訪問顯存。往這片內存上寫子就是往屏幕上打印內容。
2、外設是通過IO接口與CPU進行通信的,CPU訪問外設,就是訪問IO接口,由IO接口將信息傳遞給另一端的外設。CPU只管向指定外設收發數據,IO接口會根據數據進行處理,將CPU傳遞的數據處理成外設程序可識別的數據進行響應。IO接口本質就是寄存器。寄存器本質就是一組8位電路。(PS:閱讀《0x86從實模式到保護模式》可知)

0x2 應用程序與操作系統是如何配合到一起的

應用程序是需要藉助編程語言編寫,而語言又依賴於編譯器爲其抓換成機器碼。所以根本沒有語言,有的只是編譯器。編譯器決定怎麼解釋編程語言的關鍵字及某種語法。語言只是編譯器和大家的約定,只要寫入這樣的代碼,編譯其便將其翻譯成某種機器指令,翻譯成什麼樣取決於編譯器行爲,和語言無關。比如:printf函數,如果其他非c/c++編譯器進行編譯的話,完全可以把這個關鍵子的內容編譯成響鈴,換行等機器碼,可以不編譯成把指定字符顯示到屏幕上。

0x2.1 程序語言運行庫

編譯器提供了一套庫函數,可函數中又有封裝的系統調用,這樣的代碼合集稱之爲運行庫。C語言的運行庫稱爲C運行庫,就是所謂的CRT。(所以說C語言早於操作系統是不正確的,因爲C語言也依賴於操作系統提供的系統調用。Linux下的CRT有Linux的系統調用,Windows下有對應Windows的系統調用。)

0x2.2 什麼是應用程序

應用程序應用程序加上操作系統所提供的功能纔算是完整的程序。由於有了操作系統的支持,操作系統才能正常運行,我們平時編程寫的都是“半成品”,需要調用操作系統提供好的函數才能完整的做成一件事,而這個函數便是系統調用。

0x2.3 什麼是用戶態和內核態

用戶態和內核態是CPU的機制,跟操作系統無關。是指CPU運行在用戶態(特權等級3)還是內核態(特權等級0),跟操作系統和應用程序都沒關係。
用戶態進程陷入內核態是指:用內部或外部中斷髮生,當前進程被暫時終止執行,其上下文被內核的中斷程序保存起來後,開始執行一段內核代碼。是內核的代碼,不是用戶程序在內核的代碼,用戶程序的代碼不可能在內核中。
當應用程序陷入內核後,他自己已經下CPU了,以後發生的事,應用程序完全不知道,它的上下文環境已經被保存到了自己的0特權級棧中了,那時在CPU上運行的程序已經是內核程序了。用戶進程永遠不會因進入內核態而變身爲操作系統。

0x3 爲什麼稱爲“陷入”內核

應用程序處於特權級 3,操作系統內核處於特權級 0。當用戶程序與訪問系統資源時(無論是硬件,還是內核數據結構),它需要進行系統調用。這樣CPU便進入了內核態,也稱管態。

0x4 內存訪問爲什麼要分段

首先,分段式CPU使用訪問內存的機制,只有CPU才關注段。而且分段機制是歷史遺留問題。CPU從8086開始的,限制於技術和經濟,CPU和寄存器都是16位的。那時的計算機只有物理地址,沒有虛擬地址,編譯器編譯出來的都是絕對物理地址,但是如果編譯出來的兩個程序的加載地址相同或者有重疊則這兩個程序只能有一個運行。爲了解決這個問題,有人提出了讓CPU採用:段基地址+段內偏移地址的方式訪問任意內存,這樣即使程序物理地址偏移重複也可以根據修改段基地址進行重定位。這樣就可以運行多個程序了。
內存是隨機讀寫設備,也就是說只要給定內存地址就可知直接找到該地址位置,不需要從0開始。如訪問內存0xC00,只要將此地址寫入地址總線便可。

0x5 代碼分段

軟件程序段一般是由編譯器分配的,也有的是程序員自己進行劃分。在多段模型下需要分配多個段,然後不斷地切換段寄存器指向的段寄存器指向的段才能訪問到不同段中的數據。(其實多段模型是16位彙編設計的,因爲訪問地址有限纔有段基址)
在平坦模式下,段寄存器可以指向4GB空間,就沒有必要採取多段模型切換段寄存器內容進行訪問數據。所以對於代碼是否分段取決於操作系統是否在平坦模式下。
CPU是一個高度集成化的芯片,只要給出CPU第一條指令的起始地址,CPU在執行它指令的同時會自動獲取下一條指令。而且被讀取的指令必須被要求沒有空隙,下條指令的地址是按照前面指令的尺寸大小排下來的,這就是Intel處理器的程序計數器cs:eip能過自動獲得下一條指令的原理。
爲了讓程序內指令接連不斷地執行,要把所有指令排在一起,形成一片連續的指令區,這就是代碼段。指令是由操作碼和操作數組成的,操作數就是程序中的數據。把數據連續地排在一起存儲形成的段落,就稱爲數據端。
數據端和代碼段的屬性是由CPU、操作系統和編譯器共同作用的結果:
1)編譯器在編譯時挑出不同數據具備的屬性。從而根據屬性將程序片段分類。
2)操作系統通過設置GDT(全局描述符表)來構建段描述符,在段描述符種指定段的位置、大小及屬性(包括S段和TYPE段),這纔是真正給段添加屬性的地方。
3)CPU中的段寄存器,提前被操作系統賦予了對應的段選擇子,從而確定了指向的段。在執行指令時,會根據該段的屬性來判斷指令的行爲,若有返回則發出異常。
內存分段是CPU的訪問內存的機制,程序分段是軟件種認爲邏輯劃分的內存區域,它本身也是內存,所以處理器在訪問該區域時,也會採用內存分段機制,用段寄存器指向該區域的起始地址。

0x6 物理地址、邏輯地址、有效地址、線性地址、虛擬地址的區別

實模式下,“段基址+段內偏移地址”經過段部件的處理,直接輸出的就是物理地址,CPU可以直接使用此地址訪問內存。
保護模式下,“段基址+段內偏移地址”稱爲線性地址,此時的段基址不再是真正的地址,而是叫做段選擇子的數據結構。它本身是一個索引,類似於數組下標,通過這個索引便能在GDT種找到對應的段描述符,在找到的描述符種記錄了該段的起始、大小等信息,這樣便得到了段基址。若沒有開啓分頁功能,此線性地址就直接被當作物理地址使用。若開啓,此線性地址是虛擬地址(虛擬地址、線性地址在分頁機制下是一個意思)。虛擬地址要經過CPU頁部件轉換成具體的物理地址,這樣CPU才能將其送上地址總線去訪問內存。
無論在實模式還是保護模式下,段內地址偏移地址又被稱爲有效地址,也被稱爲邏輯地址。

0x7 爲什麼Linux系統下的應用程序不能在Windows系統下運行

不同操作系統文件格式不同,系統API不同導致兩個系統下的應用不互通。

0x8 局部變量和函數參數爲什麼要放在棧中

局部變量不像static那樣屬於全局性的。全局變量可以隨時訪問,而局部變量只是在特定條件下才可以訪問,隨時可以清理,所以要將局部變量放在堆棧裏。(堆棧就是棧,和堆一毛錢關係都沒有,就是一種習慣)
在C程序的內存佈局中,由於堆和棧的地址空間是接壤的,棧從高地址往低地址發展,堆從低地址向高地址發展,堆和棧早晚會碰頭,他們各自大小取決於實際使用的情況,界限並不明朗。可能這就是稱爲堆棧的原因。
函數放在堆棧的原因:
1)局部性:函數使用的參數是局部變量
2)不確定性:編譯器無法預測函數調用的次數,而函數的返回值和參數都需要內存來存儲。內存空間不確定。

0x9 爲什麼說彙編語言比C語言快

這種說法是錯誤的。因爲不管什麼語言,想要讓其運行必須都要通過CPU。CPU不知道什麼是彙編語言、C語言,甚至Java、PHP、Python等,它根本不知道交給它的指令曾經歷那麼多的解釋、編譯工序。不管什麼語言,編譯器最終翻譯出來的都是機器指令和C編譯器編譯出來的機器指令無異。
之所以說彙編語言比C語言快,是因爲彙編語言生成的機器指令更少,從而“顯得”更快。用匯編語言寫程序相當於在直接寫機器指令,彙編語言不會添加額外的語句,CPU不會因爲多執行一些無關指令而浪費時間,當然會更快。
而C編譯器爲了讓程序員更加方便的編程,它在背後做了大量的工作,不僅如此,出於通用性、易用性的其他方面考慮,C編譯器往往會在背後加入額外的代碼,然後再由彙編器將彙編代碼翻譯成機器指令,所以生成的機器指令會產生冗餘。

0xA 編譯型程序與解釋型程序的區別

解釋型語言,也成爲腳本語言,如JavaScript、Python、Perl、PHP、Shell腳本等。他們本身是文本文件,是某個應用程序的輸入,這個應用程序是腳本解釋器。
由於只是文本,這些腳本中的代碼在腳本解釋器看來與字符串無異。也就是說,腳本中的代碼從沒有正真到CPU上執行過,CPU的cs:ip寄存器從來沒有指向它們。在CPU眼裏只看得到腳本解釋器。腳本的執行本質上是腳本解釋器在時時分析這個腳本,動態根據關鍵字和語法來做出相應行爲。因此腳本中若出現錯誤,先前正確的部分也會正常執行,這和編譯型程序有很大區別。

0xB BIOS中斷、DOS中斷、Linux中斷

在計算機系統中,無論是在實模式,還是在保護模式,在任何情況下就會有來自外部或內部的事件發生。如果事件來自於CPU內部就稱爲異常,即Exception。例如,CPU在計算算法時,發現分母爲0,就拋出了除0異常。如果事件來自於外部,也就是該事件由外部設備法出並通知了CPU,這個設備就稱爲中斷。
BIOS和DOS都是存在於實模式下的程序,由他們建立的中斷調用都是建立在中斷向量表(Interrupt Vector Table,IVT)中的。他們都是通過軟中斷指令int中斷號來調用的。
中斷向量表中的每個中斷向量大小是4字節。這4字節描述了一箇中斷處理例程(程序)
中斷向量表中每個中斷向量大小是4個字節。這4個字節描述了一箇中斷處理例程(程序)的段基地址和段內偏移地址。因爲中斷向量表長度爲1024個字節,故該表最多容納256箇中斷向量處理程序。計算機啓動之初,中斷向量表中的中斷例程是由BIOS建立的,它從物理地址0x0000處初始化並在中斷向量表中添加各種處理例程。
BIOS中斷調用的主要功能是提供了硬件的訪問方法。BIOS只是爲了對硬件操作變得簡單易行,也可以不用BIOS對硬件進行訪問。操作硬件無非是通過in/out指令來讀寫外設的端口,BIOS中斷程序處理就是包含了in/out指令的各種操作。
BIOS填寫中斷處理例程的原因:
1)給自己用。因爲BIOS也是一段程序,是程序就很可能重複執行某段代碼,它直接將其寫成中斷函數,進行直接調用。
2)給後來的程序使用,如加載器或boot loader。它們在調用硬件資源時就不需要自己重寫代碼了。
BIOS設置中斷例程時也需要調用別人的函數例程。BIOS也是軟件,也要求於別人。首先硬件廠商爲了讓自己生產的產品易用,肯定實現寫好了一組調用接口,必然是越簡單越好,直接給接口函數傳遞一個參數,硬件就能返回一個輸出。
每個外設,包括顯卡、鍵盤、各種控制器等,都有自己的內存(主板也有自己的內存,BIOS就存放在裏面),不過這種內存都只是讀取ROM。硬件自己的功能調用例程以及初始化代碼就放在這ROM中。根據規範,第1個內存單元的內容是0x55,第二個存儲單元是0xAA,第三個存儲單位是該rom中,以512字節爲單位的代碼長度。從第4個存儲單元起就是實際代碼了,知道第三個存儲單元所示長度爲止。
訪問外設有兩種方式。
1)內存映射:通過地址總線將外設自己內存映射到某個內存區域(並不是映射到主板上插的內存條中)。
2)端口操作:外設都有自己的控制器,控制器上有寄存器,這些寄存器就是所謂的端口,通過in/out指令讀些端口來訪問硬件的內存。
DOS是運行在實模式下的,故其建立的中斷調用也建立在中斷向量表中,只不過其中斷向量號和BIOS的不能衝突。0x20~0x27是DOS中斷。
DOS中斷調用那麼多功能是通過先往ah寄存器中寫好子功能號,再執行int 0x21。這時在中斷向量表中第0x21個表項,即物理地址0x21*4處中的中斷處理程序開始根據寄存器ah中的值來調用相應的子功能。
而Linux內核是在進入保護模式後才建立中斷例程的,不過在保護模式下,中斷向量表已被中斷描述符表取代(Interrupt Descriptor Table,IDT)。

0xC 庫函數是用戶進程與內核的橋樑

在Linux下C編程時,我們寫的程序通常是用戶級程序。爲了輸出文本,我們一般會在文件開始include <stdio.h>,這樣程序就可以使用printf這樣的函數完成打印輸出。這是因爲用戶程序不具有獨立打印字符的功能,他必須藉助操作系統的力量纔可以。操作系統提供了一套系統調用接口,用戶進程直接調用這些接口就行了。所以我們使用的庫函數都是這種接口,調用庫函數就相當於在使用函數接口。

0xD MBR、EBR、DBR和OBR各是什麼

BIOS是主板內存上的小程序,所在空間有限,代碼量較少,功能受限,所以必須採取功能接力的形式來移交控制權。BIOS只完成簡單的測試初始化工作,然後找機會把處理器使用權移交給MBR中的程序。爲了方便OS找到MBR,MBR程序必須存放在指定位置,MBR放置在整個磁盤的第一個扇區內,所以又被稱爲引導扇區。
MBR是主引導記錄,Master或Main Boot Record,它位於整個硬盤最開始的扇區,即0盤0道1扇區。一般情況下扇區大小是512字節,只是一般情況。
MBR引導扇區中內容:
1)446字節的引導程序及參數
2)64字節的分區表
3)2字節結束標記0x55和0xaa
在MBR引導扇區中存儲引導程序,爲的是從BIOS手中接管系統控制權,也就是處理器的使用權。BIOS從引導扇區數據後或默認將引導扇區數據讀取道0x7c00,然後調用cs:ip遠跳轉指令跳轉過去執行MBR裏的程序。這就是控制權移交過程。
除了MBR主引導扇區,還有“次”引導。MBR主要任務就是結合當前程序運行情況挑選最好的次引導程序,將系統權限轉交給次引導程序。MBR就是幫忙轉交沒有其他太多作用。MBR除了引導程序外,還有64字節的分區表,裏面是分區信息,每個分區信息表項佔用16字節。因此MBR的分區表中只能容納4個表項,轉交控制權就是在這4個表項中遍歷選擇。
通常次引導程序就是通過操作系統加載器的,因此MBR引導任務就是將控制權交給系統加載器。
爲了讓MBR知道哪有操作系統,我們在分區時,如果像在某個分區中安裝操作系統,就用分區工具將該分區設置爲活動分區,設置活動分區的本質就是把分區表中該分區對應的分區表項中的活動標記爲0x80。0x80表示此分區上有引導程序,0表示沒有引導程序,該分區不可引導。此引導程序通常是內核加載器。這個存放內核加載器的扇區也被稱爲操作系統引導記錄OBR,即OS Boot Record,此扇區也被稱爲OBR引導扇區。
OBR開頭的跳轉指令跳往的目標地址並不固定,這是由所創建的文件系統決定的,堆與FAT32文件系統來說,此跳轉指令會跳轉到本扇區偏移0x5A字節的操作系統引導程序處。不管目標地址是多少,總之哪裏通常是操作系統的內核加載器。
OBR是DBR遺留下來的。DBR是DOS Boot Record,也就是DOS系統的引導程序,DBR中內容大致是:
1)跳轉指令,使MBR跳轉到引導代碼
2)廠商信息、DOS版本信息
3)BIOS參數快BPB,即BIOS Parameter Block
4)操作系統引導程序
5)結束標記0x55和0xaa
在DOS時代只有4個分區,不存在拓展分區,這4個分區都想與主分區,所以個主分區最開始的扇區稱爲DBR引導扇區。因爲其他操作系統也有這種習慣,而且DOS退出了歷史舞臺,所以改爲了OBR。
當初爲了解決分區數量問題纔有了拓展分區,EBR是拓展分區爲了兼容MBR才提出的概念,主要是兼容MBR的分區表。拓展分區是一個邏輯分區,因此拓展分區中也要由分區表,爲拓展分區存儲分區表的扇區稱爲EBR,即Expand Boot Record。兼容的內容是分區表,因此它與MBR結構相同,只是位置不同,EBR位於個子拓展分區中最開始的扇區(注意,各個主分區和邏輯分區中最開始的扇區是操作系統引導扇區,不要把邏輯分區和扇區弄混,是兩個完全不一樣的概念)
MBR和EBR是分區工具創建的,不屬於操作系統管理範圍,因此操作系統不可以向其寫入內容(這裏說的不可以只是操作系統的一種限制,其實操作系統可以訪問所有內存地址進行讀寫),每個子拓展分區只有一個EBR。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章