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