處理器是如何驅動外設的

一、外設的組成html

外圍設備一般包含一個機械部件和一個電子部件。爲了達到設計的模塊性和通用性,通常將其分開。程序員

電子部分稱爲設備控制器或適配器(在我的計算機中,它經常是一塊能夠插入主板擴展槽的印刷電路板卡)。算法

機械部分則是設備自己。windows

控制器卡上通常都有一個接線器,能夠將與設備相連的線接進來。網絡

 

注:處理器經過控制外設的設備控制器上的寄存器來驅動外設工做。數據結構

 

二、處理器芯片上會集成哪些外設和接口架構

處理器芯片設計廠家根據本身芯片所面對的市場,在處理器核(如 ARM)上集成須要的外圍設備和接口。函數

例如: GPIO, UART, IIC, SPI, ADC, NAND FLASH, DDR 等。優化

 

三、處理器芯片和外設鏈接的橋樑spa

橋樑1: 寄存器(位於設備控制器 / 適配器上)。

    採用寄存器方式的有 GPIO,UART,IIC,SPI,ADC,NAND FLASH,DDR 等。

  橋樑2: 總線接口。

    採用總線接口方式的有 Nor Flash,SDRAM 控制器等。

 

四、外設的地址編址

  通常來講,外設的地址編址分紅兩種類型:

  (1) 集成在 CPU 內部的,該類外設的物理地址是固定的(查 CPU 的 DataSheet 可知)。

  (2) 與 CPU 物理上分離的,如 Nor Flash,SDRAM 網絡控制器,該類外設的物理地址由片選接線決定。

  注:在 CPU 上都會引出多個片選線。每個片選線表示對應一個固定的地址範圍,在術語上稱爲(Bank)。

 

五、參考(轉)

  節選自:https://wenku.baidu.com/view/ad93508171fe910ef12df8fa.html 

首先,講講外設的基本構成。每一個外設都有一個控制器,這個控制器是數字電路,控制器裏有一些叫「寄存器」的存儲單元,這些東東的物理結構跟內存單元不同,但做用跟內存單元同樣,都能保存信息。寄存器各有各的做用,好比:軟驅、硬盤上有保存磁頭號、磁道號、扇區號等參數的寄存器,這些寄存器的值告訴硬盤此次讀磁盤操做要讀的是哪一個盤面哪一個磁道哪一個扇區的數據。根據寄存器的做用,可將寄存器分爲兩類,分別叫控制寄存器和狀態寄存器。控制寄存器用來告訴外設: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的詳細用法及編譯優化的內容請看個人另外一篇文章(還沒寫,因此無法在此定題目,呵呵)。*((volatile unsigned char*) 10000)的意思是取指針所指向的存儲單元的值,跟咱們常常用的*p是一個道理。(*((volatile unsigned char*) 10000))中最外面的括號是爲了保證編譯器正確理解咱們的宏而添加的。由於C語言的宏只是進行簡單的替換,若是不在宏的外面加括號,宏被替換後,其意義可能就變了。請看下面的例子:

                 #define t 20+30

                 h=t*10;

      程序員的原意是讓t的值爲20+30,即50,而後拿50乘以10,結果是500。惋惜宏被替換後,h=t*10就變成了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++的最大功績就是強迫程序員增長源代碼的可讀性,從而大大減小程序員犯錯誤的機率。

相關文章
相關標籤/搜索