日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

cpu与外设工作原理

發布時間:2025/4/17 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 cpu与外设工作原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

總結來說,就是插上外設后,cpu就可以檢測和連接到外設上的寄存器,把它當成內存來使用,然后就是對這些寄存器進行讀寫,寫控制寄存器來控制外設,讀狀態寄存器來檢測外設狀態(外設會把當前狀態信息放到指定寄存器上),通過讀寫數據寄存器來交換數據。


一直以來,發現很多搞上層軟件的朋友沒有時間了解CPU、編譯器、操作系統等底層技術,偶恰好在計算機微體系結構與集成電路實驗室,有幸接觸到這些底層的東東,所以想寫一些自己以前學這些東東的感想,以消除對底層技術不熟悉的朋友對底層技術的神秘感,同時想和搞底層技術的朋友切磋切磋,共同提高。當然偶所談的內容都不是先進或深奧的,而是最直觀和最容易理解的,偶所寫的文章不是闡述各個專題的專著,而是入門讀物,希望讀者讀完偶的文章后具有讀懂各個專題專著的能力。

閑話少說,讓我們切入正題。我們從驅動程序出發,慢慢講解計算機的各個部分是如何各自為政,而又互相協作,從而完全各種復雜功能的。本文不具體闡述如何編寫驅動程序,而是從體系結構的觀點著手,力爭用通俗易懂的語言闡述各種外設的共同特點,使讀者具備舉一反三、融會貫通、駕馭各種外設的能力。另外,筆者喜歡從不同的角度分析同一個問題,所以行文中難免出現重復的內容,累贅的闡述,筆者正是希望通過這種重復和累贅來加深讀者對所述內容的理解。

計算機發展到今天,其外設早已是五花八門,象硬盤、軟盤、光盤、U盤、鼠標、鍵盤、聲卡、網卡、SD卡、手柄等等,真是層出不窮。五花八門的外設給我們帶來便利的同時也帶來了許多問題,比如:

主板上的接口個數有限,怎樣保證各種離奇古怪的外設能連接主板并跟主機通信?

怎樣保證CPU能一個不漏地控制外設工作?CPU能夠控制什么樣的外設?

CPU對外設的控制能達到什么程度?

怎樣保證CPU不會誤操作外設?

怎樣保證外設之間不會“打架”、互相干擾?

外設怎樣向CPU報告處理結果?

多個進程怎樣共享外設?

高級語言怎樣支持驅動程序的編寫?

外設怎樣給CPU提供配置信息等等,這些問題是否讓各位看官頭大了?不要緊張,且聽我慢慢道來。 首先,講講外設的基本構成。每個外設都有一個控制器,這個控制器是數字電路,控制器里有一些叫“寄存器”的存儲單元,這些東西的物理結構跟內存單元不一樣,但作用跟內存單元一樣,都能保存信息。

寄存器各有各的作用,比如:軟驅、硬盤上有保存磁頭號、磁道號、扇區號等參數的寄存器,這些寄存器的值告訴硬盤這次讀磁盤操作要讀的是哪個盤面哪個磁道哪個扇區的數據。根據寄存器的作用,可將寄存器分為兩類,分別叫控制寄存器和狀態寄存器。控制寄存器用來告訴外設:CPU要求它干什么活以及它干活時需要的參數;狀態寄存器用于外設向CPU報告外設目前的狀態,比如,外設目前在干什么活,在干活的過程中是否發生了錯誤,外設是否還有能力接受新任務等等,狀態寄存器沒有能力主動告訴CPU外設當前的狀態,而是被動地等待著CPU來取狀態信息,CPU把狀態寄存器的值讀出來就能知道外設當前的工作狀態。當然,外設也有主動報告CPU的能力——中斷。寄存器有的是只寫的,有的是只讀的,還有的是可讀可寫的。一般而言,控制寄存器是只寫或可讀可寫的,狀態寄存器是只讀的。

除了控制器外,大多數外設還有一個用來具體干活的模擬電路,如硬盤有控制磁頭移動、盤片轉動的模擬電路,打印機有控制打印紙滾動,控制噴墨或打印針擊打打印紙的模擬電路,MP3有數模轉換器和功率放大器等等。控制器和模擬電路通常是集成在一塊芯片里,這種集成電路叫數模混合電路。數模混合電路是目前IT領域頗具挑戰性的技術之一,如果某天你能設計數模混合電路了,那么恭喜你,這輩子你再也不用愁吃穿住行了!當然,也有純數字電路的外設,如DMA控制器。以前的外設由于技術不成熟,其控制器、模擬電路、電機等部件是分離的,現在大多數外設把控制器、模擬電路及電機、盤片(如果有的話)等等各個部件集成在一起,如硬盤。有的外設只是把控制器、模擬電路及電機集成在一起,盤片是可移動的,如光驅、軟驅。這種把控制器、模擬電路及電機等部件集成在一起的外設稱為智能外設。

那么,怎樣保證CPU能一個不漏地控制多個外設呢?原來,多個外設和CPU都掛在一組總線上,硬件工程師給外設的每個寄存器都分配一個地址,CPU拿一個地址去訪問某個寄存器時,只有該寄存器發生動作,或接收數據總線上的數據(對應于寫操作),或把自己的數據送到數據總線上(對應于讀操作),同一個外設的其他寄存器和其他外設的寄存器都不會動作。這樣,CPU用不同的地址就可以訪問不同的寄存器,也就可以一個不漏地控制多個外設了。CPU訪問某個寄存器時,別的寄存器不會發生動作,所以,外設之間不會“打架”、不會互相干擾。同樣地,CPU訪問內存時,其地址不是外設的寄存器的地址,所有的外設都不會動作,所以CPU不會誤操作外設。

根據外設的基本結構,你是否已經猜到CPU控制外設的能力了?顯然,CPU控制外設的方法和能力無非就是讀寫寄存器。比如,CPU要從硬盤讀文件,那么CPU只需要把磁頭號、磁道號、扇區號、要讀的數據量等參數填入硬盤控制器的對應寄存器,然后向硬盤控制器的對應寄存器填一個開始命令,硬盤控制器就命令接在其后面的模擬電路開始工作——如:控制電機移動磁頭到對應的磁道、對準扇區,讀數據等等。至于磁頭目前在什么位置,怎樣移動到對應的磁道,順指針移動還是逆時針移動,以多快的速度移動,磁頭移動到對應磁道后以多大的加速度減速等等,這些事情不是CPU所能控制的,而是由硬盤控制器和接在硬盤控制器后面的模擬電路共同控制的。遺憾的是,集成電路和印制電路板(PCB板)的技術已經很成熟,硬盤控制器、接在硬盤控制器后面的模擬電路以及磁頭、盤片、控制磁頭移動的電機等部件早已集成在一個小小的長方體盒子里,我們已經沒有機會一睹各個部件的芳容了。總之,CPU只能控制外設中數字部分的程序員可見的寄存器,無法控制程序員不可見的寄存器,更加無法控制模擬電路、電機等部件,也就是說CPU只能告訴外設要干什么活以及干活過程中需要的參數,至于外設是怎么干活,如:硬盤怎么移動磁頭、音頻芯片怎么把數字信號轉成模擬信號,怎么把模擬信號放大等等,這些事情是CPU無法控制的。

外設一般有兩種方式報告CPU外設的工作狀態——程序查詢方式和中斷方式。程序查詢方式就是利用狀態寄存器報告CPU外設的工作狀態,外設只需要把其工作狀態的信息填到狀態寄存器里,可惜的是狀態寄存器沒有能力主動告訴CPU它里面的值是多少,而只能被動地等待著CPU讀取它的值。所以,CPU需要不斷地讀取狀態寄存器,來判斷外設是否已經干完活。顯然,這種方法的效率很低,程序每讓外設干一次活就得不斷查詢狀態寄存器,一直在做無用功,無法把CPU時間讓給別的進程,直到外設干完活后,程序才能往下執行。中斷方式要求外設具有向CPU發送中斷請求的能力,外設每次干完活后就主動向CPU發中斷請求,注意是主動發中斷請求,可惜的是,中斷請求只能告訴CPU外設已經干完活,至于在干活的過程中外設是否發生錯誤,外設的空閑緩沖區還剩多少等其他信息無法在中斷請求中表達,所以中斷方式也離不開狀態寄存器,CPU響應中斷后,可以讀一下狀態寄存器,以了解外設的更多更詳細的信息。由于中斷方式是主動方式,所以進程讓外設干活后就可以把CPU時間讓給別的進程,外設干完活后,中斷處理程序會喚醒該進程,這就是中斷方式比程序查詢方式高效的原因。

下面,講講多個進程怎樣共享外設。從共享的角度劃分,外設分為共享設備和獨占設備。共享設備就是在某個活沒干完時,別的進程可以讓該設備干別的活,如進程A要從硬盤讀10MB的數據,讀完8MB數據時,進程B要求硬盤讀5MB數據給它,這時磁盤調度算法可能讓硬盤先把B需要的5MB數據讀給B,回頭再給A讀最后的2MB數據,具有硬盤這種特點的設備就叫共享設備。獨占設備就是外設在干某個活時,一定要先干完這個活才能干別的活,如打印機正在打印進程A的文檔,那么在打印A的文檔的過程中,打印機不能給其他進程打印東西,否則,打印出來的東西就面目全非了,具有打印機這種特點的設備就叫獨占設備。

下面,我們以打印機為例來說明多個進程怎樣共享“獨占設備”的。操作系統可以設置一個打印隊列,準備一個打印機的驅動程序C,打印機每打印完一個作業時,給CPU發中斷,CPU響應中斷,轉入內核態,并跳到C執行,C把該作業對應的進程喚醒,從打印隊列里取出一項新作業,把相關參數如待打印數據的開始地址、數據量等,填到打印機的對應寄存器里,然后發一個“開始”命令,打印機開始打印新的作業,打印完后再給CPU發中斷,如此周而復始地工作。某個進程想打印數據是,調用相應的API函數D,D把待打印的數據組織成一個打印作業,插入到打印隊列的末尾,把進程狀態設為掛起狀態,然后調用進程調度函數切換別的進程執行,在以后的某個時刻,該進程的作業被打印完,C隨即把該進程喚醒,將進程狀態設為就緒狀態,該進程就能往下執行了。

OK,獨占設備到此結束,下面以硬盤為例講講多個進程是怎樣共享“共享設備”的。硬盤在其控制器上設置有一個緩沖區用來暫時保存從盤片讀來的數據或從內存寫過來的將要寫到盤片去的數據。緩沖區的大小有限,如8MB,而讀寫的文件可能很大,如一個視頻文件可能有幾百MB大,所以,一個讀寫作業可能需要讀寫多次才能完成。同樣地,操作系統需要設置一個類似于剛才所說的“打印隊列”的數據結構用來記錄各個進程待讀寫的數據,需要準備一個硬盤中斷處理程序E。硬盤完成一次讀寫后給CPU發中斷,CPU轉入內核態并跳到E執行,如果是寫操作,E把硬盤緩沖區里的數據搬到內存,然后根據某種磁盤調度算法,如:先來先服務、電梯算法、最短尋道優先等算法從各個讀寫作業中調一個它認為最好的作業出來,并命令硬盤處理該作業。如果在某次中斷處理過程中發現某個進程的待讀寫數據的剩余數據量為0,則表明該進程的讀寫作業已經完成,E把該進程喚醒,并把進程狀態設為就緒狀態,該進程就能往下執行了。

主板上的接口個數有限,怎樣保證各種離奇古怪的外設能連接主板并跟主機通信呢?答案是標準接口。主板上只設置了所謂的標準接口,如IDE接口、串口、并口、PS/2接口、USB接口、PCI接口等等,至于你拿USB口接打印機還是游戲手柄還是數碼相機還是別的什么東東,主板就管不了了。如果你想做一個新外設,那么首先要考慮好用什么接口跟主板連接,當然只能從標準接口里選擇,然后還要寫一個驅動程序,把外設連同驅動程序一起給用戶,用戶就能使用該外設了。當然,操作系統自帶了常用外設的驅動程序,據說windowXP自帶了2000多個驅動程序,暈,怪不得弄得windows越來越大,有些驅動程序可能我們一輩子也用不上,可它偏偏躺在那占用我們的硬盤空間! 我們經常說,電腦開機時BIOS首先要進行自檢,即檢查電腦連著什么外設,這些外設是否能正常工作,如果某個外設出現故障,BIOS還能根據不同的故障發出不同的報警聲。BIOS也是一段程序,它憑什么能做到上面所說的事情呢?我們自己寫一段程序,是不是也能做到上面所說的事情呢?不要急,請聽我慢慢道來。

原來,人們在設計外設時就考慮了自檢功能,如鼠標設置了一個查詢/應答命令,BIOS檢查電腦是否連著鼠標時只需要向鼠標對應的寄存器發一個查詢命令,如0xaa。如果電腦連著鼠標,鼠標就把此查詢命令原封不動地送到另一個寄存器F,然后,BIOS再讀F的值,如果F的值是0xaa,則表明鼠標存在,否則,讀進來的值就是0xff或0x00,這表明鼠標不存在。如果你熟悉數字電路,你一定知道為什么此時讀進來的值會是0xff或0x00。現在,你清楚BIOS怎樣檢查外設是否存在了吧。

那么,BIOS怎樣檢查存儲體如內存、硬盤的大小呢?對于內存,BIOS從0地址開始,每隔1KB的間隔寫一個數(如0xaa)到內存,然后再從這個地址讀數,如果讀出來的數跟寫進去的數相等,則表明這1KB的內存是存在的,據此把內存容量增加1KB,如果你的電腦比較慢,你可以在電腦開機時看到屏幕上顯示的檢測到的內存容量是以1KB的步長不斷增大的。對于32位CPU而言,只要在0~4GB的地址范圍檢查一遍,就能知道內存的大小。BIOS怎么檢查硬盤的大小呢?不會也象檢查內存一樣寫一遍硬盤吧?如果寫一遍硬盤豈不是把硬盤原來的數據給擦了???當然不會寫一遍硬盤!還記得上面提到的智能外設嗎?原來,智能外設里一般有一些只讀的寄存器保存著這個外設的配置信息,硬盤里就要這樣的寄存器保存著該硬盤的大小,BIOS只需要讀一下該寄存器就知道硬盤的大小了。由于硬盤的盤片是固定的,一旦出廠,硬盤的容量是不變的,所以BIOS讀到的硬盤大小是不會錯的。

那么,光盤和軟盤呢?它們可不是固定的?我拿來一張光盤,你怎么知道光盤的容量?答案是工業標準。雖然從理論上說,一張光盤的容量可以是任意值,如1.23MB,可惜工業標準規定了這種容量是非法的,工業標準只允許光盤的容量是少數幾個值,如VCD的容量是700多MB,DVD的容量是4000多MB,把一張光盤插入光驅后,光驅先檢測該光盤是VCD格式還是DVD格式(這可以從數據密度不同檢查出來),并據此判斷該光盤的容量。如果你有能力制作光盤,你當然可以制作一張容量只有1.23MB的光盤,只可惜這張光盤違反了標準的規定,別人都不懂怎么使用這張光盤罷了。

說了這么多,你清楚BIOS怎樣檢測外設了嗎?你能自己寫一段程序,象BIOS那樣檢測外設了嗎?我想這兩個問題已經難不倒聰明的你了,但你是否看到了BIOS自檢的一些缺陷呢?比如,我的內存的地址為1500的存儲單元壞了,BIOS能檢測到嗎?又如,鼠標雖然能應答查詢命令,但保存鼠標移動量的寄存器壞了,BIOS能檢測到嗎?答案當然是不能。所以,如果BIOS發出報警聲,電腦一定有問題;BIOS沒發出報警聲,電腦也有可能有問題,這種問題更讓你郁悶,因為你根本不知道哪出了問題。我的同學就遇到過裝系統時,裝了一半就莫名其妙地不動了,檢來檢去原來是內存壞了一個單元,狂暈!

最后,我們以C語言為例,講講高級語言怎樣支持驅動程序的編寫,使程序員的開發效率更高。編寫驅動程序無非就是讀寫外設的寄存器,那么在C語言里怎樣讀寫外設的寄存器呢?在內存空間和I/O空間統一編址的CPU中(如采用ARM、MIPS架構的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賦值就能改變,編譯器不能優化與此值有關的代碼。((volatile unsigned char) 10000)的意思是取指針所指向的存儲單元的值,跟我們經常用的p是一個道理。(((volatile unsigned char*) 10000))中最外面的括號是為了保證編譯器正確理解我們的宏而添加的。因為C語言的宏只是進行簡單的替換,如果不在宏的外面加括號,宏被替換后,其意義可能就變了。請看下面的例子:

#define t 20+30

h=t10;
程序員的原意是讓t的值為20+30,即50,然后拿50乘以10,結果是500。可惜宏被替換后,h=t10就變成了h=20+30*10,執行完這個語句后,h的值是320,而不是500!!!現在,你體會到在宏定義的最外層加括號的重要意義了嗎?

現在,我們清楚了在內存空間和I/O空間統一編址的CPU中怎樣訪問寄存器了,可惜我們最常用的intelCPU卻是把內存空間和I/O空間分別編址的,其實“最常用”這個詞很不準確,ARM、MIPS等嵌入式CPU比intel的CPU用得更廣泛,只不過不搞嵌入式的朋友對這些真正最常用的CPU不熟悉罷了。嘿嘿,又扯遠了,還是說說intelCPU怎樣訪問外設的寄存器吧,很遺憾目前我只知道用內嵌匯編在intelCPU中訪問外設的寄存器,但我想C語言編譯器可以增加一個關鍵詞,用來指示某個變量或指針是位于I/O空間的,這樣就可以在C語言中象訪問普通變量一樣訪問外設的寄存器了。 外設的一個寄存器可能用來表示多種意義,如:某個8位寬的寄存器表示的意義可能是這樣的:權值最高的3位表示外設的工作模式,次高的3位表示工作速度,最低兩位表示傳輸方式。現在你想讓這個外設用某種工作模式、工作速度和傳輸方式工作,你怎樣填寫這個寄存器呢?一種直觀的方法就是用移位、與、或等位操作的方法拼湊好這個命令,然后一次性地把命令填到寄存器中。顯然,拼湊的方法比較繁瑣,容易出錯,并且寄存器各位表示的意義在源代碼中體現不出來。幸運的是,C語言對這種操作進行了支持,你可以象下面這個例子這樣快速、高效地組織一個命令:

struct command
{
unsigned char work_mode : 3;
unsigned char work_speed : 3;
unsigned char transfer_mode : 2;
};
在上面的結構體定義中,冒號后面的數字表示該域所占的二進制位,我們暫且稱之為位段,各個位段是挨在一起的。定義一個類型為command的結構體A后,我們就能象訪問一個普通結構體那樣去訪問各個位段了。下面我們組織一個命令:

A.work_mode=3; //填好工作模式
A.work_speed=2; //填好工作速度
A.transfer_mode=3; //填好傳輸模式
我們用3句話就組織好了一個命令,這顯然比拼湊的方法高效,更加重要的是,這種方法在源代碼中體現了各個位段表示的意義,也就是增加了源代碼的可讀性,不要小看這點哦,它能大大減少程序員由于疏忽所犯的錯誤!!!我認為大名鼎鼎的C++的最大功績就是強迫程序員增加源代碼的可讀性,從而大大減少程序員犯錯誤的概率。

OK,我能寫的也就這么多了,寫得好累,希望這篇文章能使讀者對外設和驅動程序有一個初步的認識,有一些啟發作用,那我就心滿意足了。

總結

以上是生活随笔為你收集整理的cpu与外设工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。