免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 812 | 回复: 0
打印 上一主题 下一主题

如何在Linux下撰写程式来使用I/O埠 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-10-09 16:46 |只看该作者 |倒序浏览
如何在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
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP