cpu与外设工作原理
總結(jié)來說,就是插上外設(shè)后,cpu就可以檢測和連接到外設(shè)上的寄存器,把它當(dāng)成內(nèi)存來使用,然后就是對這些寄存器進行讀寫,寫控制寄存器來控制外設(shè),讀狀態(tài)寄存器來檢測外設(shè)狀態(tài)(外設(shè)會把當(dāng)前狀態(tài)信息放到指定寄存器上),通過讀寫數(shù)據(jù)寄存器來交換數(shù)據(jù)。
一直以來,發(fā)現(xiàn)很多搞上層軟件的朋友沒有時間了解CPU、編譯器、操作系統(tǒng)等底層技術(shù),偶恰好在計算機微體系結(jié)構(gòu)與集成電路實驗室,有幸接觸到這些底層的東東,所以想寫一些自己以前學(xué)這些東東的感想,以消除對底層技術(shù)不熟悉的朋友對底層技術(shù)的神秘感,同時想和搞底層技術(shù)的朋友切磋切磋,共同提高。當(dāng)然偶所談的內(nèi)容都不是先進或深奧的,而是最直觀和最容易理解的,偶所寫的文章不是闡述各個專題的專著,而是入門讀物,希望讀者讀完偶的文章后具有讀懂各個專題專著的能力。
閑話少說,讓我們切入正題。我們從驅(qū)動程序出發(fā),慢慢講解計算機的各個部分是如何各自為政,而又互相協(xié)作,從而完全各種復(fù)雜功能的。本文不具體闡述如何編寫驅(qū)動程序,而是從體系結(jié)構(gòu)的觀點著手,力爭用通俗易懂的語言闡述各種外設(shè)的共同特點,使讀者具備舉一反三、融會貫通、駕馭各種外設(shè)的能力。另外,筆者喜歡從不同的角度分析同一個問題,所以行文中難免出現(xiàn)重復(fù)的內(nèi)容,累贅的闡述,筆者正是希望通過這種重復(fù)和累贅來加深讀者對所述內(nèi)容的理解。
計算機發(fā)展到今天,其外設(shè)早已是五花八門,象硬盤、軟盤、光盤、U盤、鼠標、鍵盤、聲卡、網(wǎng)卡、SD卡、手柄等等,真是層出不窮。五花八門的外設(shè)給我們帶來便利的同時也帶來了許多問題,比如:
主板上的接口個數(shù)有限,怎樣保證各種離奇古怪的外設(shè)能連接主板并跟主機通信?
怎樣保證CPU能一個不漏地控制外設(shè)工作?CPU能夠控制什么樣的外設(shè)?
CPU對外設(shè)的控制能達到什么程度?
怎樣保證CPU不會誤操作外設(shè)?
怎樣保證外設(shè)之間不會“打架”、互相干擾?
外設(shè)怎樣向CPU報告處理結(jié)果?
多個進程怎樣共享外設(shè)?
高級語言怎樣支持驅(qū)動程序的編寫?
外設(shè)怎樣給CPU提供配置信息等等,這些問題是否讓各位看官頭大了?不要緊張,且聽我慢慢道來。 首先,講講外設(shè)的基本構(gòu)成。每個外設(shè)都有一個控制器,這個控制器是數(shù)字電路,控制器里有一些叫“寄存器”的存儲單元,這些東西的物理結(jié)構(gòu)跟內(nèi)存單元不一樣,但作用跟內(nèi)存單元一樣,都能保存信息。
寄存器各有各的作用,比如:軟驅(qū)、硬盤上有保存磁頭號、磁道號、扇區(qū)號等參數(shù)的寄存器,這些寄存器的值告訴硬盤這次讀磁盤操作要讀的是哪個盤面哪個磁道哪個扇區(qū)的數(shù)據(jù)。根據(jù)寄存器的作用,可將寄存器分為兩類,分別叫控制寄存器和狀態(tài)寄存器。控制寄存器用來告訴外設(shè):CPU要求它干什么活以及它干活時需要的參數(shù);狀態(tài)寄存器用于外設(shè)向CPU報告外設(shè)目前的狀態(tài),比如,外設(shè)目前在干什么活,在干活的過程中是否發(fā)生了錯誤,外設(shè)是否還有能力接受新任務(wù)等等,狀態(tài)寄存器沒有能力主動告訴CPU外設(shè)當(dāng)前的狀態(tài),而是被動地等待著CPU來取狀態(tài)信息,CPU把狀態(tài)寄存器的值讀出來就能知道外設(shè)當(dāng)前的工作狀態(tài)。當(dāng)然,外設(shè)也有主動報告CPU的能力——中斷。寄存器有的是只寫的,有的是只讀的,還有的是可讀可寫的。一般而言,控制寄存器是只寫或可讀可寫的,狀態(tài)寄存器是只讀的。
除了控制器外,大多數(shù)外設(shè)還有一個用來具體干活的模擬電路,如硬盤有控制磁頭移動、盤片轉(zhuǎn)動的模擬電路,打印機有控制打印紙滾動,控制噴墨或打印針擊打打印紙的模擬電路,MP3有數(shù)模轉(zhuǎn)換器和功率放大器等等。控制器和模擬電路通常是集成在一塊芯片里,這種集成電路叫數(shù)模混合電路。數(shù)模混合電路是目前IT領(lǐng)域頗具挑戰(zhàn)性的技術(shù)之一,如果某天你能設(shè)計數(shù)模混合電路了,那么恭喜你,這輩子你再也不用愁吃穿住行了!當(dāng)然,也有純數(shù)字電路的外設(shè),如DMA控制器。以前的外設(shè)由于技術(shù)不成熟,其控制器、模擬電路、電機等部件是分離的,現(xiàn)在大多數(shù)外設(shè)把控制器、模擬電路及電機、盤片(如果有的話)等等各個部件集成在一起,如硬盤。有的外設(shè)只是把控制器、模擬電路及電機集成在一起,盤片是可移動的,如光驅(qū)、軟驅(qū)。這種把控制器、模擬電路及電機等部件集成在一起的外設(shè)稱為智能外設(shè)。
那么,怎樣保證CPU能一個不漏地控制多個外設(shè)呢?原來,多個外設(shè)和CPU都掛在一組總線上,硬件工程師給外設(shè)的每個寄存器都分配一個地址,CPU拿一個地址去訪問某個寄存器時,只有該寄存器發(fā)生動作,或接收數(shù)據(jù)總線上的數(shù)據(jù)(對應(yīng)于寫操作),或把自己的數(shù)據(jù)送到數(shù)據(jù)總線上(對應(yīng)于讀操作),同一個外設(shè)的其他寄存器和其他外設(shè)的寄存器都不會動作。這樣,CPU用不同的地址就可以訪問不同的寄存器,也就可以一個不漏地控制多個外設(shè)了。CPU訪問某個寄存器時,別的寄存器不會發(fā)生動作,所以,外設(shè)之間不會“打架”、不會互相干擾。同樣地,CPU訪問內(nèi)存時,其地址不是外設(shè)的寄存器的地址,所有的外設(shè)都不會動作,所以CPU不會誤操作外設(shè)。
根據(jù)外設(shè)的基本結(jié)構(gòu),你是否已經(jīng)猜到CPU控制外設(shè)的能力了?顯然,CPU控制外設(shè)的方法和能力無非就是讀寫寄存器。比如,CPU要從硬盤讀文件,那么CPU只需要把磁頭號、磁道號、扇區(qū)號、要讀的數(shù)據(jù)量等參數(shù)填入硬盤控制器的對應(yīng)寄存器,然后向硬盤控制器的對應(yīng)寄存器填一個開始命令,硬盤控制器就命令接在其后面的模擬電路開始工作——如:控制電機移動磁頭到對應(yīng)的磁道、對準扇區(qū),讀數(shù)據(jù)等等。至于磁頭目前在什么位置,怎樣移動到對應(yīng)的磁道,順指針移動還是逆時針移動,以多快的速度移動,磁頭移動到對應(yīng)磁道后以多大的加速度減速等等,這些事情不是CPU所能控制的,而是由硬盤控制器和接在硬盤控制器后面的模擬電路共同控制的。遺憾的是,集成電路和印制電路板(PCB板)的技術(shù)已經(jīng)很成熟,硬盤控制器、接在硬盤控制器后面的模擬電路以及磁頭、盤片、控制磁頭移動的電機等部件早已集成在一個小小的長方體盒子里,我們已經(jīng)沒有機會一睹各個部件的芳容了。總之,CPU只能控制外設(shè)中數(shù)字部分的程序員可見的寄存器,無法控制程序員不可見的寄存器,更加無法控制模擬電路、電機等部件,也就是說CPU只能告訴外設(shè)要干什么活以及干活過程中需要的參數(shù),至于外設(shè)是怎么干活,如:硬盤怎么移動磁頭、音頻芯片怎么把數(shù)字信號轉(zhuǎn)成模擬信號,怎么把模擬信號放大等等,這些事情是CPU無法控制的。
外設(shè)一般有兩種方式報告CPU外設(shè)的工作狀態(tài)——程序查詢方式和中斷方式。程序查詢方式就是利用狀態(tài)寄存器報告CPU外設(shè)的工作狀態(tài),外設(shè)只需要把其工作狀態(tài)的信息填到狀態(tài)寄存器里,可惜的是狀態(tài)寄存器沒有能力主動告訴CPU它里面的值是多少,而只能被動地等待著CPU讀取它的值。所以,CPU需要不斷地讀取狀態(tài)寄存器,來判斷外設(shè)是否已經(jīng)干完活。顯然,這種方法的效率很低,程序每讓外設(shè)干一次活就得不斷查詢狀態(tài)寄存器,一直在做無用功,無法把CPU時間讓給別的進程,直到外設(shè)干完活后,程序才能往下執(zhí)行。中斷方式要求外設(shè)具有向CPU發(fā)送中斷請求的能力,外設(shè)每次干完活后就主動向CPU發(fā)中斷請求,注意是主動發(fā)中斷請求,可惜的是,中斷請求只能告訴CPU外設(shè)已經(jīng)干完活,至于在干活的過程中外設(shè)是否發(fā)生錯誤,外設(shè)的空閑緩沖區(qū)還剩多少等其他信息無法在中斷請求中表達,所以中斷方式也離不開狀態(tài)寄存器,CPU響應(yīng)中斷后,可以讀一下狀態(tài)寄存器,以了解外設(shè)的更多更詳細的信息。由于中斷方式是主動方式,所以進程讓外設(shè)干活后就可以把CPU時間讓給別的進程,外設(shè)干完活后,中斷處理程序會喚醒該進程,這就是中斷方式比程序查詢方式高效的原因。
下面,講講多個進程怎樣共享外設(shè)。從共享的角度劃分,外設(shè)分為共享設(shè)備和獨占設(shè)備。共享設(shè)備就是在某個活沒干完時,別的進程可以讓該設(shè)備干別的活,如進程A要從硬盤讀10MB的數(shù)據(jù),讀完8MB數(shù)據(jù)時,進程B要求硬盤讀5MB數(shù)據(jù)給它,這時磁盤調(diào)度算法可能讓硬盤先把B需要的5MB數(shù)據(jù)讀給B,回頭再給A讀最后的2MB數(shù)據(jù),具有硬盤這種特點的設(shè)備就叫共享設(shè)備。獨占設(shè)備就是外設(shè)在干某個活時,一定要先干完這個活才能干別的活,如打印機正在打印進程A的文檔,那么在打印A的文檔的過程中,打印機不能給其他進程打印東西,否則,打印出來的東西就面目全非了,具有打印機這種特點的設(shè)備就叫獨占設(shè)備。
下面,我們以打印機為例來說明多個進程怎樣共享“獨占設(shè)備”的。操作系統(tǒng)可以設(shè)置一個打印隊列,準備一個打印機的驅(qū)動程序C,打印機每打印完一個作業(yè)時,給CPU發(fā)中斷,CPU響應(yīng)中斷,轉(zhuǎn)入內(nèi)核態(tài),并跳到C執(zhí)行,C把該作業(yè)對應(yīng)的進程喚醒,從打印隊列里取出一項新作業(yè),把相關(guān)參數(shù)如待打印數(shù)據(jù)的開始地址、數(shù)據(jù)量等,填到打印機的對應(yīng)寄存器里,然后發(fā)一個“開始”命令,打印機開始打印新的作業(yè),打印完后再給CPU發(fā)中斷,如此周而復(fù)始地工作。某個進程想打印數(shù)據(jù)是,調(diào)用相應(yīng)的API函數(shù)D,D把待打印的數(shù)據(jù)組織成一個打印作業(yè),插入到打印隊列的末尾,把進程狀態(tài)設(shè)為掛起狀態(tài),然后調(diào)用進程調(diào)度函數(shù)切換別的進程執(zhí)行,在以后的某個時刻,該進程的作業(yè)被打印完,C隨即把該進程喚醒,將進程狀態(tài)設(shè)為就緒狀態(tài),該進程就能往下執(zhí)行了。
OK,獨占設(shè)備到此結(jié)束,下面以硬盤為例講講多個進程是怎樣共享“共享設(shè)備”的。硬盤在其控制器上設(shè)置有一個緩沖區(qū)用來暫時保存從盤片讀來的數(shù)據(jù)或從內(nèi)存寫過來的將要寫到盤片去的數(shù)據(jù)。緩沖區(qū)的大小有限,如8MB,而讀寫的文件可能很大,如一個視頻文件可能有幾百MB大,所以,一個讀寫作業(yè)可能需要讀寫多次才能完成。同樣地,操作系統(tǒng)需要設(shè)置一個類似于剛才所說的“打印隊列”的數(shù)據(jù)結(jié)構(gòu)用來記錄各個進程待讀寫的數(shù)據(jù),需要準備一個硬盤中斷處理程序E。硬盤完成一次讀寫后給CPU發(fā)中斷,CPU轉(zhuǎn)入內(nèi)核態(tài)并跳到E執(zhí)行,如果是寫操作,E把硬盤緩沖區(qū)里的數(shù)據(jù)搬到內(nèi)存,然后根據(jù)某種磁盤調(diào)度算法,如:先來先服務(wù)、電梯算法、最短尋道優(yōu)先等算法從各個讀寫作業(yè)中調(diào)一個它認為最好的作業(yè)出來,并命令硬盤處理該作業(yè)。如果在某次中斷處理過程中發(fā)現(xiàn)某個進程的待讀寫數(shù)據(jù)的剩余數(shù)據(jù)量為0,則表明該進程的讀寫作業(yè)已經(jīng)完成,E把該進程喚醒,并把進程狀態(tài)設(shè)為就緒狀態(tài),該進程就能往下執(zhí)行了。
主板上的接口個數(shù)有限,怎樣保證各種離奇古怪的外設(shè)能連接主板并跟主機通信呢?答案是標準接口。主板上只設(shè)置了所謂的標準接口,如IDE接口、串口、并口、PS/2接口、USB接口、PCI接口等等,至于你拿USB口接打印機還是游戲手柄還是數(shù)碼相機還是別的什么東東,主板就管不了了。如果你想做一個新外設(shè),那么首先要考慮好用什么接口跟主板連接,當(dāng)然只能從標準接口里選擇,然后還要寫一個驅(qū)動程序,把外設(shè)連同驅(qū)動程序一起給用戶,用戶就能使用該外設(shè)了。當(dāng)然,操作系統(tǒng)自帶了常用外設(shè)的驅(qū)動程序,據(jù)說windowXP自帶了2000多個驅(qū)動程序,暈,怪不得弄得windows越來越大,有些驅(qū)動程序可能我們一輩子也用不上,可它偏偏躺在那占用我們的硬盤空間! 我們經(jīng)常說,電腦開機時BIOS首先要進行自檢,即檢查電腦連著什么外設(shè),這些外設(shè)是否能正常工作,如果某個外設(shè)出現(xiàn)故障,BIOS還能根據(jù)不同的故障發(fā)出不同的報警聲。BIOS也是一段程序,它憑什么能做到上面所說的事情呢?我們自己寫一段程序,是不是也能做到上面所說的事情呢?不要急,請聽我慢慢道來。
原來,人們在設(shè)計外設(shè)時就考慮了自檢功能,如鼠標設(shè)置了一個查詢/應(yīng)答命令,BIOS檢查電腦是否連著鼠標時只需要向鼠標對應(yīng)的寄存器發(fā)一個查詢命令,如0xaa。如果電腦連著鼠標,鼠標就把此查詢命令原封不動地送到另一個寄存器F,然后,BIOS再讀F的值,如果F的值是0xaa,則表明鼠標存在,否則,讀進來的值就是0xff或0x00,這表明鼠標不存在。如果你熟悉數(shù)字電路,你一定知道為什么此時讀進來的值會是0xff或0x00。現(xiàn)在,你清楚BIOS怎樣檢查外設(shè)是否存在了吧。
那么,BIOS怎樣檢查存儲體如內(nèi)存、硬盤的大小呢?對于內(nèi)存,BIOS從0地址開始,每隔1KB的間隔寫一個數(shù)(如0xaa)到內(nèi)存,然后再從這個地址讀數(shù),如果讀出來的數(shù)跟寫進去的數(shù)相等,則表明這1KB的內(nèi)存是存在的,據(jù)此把內(nèi)存容量增加1KB,如果你的電腦比較慢,你可以在電腦開機時看到屏幕上顯示的檢測到的內(nèi)存容量是以1KB的步長不斷增大的。對于32位CPU而言,只要在0~4GB的地址范圍檢查一遍,就能知道內(nèi)存的大小。BIOS怎么檢查硬盤的大小呢?不會也象檢查內(nèi)存一樣寫一遍硬盤吧?如果寫一遍硬盤豈不是把硬盤原來的數(shù)據(jù)給擦了???當(dāng)然不會寫一遍硬盤!還記得上面提到的智能外設(shè)嗎?原來,智能外設(shè)里一般有一些只讀的寄存器保存著這個外設(shè)的配置信息,硬盤里就要這樣的寄存器保存著該硬盤的大小,BIOS只需要讀一下該寄存器就知道硬盤的大小了。由于硬盤的盤片是固定的,一旦出廠,硬盤的容量是不變的,所以BIOS讀到的硬盤大小是不會錯的。
那么,光盤和軟盤呢?它們可不是固定的?我拿來一張光盤,你怎么知道光盤的容量?答案是工業(yè)標準。雖然從理論上說,一張光盤的容量可以是任意值,如1.23MB,可惜工業(yè)標準規(guī)定了這種容量是非法的,工業(yè)標準只允許光盤的容量是少數(shù)幾個值,如VCD的容量是700多MB,DVD的容量是4000多MB,把一張光盤插入光驅(qū)后,光驅(qū)先檢測該光盤是VCD格式還是DVD格式(這可以從數(shù)據(jù)密度不同檢查出來),并據(jù)此判斷該光盤的容量。如果你有能力制作光盤,你當(dāng)然可以制作一張容量只有1.23MB的光盤,只可惜這張光盤違反了標準的規(guī)定,別人都不懂怎么使用這張光盤罷了。
說了這么多,你清楚BIOS怎樣檢測外設(shè)了嗎?你能自己寫一段程序,象BIOS那樣檢測外設(shè)了嗎?我想這兩個問題已經(jīng)難不倒聰明的你了,但你是否看到了BIOS自檢的一些缺陷呢?比如,我的內(nèi)存的地址為1500的存儲單元壞了,BIOS能檢測到嗎?又如,鼠標雖然能應(yīng)答查詢命令,但保存鼠標移動量的寄存器壞了,BIOS能檢測到嗎?答案當(dāng)然是不能。所以,如果BIOS發(fā)出報警聲,電腦一定有問題;BIOS沒發(fā)出報警聲,電腦也有可能有問題,這種問題更讓你郁悶,因為你根本不知道哪出了問題。我的同學(xué)就遇到過裝系統(tǒng)時,裝了一半就莫名其妙地不動了,檢來檢去原來是內(nèi)存壞了一個單元,狂暈!
最后,我們以C語言為例,講講高級語言怎樣支持驅(qū)動程序的編寫,使程序員的開發(fā)效率更高。編寫驅(qū)動程序無非就是讀寫外設(shè)的寄存器,那么在C語言里怎樣讀寫外設(shè)的寄存器呢?在內(nèi)存空間和I/O空間統(tǒng)一編址的CPU中(如采用ARM、MIPS架構(gòu)的CPU),只要定義一個指針就能象訪問普通變量一樣訪問寄存器,如某個寄存器是8位寬,地址為10000,則在C語言中,你可以象下面這樣訪問這個寄存器:
#define (((volatile unsigned char) 10000)) a a=100; //寫寄存器 b=a; //讀寄存器
對于上面的例子,(volatile unsigned char*) 10000)表示定義一個值為10000的指針,這個指針的類型是unsigned char型,也就是8位寬,如果你想訪問的寄存器是16為寬,那類型可以定義為unsigned short int,如果是32位寬,類型可以定義為unsigned int。volatile的意思是告訴編譯器這個指針所指向的值可以不由CPU賦值就能改變,編譯器不能優(yōu)化與此值有關(guān)的代碼。((volatile unsigned char) 10000)的意思是取指針所指向的存儲單元的值,跟我們經(jīng)常用的p是一個道理。(((volatile unsigned char*) 10000))中最外面的括號是為了保證編譯器正確理解我們的宏而添加的。因為C語言的宏只是進行簡單的替換,如果不在宏的外面加括號,宏被替換后,其意義可能就變了。請看下面的例子:
#define t 20+30
h=t10;
程序員的原意是讓t的值為20+30,即50,然后拿50乘以10,結(jié)果是500。可惜宏被替換后,h=t10就變成了h=20+30*10,執(zhí)行完這個語句后,h的值是320,而不是500!!!現(xiàn)在,你體會到在宏定義的最外層加括號的重要意義了嗎?
現(xiàn)在,我們清楚了在內(nèi)存空間和I/O空間統(tǒng)一編址的CPU中怎樣訪問寄存器了,可惜我們最常用的intelCPU卻是把內(nèi)存空間和I/O空間分別編址的,其實“最常用”這個詞很不準確,ARM、MIPS等嵌入式CPU比intel的CPU用得更廣泛,只不過不搞嵌入式的朋友對這些真正最常用的CPU不熟悉罷了。嘿嘿,又扯遠了,還是說說intelCPU怎樣訪問外設(shè)的寄存器吧,很遺憾目前我只知道用內(nèi)嵌匯編在intelCPU中訪問外設(shè)的寄存器,但我想C語言編譯器可以增加一個關(guān)鍵詞,用來指示某個變量或指針是位于I/O空間的,這樣就可以在C語言中象訪問普通變量一樣訪問外設(shè)的寄存器了。 外設(shè)的一個寄存器可能用來表示多種意義,如:某個8位寬的寄存器表示的意義可能是這樣的:權(quán)值最高的3位表示外設(shè)的工作模式,次高的3位表示工作速度,最低兩位表示傳輸方式。現(xiàn)在你想讓這個外設(shè)用某種工作模式、工作速度和傳輸方式工作,你怎樣填寫這個寄存器呢?一種直觀的方法就是用移位、與、或等位操作的方法拼湊好這個命令,然后一次性地把命令填到寄存器中。顯然,拼湊的方法比較繁瑣,容易出錯,并且寄存器各位表示的意義在源代碼中體現(xiàn)不出來。幸運的是,C語言對這種操作進行了支持,你可以象下面這個例子這樣快速、高效地組織一個命令:
struct command
{
unsigned char work_mode : 3;
unsigned char work_speed : 3;
unsigned char transfer_mode : 2;
};
在上面的結(jié)構(gòu)體定義中,冒號后面的數(shù)字表示該域所占的二進制位,我們暫且稱之為位段,各個位段是挨在一起的。定義一個類型為command的結(jié)構(gòu)體A后,我們就能象訪問一個普通結(jié)構(gòu)體那樣去訪問各個位段了。下面我們組織一個命令:
A.work_mode=3; //填好工作模式
A.work_speed=2; //填好工作速度
A.transfer_mode=3; //填好傳輸模式
我們用3句話就組織好了一個命令,這顯然比拼湊的方法高效,更加重要的是,這種方法在源代碼中體現(xiàn)了各個位段表示的意義,也就是增加了源代碼的可讀性,不要小看這點哦,它能大大減少程序員由于疏忽所犯的錯誤!!!我認為大名鼎鼎的C++的最大功績就是強迫程序員增加源代碼的可讀性,從而大大減少程序員犯錯誤的概率。
OK,我能寫的也就這么多了,寫得好累,希望這篇文章能使讀者對外設(shè)和驅(qū)動程序有一個初步的認識,有一些啟發(fā)作用,那我就心滿意足了。
總結(jié)
以上是生活随笔為你收集整理的cpu与外设工作原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 汉字在屏幕上的显示过程以及乱码的原因
- 下一篇: 程序包不存在?无源文件?找不到文件?找不