?? (ldd) ch08-硬件管理(轉載).htm
字號:
color=#ffffff size=3>
<P>的是上面給出的循環,/dev/short0p使用outb_p和inb_p來替代前者使用的“較快的”函<BR>數,而/dev/short0s使用了串指令。共有四個這樣的設備,從short0到short3,每個都<BR>讀寫一個端口。這四個端口是連續的。<BR> <BR> <BR> <BR> 在Alpha上編譯時,由于不提供insb和outb,short0s設備就和short0一樣了。<BR> <BR> <BR> <BR> 雖然short不能進行“真正的”硬件控制,但它可以作為一個對不同的指令計時<BR>的有趣的測試平臺,并且可以作為一個學習如何進行“真正的”硬件控制的開始。任何<BR>對寫驅動程序有興趣的人肯定擁有更多更有趣的設備,但老的傻瓜式的并口還是能作些<BR>有用的工作的-我自己就是在早上打開收音機后用它來為我準備咖啡的。<BR> <BR>訪問設備卡上的內存<BR> 上一章介紹了分配RAM內存的所有各種方法;現在我們要介紹計算機能提供的另<BR>一種內存:擴展卡上的內存。外設也有內存。顯卡有幀緩沖區,視頻捕捉卡用來存放捕<BR>捉到的數據,而網卡將接受到的數據包存放在內存區域中;此外,大多數設備卡上還有<BR>卡上ROM,存放著系統啟動時處理器要執行的代碼。所有這些都是“內存”,因為處理器<BR>通過訪存指令來對它們進行讀寫。這里我只討論ISA和PCI設備,因為現在它們最常用。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 在標準的x86機器上共有三種通用的設備內存:640KB到1MB地址范圍內的ISA內存<BR>,14MB到16MB地址范圍內的ISA內存,和在物理地址空間之上的PCI內存。上面這些用到<BR>的地址都是物理地址,是在計算機的地址總線上的跑的數值,與程序代碼中所使用的虛<BR>擬地址沒有任何關系(參見第7章“獲取內存”的“vmalloc和相關函數”一節)。I/O內存<BR>使用這些物理位置主要是出于歷史的原因,下面介紹這三種內存區域時會作出解釋。<BR> <BR> <BR> <BR> 不幸的是(或者,幸運的是,如果你更傾向于好的體系結構設計而不是容易移植<BR>的話)。不是所有的Linux平臺都支持ISA和PCI;本節只限于討論支持ISA和PCI的Linux平<BR>臺。<BR> <BR>1M地址空間之下的ISA內存<BR> 在第2章“編寫和運行模塊”的“ISA內存”一節中我已經介紹過一種“容易的”<BR>(但有些毛病的)訪問這種內存的方法,在那里我使用了存放物理地址的指針來正確地指<BR>向所申請的I/O內存。盡管這種技術在x86平臺可行,但卻不能移植到其它的Linux平臺上<BR>。使用指針的方法對小的生命期較短的項目是較好的選擇,但對作為產品的驅動程序并<BR>不推薦。<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> 推薦的I/O內存接口是在linux1.3版的開發樹中被引入內核的,更早的版本并不<BR>提供。與short范例模塊一起發布的sysdep.h頭文件為1.2版以來的各個內核版本實現了<BR>這種新的語義。<BR> <BR> <BR> <BR> 新的接口包括一系列宏和函數調用,取代了使用指針來訪問I/O內存的方法。這<BR>些宏和函數是可移植的;這意味著相同的源碼可以在不同的體系結構上編譯和運行,只<BR>要它們擁有類型相同的設備總線。<BR> <BR> <BR> <BR> 當你在基于Intel處理器的平臺上編譯代碼時,這些宏現在大部分會擴展對指針<BR>的操作,但它們的內部實現會在將來被適當地修改。例如,在Linux最初從2.0版轉到2.1<BR>版時,這樣的修改就發生過,Linus決定改變虛存的布局。在新的布局下,就不能再以第<BR>2章中介紹的舊的方式訪問ISA內存了。<BR> <BR> <BR> <BR> 新的I/O內存接口由下面這些函數組成:<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR>unsigned readb(address);<BR> <BR>unsigned readw(address);<BR> <BR>unsigned readl(address);<BR> <BR>這些宏用于從I/O內存中取得8位,16位和32位的數據值。1.2版的Linux不提供。使用宏<BR>的優點是對參數的類型不作要求;參數address在使用前會被強制類型轉換,因為這個值<BR>“不清楚是整數還是指針,但我們兩者都能接受”(見 asm-alpha/io.h)。讀和寫函數都<BR>不會檢查address參數的合法性,因為使用它就是為了能和使用指針一樣快(我們已經知<BR>道有時它們實際上就是被擴展成指針操作)。<BR> <BR> <BR> <BR>unsigned writeb(unsigned value, address);<BR> <BR>unsigned writew(unsigned value, address);<BR> <BR>unsigned writel(unsigned value, address);<BR> <BR> 和前面的函數類似的,這些函數(宏)用于寫8位,16位和32位的數據項。<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 和前面的函數類似的,這些函數(宏)用于寫8位,16位和32位的數據項。<BR> <BR> <BR> <BR>memset_io(address,value,count);<BR> <BR>當你要對I/O調用memset進行操作時,這個函數可以滿足你的需要,并且也保留了memset<BR>原來的語義。<BR> <BR> <BR> <BR>memcpy_fromio(dest, source, nbytes);<BR> <BR>memcpy_toio(dest, source, nbytes);<BR> <BR>這些函數用于成塊傳輸I/O內存的數據,和memcpy_tofs的功能有些相似。它們是和上面<BR>這些函數一起引入Linux中的,1.2版的Linux不提供。與示例代碼一起發布的sysdep.h頭<BR>文件修正了函數的版本相關性問題,為1.2版以后的所有內核提供了這些函數的定義。<BR> <BR> <BR> <BR> 和I/O端口函數一樣的,這些函數在能支持的體系結構間的移植性現在也很有限<BR>。一些平臺根本不提供這些函數;一些平臺上它們是被擴展為指針操作的宏,而在另一<BR></P></FONT><FONT
color=#ffffff size=3>
<P>。一些平臺根本不提供這些函數;一些平臺上它們是被擴展為指針操作的宏,而在另一<BR>些平臺上它們則是真正的函數。<BR> <BR> <BR> <BR> 象我這種習慣于舊PC機的平的(flat)內存模式的人,可能會不愿意為了只是讀寫<BR>“物理地址區域”而麻煩地使用新的一套接口。實際上,要習慣使用某套接口只需要練<BR>習練習使用這些函數。當然,沒有比看看一個訪問I/O內存的傻瓜式(silly)模塊更好的<BR>獲得自信的方法了。我下面要給你展示的模塊就叫作silly,是“Simple Tool for<BR>Unloading and Printing ISA Data,卸載和打印ISA數據的簡單工具”的簡稱。<BR> <BR> <BR> <BR>該模塊包括四個用了不同的數據傳輸函數來完成相同任務的設備節點。silly設備是作為<BR>I/O內存之上的一個窗口,與/dev/mem有些類似。對該設備,你可以讀寫數據,lseek到<BR>一個任意的I/O內存地址,也可以將I/O內存區域mmap到你的進程中來(參見第13章“Mmap<BR>和DMA”的“mmap設備操作”一節)。<BR> <BR> <BR> <BR>/dev/sillyb,次設備號為0,調用readb和writeb函數來讀寫內存空間。下面的代碼給出<BR>了read的實現,它將0xA0000到0xFFFFF的地址范圍重映射到設備文件的偏移量從0到0x5F<BR>FFF的位置。read函數將不同的訪問模式組織進一個switch語句中;下面是sillyb設備的<BR></P></FONT><FONT
color=#ffffff size=3>
<P>FFF的位置。read函數將不同的訪問模式組織進一個switch語句中;下面是sillyb設備的<BR>case分支:<BR> <BR> <BR> <BR>case M_8:<BR> <BR> while (count){<BR> <BR> *ptr=readb(add);<BR> <BR> add++;count--;ptr++;<BR> <BR> }<BR> <BR> break;<BR> <BR> <BR> <BR>接下來的兩種設備是/dev/sillyw(次設備號為1)和/dev/sillyl(次設備號為2)。它們和/<BR>dev/sillyb設備差不多,只是分別使用了16位和32位的函數。下面是sillyl設備的write<BR>函數的實現,也是switch語句的一部分。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR>case M_32:<BR> <BR> while (count>=4 ){<BR> <BR> writel(*(u32*)ptr,add);<BR> <BR> add+=4;count-=4;ptr+=4;<BR> <BR> }<BR> <BR> break;<BR> <BR> <BR> <BR> 最后一種設備是/dev/sillycp(次設備號為3),該設備用memcpy_*io函數來執行<BR>相同的任務。下面是它的read函數實現的核心:<BR> <BR> <BR> <BR> case M_memcpy:<BR></P></FONT><FONT
color=#ffffff size=3>
<P> case M_memcpy:<BR> <BR> memcpy_fromio(ptr, add, count);<BR> <BR> break;<BR> <BR>1M地址空間之上的ISA內存<BR> 有些ISA設備卡帶有的卡上內存會映射到物理地址空間的14MB和16MB范圍內。這<BR>些設備正在逐漸消失,但仍值得介紹一下如何訪問它們的內存區域。但本討論僅適用于x<BR>86體系結構;我沒有這種ISA卡在Alpha或其它體系結構上相應行為方面的資料。<BR> <BR> <BR> <BR> 在使用80286處理器的舊時代,物理地址空間的寬度是20個位(16MB),所有的地<BR>址線都在ISA總線上,幾乎沒有計算機帶的RAM會超過1兆或2兆。那為什么擴展卡不能“<BR>偷”點高端的內存地址用做它的緩沖區呢?這種想法并不新鮮;相同的概念已經出現在<BR>對1M地址空間之下的ISA內存的使用上了,后面又會再次用來實現對高端PCI內存的使用<BR>上。選作ISA設備卡內存的地址范圍是最頂端的2M,盡管大多數卡只使用了最頂端的1M。<BR> <BR> <BR> <BR> <BR> 只到今天,在一些主板仍能使用舊式的設備卡,即使物理內存超過了14M。要正<BR></P></FONT><FONT
color=#ffffff size=3>
<P> 只到今天,在一些主板仍能使用舊式的設備卡,即使物理內存超過了14M。要正<BR>確地處理這段內存區域要求你對這段地址范圍作些處理,避免將內存地址和總線地址重<BR>疊起來。<BR> <BR> <BR> <BR> 如果你有一塊帶有高端內存的ISA設備,又不幸地RAM小于16MB,那管理內存就很<BR>容易。你的軟件在處理時就好象擁有著高端PCI緩沖區(參見下一節),只是更慢些,因為<BR>ISA內存比較慢。<BR> <BR> <BR> <BR> 而如果你擁有的ISA設備帶了高端內存而你的RAM要多于16MB,那么就有麻煩了。<BR> <BR> <BR> <BR> <BR> 一種可能就是你的主板不能正確地支持“ISA空洞”。在這種情況下,除非你拔<BR>掉一些內存,否則無法訪問卡上內存。另一種可能是,主板能處理ISA空洞,你仍要告訴<BR>Linux內核這種內存的存在,作一些處理以便能訪問RAM的其它部分(超過16M的地址范圍)<BR>。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 你需要作些“黑客”的工作來正確保留高端的ISA內存,同時又仍能對其余的RAM<BR>進行正常訪問,要修改的部分是對計算機的物理內存進行映射的部分。這個映射部分是<BR>在源文件arch/i386/mm/init.c的函數mem_init中實現的。數組mem_map中存放著與內存<BR>頁有關的信息;如果某頁的PG_reserved位被設置了,內核就不會對該頁進行正常的頁面<BR>處理(也即,該頁被“保留”了,不要碰它)。但驅動程序仍可以使用保留頁;640KB到1M<BR>B間的地址范圍被標記為“保留的”,但仍可以被用作為設備內存。<BR> <BR> <BR> <BR> 下面的代碼,插在mem_init函數中,正確地保留了15MB和16MB間的地址空間:<BR> <BR> <BR> <BR>while(start_mem<high_memory){<BR> <BR> if (start_mem>=0xf00000 && start_mem<0x1000000){<BR> <BR> /* keep it reserved, and prevent couting as data */<BR> <BR> reservedpages++; datapages--;<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> }<BR> <BR> else<BR> <BR> clear_bit(PG_reserved,<BR>&mem_map[MAP_NR(start_mem)].falgs);<BR> <BR> start_mem += PAGE_SIZE;<BR> <BR> }<BR> <BR> <BR> <BR> 最初所有的內存區域都被標記為“保留的”,上面給出的代碼行保證了不會將對<BR>高端的I/O內存去除“保留”標記;原來的代碼只有上面給出的循環的else分支。因為在<BR>內核代碼之后的每個保留頁都算作內核數據,要修改兩個計數器reservedpages和datapa<BR>ges以避免啟動時給出不匹配的信息。我的機器,有32MB內存,用前面的代碼來訪問ISA<BR>空洞,啟動是的報告如下:<BR> <BR> <BR> <BR> Memory: 30120/32768k available (512k kernel code, 1408k reserved,<BR></P></FONT><FONT color=#ffffff size=3>
<P> Memory: 30120/32768k available (512k kernel code, 1408k reserved,<BR>728k data)<BR> <BR> <BR> <BR> 我是在自己的安裝了2.0.29版內核的Intel機器(主板支持ISA空洞)上測試這段代<BR>碼的。如果你運行的內核版本并不相同,你可能需要修改一下代碼-與內存管理有關的<BR>內部數據結構在2.1版的內核中有一點改動,而在1.2版的內核中則很不一樣。要支持舊<BR>式的(有時是不良設計的)硬件設備不可避免地必須“黑客”內核代碼。<BR> <BR>高端PCI內存<BR> 訪問高端PCI內存比訪問高端ISA內存要更容易。PCI卡上的高端內存真的很高-<BR>高于任何合理的物理RAM地址(至少對未來若干年而言)。<BR> <BR> <BR> <BR> 正如第7章的“vmalloc和相關函數”一節討論到的,訪問該內存只要調用vremap<BR>(2.1版的內核是ioremap)。但是如果你希望代碼能在不同平臺上移植的話,你應該只通<BR>過readb和其它類似函數來訪問重映射的內存區域。由于不是所有平臺都能將PCI緩沖區<BR>直接映射到處理器的地址空間,所以必須加上這個限制。<BR> <BR>訪問字符模式的視頻緩沖區<BR> silly模塊解釋了如何訪問內存640KB到1MB地址范圍內的視頻緩沖區,而下面要<BR></P></FONT><FONT
color=#ffffff size=3>
<P> silly模塊解釋了如何訪問內存640KB到1MB地址范圍內的視頻緩沖區,而下面要<BR>介紹的更“直觀的”演示程序則能幫助你熟悉readb和writeb函數。silly模塊擁有兩個<BR>額外的設備節點:/dev/sillytxt(次設備號為4)和/dev/sillitest(次設備號為5)。<BR> <BR> <BR> <BR>——————————————————————————————————————<BR>—<BR> <BR>警告 這樣的設備只能在運行在字符模式下的VGA兼容的顯示卡上使用;在沒有VGA顯<BR>示卡的系統上使用這樣的設備,和任何對硬件資源的不受控制的讀寫一樣,具有潛在的<BR>破壞性。<BR> <BR>——————————————————————————————————————<BR>—<BR> <BR> <BR> <BR> 第一個設備,sillytxt,只是VGA字符緩沖區上的一個窗口。與其它的silly節點<BR>不同的是,它可以作為輸出重定向的目標和用于覆蓋控制臺上的顯示。這讓我們想起/de<BR>v/vcs,但silly的實現是不可移植的而且也沒有象vcs那樣集成進內核。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> 最后一個設備只是和你開個玩笑:它將字母從字符屏幕上移走。每向該設備寫進<BR>一個字符都會導致屏幕上的一個字符落到屏幕的底部。提供這個設備只是為了演示在I/O<BR>內存如何進行更復雜的操作-可以用同樣的代碼來操作VGA緩沖區或其它內存,比如網絡<BR>數據包或者幀捕捉卡上的視頻數據。<BR> <BR> <BR> <BR> 要注意對字符屏幕的任何修改都是易喪失的,并且會干擾內核自己的字符處理。<BR>如果你真的需要在應用程序中訪問字符緩沖區,那么有更好的完成這個任務的方法:通<BR>過ncurses庫或者通過/dev/vcs設備。vcs設備是“Virtual Console Screen,虛擬控制<BR>臺屏幕”,可以用它來獲得每個虛擬控制臺的字符緩沖區當前的快照或者進行修改。vcs<BR>設備的文檔就在它自己的源碼中:內核源碼樹中的drivers/char/vc_screen.c文件。或<BR>者,你可以在最新的man-pages幫助頁發布中找到關于該設備的描述。<BR> <BR>快速參考<BR> 本章引入下面一些與操縱硬件有關的符號:<BR> <BR> <BR> <BR>#include <asm/io.h><BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>unsigned inb(unsigned port);<BR> <BR>void outb(unsigned char byte, unsigned port);<BR> <BR>unsigned inw(unsigned port);<BR> <BR>void outw(unsigned short word, unsigned port);<BR> <BR>unsigned inl(unsigned port);<BR> <BR>void outl(unsigned doubleword, unsigned port);<BR> <BR>這些函數用于讀寫I/O端口。如果能正確獲取訪問端口的權限,它們也可以被用戶空間的<BR>程序調用。不是所有平臺都支持所有這些函數,它們與底層的硬件設計有關。<BR> <BR> <BR> <BR>SLOW_DOWN_IO;<BR> <BR>unsigned inb_p(unsigned port);<BR> <BR>…<BR></P></FONT><FONT
color=#ffffff size=3>
<P>…<BR> <BR>有時需要用SLOW_DOWN_IO語句來處理x86平臺上低速的ISA卡。如果在每個I/O操作之后需<BR>要一小段延遲,你可以用與上面引入的6個函數相應的暫停式版本,它們得在相應的函數<BR>名后要加個_p。<BR> <BR> <BR> <BR>void insb(unsigned port, void *addr, unsigned long count);<BR> <BR>void outsb(unsigned port, void *addr, unsigned long count);<BR> <BR>void insw(unsigned port, void *addr, unsigned long count);<BR> <BR>void outsw(unsigned port, void *addr, unsigned long count);<BR> <BR>void insl(unsigned port, void *addr, unsigned long count);<BR> <BR>void outsl(unsigned port, void *addr, unsigned long count);<BR> <BR>“串操作”用于優化從輸入端口到內存區域(或者反過來)的數據傳輸。這種傳輸是通過<BR>對同一個端口讀寫count次完成的。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR> <BR>unsigned readb(address);<BR> <BR>unsigned readw(address);<BR> <BR>void writeb(unsigned value, address);<BR> <BR>void writew(unsigned value, address);<BR> <BR>void writel(unsigned value, address);<BR> <BR>memset_io(address, value, count);<BR> <BR>memcpy_fromio(dest, source, nbytes);<BR> <BR>memcpy(dest, source, nbytes);<BR> <BR>所有這些函數都用于訪問I/O內存區域-低端的ISA內存或者高端的PCI緩沖區(在調用vre<BR>map后)。<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P>所有這些函數都用于訪問I/O內存區域-低端的ISA內存或者高端的PCI緩沖區(在調用vre<BR>map后)。<BR> <BR> <BR> <BR> <BR> <BR>-----------------------------------------------------------------------------<BR> <BR>* 實際上,有時I/O端口是和內存一樣對待的,(例如)你可以將2個8位的操作合并成一個<BR>16位的操作。例如,PC的顯示卡就可以,但一般來說不能認為一定具有這種特性。<BR>--<BR><FONT
color=#00ff00>※ 來源:.華南網木棉站 bbs.gznet.edu.cn.[FROM: 202.38.196.234]</FONT><BR>--<BR><FONT
color=#00ffff>※ 轉寄:.華南網木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#0000ff>※ 轉寄:.華南網木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#ffff00>※ 轉載:.南京大學小百合站 bbs.nju.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#ff0000>※ 轉載:·飲水思源 bbs.sjtu.edu.cn·[FROM: 211.80.41.106]</FONT><BR></P></FONT>
<P align=center><A href="http://joyfire.net/lsdp/index.htm"><FONT
color=#ffffff size=2>目錄頁</FONT></A> | <A
href="http://joyfire.net/lsdp/9.htm"><FONT color=#ffffff
size=2>上一頁</FONT></A> | <A href="http://joyfire.net/lsdp/11.htm"><FONT
color=#ffffff size=2>下一頁</FONT></A></P></SPAN></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>
<TBODY>
<TR>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -