如何在 Linux 下撰写程序来使用 I.docx
- 文档编号:26552470
- 上传时间:2023-06-20
- 格式:DOCX
- 页数:15
- 大小:29.44KB
如何在 Linux 下撰写程序来使用 I.docx
《如何在 Linux 下撰写程序来使用 I.docx》由会员分享,可在线阅读,更多相关《如何在 Linux 下撰写程序来使用 I.docx(15页珍藏版)》请在冰豆网上搜索。
如何在Linux下撰写程序来使用I
如何在Linux下撰写程序来使用I/O端口
1.介绍
本文的内容说明了Intelx86架构下如何在使用者模式(user-mode)中撰写程序来使用硬件I/O端口以及等待一小段的时间周期.内容源自於一篇非常短的文章IO-Portmini-HOWTO其作者与本文同.
本文1995-1997的版权属於RikuSaikkonen所有.版权声明详见网页LinuxHOWTOcopyright.
如果您对本文有任何指教不论是错误修正或是内容补述,都欢迎寄信给我(Riku.Saikkonen@hut.fi)...
本文对前一次发行的版本(Mar301997)作了如下的修正:
∙对于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文件里).这些常式是以单行宏(inlinemacros)的方式写成的,所以使用时只要以#include
译注:
常式(Routine)通常是指系统调用(SystemCall)与函数(Function)的总称.
因为gcc(至少出现在2.7.2.3和以前的版本)以及egcs(所有的版本)的限制,你在编译任何使用到这些常式的源代码时必须打开最佳化选项(gcc-O1或较高层次的),或者是在做#include
为了调试的目的,你编译时可以使用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()的方式却会有所影响(虽然父行程(parentprocess)保有存取权限,但是子行程(childprocess)却无法取得存取权限).
函数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
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.你必需撰写一个核心驱动程序;相关的细节请参考网页TheLinuxKernelHacker'sGuide以及拿核心程序源代码来当范例.
也就是说,你在使用者模式(user-mode)中所写的程序无法抑制硬件中断的产生.
4.高精确的时序
4.1延迟时间
首先,我会说不保证你在使用者模式(user-mode)中执行的行程(process)能够精确地控制时序因为Linux是个多工的操作环境.你在执行中的行程(process)随时会因为各种原因被暂停大约10毫秒到数秒(在系统负荷非常高的时候).然而,对于大多数使用I/O端口的应用而言,这个延迟时间实际上算不了什么.要缩短延迟时间,你得使用函数nice将你在执行中的行程(process)设定成高优先权(请参考nice
(2)使用说明文件)或使用实时排程法(real-timescheduling)(请看下面).
如果你想获得比在一般使用者模式(user-mode)中执行的行程(process)还要精确的时序,有一些方法可以让你在使用者模式(user-mode)中做到`实时'排程的支持.Linux2.x版本的核心中有软件方式的实时排程支持;详细的说明请参考sched_setscheduler
(2)使用说明文件.有一个特殊的核心支持植件的实时排程;详细的信息请参考网页http:
//luz.cs.nmt.edu/~rtlinux/
休息中(Sleeping):
sleep()与usleep()
现在,让我们开始较简单的时序函数调用.想要延迟数秒的时间,最佳的方法大概是使用函数sleep().想要延迟至少数十毫秒的时间(10ms似乎已是最短的延迟时间了),函数usleep()应该可以使用.这些函数是让出CPU的使用权给其它想要执行的行程(processes)(``自己休息去了''),所以没有浪费掉CPU的时间.细节请参考sleep(3)与usleep(3)的说明文件.
如果让出CPU的使用权因而使得时间延迟了大约50毫秒(这取决於处理器与机器的速度,以及系统的负荷),就浪费掉CPU太多的时间,因为Linux的排程器(scheduler)(单就x86架构而言)在将控制权发还给你的行程(process)之前通常至少要花费10-30毫秒的时间.因此,短时间的延迟,使用函数usleep(3)所得到的延迟结果通常会大於你在参数所指定的值,大约至少有10ms.
nanosleep()
在Linux2.0.x一系列的核心发行版本中,有一个新的系统调用(systemcall),nanosleep()(请参考nanosleep
(2)的说明文件),他让你能够休息或延迟一个短的时间(数微秒或更多).
如果延迟的时间<=2ms,若(且唯若)你执行中的行程(process)设定了软件的实时排程(就是使用函数tt/sched_setscheduler()/),调用函数nanosleep()时不是使用一个忙碌循环来延迟时间;就是会像函数usleep()一样让出CPU的使用权休息去了.
这个忙碌循环使用函数udelay()(一个驱动程序常会用到的核心内部的函数)来达成,并且使用BogoMips值(BogoMips可以准确量测这类忙碌循环的速度)来计算循环延迟的时间长度.其如何动作的细节请参考/usr/include/asm/delay.h).
使用I/O端口来延迟时间
另一个延迟数微秒的方法是使用I/O端口.就是从端口地址0x80输入或输出任何byte的数据(请参考前面)等待的时间应该几乎只要1微秒这要看你的处理器的型别与速度.如果要延迟数微秒的时间你可以将这个动作多做几次.在任何标准的机器上输出数据到该端口地址应该不会有不良的後果□对(而且有些核心的设备驱动程序也在使用他).{in|out}[bw]_p()等函数就是使用这个方法来产生时间延迟的(请参考文件asm/io.h).
实际上,一个使用到端口地址范围为0-0x3ff的I/O端口指令几乎只要1微秒的时间,所以如果你要如此做,例如,直接使用并口,只要加上几个inb()函数从该端口地址范围读入byte的数据即可.
使用组合语言来延迟时间
如果你知道执行程序所在机器的处理器型别与时种速度,你可以执行某些组合语言指令以便获得较短的延迟时间(但是记住,你在执行中的行程(process)随时会被暂停,所以有时延迟的时间会比实际长).如下面的表格所示,内部处理器的速度决定了所要使用的时种周期数;如,一个50MHz的处理器(486DX-50或486DX2-50),一个时种周期要花费1/50000000秒(=200奈秒).
指令i386时种周期数i486时种周期数
nop31
xchg%ax,%ax33
or%ax,%ax21
mov%ax,%ax21
add%ax,021
(对不起,我不知道Pentiums的数据,或许与i486接近吧.我无法在i386的数据上找到只花费一个时种周期的指令.如果能够就请使用花费一个时种周期的指令,要不然就使用管线技术的新式处理器也是可以缩短时间的.)
上面的表格中指令nop与xchg应该不会有不良的後果.指令最后可能会改变旗号暂存器的内容,但是这没关系因为gcc会处理.指令nop是个好的选择.
想要在你的程序中使用到这些指令,你得使用asm("instruction").指令的语法就如同上面表格的用法;如果你想要在单一的asm()叙述中使用多个指令,可以使用分号将他们隔开.例如,asm("nop;nop;nop;nop")会执行四个nop指令,在i486或Pentium处理器中会延迟四个时种周期(或是i386会延迟12个时种周期).
gcc会将asm()翻译成单行组合语言程序代码,所以不会有调用函数的负荷.
在Intelx86架构中不可能有比一个时种周期还短的时间延迟.
在Pentiums处理器上使用函数rdtsc
对于Pentiums处理器而言,你可以使用下面的C语言程序代码来取得自从上次重新开机到现在经过了多少个时种周期:
extern__inline__unsignedlonglongintrdtsc()
{
unsignedlonglongintx;
__asm__volatile(".byte0x0f,0x31":
"=A"(x));
returnx;
}
你可以询问参考此值以便延迟你想要的时种周期数.
4.2时间的量测
想要时间精确到一秒种,使用函数time()或许是最简单的方法.想要时间更精确,函数gettimeofday()大约可以精确到微秒(但是如前所述会受到CPU排程的影响).至於Pentiums处理器,使用上面的程序代码片断就可以精确到一个时种周期.
如果你要你执行中的行程(process)在一段时间到了之后能够被通知(getasignal),你得使用函数setitimer()或alarm().细节请参考函数的使用说明文件.
5使用其它程序语言
上面的说明集中在C程序语言.他应该可以直接应用在C++及ObjectiveC之上.至於组合语言部分,虽然你必须先在C语言中调用函数ioperm()或iopl(),但是之后你就可以直接使用I/O端口读写指令.
至於其它程序语言,除非你可以在该程序语言中插入单行组合语言或C语言之程序代码或者使用上面所说的系统调用,否则倒不如撰写一个内含有存取I/O端口或延迟时间所必需函数之简单的C原始程序代码或许还比较容易,编译之后再与你的程序链结.要不然就是使用前面所说的/dev/port字符装置文件.
6.一些有用的I/O端口
本节提供一些常用I/O端口的程序撰写信息这些都是可以直接拿来用的一般目的TTL(或CMOS)逻辑位准的I/O端口.
如果你要按照其原始的设计目的来使用这些或其它常用的I/O端口(例如,控制一般的打印机或调制解调器),你应该会使用现成的装置驱动程序(他通常被含在核心中)而不会如本文所说地去撰写I/O端口程序.本节主要是提供给那些想要将LCD显示器,步进马达,或是其它商业电子产品连接到PC标准I/O端口的人.
如果你想要控制大众市场所贩卖的装置像是扫描器(已经在市场贩卖了一段期间),去找看看是否有现成的Linux装置驱动程序.网页Hardware-HOWTO是个好的参考起点.
至於想要知道更多有关如何连接电子装置到计算机(以及一般的电子学原理)的相关信息则网页http:
//www.hut.fi/Misc/Electronics/是个好的数据来源.
6.1并口(parallelport)
并口的基本端口地址(以下称之为``BASE'')之於/dev/lp0是0x3bc,之於/dev/lp1是0x378,之於/dev/lp2是0x278.如果你只是想要控制一些像是一般打印机的动作,可以参考网页Printing-HOWTO.
除了下面即将描述的标准仅输出(output-only)模式,大多数的并口都有`扩充的'双向(bidirectional)模式.至於较新的ECP/EPP模式(以及一般的IEEE1284标准)端口口的相关数据,可以参考网页以及.au/~cpeacock/parallel.htm.因为在使用者模式(user-mode)中的程序无法使用IRQs或DMA,想要使用ECP/EPP模式你或许得撰写一个核心的装置驱动程序;我想应该有人写了这类的装置驱动程序,但是详情我并不知道.
端口地址BASE+0(数据端口)用来控制数据端口的信号位准(D0到D7分别代表著bits0到7,位准状态:
0=低位准(0V),1=高位准(5V)).一个写入数据到该端口的动作会将数据信号位准拴住(latches)在端口的脚位(pins)上.一个将该端口的数据读出的动作会将上一次以标准仅输出(output-only)模式或扩充的写入模式所拴住的数据信号位准读回,或是以扩充读出模式从另外一个装置将脚位上的数据信号位准读回.
端口地址BASE+1(状态端口)是个仅读(read-only)的端口,会将下面的输入信号位准读回:
∙Bits0和1保留不用.
∙Bit2IRQ的状态(不是个脚位(pin),我不知道他的工作原理)
∙Bit3ERROR(1=高位准)
∙Bit4SLCT(1=高位准)
∙Bit5PE(1=高位准)
∙Bit6ACK(1=高位准)
∙Bit7-BUSY(0=高位准)
(我不确定高低位准的电压状态.)
端口地址BASE+2(控制端口)是个仅写(write-only)的端口(一个将该端口的数据读出的动作仅会将上一次写入的数据信号位准读回),用来控制下面的状态信号:
∙Bit0-STROBE(0=高位准)
∙Bit1AUTO_FD_XT(1=高位准)
∙Bit2-INIT(0=高位准)
∙Bit3SLCT_IN(1=高位准)
∙Bit4当被设定为1时允许并口产生IRQ信号(发生在ACK脚位的位准由低变高的瞬间)
∙Bit5用来控制扩充模式时端口的输出入方向(0=写,1=读),这是个仅写(write-only)的端口(一个将该端口的数据读出的动作对此bit一点用处也没有).
∙Bits6and7保留不用.
(同样地,我不确定高低位准的电压状态.)
端口的脚位排列(Pinout)方式(该端口是一个25只脚D字形外壳(D-shell)的母头连接器)(i=输入,o=输出):
1io-STROBE,2ioD0,3ioD1,4ioD2,5ioD3,6ioD4,7ioD5,8ioD6,
9ioD7,10iACK,11i-BUSY,12iPE,13iSLCT,14oAUTO_FD_XT,
15iERROR,16o-INIT,17oSLCT_IN,18-25Ground
IBM的规格文件上说脚位1,14,16,和17(控制信号的输出)采用电晶体的开集极(opencollector)驱动方式必需使用4.7仟欧姆(kiloohm)的提升电阻接至5V的电压(可流入电流20mA,流出电流0.55mA,高位准的输出电压就是5.0V减去提升电阻的电压).剩下来的脚位可流入电流24mA,流出电流15mA,高位准的输出电压最小2.4V.低位准的输出电压二者都是最大0.5V.那些非IBM规格的并口或许会偏离这个标准.更多的相关数据请参考网页http:
//www.hut.fi/Misc/Electronics/circuits/lptpower.html.
最后,给你一个警告:
留心接地的问题.我曾经在计算机还是开机的状况就去连接他因而弄坏好几个并口.发生了这种事情你可能会觉得还是不要将并口整合到主机板里面比较好.(你通常可以拿一片便宜的标准`multi-I/O'卡安装第二个并口;只要将其它不需要的端口停用,然后将卡片上并口的端口地址设定在空著的地址即可.你不需在意并口的IRQ设定,因为通常不会被用到.)
6.2游戏(操纵□)端口(gameport)
游戏端口的端口地址范围为0x200-0x207.想要控制一般的操纵□,有一个核心层次的操纵□驱动程序,可参考网址ftp:
//sunsite.unc.edu/pub/Linux/kernel/patches/,文件名joystick-*.
端口的脚位排列(Pinout)方式(该端口是一个15
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 如何在 Linux 下撰写程序来使用 如何 撰写 程序 使用