1. 正規(guī)的方法
用來(lái)存取 i/o 埠的常式 (routine) 都放在檔案 /usr/include/asm/io.h 里 (或放在核心原始碼程式集的 linux/include/asm-i386/io.h 檔案里). 這些常式是以單行巨集 (inline macros) 的方式寫(xiě)成的, 所以使用時(shí)只要以 #include 的方式引用就夠了; 不需要附加任何函式館 (libraries).
譯注: 常式(routine) 通常是指系統(tǒng)呼叫(system call)與函式(function)的總稱(chēng).
因?yàn)?gcc (至少出現(xiàn)在 2.7.2.3 和以前的版本) 以及 egcs (所有的版本) 的限制, 你在編譯任何使用到這些常式的原始碼時(shí) 必須 打開(kāi)化選項(xiàng) (gcc -o1 或較高層次的), 或者是在做 #include 這個(gè)動(dòng)作前使用 #define extern 將 extern 定義成空白.
為了除錯(cuò)的目的, 你編譯時(shí)可以使用 gcc -g -o (至少現(xiàn)在的 gcc 版本是這樣), 但是化之后有時(shí)可能會(huì)讓除錯(cuò)器 (debugger) 的行為變的有點(diǎn)奇怪. 如果這個(gè)狀況對(duì)你而言是個(gè)困擾, 你可以將所有使用到 i/o 埠的常式集中放在一個(gè)檔案里并只在編譯該檔案時(shí)才打開(kāi)化選項(xiàng).
在你存取任何 i/o 埠之前, 你必須讓你的程式有如此做的權(quán)限. 要達(dá)成這個(gè)目的你可以在你的程式一開(kāi)始的地方 (但是要在任何 i/o 埠存取動(dòng)作之前) 呼叫 ioperm() 這個(gè)函式 (該函式被宣告于檔案 unistd.h , 并且被定義在核心中). 使用語(yǔ)法是 ioperm(from, num, turn_on), 其中 from 是第一個(gè)允許存取的 i/o 埠位址, num 是接著連續(xù)存取 i/o 埠位址的數(shù)目. 例如, ioperm(0x300, 5, 1) 的意思就是說(shuō)允許存取埠 0x300 到 0x304 (一共五個(gè)埠位址). 而最后一個(gè)參數(shù)是一個(gè)布林代數(shù)值用來(lái)指定是否給予程式存取 i/o 埠的權(quán)限 (true (1)) 或是除去存取的權(quán)限 (false (0)). 你可以多次呼叫函式 ioperm() 以便使用多個(gè)不連續(xù)的埠位址. 至于語(yǔ)法的細(xì)節(jié)請(qǐng)參考 ioperm(2) 的使用說(shuō)明文件.
你的程式必須擁有 root 的權(quán)限才能呼叫函式 ioperm() ; 所以你如果不是以 root 的身份執(zhí)行該程式, 就是得將該程式 setuid 成 root. 當(dāng)你呼叫過(guò)函式 ioperm() 打開(kāi) i/o 埠的存取權(quán)限后你便可以拿掉 root 的權(quán)限. 在你的程式結(jié)束之后并不特別要求你以 ioperm(..., 0) 這個(gè)方式拿掉 i/o 埠的存取權(quán)限; 因?yàn)楫?dāng)你的程式執(zhí)行完畢之后這個(gè)動(dòng)作會(huì)自動(dòng)完成.
呼叫函式 setuid() 將目前執(zhí)行程式的有效使用者識(shí)別碼 (id) 設(shè)定成非 root 的使用者并不影響其先前以 ioperm() 的方式所取得的 i/o 埠存取權(quán)限, 但是呼叫函式 fork() 的方式卻會(huì)有所影響 (雖然父行程 (parent process) 保有存取權(quán)限, 但是子行程 (child process) 卻無(wú)法取得存取權(quán)限).
函式 ioperm() 只能讓你取得埠位址 0x000 到 0x3ff 的存取權(quán)限; 至于較高位址的埠, 你得使用函式 iopl() (該函式讓你一次可以存取所有的埠位址). 將權(quán)限等級(jí)參數(shù)值設(shè)為 3 (例如, iopl(3)) 以便你的程式能夠存取 所有的 i/o 埠 (因此要小心 --- 如果存取到錯(cuò)誤的埠位址將對(duì)你的電腦造成各種不可預(yù)期的損害. 同樣地, 呼叫函式 iopl() 你得擁有 root 的權(quán)限.至于語(yǔ)法的細(xì)節(jié)請(qǐng)參考 iopl(2) 的使用說(shuō)明文件.
接著, 我們來(lái)實(shí)際地存取 i/o 埠... 要從某個(gè)埠位址輸入一個(gè) byte (8 個(gè) bits) 的資料, 你得呼叫函式 inb(port) , 該函式會(huì)傳回所取得的一個(gè) byte 的資料. 要輸出一個(gè) byte 的資料, 你得呼叫函式 outb(value, port) (請(qǐng)記住參數(shù)的次序). 要從某二個(gè)埠位址 x 和 x+1 (二個(gè) byte 組成一個(gè) word, 故使用組合語(yǔ)言指令 inw) 輸入一個(gè) word (16 個(gè) bits) 的資料, 你得呼叫函式 inw(x) ; 要輸出一個(gè) word 的資料到二個(gè)埠位址, 你得呼叫函式 outw(value, x) . 如果你不確定使用那個(gè)埠指令 (byte 或 word), 你大概須要 inb() 與 outb() 這二個(gè)埠指令 --- 因?yàn)榇蠖鄶?shù)的裝置都是采用 byte 大小的埠存取方式來(lái)設(shè)計(jì)的. 注意所有的埠存取指令都至少需要大約一微秒的時(shí)間來(lái)執(zhí)行.
如果你使用的是 inb_p(), outb_p(), inw_p(), 以及 outw_p() 等巨集指令, 在你對(duì)埠位作址存取動(dòng)作之后只需很短的(大約一微秒)延遲時(shí)間就可以完成; 你也可以讓延遲時(shí)間變成大約四微秒方法是在使用 #include 之前使用 #define really_slow_io. 這些巨集指令通常 (除非你使用的是 #define slow_io_by_jumping, 這個(gè)方法可能較不準(zhǔn)確) 會(huì)利用輸出資料到埠位址 0x80 以便達(dá)到延遲時(shí)間的目的, 所以你得先以函式 ioperm() 取得埠位址 0x80 的使用權(quán)限 (輸出資料到埠位址 0x80 不應(yīng)該會(huì)對(duì)系統(tǒng)的其他其他部分造成影響). 至于其他通用的延遲時(shí)間的方法, 請(qǐng)繼續(xù)讀下去.
ioperm(2), iopl(2) 等函式, 和上面所述及的巨集指令的使用說(shuō)明會(huì)收錄在最近出版的 linux 使用說(shuō)明文件集中.
2. 另一個(gè)替代的方法: /dev/port
另一個(gè)存取 i/o 埠的方法是以函式 open() 開(kāi)啟檔案 /dev/port (一個(gè)字元裝置,主要裝置編號(hào)為 1, 次要裝置編號(hào)為 4) 以便執(zhí)行讀且/或?qū)懙膭?dòng)作 (注意標(biāo)準(zhǔn)輸出入 (stdio) 函式 f*() 有內(nèi)部的緩沖 (buffering), 所以要避免使用). 接著使用 lseek() 函式以便在該字元裝置檔案中找到某個(gè) byte 資料的正確位置 (檔案位置 0 = 埠位址 0x00, 檔案位置 1 = 埠位址 0x01, 以此類(lèi)推), 然后你可以使用 read() 或 write() 函式對(duì)某個(gè)埠位址做讀或?qū)懸粋€(gè) byte 或 word 資料的動(dòng)作.
這個(gè)替代的方法就是在你的程式里使用 read/write 函式來(lái)存取 /dev/port 字元裝置檔案. 這個(gè)方法的執(zhí)行速度或許比前面所講的一般方法還慢, 但是不需要編譯器的化功能也不需要使用函式 ioperm() . 如果你允許非 root 使用者或群組存取 /dev/port 字元設(shè)裝置案, 操作時(shí)就不需擁有 root 權(quán)限 -- 但是對(duì)于系統(tǒng)安全而言是個(gè)非常糟糕的事情, 因?yàn)樗赡軅Φ侥愕南到y(tǒng), 或許會(huì)有人因而取的 root 的權(quán)限, 利用 /dev/port 字元裝置檔案直接存取硬碟, 網(wǎng)路卡, 等設(shè)備.
用來(lái)存取 i/o 埠的常式 (routine) 都放在檔案 /usr/include/asm/io.h 里 (或放在核心原始碼程式集的 linux/include/asm-i386/io.h 檔案里). 這些常式是以單行巨集 (inline macros) 的方式寫(xiě)成的, 所以使用時(shí)只要以 #include 的方式引用就夠了; 不需要附加任何函式館 (libraries).
譯注: 常式(routine) 通常是指系統(tǒng)呼叫(system call)與函式(function)的總稱(chēng).
因?yàn)?gcc (至少出現(xiàn)在 2.7.2.3 和以前的版本) 以及 egcs (所有的版本) 的限制, 你在編譯任何使用到這些常式的原始碼時(shí) 必須 打開(kāi)化選項(xiàng) (gcc -o1 或較高層次的), 或者是在做 #include 這個(gè)動(dòng)作前使用 #define extern 將 extern 定義成空白.
為了除錯(cuò)的目的, 你編譯時(shí)可以使用 gcc -g -o (至少現(xiàn)在的 gcc 版本是這樣), 但是化之后有時(shí)可能會(huì)讓除錯(cuò)器 (debugger) 的行為變的有點(diǎn)奇怪. 如果這個(gè)狀況對(duì)你而言是個(gè)困擾, 你可以將所有使用到 i/o 埠的常式集中放在一個(gè)檔案里并只在編譯該檔案時(shí)才打開(kāi)化選項(xiàng).
在你存取任何 i/o 埠之前, 你必須讓你的程式有如此做的權(quán)限. 要達(dá)成這個(gè)目的你可以在你的程式一開(kāi)始的地方 (但是要在任何 i/o 埠存取動(dòng)作之前) 呼叫 ioperm() 這個(gè)函式 (該函式被宣告于檔案 unistd.h , 并且被定義在核心中). 使用語(yǔ)法是 ioperm(from, num, turn_on), 其中 from 是第一個(gè)允許存取的 i/o 埠位址, num 是接著連續(xù)存取 i/o 埠位址的數(shù)目. 例如, ioperm(0x300, 5, 1) 的意思就是說(shuō)允許存取埠 0x300 到 0x304 (一共五個(gè)埠位址). 而最后一個(gè)參數(shù)是一個(gè)布林代數(shù)值用來(lái)指定是否給予程式存取 i/o 埠的權(quán)限 (true (1)) 或是除去存取的權(quán)限 (false (0)). 你可以多次呼叫函式 ioperm() 以便使用多個(gè)不連續(xù)的埠位址. 至于語(yǔ)法的細(xì)節(jié)請(qǐng)參考 ioperm(2) 的使用說(shuō)明文件.
你的程式必須擁有 root 的權(quán)限才能呼叫函式 ioperm() ; 所以你如果不是以 root 的身份執(zhí)行該程式, 就是得將該程式 setuid 成 root. 當(dāng)你呼叫過(guò)函式 ioperm() 打開(kāi) i/o 埠的存取權(quán)限后你便可以拿掉 root 的權(quán)限. 在你的程式結(jié)束之后并不特別要求你以 ioperm(..., 0) 這個(gè)方式拿掉 i/o 埠的存取權(quán)限; 因?yàn)楫?dāng)你的程式執(zhí)行完畢之后這個(gè)動(dòng)作會(huì)自動(dòng)完成.
呼叫函式 setuid() 將目前執(zhí)行程式的有效使用者識(shí)別碼 (id) 設(shè)定成非 root 的使用者并不影響其先前以 ioperm() 的方式所取得的 i/o 埠存取權(quán)限, 但是呼叫函式 fork() 的方式卻會(huì)有所影響 (雖然父行程 (parent process) 保有存取權(quán)限, 但是子行程 (child process) 卻無(wú)法取得存取權(quán)限).
函式 ioperm() 只能讓你取得埠位址 0x000 到 0x3ff 的存取權(quán)限; 至于較高位址的埠, 你得使用函式 iopl() (該函式讓你一次可以存取所有的埠位址). 將權(quán)限等級(jí)參數(shù)值設(shè)為 3 (例如, iopl(3)) 以便你的程式能夠存取 所有的 i/o 埠 (因此要小心 --- 如果存取到錯(cuò)誤的埠位址將對(duì)你的電腦造成各種不可預(yù)期的損害. 同樣地, 呼叫函式 iopl() 你得擁有 root 的權(quán)限.至于語(yǔ)法的細(xì)節(jié)請(qǐng)參考 iopl(2) 的使用說(shuō)明文件.
接著, 我們來(lái)實(shí)際地存取 i/o 埠... 要從某個(gè)埠位址輸入一個(gè) byte (8 個(gè) bits) 的資料, 你得呼叫函式 inb(port) , 該函式會(huì)傳回所取得的一個(gè) byte 的資料. 要輸出一個(gè) byte 的資料, 你得呼叫函式 outb(value, port) (請(qǐng)記住參數(shù)的次序). 要從某二個(gè)埠位址 x 和 x+1 (二個(gè) byte 組成一個(gè) word, 故使用組合語(yǔ)言指令 inw) 輸入一個(gè) word (16 個(gè) bits) 的資料, 你得呼叫函式 inw(x) ; 要輸出一個(gè) word 的資料到二個(gè)埠位址, 你得呼叫函式 outw(value, x) . 如果你不確定使用那個(gè)埠指令 (byte 或 word), 你大概須要 inb() 與 outb() 這二個(gè)埠指令 --- 因?yàn)榇蠖鄶?shù)的裝置都是采用 byte 大小的埠存取方式來(lái)設(shè)計(jì)的. 注意所有的埠存取指令都至少需要大約一微秒的時(shí)間來(lái)執(zhí)行.
如果你使用的是 inb_p(), outb_p(), inw_p(), 以及 outw_p() 等巨集指令, 在你對(duì)埠位作址存取動(dòng)作之后只需很短的(大約一微秒)延遲時(shí)間就可以完成; 你也可以讓延遲時(shí)間變成大約四微秒方法是在使用 #include 之前使用 #define really_slow_io. 這些巨集指令通常 (除非你使用的是 #define slow_io_by_jumping, 這個(gè)方法可能較不準(zhǔn)確) 會(huì)利用輸出資料到埠位址 0x80 以便達(dá)到延遲時(shí)間的目的, 所以你得先以函式 ioperm() 取得埠位址 0x80 的使用權(quán)限 (輸出資料到埠位址 0x80 不應(yīng)該會(huì)對(duì)系統(tǒng)的其他其他部分造成影響). 至于其他通用的延遲時(shí)間的方法, 請(qǐng)繼續(xù)讀下去.
ioperm(2), iopl(2) 等函式, 和上面所述及的巨集指令的使用說(shuō)明會(huì)收錄在最近出版的 linux 使用說(shuō)明文件集中.
2. 另一個(gè)替代的方法: /dev/port
另一個(gè)存取 i/o 埠的方法是以函式 open() 開(kāi)啟檔案 /dev/port (一個(gè)字元裝置,主要裝置編號(hào)為 1, 次要裝置編號(hào)為 4) 以便執(zhí)行讀且/或?qū)懙膭?dòng)作 (注意標(biāo)準(zhǔn)輸出入 (stdio) 函式 f*() 有內(nèi)部的緩沖 (buffering), 所以要避免使用). 接著使用 lseek() 函式以便在該字元裝置檔案中找到某個(gè) byte 資料的正確位置 (檔案位置 0 = 埠位址 0x00, 檔案位置 1 = 埠位址 0x01, 以此類(lèi)推), 然后你可以使用 read() 或 write() 函式對(duì)某個(gè)埠位址做讀或?qū)懸粋€(gè) byte 或 word 資料的動(dòng)作.
這個(gè)替代的方法就是在你的程式里使用 read/write 函式來(lái)存取 /dev/port 字元裝置檔案. 這個(gè)方法的執(zhí)行速度或許比前面所講的一般方法還慢, 但是不需要編譯器的化功能也不需要使用函式 ioperm() . 如果你允許非 root 使用者或群組存取 /dev/port 字元設(shè)裝置案, 操作時(shí)就不需擁有 root 權(quán)限 -- 但是對(duì)于系統(tǒng)安全而言是個(gè)非常糟糕的事情, 因?yàn)樗赡軅Φ侥愕南到y(tǒng), 或許會(huì)有人因而取的 root 的權(quán)限, 利用 /dev/port 字元裝置檔案直接存取硬碟, 網(wǎng)路卡, 等設(shè)備.