?? io-port-programming.txt
字號:
如何在 Linux 下撰寫程式來使用 I/O 埠 作者: Riku Saikkonen <Riku.Saikkonen@hut.fi>譯者: Da-Wei Chiang <dawei@sinica.edu.tw> v, 28 December 1997 翻譯日期: 22 Jul. - 1 Aug. 1998 _________________________________________________________________ 本文的內容說明了 Intel x86 架構下如何在使用者模式 (user-mode) 中撰寫程 式來使用硬體I/O 埠以及等待一小段的時間周期. _________________________________________________________________ 1. 介紹2. 如何在 C 語言下使用 I/O 埠 * 2.1 正規(guī)的方法 * 2.2 另一個替代的方法: /dev/port 3. 硬體中斷 (IRQs) 與 DMA 存取4. 高精確的時序 * 4.1 延遲時間 * 4.2 時間的量測 5. 使用其他程式語言6. 一些有用的 I/O 埠 * 6.1 并列埠 (parallel port) * 6.2 游戲 (操縱□) 埠 (game port) * 6.3 串列埠 (serial port) 7. 提示8. 問題排除9. 程式碼□例10. 致謝 _________________________________________________________________ 1. 介紹 本文的內容說明了 Intel x86 架構下如何在使用者模式 (user-mode) 中撰寫程 式來使用硬體 I/O 埠以及等待一小段的時間周期. 內容源自於一篇非常短的文章 IO-Port mini-HOWTO 其作者與本文同. 本文 1995-1997 的版權屬於 Riku Saikkonen 所有. 版權聲明詳見網(wǎng)頁 [1]Linux HOWTO copyright. 如果您對本文有任何指教不論是錯誤修正或是內容補述, 都歡迎寄信給我 (Riku.Saikkonen@hut.fi)... 本文對前一次發(fā)行的版本 (Mar 30 1997) 作了如下的修正: * 對於 inb_p/outb_p 和埠位址 0x80 之間的關系做出了澄清. * 刪除了關於 udelay() 函式的資料, 因為 nanosleep() 函式 提供了比較明 確的使用方法. * 將內容轉換成 Linuxdoc-SGML 格式, 并且重新作了些許的編排. * 對很多地方作了些許的補述與修正. 2. 如何在 C 語言下使用 I/O 埠2.1 正規(guī)的方法 用來存取 I/O 埠的常式 (Routine) 都放在檔案 /usr/include/asm/io.h 里 (或 放在核心原始碼程式集的 linux/include/asm-i386/io.h 檔案里). 這些常式是 以單行巨集 (inline macros) 的方式寫成的, 所以使用時只要以 #include <asm/io.h> 的方式引用就夠了; 不需要附加任何函式館 (libraries). 譯注: 常式(Routine) 通常是指系統(tǒng)呼叫(System Call)與函式(Function)的總 稱. 因為 gcc (至少出現(xiàn)在 2.7.2.3 和以前的版本) 以及 egcs (所有的版本) 的限 制, 你在編譯任何使用到這些常式的原始碼時 必須 打開最佳化選項 (gcc -O1 或較高層次的), 或者是在做 #include <asm/io.h> 這個動作前使用 #define extern 將 extern 定義成空白. 為了除錯的目的, 你編譯時可以使用 gcc -g -O (至少現(xiàn)在的 gcc 版本是這 樣), 但是最佳化之後有時可能會讓除錯器 (debugger) 的行為變的有點奇怪. 如 果這個狀況對你而言是個困擾, 你可以將所有使用到 I/O 埠的常式集中放在一個 檔案里并只在編譯該檔案時□打開最佳化選項. 在你存取任何 I/O 埠之前, 你必須讓你的程式有如此做的權限. 要達成這個目的 你可以在你的程式一開始的地方 (但是要在任何 I/O 埠存取動作之前) 呼叫 ioperm() 這個函式 (該函式被宣告於檔案 unistd.h , 并且被定義在 核心中). 使用語法是 ioperm(from, num, turn_on), 其中 from 是第一個允許存取的 I/O 埠位址, num 是接著連續(xù)存取 I/O 埠位址的數(shù)目. 例如, ioperm(0x300, 5, 1) 的意思就是說允許存取埠 0x300 到 0x304 (一共五個埠位址). 而最後一 個參數(shù)是一個布林代數(shù)值用來指定是否 給予程式存取 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() (該函式讓你一次可以存取所有的埠位址). 將權 限等級參數(shù)值設為 3 (例如, iopl(3)) 以便你的程式能夠存取 所有的 I/O 埠 (因此要小心 --- 如果存取到錯誤的埠位址將對你的電腦造成各種不可預期的損 害. 同樣地, 呼叫函式 iopl() 你得擁有 root 的權限.至於語法的細節(jié)請參考 iopl(2) 的使用說明文件. 接著, 我們來實際地存取 I/O 埠... 要從某個埠位址輸入一個 byte (8 個 bits) 的資料, 你得呼叫函式 inb(port) , 該函式會傳回所取得的一個 byte 的 資料. 要輸出一個 byte 的資料, 你得呼叫函式 outb(value, port) (請記住參 數(shù)的次序). 要從某二個埠位址 x 和 x+1 (二個 byte 組成一個 word, 故使用組 合語言 指令 inw) 輸入一個 word (16 個 bits) 的資料, 你得呼叫函式 inw(x) ; 要輸出一個 word 的資料到二個埠位址, 你得呼叫函式 outw(value, x) . 如果你不確定使用那個埠指令 (byte 或 word), 你大概須要 inb() 與 outb() 這二個埠指令 --- 因為大多數(shù)的裝置都是采用 byte 大小的埠存取方式 來設計的. 注意所有的埠存取指令都至少需要大約一微秒的時間來執(zhí)行. 如果你使用的是 inb_p(), outb_p(), inw_p(), 以及 outw_p() 等巨集指令, 在 你對埠位作址存取動作之後只需很短的(大約一微秒)延遲時間就可以完成; 你也 可以讓延遲時間變成大約四微秒方法是在使用 #include <asm/io.h> 之前使用 #define REALLY_SLOW_IO. 這些巨集指令通常 (除非你使用的是 #define SLOW_IO_BY_JUMPING, 這個方法可能較不準確) 會利用輸出資料到埠位址 0x80 以便達到延遲時間的目的, 所以你得先以函式 ioperm() 取得埠位址 0x80 的使 用權限 (輸出資料到埠位址 0x80 不應該會對系統(tǒng)的其他其他部分造成影響). 至 於 其他通用的延遲時間的方法, 請繼續(xù)讀下去. ioperm(2), iopl(2) 等函式, 和上面所述及的巨集指令的使用說明會收錄在 最 近出版的 Linux 使用說明文件集中. 2.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 字元裝置檔案直接存取硬碟, 網(wǎng)路卡, 等 設備. 3. 硬體中斷 (IRQs) 與 DMA 存取 你的程式如果在使用者模式 (user-mode) 下執(zhí)行不可以直接使用硬體中斷 (IRQs) 或 DMA. 你必需撰寫一個核心驅動程式; 相關的細節(jié)請參考網(wǎng)頁 [2]The Linux Kernel Hacker's Guide 以及拿核心程式原始碼來當□例. 也就是說, 你在使用者模式 (user-mode) 中所寫的程式無法抑制硬體中斷的產 生. 4. 高精確的時序4.1 延遲時間 首先, 我會說不保證你在使用者模式 (user-mode) 中執(zhí)行的行程 (process) 能 夠精確地控制時序因為 Linux 是個多工的作業(yè)環(huán)境. 你在執(zhí)行中的行程 (process) 隨時會因為各種原因被暫停大約 10 毫秒到數(shù)秒 (在系統(tǒng)負荷非常高 的時候). 然而, 對於大多數(shù)使用 I/O 埠的應用而言, 這個延遲時間實際上算不 了什麼. 要縮短延遲時間, 你得使用函式 nice 將你在執(zhí)行中的行程 (process ) 設定成高優(yōu)先權(請參考 nice(2) 使用說明文件) 或使用即時排程法 (real-time scheduling) (請看下面). 如果你想獲得比在一般使用者模式 (user-mode) 中執(zhí)行的行程 (process) 還要 精確的時序, 有一些方法可以讓你在使用者模式 (user-mode) 中做到 `即時' 排 程的支援. Linux 2.x 版本的核心中有軟體方式的即時排程支援; 詳細的說明請 參考 sched_setscheduler(2) 使用說明文件. 有一個特殊的核心支援硬體的即時 排程; 詳細的資訊請參考網(wǎng)頁 [3]http://luz.cs.nmt.edu/~rtlinux/ 休息中 (Sleeping) : sleep() 與 usleep() 現(xiàn)在, 讓我們開始較簡單的時序函式呼叫. 想要延遲數(shù)秒的時間, 最佳的方法大 概 是使用函式 sleep() . 想要延遲至少數(shù)十毫秒的時間 (10 ms 似乎已是最短 的 延遲時間了), 函式 usleep() 應該可以使用. 這些函式是讓出 CPU 的使用權 給其他想要執(zhí)行的行程 (processes) (``自己休息去了''), 所以沒有浪費掉 CPU 的時間. 細節(jié)請參考 sleep(3) 與 usleep(3) 的說明文件. 如果讓出 CPU 的使用權因而使得時間延遲了大約 50 毫秒 (這取決於處理器與機
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -