歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux系統編程基礎(一) 計算機體系結構一點基礎知識

linux系統編程基礎(一) 計算機體系結構一點基礎知識

日期:2017/3/3 16:24:34   编辑:關於Linux

無論是在CPU外部接總線的設備還是在CPU內部接總線的設備都有各自的地址范圍,都可以像訪問內存一樣訪問,很多體系結構(比如ARM)采用這種方式操作設備,稱為內存映射I/O(Memory-mappedI/O)。但是x86比較特殊,x86對於設備有獨立的端口地址空間,CPU核需要引出額外的地址線來連接片內設備(和訪問內存所用的地址線不同),訪問設備寄存器時用特殊的in/out指令(匯編),而不是和訪問內存用同樣的指令,這種方式稱為端口I/O(PortI/O)。

在x86平台上,硬盤是掛在IDE、SATA或SCSI總線上的設備,保存在硬盤上的程序是不能被CPU直接取指令執行的,操作系統在執行程序時會把它從硬盤拷貝到內存,這樣CPU才能取指令執行,這個過程稱為加載(Load)。程序加載到內存之後,成為操作系統調度執行的一個任務,就稱為進程(Process)。進程和程序不是一一對應的。一個程序可以多次加載到內存,成為同時運行的多個進程,例如可以同時開多個終端窗口,每個窗口都運行一個Shell進程,而它們對應的程序都是磁盤上的/bin/bash文件。

操作系統(OperatingSystem)本身也是一段保存在磁盤上的程序,計算機在啟動時執行一段固定的啟動代碼(稱為Bootloader)首先把操作系統從磁盤加載到內存,然後執行操作系統中的代碼把用戶需要的其它程序加載到內存。操作系統和其它用戶程序的不同之處在於:操作系統是常駐內存的,而其它用戶程序則不一定,用戶需要運行哪個程序,操作系統就把它加載到內存,用戶不需要哪個程序,操作系統就把它終止掉,釋放它所占的內存。操作系統最核心的功能是管理進程調度、管理內存的分配使用和管理各種設備,做這些工作的程序稱為內核(Kernel),在某個系統上內核程序是/boot/vmlinuz-2.6.28-13-generic文件,它在計算機啟動時加載到內存並常駐內存。廣義上操作系統的概念還包括一些必不可少的用戶程序,比如Shell是每個Linux系統必不可少的,而Office辦公套件則是可有可無的,所以前者也屬於廣義上操作系統的范疇,而後者屬於應用軟件。

訪問設備還有一點和訪問內存不同。內存只是保存數據而不會產生新的數據,如果CPU不去讀它,它也不需要主動提供數據給CPU,所以內存總是被動地等待被讀或者被寫。而設備往往會自己產生數據,並且需要主動通知CPU來讀這些數據,例如敲鍵盤產生一個輸入字符,用戶希望計算機馬上響應自己的輸入,這就要求鍵盤設備主動通知CPU來讀這個字符並做相應處理(硬中斷),給用戶響應。這是由中斷(Interrupt)機制實現的,每個設備都有一條中斷線,通過中斷控制器連接到CPU,當設備需要主動通知CPU時就引發一個中斷信號,CPU正在執行的指令將被打斷,程序計數器會指向某個固定的地址(這個地址由體系結構定義),於是CPU從這個地址開始取指令(或者說跳轉到這個地址),執行中斷服務程序(ISR,InterruptService Routine),完成中斷處理之後再返回先前被打斷的地方執行後續指令。比如某種體系結構規定發生中斷時跳轉到地址0x00000010執行,那麼就要事先把一段ISR程序加載到這個地址,ISR程序是內核代碼的一部分,在這段代碼中首先判斷是哪個設備引發了中斷,然後調用該設備的中斷處理函數做進一步處理。由於各種設備的操作方法各不相同,每種設備都需要專門的設備驅動程序(DeviceDriver),一個操作系統為了支持廣泛的設備就需要有大量的設備驅動程序,事實上Linux內核源代碼中絕大部分是設備驅動程序。設備驅動程序通常是內核裡的一組函數,通過讀寫設備寄存器實現對設備的初始化、讀、寫等操作,有些設備還要提供一個中斷處理函數供ISR調用。

MMU(MemoryManagement Unit)將VA映射到PA是以頁(Page)為單位的,32位處理器的頁尺寸通常是4KB。例如,MMU可以通過一個映射項將VA的一頁0xb7001000~0xb7001fff映射到PA的一頁0x2000~0x2fff,如果CPU執行單元要訪問虛擬地址0xb7001008,則實際訪問到的物理地址是0x2008。物理內存中的頁稱為物理頁面或者頁幀(PageFrame)。虛擬內存的哪個頁面映射到物理內存的哪個頁幀是通過頁表(PageTable)來描述的,頁表保存在物理內存中,MMU會查找頁表來確定一個VA應該映射到什麼PA。

操作系統和MMU是這樣配合的:

1.操作系統在初始化或分配、釋放內存時會執行一些指令在物理內存中填寫頁表,然後用指令

設置MMU,告訴MMU頁表在物理內存中的什麼位置。

2.設置好之後,CPU每次執行訪問內存的指令都會自動引發MMU做查表和地址轉換操作,地址轉換操作由硬件自動完成,不需要用指令控制MMU去做。

我們在程序中使用的變量和函數都有各自的地址,程序被編譯後,這些地址就成了指令中的地址,

指令中的地址被CPU解釋執行,就成了CPU執行單元發出的內存地址,所以在啟用MMU的情況下,程序中使用的地址都是虛擬地址,都會引發MMU做查表和地址轉換操作。

MMU除了做地址轉換之外,還提供內存保護機制。各種體系結構都有用戶模式(UserMode)和特權模式(PrivilegedMode)之分,操作系統可以在頁表中設置每個內存頁面的訪問權限,有些頁面不允許訪問,有些頁面只有在CPU處於特權模式時才允許訪問,有些頁面在用戶模式和特權模式都可以訪問,訪問權限又分為可讀、可寫和可執行三種。這樣設定好之後,當CPU要訪問一個VA時,MMU會檢查CPU當前處於用戶模式還是特權模式,訪問內存的目的是讀數據、寫數據還是取指令,如果和操作系統設定的頁面權限相符,就允許訪問,把它轉換成PA,否則不允許訪問,產生一個異常(Exception)。異常的處理過程和中斷類似,不同的是中斷由外部設備產生而異常由CPU內部產生,中斷產生的原因和CPU當前執行的指令無關,而異常的產生就是由於CPU當前執行的指令出了問題,例如訪問內存的指令被MMU檢查出權限錯誤,除法指令的除數為0等都會產生異常。

通常操作系統把虛擬地址空間劃分為用戶空間和內核空間,例如x86平台的Linux系統虛擬地址空間是0x00000000~0xffffffff,前3GB(0x00000000~0xbfffffff)是用戶空間,後1GB(0xc0000000~0xffffffff)是內核空間。用戶程序加載到用戶空間,在用戶模式下執行,不能訪問內核中的數據,也不能跳轉到內核代碼中執行。這樣可以保護內核,如果一個進程訪問了非法地址,頂多這一個進程崩潰,而不會影響到內核和整個系統的穩定性。CPU在產生中斷或異常時不僅會跳轉到中斷或異常服務程序,還會自動切換模式,從用戶模式切換到特權模式,因此從中斷或異常服務程序可以跳轉到內核代碼中執行。事實上,整個內核就是由各種中斷和異常處理程序組成的。總結一下:在正常情況下處理器在用戶模式執行用戶程序,在中斷或異常情況下處理器切換到特權模式執行內核程序,處理完中斷或異常之後再返回用戶模式繼續執行用戶程序.

段錯誤是這樣產生的:

1.用戶程序要訪問的一個VA,經MMU檢查無權訪問。

2.MMU產生一個異常,CPU從用戶模式切換到特權模式,跳轉到內核代碼中執行異常服務程序。

3.內核把這個異常解釋為段錯誤,把引發異常的進程終止掉。

中斷與異常:

(硬件)外部中斷:非拼比NMI,可屏蔽INTR(可擴8259A中斷控制器,則最高可達64個中斷源(I/O設備))。

(軟件)內部中斷:

1、DIV指令除數為0,或者商太大,產生類型0的中斷。

2、INT指令,INT n

3、INTO指令,若上一條指令執行結果使溢出標志位OF=1,引起類型為4的中斷。

4、單步執行,若標志位TF=1,每一條指令執行完都會引起類型1的中斷。

中斷的優先次序:內部中斷(單步執行優先級最低),NMI,INTR。

異常:

1、故障Fault:指令執行前報告(段和頁異常)

2、陷阱trap:指令執行後報告(單步和數據斷點)

3、夭折abort:硬件故障或系統表不一致

異常0~16號

軟件中斷int n(0~255) --》陷阱trap

movl$1, %eax

movl$4, %ebx

int$0x80

int指令稱為軟中斷指令,可以用這條指令故意產生一個異常,異常的處理和中斷類似,CPU從用戶模式切換到特權模式,然後跳轉到內核代碼中執行異常處理程序。int指令中的立即數0x80(0~255)是一個參數,在異常處理程序中要根據這個參數決定如何處理,在Linux內核中int$0x80這種異常稱為系統調用(SystemCall)。內核提供了很多系統服務供用戶程序使用,但這些系統服務不能像庫函數(比如printf)那樣調用,因為在執行用戶程序時CPU處於用戶模式,不能直接調用內核函數,所以需要通過系統調用切換CPU模式,經由異常處理程序進入內核,用戶程序只能通過寄存器傳幾個參數,之後就要按內核設計好的代碼路線走,而不能由用戶程序隨心所欲,想調哪個內核函數就調哪個內核函數,這樣可以保證系統服務被安全地調用。在調用結束之後,CPU再切換回用戶模式,繼續執行int$0x80的下一條指令,在用戶程序看來就像函數調用和返回一樣。eax和ebx的值是傳遞給系統調用的兩個參數。eax的值是系統調用號,Linux的各種系統調用都是由int$0x80指令引發的,內核需要通過eax判斷用戶要調哪個系統調用,_exit的系統調 用號是1。ebx的值是傳給_exit的參數,表示退出狀態。大多數系統調用完成之後會返回用戶空間繼續執行後面的指令,而_exit系統調用比較特殊,它會終止掉當前進程,而不是返回用戶空間繼續執行。可以說如read,write這樣的系統調用的底層實現都是利用了這3條匯編指令,系統調用只是進入內核程序的一個接口,內核調用內核函數(如中斷異常服務程序,實現各種普通文件操作的內核函數,各種設備驅動程序等)進行服務.

Copyright © Linux教程網 All Rights Reserved