- 论坛徽章:
- 0
|
如何在Linux下撰写程式来使用I/O埠
作者: Riku Saikkonen
译者: Da-Wei Chiang
v, 28 December 1997翻译日期: 22 Jul. - 1 Aug. 1998
_________________________________________________________________
本文的内容说明了Intel x86架构下如何在使用者模式(user-mode)中撰写程
式来使用硬体I/O埠以及等待一小段的时间周期.
_________________________________________________________________
1.介绍
2.如何在C语言下使用I/O埠
* 2.1正规的方法
* 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所有.版权声明详见网页
[1]Linux HOWTO copyright.
如果您对本文有任何指教不论是错误修正或是内容补述,都欢迎寄信给我
(Riku.Saikkonen@hut.fi)...
本文对前一次发行的版本(Mar 30 1997)作了如下的修正:
*对于inb_p/outb_p和埠位址0x80之间的关系做出了澄清.
*删除了关于udelay()函式的资料,因为nanosleep()函式提供了比较明
确的使用方法.
*将内容转换成Linuxdoc-SGML格式,并且重新作了些许的编排.
*对很多地方作了些许的补述与修正.
2.如何在C语言下使用I/O埠
2.1正规的方法
用来存取I/O埠的常式(Routine)都放在档案/usr/include/asm/io.h里(或
放在核心原始码程式集的linux/include/asm-i386/io.h档案里).这些常式是
以单行巨集(inline macros)的方式写成的,所以使用时只要以#include
的方式引用就够了;不需要附加任何函式馆(libraries).
译注:常式(Routine)通常是指系统呼叫(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是接着连续存取I/O埠位址的数目.例如, ioperm(0x300,
5, 1)的意思就是说允许存取埠0x300到0x304 (一共五个埠位址).而最后一
个参数是一个布林代数值用来指定是否给予程式存取I/O埠的权限(true
(1))或是除去存取的权限(false (0)).你可以多次呼叫函式ioperm()以便
使用多个不连续的埠位址.至于语法的细节请参考ioperm(2)的使用说明文
件.
你的程式必须拥有root的权限才能呼叫函式ioperm() ;所以你如果不是以
root的身份执行该程式,就是得将该程式setuid成root.当你呼叫过函式
ioperm()打开I/O埠的存取权限后你便可以拿掉root的权限.在你的程式结
束之后并不特别要求你以ioperm(..., 0)这个方式拿掉I/O埠的存取权限;
因为当你的程式执行完毕之后这个动作会自动完成.
呼叫函式setuid()将目前执行程式的有效使用者识别码(ID)设定成非root
的使用者并不影响其先前以ioperm()的方式所取得的I/O埠存取权限,但是呼
叫函式fork()的方式却会有所影响(虽然父行程(parent process)保有存取
权限,但是子行程(child process)却无法取得存取权限).
函式ioperm()只能让你取得埠位址0x000到0x3ff的存取权限;至于较高位
址的埠,你得使用函式iopl() (该函式让你一次可以存取所有的埠位址).将权
限等级参数值设为3 (例如, iopl(3))以便你的程式能够存取所有的I/O埠
(因此要小心---如果存取到错误的埠位址将对你的电脑造成各种不可预期的损
害.同样地,呼叫函式iopl()你得拥有root的权限.至于语法的细节请参考
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大小的埠存取方式
来设计的.注意所有的埠存取指令都至少需要大约一微秒的时间来执行.
如果你使用的是inb_p(), outb_p(), inw_p(),以及outw_p()等巨集指令,在
你对埠位作址存取动作之后只需很短的(大约一微秒)延迟时间就可以完成;你也
可以让延迟时间变成大约四微秒方法是在使用#include 之前使用
#define REALLY_SLOW_IO.这些巨集指令通常(除非你使用的是#define
SLOW_IO_BY_JUMPING,这个方法可能较不准确)会利用输出资料到埠位址0x80
以便达到延迟时间的目的,所以你得先以函式ioperm()取得埠位址0x80的使
用权限(输出资料到埠位址0x80不应该会对系统的其他其他部分造成影响).至
于其他通用的延迟时间的方法,请继续读下去.
ioperm(2), iopl(2)等函式,和上面所述及的巨集指令的使用说明会收录在最
近出版的Linux使用说明文件集中.
2.2另一个替代的方法: /dev/port
另一个存取I/O埠的方法是以函式open()开启档案/dev/port (一个字元装
置,主要装置编号为1,次要装置编号为4)以便执行读且/或写的动作(注意标
准输出入(stdio)函式f*()有内部的缓冲(buffering),所以要避免使用).
接着使用lseek()函式以便在该字元装置档案中找到某个byte资料的正确位置
(档案位置0 =埠位址0x00,档案位置1 =埠位址0x01,以此类推),然后你
可以使用read()或write()函式对某个埠位址做读或写一个byte或word资
料的动作.
这个替代的方法就是在你的程式里使用read/write函式来存取/dev/port字元
装置档案.这个方法的执行速度或许比前面所讲的一般方法还慢,但是不需要编
译器的最佳化功能也不需要使用函式ioperm() .如果你允许非root使用者或
群组存取/dev/port字元设装置案,操作时就不需拥有root权限--但是对于
系统安全而言是个非常糟糕的事情,因为他可能伤害到你的系统,或许会有人因
而取的root的权限,利用/dev/port字元装置档案直接存取硬碟,网路卡,等
设备.
3.硬体中断(IRQs)与DMA存取
你的程式如果在使用者模式(user-mode)下执行不可以直接使用硬体中断
(IRQs)或DMA.你必需撰写一个核心驱动程式;相关的细节请参考网页[2]The
Linux Kernel Hacker's Guide以及拿核心程式原始码来当范例.
也就是说,你在使用者模式(user-mode)中所写的程式无法抑制硬体中断的产
生.
4.高精确的时序
4.1延迟时间
首先,我会说不保证你在使用者模式(user-mode)中执行的行程(process)能
够精确地控制时序因为Linux是个多工的作业环境.你在执行中的行程
(process)随时会因为各种原因被暂停大约10毫秒到数秒(在系统负荷非常高
的时候).然而,对于大多数使用I/O埠的应用而言,这个延迟时间实际上算不
了什么.要缩短延迟时间,你得使用函式nice将你在执行中的行程(process
)设定成高优先权(请参考nice(2)使用说明文件)或使用即时排程法
(real-time scheduling) (请看下面).
如果你想获得比在一般使用者模式(user-mode)中执行的行程(process)还要
精确的时序,有一些方法可以让你在使用者模式(user-mode)中做到`即时'排
程的支援. Linux 2.x版本的核心中有软体方式的即时排程支援;详细的说明请
参考sched_setscheduler(2)使用说明文件.有一个特殊的核心支援硬体的即时
排程;详细的资讯请参考网页[3]http://luz.cs.nmt.edu/~rtlinux/
休息中(Sleeping) : sleep()与usleep()
现在,让我们开始较简单的时序函式呼叫.想要延迟数秒的时间,最佳的方法大
概是使用函式sleep() .想要延迟至少数十毫秒的时间(10 ms似乎已是最短
的延迟时间了),函式usleep()应该可以使用.这些函式是让出CPU的使用权
给其他想要执行的行程(processes) (``自己休息去了''),所以没有浪费掉
CPU的时间.细节请参考sleep(3)与usleep(3)的说明文件.
如果让出CPU的使用权因而使得时间延迟了大约50毫秒(这取决于处理器与机
器的速度,以及系统的负荷),就浪费掉CPU太多的时间,因为Linux的排程器
(scheduler) (单就x86架构而言)在将控制权发还给你的行程(process)之前
通常至少要花费10-30毫秒的时间.因此,短时间的延迟,使用函式
usleep(3)所得到的延迟结果通常会大于你在参数所指定的值,大约至少有10
ms.
nanosleep()
在Linux 2.0.x一系列的核心发行版本中,有一个新的系统呼叫(system
call), nanosleep() (请参考nanosleep(2)的说明文件),他让你能够休息或
延迟一个短的时间(数微秒或更多).
如果延迟的时间.
Q3.
out*()没有动作,或是动作怪怪的.
A3.
检查参数所放置的次序;他应该是这样outb(value, port) ,而不是
MS-DOS上常用的那样outportb(port, value)
Q4.
我想要控制一个标准的RS-232装置/连接并列埠的印表机/操纵杆...
A4.
你最好能停止此事而使用现有的驱动程式(他们存在于Linux的核心中
或X伺服器中或其他的地方)来达成你的目标.这些驱动程式通常相当
具通用性,所以就算是有点不标准的装置,他们通常都能正常运作.这些
标准I/O埠的相关资讯请参考前面说过的文件指引.
9.程式码范例
这边是一段用来存取I/O埠的简单的程式码范例:
______________________________________________________________
/*
* example.c:一个用来存取I/O埠的非常简单的范例
*
*这个程式码并没有什么用处,他只是做了埠的写入,暂停,
*以及埠的读出几个动作.编译时请使用`gcc -O2 -o example example.c',
*并以root的身份执行`./example'.
*/
#include
#include
#include
#define BASEPORT 0x378 /* lp1 */
int main()
{
/*取得埠位址的存取权限*/
if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}
/*设定埠的输出资料信号(D0-7)全为零(0) */
outb(0, BASEPORT);
/*休息一下(100 ms) */
usleep(100000);
/*从状态埠(BASE+1)读出资料并显示结果*/
printf("status: %d\n", inb(BASEPORT + 1));
/*我们不再需要这些埠位址*/
if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}
exit(0);
}
/*结束example.c */
______________________________________________________________
10.致谢
协助过我的人实在太多无法一一列出,但还是要跟各位说声多谢了.对所有来信
协助我的人并没有一一回覆致上抱歉之意,并再次谢谢你们的协助.
References
1. http://sunsite.unc.edu/pub/Linux/docs/HOWTO/COPYRIGHT
2. http://www.redhat.com:8080/HyperNews/get/khg.html
3. http://luz.cs.nmt.edu/~rtlinux/
4. http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Hardware-HOWTO
5. http://www.hut.fi/Misc/Electronics/
6. http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Printing-HOWTO
7. http://www.fapo.com/
8. http://www.senet.com.au/~cpeacock/parallel.htm
9. http://www.hut.fi/Misc/Electronics/circuits/lptpower.html
10. ftp://sunsite.unc.edu/pub/Linux/kernel/patches/
11. http://www.easysw.com/~mike/serial/index.html
12. ftp://sunsite.unc.edu/pub/Linux/apps/circuits/
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/39518/showart_1277589.html |
|