3. 設備

CPU執行指令除了訪問內存之外還要訪問很多設備(Device),如鍵盤、滑鼠、硬碟、顯示器等,那麼它們和CPU之間如何連接呢?如下圖所示。

圖 17.4. 設備

設備

有些設備像內存晶片一樣連接到處理器的地址匯流排和數據匯流排,正因為地址綫和數據線上可以掛多個設備和內存晶片所以才叫“匯流排”,但不同的設備和內存晶片應該占不同的地址範圍。訪問這種設備就像訪問內存一樣,按地址讀寫即可,但和訪問內存不同的是,往一個地址寫數據只是給設備發一個命令,數據不一定要保存,而從一個地址讀數據也不一定是讀先前保存在這個地址的數據,而是得到設備的當前狀態。設備中可供讀寫訪問的單元通常稱為設備寄存器(注意和CPU寄存器不是一回事),操作設備的過程就是讀寫這些設備寄存器的過程,比如向串口發送寄存器裡寫數據,串口設備就會把數據發送出去,讀串口接收寄存器的值,就可以讀取串口設備接收到的數據。

還有一些設備整合在處理器晶片中。在上圖中,從CPU核引出的地址和數據匯流排有一端經匯流排介面引出到晶片引腳上了,還有一端沒有引出,而是接到晶片內部整合的設備上,無論是在CPU外部接匯流排的設備還是在CPU內部接匯流排的設備都有各自的地址範圍,都可以像訪問內存一樣訪問,很多體繫結構(比如ARM)採用這種方式操作設備,稱為內存映射I/O(Memory-mapped I/O)。但是x86比較特殊,x86對於設備有獨立的連接埠地址空間,CPU核需要引出額外的地址綫來連接片內設備(和訪問內存所用的地址綫不同),訪問設備寄存器時用特殊的in/out指令,而不是和訪問內存用同樣的指令,這種方式稱為連接埠I/O(Port I/O)。

從CPU的角度來看,訪問設備只有內存映射I/O和連接埠I/O兩種,要麼像內存一樣訪問,要麼用一種專用的指令訪問。其實訪問設備是相當複雜的,計算機的設備五花八門,各種設備的性能要求都不一樣,有的要求頻寬大,有的要求響應快,有的要求熱插拔,於是出現了各種適應不同要求的設備匯流排,比如PCI、AGP、USB、1394、SATA等等,這些設備匯流排並不直接和CPU相連,CPU通過內存映射I/O或連接埠I/O訪問相應的匯流排控製器,通過匯流排控製器再去訪問掛在匯流排上的設備。所以上圖中標有“設備”的框可能是實際的設備,也可能是設備匯流排的控製器。

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

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

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

由於各種設備的操作方法各不相同,每種設備都需要專門的設備驅動程式(Device Driver),一個操作系統為了支持廣泛的設備就需要有大量的設備驅動程式,事實上Linux內核原始碼中絶大部分是設備驅動程式。設備驅動程式通常是內核裡的一組函數,通過讀寫設備寄存器實現對設備的初始化、讀、寫等操作,有些設備還要提供一個中斷處理函數供ISR調用。