W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
I/O 端口是驅(qū)動(dòng)用來(lái)和很多設(shè)備通訊的方法, 至少部分時(shí)間. 這節(jié)涉及可用的各種函數(shù)來(lái)使用 I/O 端口; 我們也觸及一些可移植性問(wèn)題.
如同你可能希望的, 你不應(yīng)當(dāng)離開(kāi)并開(kāi)始抨擊 I/O 端口而沒(méi)有首先確認(rèn)你對(duì)這些端口有唯一的權(quán)限. 內(nèi)核提供了一個(gè)注冊(cè)接口以允許你的驅(qū)動(dòng)來(lái)聲明它需要的端口. 這個(gè)接口中的核心的函數(shù)是 request_region:
#include <linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
這個(gè)函數(shù)告訴內(nèi)核, 你要使用 n 個(gè)端口, 從 first 開(kāi)始. name 參數(shù)應(yīng)當(dāng)是你的設(shè)備的名子. 如果分配成功返回值是非 NULL. 如果你從 request_region 得到 NULL, 你將無(wú)法使用需要的端口.
所有的的端口分配顯示在 /proc/ioports 中. 如果你不能分配一個(gè)需要的端口組, 這是地方來(lái)看看誰(shuí)先到那里了.
當(dāng)你用完一組 I/O 端口(在模塊卸載時(shí), 也許), 應(yīng)當(dāng)返回它們給系統(tǒng), 使用:
void release_region(unsigned long start, unsigned long n);
還有一個(gè)函數(shù)以允許你的驅(qū)動(dòng)來(lái)檢查是否一個(gè)給定的 I/O 端口組可用:
int check_region(unsigned long first, unsigned long n);
這里, 如果給定的端口不可用, 返回值是一個(gè)負(fù)錯(cuò)誤碼. 這個(gè)函數(shù)是不推薦的, 因?yàn)樗姆祷刂挡槐WC是否一個(gè)分配會(huì)成功; 檢查和后來(lái)的分配不是一個(gè)原子的操作. 我們列在這里因?yàn)閹讉€(gè)驅(qū)動(dòng)仍然在使用它, 但是你調(diào)用一直使用 request_region, 它進(jìn)行要求的加鎖來(lái)保證分配以一個(gè)安全的原子的方式完成.
在驅(qū)動(dòng)硬件請(qǐng)求了在它的活動(dòng)中需要使用的 I/O 端口范圍之后, 它必須讀且/或?qū)懙竭@些端口. 為此, 大部分硬件區(qū)別8-位, 16-位, 和 32-位端口. 常常你無(wú)法混合它們, 象你正常使用系統(tǒng)內(nèi)存存取一樣.[33]
一個(gè) C 程序, 因此, 必須調(diào)用不同的函數(shù)來(lái)存取不同大小的端口. 如果在前一節(jié)中建議的, 只支持唯一內(nèi)存映射 I/O 寄存器的計(jì)算機(jī)體系偽裝端口 I/O , 通過(guò)重新映射端口地址到內(nèi)存地址, 并且內(nèi)核向驅(qū)動(dòng)隱藏了細(xì)節(jié)以便易于移植. Linux 內(nèi)核頭文件(特別地, 體系依賴的頭文件 <asm/io.h>) 定義了下列內(nèi)聯(lián)函數(shù)來(lái)存取 I/O 端口:
unsigned inb(unsigned port);void outb(unsigned char byte, unsigned port);
讀或?qū)懽止?jié)端口( 8 位寬 ). port 參數(shù)定義為 unsigned long 在某些平臺(tái)以及 unsigned short 在其他的上. inb 的返回類型也是跨體系而不同的.
unsigned inw(unsigned port);void outw(unsigned short word, unsigned port);
這些函數(shù)存取 16-位 端口( 一個(gè)字寬 ); 在為 S390 平臺(tái)編譯時(shí)它們不可用, 它只支持字節(jié) I/O.
unsigned inl(unsigned port);void outl(unsigned longword, unsigned port);
這些函數(shù)存取 32-位 端口. longword 聲明為或者 unsigned long 或者 unsigned int, 根據(jù)平臺(tái). 如同字 I/O, "Long" I/O 在 S390 上不可用.
從現(xiàn)在開(kāi)始, 當(dāng)我們使用 unsigned 沒(méi)有進(jìn)一步類型規(guī)定時(shí), 我們指的是一個(gè)體系相關(guān)的定義, 它的確切特性是不相關(guān)的. 函數(shù)幾乎一直是可移植的, 因?yàn)榫幾g器自動(dòng)轉(zhuǎn)換值在賦值時(shí) -- 它們是 unsigned 有助于阻止編譯時(shí)的警告. 這樣的轉(zhuǎn)換不丟失信息, 只要程序員安排明智的值來(lái)避免溢出. 我們堅(jiān)持這個(gè)"未完成的類型"傳統(tǒng)貫串本章.
注意, 沒(méi)有定義 64-位 端口 I/O 操作. 甚至在 64-位 體系中, 端口地址空間使用一個(gè)32-位(最大)的數(shù)據(jù)通路.
剛剛描述的這些函數(shù)主要打算被設(shè)備驅(qū)動(dòng)使用, 但它們也可從用戶空間使用, 至少在 PC-類 的計(jì)算機(jī). GNU C 庫(kù)在 <sys/io.h> 中定義它們. 下列條件應(yīng)當(dāng)應(yīng)用來(lái)對(duì)于 inb 及其友在用戶空間代碼中使用:
程序必須使用 -O 選項(xiàng)編譯來(lái)強(qiáng)制擴(kuò)展內(nèi)聯(lián)函數(shù).
ioperm 和 iopl 系統(tǒng)調(diào)用必須用來(lái)獲得權(quán)限來(lái)進(jìn)行對(duì)端口的 I/O 操作. ioperm 為單獨(dú)端口獲取許可, 而 iopl 為整個(gè) I/O 空間獲取許可. 這 2 個(gè)函數(shù)都是 x86 特有的.
程序必須作為 root 來(lái)調(diào)用 ioperm 或者 iopl.[34] 可選地, 一個(gè)它的祖先必須已贏得作為 root 運(yùn)行的端口權(quán)限.
如果主機(jī)平臺(tái)沒(méi)有 ioperm 和 iopl 系統(tǒng)調(diào)用, 用戶空間仍然可以存取 I/O 端口, 通過(guò)使用 /dev/prot 設(shè)備文件. 注意, 但是, 這個(gè)文件的含義是非常平臺(tái)特定的, 并且對(duì)任何東西除了 PC 不可能有用.
例子源碼 misc-progs/inp.c 和 misc-progs/outp.c 是一個(gè)從命令行讀寫端口的小工具, 在用戶空間. 它們希望被安裝在多個(gè)名子下(例如, inb, inw, 和 inl 并且操作字節(jié), 字, 或者長(zhǎng)端口依賴于用戶調(diào)用哪個(gè)名子). 它們使用 ioperm 或者 iopl 在 x86下, 在其他平臺(tái)是 /dev/port.
程序可以做成 setuid root, 如果你想過(guò)危險(xiǎn)生活并且在不要求明確的權(quán)限的情況下使用你的硬件. 但是, 請(qǐng)不要在產(chǎn)品系統(tǒng)上以 set-uid 安裝它們; 它們是設(shè)計(jì)上的安全漏洞.
除了單發(fā)地輸入和輸出操作, 一些處理器實(shí)現(xiàn)了特殊的指令來(lái)傳送一系列字節(jié), 字, 或者 長(zhǎng)字 到和自一個(gè)單個(gè) I/O 端口或者同樣大小. 這是所謂的字串指令, 并且它們完成任務(wù)比一個(gè) C 語(yǔ)言循環(huán)能做的更快. 下列宏定義實(shí)現(xiàn)字串處理的概念或者通過(guò)使用一個(gè)單個(gè)機(jī)器指令或者通過(guò)執(zhí)行一個(gè)緊湊的循環(huán), 如果目標(biāo)處理器沒(méi)有進(jìn)行字串 I/O 的指令. 當(dāng)編譯為 S390 平臺(tái)時(shí)這些宏定義根本不定義. 這應(yīng)當(dāng)不是個(gè)移植性問(wèn)題, 因?yàn)檫@個(gè)平臺(tái)通常不與其他平臺(tái)共享設(shè)備驅(qū)動(dòng), 因?yàn)樗耐庠O(shè)總線是不同的.
字串函數(shù)的原型是:
void insb(unsigned port, void addr, unsigned long count);void outsb(unsigned port, void addr, unsigned long count);
讀或?qū)憦膬?nèi)存地址 addr 開(kāi)始的 count 字節(jié). 數(shù)據(jù)讀自或者寫入單個(gè) port 端口.
void insw(unsigned port, void addr, unsigned long count);void outsw(unsigned port, void addr, unsigned long count);
讀或?qū)?16-位 值到一個(gè)單個(gè) 16-位 端口.
void insl(unsigned port, void addr, unsigned long count);void outsl(unsigned port, void addr, unsigned long count);
讀或?qū)?32-位 值到一個(gè)單個(gè) 32-位 端口.
有件事要記住, 當(dāng)使用字串函數(shù)時(shí): 它們移動(dòng)一個(gè)整齊的字節(jié)流到或自端口. 當(dāng)端口和主系統(tǒng)有不同的字節(jié)對(duì)齊規(guī)則, 結(jié)果可能是令人驚訝的. 使用 inw 讀取一個(gè)端口交換這些字節(jié), 如果需要, 來(lái)使讀取的值匹配主機(jī)字節(jié)序. 字串函數(shù), 相反, 不進(jìn)行這個(gè)交換.
一些平臺(tái) - 最有名的 i386 - 可能有問(wèn)題當(dāng)處理器試圖太快傳送數(shù)據(jù)到或自總線. 當(dāng)處理器對(duì)于外設(shè)總線被過(guò)度鎖定時(shí)可能引起問(wèn)題( 想一下 ISA )并且可能當(dāng)設(shè)備單板太慢時(shí)表現(xiàn)出來(lái). 解決方法是插入一個(gè)小的延時(shí)在每個(gè) I/O 指令后面, 如果跟隨著另一個(gè)指令. 在 x86 上, 這個(gè)暫停是通過(guò)進(jìn)行一個(gè) outb 指令到端口 0x80 ( 正常地不是常常用到 )實(shí)現(xiàn)的, 或者通過(guò)忙等待. 細(xì)節(jié)見(jiàn)你的平臺(tái)的 asm 子目錄的 io.h 文件.
如果你的設(shè)備丟失一些數(shù)據(jù), 或者如果你擔(dān)心它可能丟失一些, 你可以使用暫停函數(shù)代替正常的那些. 暫停函數(shù)正如前面列出的, 但是它們的名子以 _p 結(jié)尾; 它們稱為 inb_p, outb_p, 等等. 這些函數(shù)定義給大部分被支持的體系, 盡管它們常常擴(kuò)展為與非暫停 I/O 同樣的代碼, 因?yàn)闆](méi)有必要額外暫停, 如果體系使用一個(gè)合理的現(xiàn)代外設(shè)總線.
I/O 指令, 由于它們的特性, 是高度處理器依賴的. 因?yàn)樗鼈兪褂锰幚砥魅绾翁幚硪七M(jìn)移出的細(xì)節(jié), 是非常難以隱藏系統(tǒng)間的不同. 作為一個(gè)結(jié)果, 大部分的關(guān)于端口 I/O 的源碼是平臺(tái)依賴的.
你可以看到一個(gè)不兼容, 數(shù)據(jù)類型, 通過(guò)回看函數(shù)的列表, 這里參數(shù)是不同的類型, 基于平臺(tái)間的體系不同點(diǎn). 例如, 一個(gè)端口是 unsigned int 在 x86 (這里處理器支持一個(gè) 64-KB I/O 空間), 但是在別的平臺(tái)是 unsiged long, 這里的端口只是同內(nèi)存一樣的同一個(gè)地址空間中的特殊位置.
其他的平臺(tái)依賴性來(lái)自處理器中的基本的結(jié)構(gòu)性不同, 并且, 因此, 無(wú)可避免地. 我們不會(huì)進(jìn)入這個(gè)依賴性的細(xì)節(jié), 因?yàn)槲覀兗俣悴粫?huì)給一個(gè)特殊的系統(tǒng)編寫設(shè)備驅(qū)動(dòng)而沒(méi)有理解底層的硬件. 相反, 這是一個(gè)內(nèi)核支持的體系的能力的概括:
IA-32 (x86)x86_64
這個(gè)體系支持所有的本章描述的函數(shù). 端口號(hào)是 unsigned short 類型.
IA-64 (Itanium)
支持所有函數(shù); 端口是 unsigned long(以及內(nèi)存映射的)). 字串函數(shù)用 C 實(shí)現(xiàn).
Alpha
支持所有函數(shù), 并且端口是內(nèi)存映射的. 端口 I/O 的實(shí)現(xiàn)在不同 Alpha 平臺(tái)上是不同的, 根據(jù)它們使用的芯片組. 字串函數(shù)用 C 實(shí)現(xiàn)并且定義在 arch/alpha/lib/io.c 中定義. 端口是 unsigned long.
ARM
端口是內(nèi)存映射的, 并且支持所有函數(shù); 字串函數(shù)用 C 實(shí)現(xiàn). 端口是 unsigned int 類型.
Cris
這個(gè)體系不支持 I/O 端口抽象, 甚至在一個(gè)模擬模式; 各種端口操作定義成什么不做.
M68kM68k
端口是內(nèi)存映射的. 支持字串函數(shù), 并且端口類型是 unsigned char.
MIPSMIPS64
MIPS 端口支持所有的函數(shù). 字串操作使用緊湊匯編循環(huán)來(lái)實(shí)現(xiàn), 因?yàn)樘幚砥魅狈C(jī)器級(jí)別的字串 I/O. 端口是內(nèi)存映射的; 它們是 unsigned long.
PA
支持所有函數(shù); 端口是 int 在基于 PCI 的系統(tǒng)上以及 unsigned short 在 EISA 系統(tǒng), 除了字串操作, 它們使用 unsigned long 端口號(hào).
PowerPCPowerPC64
支持所有函數(shù); 端口有 unsigned char * 類型在 32-位 系統(tǒng)上并且 unsigned long 在 64-位 系統(tǒng)上.
S390 類似于 M68k, 這個(gè)平臺(tái)的頭文件只支持字節(jié)寬的端口 I/O, 而沒(méi)有字串操作. 端口是 char 指針并且是內(nèi)存映射的.
Super
端口是 unsigned int ( 內(nèi)存映射的 ), 并且支持所有函數(shù).
SPARC SPARC64
再一次, I/O 空間是內(nèi)存映射的. 端口函數(shù)的版本定義來(lái)使用 unsigned long 端口.
好奇的讀者能夠從 io.h 文件中獲得更多信息, 這個(gè)文件有時(shí)定義幾個(gè)結(jié)構(gòu)特定的函數(shù), 加上我們?cè)诒菊轮忻枋龅哪切? 但是, 警告有些這些文件是相當(dāng)難讀的.
有趣的是注意沒(méi)有 x86 家族之外的處理器具備一個(gè)不同的地址空間給端口, 盡管幾個(gè)被支持的家族配備有 ISA 和/或 PCI 插槽 ( 并且 2 種總線實(shí)現(xiàn)分開(kāi)的 I/O 和地址空間 ).
更多地, 有些處理器(最有名的是早期的 Alphas)缺乏一次移動(dòng)一個(gè)或 2 個(gè)字節(jié)的指令.[35] 因此, 它們的外設(shè)芯片組模擬 8-位 和 16-位 I/O 存取, 通過(guò)映射它們到內(nèi)存地址空間的特殊的地址范圍. 因此, 操作同一個(gè)端口的一個(gè) inb 和 一個(gè) inw 指令, 通過(guò) 2 個(gè)操作不同地址的 32-位內(nèi)存讀來(lái)實(shí)現(xiàn). 幸運(yùn)的是, 所有這些都對(duì)設(shè)備驅(qū)動(dòng)編寫者隱藏了, 通過(guò)本節(jié)中描述的宏的內(nèi)部, 但是我們覺(jué)得它是一個(gè)要注意的有趣的特性. 如果你想深入探究, 查找在 include/asm-alpha/core_lca.h 中的例子.
在每個(gè)平臺(tái)的程序員手冊(cè)中充分描述了I/O 操作如何在每個(gè)平臺(tái)上進(jìn)行; 這些手冊(cè)常常在 WEB 上作為 PDF 下載.
[33] 有時(shí) I/O 端口象內(nèi)存一樣安排, 并且你可(例如)綁定 2 個(gè) 8-位 寫為一個(gè)單個(gè) 16-位 操作. 例如, 這應(yīng)用于 PC 視頻板. 但是通常, 你不能指望這個(gè)特色.
[34] 技術(shù)上, 它必須有 CAP_SYS_RAWIO 能力, 但是在大部分當(dāng)前系統(tǒng)中這是與作為 root 運(yùn)行是同樣的.
[35] 單字節(jié) I/O 不是一個(gè)人可能想象的那么重要, 因?yàn)樗且粋€(gè)稀少的操作. 為讀/寫一個(gè)單字節(jié)到任何地址空間, 你需要實(shí)現(xiàn)一個(gè)數(shù)據(jù)通道, 連接寄存器組的數(shù)據(jù)總線的低位到外部數(shù)據(jù)總線的任意字節(jié)位置. 這些數(shù)據(jù)通道需要額外的邏輯門在每個(gè)數(shù)據(jù)傳輸?shù)耐ǖ郎? 丟掉字節(jié)寬的載入和存儲(chǔ)能夠使整個(gè)系統(tǒng)性能受益.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: