Linux系统对IO端口和IO内存的管理

发布者:HeavenlyMelody最新更新时间:2016-03-02 来源: eefocus关键字:Linux系统  IO端口  IO内存 手机看文章 扫描二维码
随时随地手机看文章

一、I/O端口

      端口(port)是接口电路中能被CPU直接访问的寄存器的地址。几乎每一种外设都是通过读写设备上的寄存器来进行的。CPU通过这些地址即端口向接口电路中的寄存器发送命令,读取状态和传送数据。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。

二、IO内存

       例如,在PC上可以插上一块图形卡,有2MB的存储空间,甚至可能还带有ROM,其中装有可执行代码。

三、IO端口和IO内存的区分及联系

         这两者如何区分就涉及到硬件知识,X86体系中,具有两个地址空间:IO空间和内存空间,而RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,即内存空间。
内存空间:内存地址寻址范围,32位操作系统内存空间为2的32次幂,即4G。
IO空间:X86特有的一个空间,与内存空间彼此独立的地址空间,32位X86有64K的IO空间。

IO端口:当寄存器或内存位于IO空间时,称为IO端口。一般寄存器也俗称I/O端口,或者说I/O ports,这个I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。

IO内存:当寄存器或内存位于内存空间时,称为IO内存。

 

四、外设IO端口物理地址的编址方式

        CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。

1、统一编址

  RISC指令系统的CPU(如,PowerPC、m68k、ARM等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

       统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”(很多外设有自己的内存、缓冲区,外设的寄存器和内存统称“I/O空间”)。

2、独立编址

        而另外一些体系结构的CPU(典型地如X86)则为外设专门实现了一个单独地地址空间,称为“I/O地址空间”或者“I/O端口空间”。这是一个与CPU地RAM物理地址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。CPU通过设立专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元(也即I/O端口)。与RAM物理地址空间相比,I/O地址空间通常都比较小,如x86 CPU的I/O空间就只有64KB(0-0xffff)。这是“I/O映射方式”的一个主要缺点。

        独立编址也称为“I/O端口”方式,外设寄存器位于“I/O(地址)空间”。

3、优缺点

独立编址主要优点是:
1)、I/O端口地址不占用存储器空间;使用专门的I/O指令对端口进行操作,I/O指令短,执行速度快。
2)、并且由于专门I/O指令与存储器访问指令有明显的区别,使程序中I/O操作和存储器操作层次清晰,程序的可读性强。
3)、同时,由于使用专门的I/O指令访问端口,并且I/O端口地址和存储器地址是分开的,故I/O端口地址和存储器地址可以重叠,而不会相互混淆。 
4)、译码电路比较简单(因为I/0端口的地址空间一般较小,所用地址线也就较少)。
其缺点是:只能用专门的I/0指令,访问端口的方法不如访问存储器的方法多。

统一编址优点:
1)、由于对I/O设备的访问是使用访问存储器的指令,所以指令类型多,功能齐全,这不仅使访问I/O端口可实现输入/输出操作,而且还可对端口内容进行算术逻辑运算,移位等等;
2)、另外,能给端口有较大的编址空间,这对大型控制系统和数据通信系统是很有意义的。
这种方式的缺点是端口占用了存储器的地址空间,使存储器容量减小,另外指令长度比专门I/O指令要长,因而执行速度较慢。
        究竟采用哪一种取决于系统的总体设计。在一个系统中也可以同时使用两种方式,前提是首先要支持I/O独立编址。Intel的x86微处理器都支持I/O 独立编址,因为它们的指令系统中都有I/O指令,并设置了可以区分I/O访问和存储器访问的控制信号引脚。而一些微处理器或单片机,为了减少引脚,从而减 少芯片占用面积,不支持I/O独立编址,只能采用存储器统一编址。

五、Linux下访问IO端口

           对于某一既定的系统,它要么是独立编址、要么是统一编址,具体采用哪一种则取决于CPU的体系结构。 如,PowerPC、m68k等采用统一编址,而X86等则采用独立编址,存在IO空间的概念。目前,大多数嵌入式微控制器如ARM、PowerPC等并不提供I/O空间,仅有内存空间,可直接用地址、指针访问。但对于Linux内核而言,它可能用于不同的CPU,所以它必须都要考虑这两种方式,于是它采用一种新的方法,将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region),不论你采用哪种方式,都要先申请IO区域:request_resource(),结束时释放它:release_resource()。

IO region是一种IO资源,因此它可以用resource结构类型来描述。

         访问IO端口有2种途径:I/O映射方式(I/O-mapped)、内存映射方式(Memory-mapped)。前一种途径不映射到内存空间,直接使用 intb()/outb()之类的函数来读写IO端口;后一种MMIO是先把IO端口映射到IO内存(“内存空间”),再使用访问IO内存的函数来访问 IO端口。

1、I/O映射方式

       直接使用IO端口操作函数:在设备打开或驱动模块被加载时申请IO端口区域,之后使用inb(),outb()等进行端口访问,最后在设备关闭或驱动被卸载时释放IO端口范围。

 in、out、ins和outs汇编语言指令都可以访问I/O端口。内核中包含了以下辅助函数来简化这种访问:

inb( )、inw( )、inl( )
分别从I/O端口读取1、2或4个连续字节。后缀“b”、“w”、“l”分别代表一个字节(8位)、一个字(16位)以及一个长整型(32位)。

inb_p( )、inw_p( )、inl_p( )
分别从I/O端口读取1、2或4个连续字节,然后执行一条“哑元(dummy,即空指令)”指令使CPU暂停。

outb( )、outw( )、outl( )
分别向一个I/O端口写入1、2或4个连续字节。

outb_p( )、outw_p( )、outl_p( )
分别向一个I/O端口写入1、2或4个连续字节,然后执行一条“哑元”指令使CPU暂停。

insb( )、insw( )、insl( )
分别从I/O端口读入以1、2或4个字节为一组的连续字节序列。字节序列的长度由该函数的参数给出。

outsb( )、outsw( )、outsl( )
分别向I/O端口写入以1、2或4个字节为一组的连续字节序列。

流程如下:

      虽然访问I/O端口非常简单,但是检测哪些I/O端口已经分配给I/O设备可能就不这么简单了,对基于ISA总线的系统来说更是如此。通常,I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据;但是,如果其他硬件设备已经使用这个端口,那么系统就会崩溃。为了防止这种情况的发生,内核必须使用“资源”来记录分配给每个硬件设备的I/O端口。资源表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。在这里,资源表示I/O端口地址的一个范围。每个资源对应的信息存放在resource数据结构中:

[plain] view plain copy
 
 print?
  1. struct resource {  
  2.          resource_size_t start;// 资源范围的开始  
  3.          resource_size_t end;// 资源范围的结束  
  4.          const char *name; //资源拥有者的名字  
  5.          unsigned long flags;// 各种标志  
  6.          struct resource *parent, *sibling, *child;// 指向资源树中父亲,兄弟和孩子的指针  
  7. };  


          所有的同种资源都插入到一个树型数据结构(父亲、兄弟和孩子)中;例如,表示I/O端口地址范围的所有资源都包括在一个根节点为ioport_resource的树中。节点的孩子被收集在一个链表中,其第一个元素由child指向。sibling字段指向链表中的下一个节点。

        为什么使用树?例如,考虑一下IDE硬盘接口所使用的I/O端口地址-比如说从0xf000 到 0xf00f。那么,start字段为0xf000 且end 字段为0xf00f的这样一个资源包含在树中,控制器的常规名字存放在name字段中。但是,IDE设备驱动程序需要记住另外的信息,也就是IDE链主盘使用0xf000 到0xf007的子范围,从盘使用0xf008 到0xf00f的子范围。为了做到这点,设备驱动程序把两个子范围对应的孩子插入到从0xf000 到0xf00f的整个范围对应的资源下。一般来说,树中的每个节点肯定相当于父节点对应范围的一个子范围。I/O端口资源树(ioport_resource)的根节点跨越了整个I/O地址空间(从端口0到65535)。

任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:

request_resource( )        //把一个给定范围分配给一个I/O设备。

allocate_resource( )        //在资源树中寻找一个给定大小和排列方式的可用范围;若存在,将这个范围分配给一个I/O设备(主要由PCI设备驱动程序使用,可以使用任意的端口号和主板上的内存地址对其进行配置)。

release_resource( )      //释放以前分配给I/O设备的给定范围。

内核也为以上函数定义了一些应用于I/O端口的快捷函数:request_region( )分配I/O端口的给定范围,release_region( )释放以前分配给I/O端口的范围。当前分配给I/O设备的所有I/O地址的树都可以从/proc/ioports文件中获得。

2、内存映射方式

         将IO端口映射为内存进行访问,在设备打开或驱动模块被加载时,申请IO端口区域并使用ioport_map()映射到内存,之后使用IO内存的函数进行端口访问,最后,在设备关闭或驱动模块被卸载时释放IO端口并释放映射。

映射函数的原型为:
void *ioport_map(unsigned long port, unsigned int count);
通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段“内存空间”。然后就可以在其返回的地址上像访问I/O内存一样访问这些I/O端口。但请注意,在进行映射前,还必须通过request_region( )分配I/O端口。

当不再需要这种映射时,需要调用下面的函数来撤消:
void ioport_unmap(void *addr);

在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是宜使用Linux内核的如下一组函数来完成访问I/O内存:·读I/O内存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
·写I/O内存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);

流程如下:

六、Linux下访问IO内存

         IO内存的访问方法是:首先调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址,之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放release_mem_region()申请的IO内存资源。

struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
   这个函数从内核申请len个内存地址(在3G~4G之间的虚地址),而这里的start为I/O物理地址,name为设备的名称。注意,。如果分配成功,则返回非NULL,否则,返回NULL。
另外,可以通过/proc/iomem查看系统给各种设备的内存范围。

要释放所申请的I/O内存,应当使用release_mem_region()函数:
void release_mem_region(unsigned long start, unsigned long len)

申请一组I/O内存后, 调用ioremap()函数:
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其中三个参数的含义为:
phys_addr:与requset_mem_region函数中参数start相同的I/O物理地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;

功能:将一个I/O地址空间映射到内核的虚拟地址空间上(通过release_mem_region()申请到的)

流程如下:

六、ioremap和ioport_map

下面具体看一下ioport_map和ioport_umap的源码:

[plain] view plain copy
 
 print?
  1. void __iomem *ioport_map(unsigned long port, unsigned int nr)  
  2. {  
  3.     if (port > PIO_MASK)  
  4.         return NULL;  
  5.     return (void __iomem *) (unsigned long) (port + PIO_OFFSET);  
  6. }  
  7.   
  8. void ioport_unmap(void __iomem *addr)  
  9. {  
  10.     /* Nothing to do */  
  11. }  

          ioport_map仅仅是将port加上PIO_OFFSET(64k),而ioport_unmap则什么都不做。这样portio的64k空间就被映射到虚拟地址的64k~128k之间,而ioremap返回的虚拟地址则肯定在3G之上。ioport_map函数的目的是试图提供与ioremap一致的虚拟地址空间。分析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制造的一个“假象”,并没有映射到内核虚拟地址,仅仅是为了让工程师可使用统一的I/O内存访问接口ioread8/iowrite8(......)访问I/O端口。
          最后来看一下ioread8的源码,其实现也就是对虚拟地址进行了判断,以区分IO端口和IO内存,然后分别使用inb/outb和readb/writeb来读写。

[plain] view plain copy
 
 print?
  1. unsigned int fastcall ioread8(void __iomem *addr)  
  2. {  
  3.     IO_COND(addr, return inb(port), return readb(addr));  
  4. }  
  5.   
  6. #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)  
  7. #define IO_COND(addr, is_pio, is_mmio) do { \  
  8.     unsigned long port = (unsigned long __force)addr; \  
  9.         if (port < PIO_RESERVED) { \  
  10.             VERIFY_PIO(port); \  
  11.             port &= PIO_MASK; \  
  12.             is_pio; \  
  13.         } else { \  
  14.             is_mmio; \  
  15.         } \  
  16. } while (0)  
  17.   
  18. 展开:  
  19. unsigned int fastcall ioread8(void __iomem *addr)  
  20. {  
  21.     unsigned long port = (unsigned long __force)addr;  
  22.     if( port < 0x40000UL ) {  
  23.         BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );  
  24.         port &= PIO_MASK;  
  25.         return inb(port);  
  26.     }else{  
  27.         return readb(addr);  
  28.     }  
  29. }  

七、总结

         外设IO寄存器地址独立编址的CPU,这时应该称外设IO寄存器为IO端口,访问IO寄存器可通过ioport_map将其映射到虚拟地址空间,但实际上这是给开发人员制造的一个“假象”,并没有映射到内核虚拟地址,仅仅是为了可以使用和IO内存一样的接口访问IO寄存器;也可以直接使用in/out指令访问IO寄存器。

          例如:Intel x86平台普通使用了名为内存映射(MMIO)的技术,该技术是PCI规范的一部分,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就如同访 问内存一样。

          外设IO寄存器地址统一编址的CPU,这时应该称外设IO寄存器为IO内存,访问IO寄存器可通过ioremap将其映射到虚拟地址空间,然后再使用read/write接口访问。

关键字:Linux系统  IO端口  IO内存 引用地址:Linux系统对IO端口和IO内存的管理

上一篇:Ubuntu11.10安装BusyBox:致命错误
下一篇:在ARM中使用Ramdisk

推荐阅读最新更新时间:2024-03-16 14:46

Linux系统启动时间优化方案
(1)首先是对Linux启动过程的跟踪和分析,生成详细的启动时间报告。 较为简单可行的方式是通过PrintkTime功能为启动过程的所有内核信息增加时间戳,便于汇总分析。PrintkTime最早为CELF所提供的一个内核补丁,在后来的Kernel 2.6.11版本中正式纳入标准内核。所以大家可能在新版本的内核中直接启用该功能。如果你的Linux内核因为某些原因不能更新为2.6.11之后的版本,那么可以参考CELF提供的方法修改或直接下载它们提供的补丁:http://tree.celinuxforum.org/CelfPubWiki/PrintkTimes 开启PrintkTime功能的方法很简单,只需在内核启动参数中增加“tim
[嵌入式]
基于ARM处理器S3C2440和Linux系统的I2C触摸屏设计
0 引言 随着计算机相关技术的发展,ARM嵌入式系统受到越来越广泛的应用,与人们生活的结合也越来越紧密。触摸屏设备因其友善的人机交互性,操作方便灵活,输入速度快,被广泛的应用于这种嵌入式领域中。嵌入式Linux系统具有开发源代码、内核稳定、可裁减性等特点,吸引着众多商业公司和自由软件开发者的目光,成为嵌入式系统领域不可或缺的操作系统之一。触摸屏是一种输入设备,操作简单易学,可靠性高,不占额外的空间,是最常用的便携式系统的输入设备。特别是电阻式触摸屏,它结构简单,成本低,透光效果好,工作环境和外界完全隔离,不怕灰尘和水气,同时具有高解析度、高速传输反应、一次校正、稳定性高、不漂移等特点,因而被广泛用于工业控制领域。 1 电阻式触摸屏
[单片机]
基于ARM处理器S3C2440和<font color='red'>Linux系统</font>的I2C触摸屏设计
运行在网络处理器上的嵌入式Linux系统
   摘要:网络处理器已经可能的未来的多核处理器会将越来越多的应用在从家庭,企业到核心网络设备中,LinuxOS正在替代传统的RTOS运行在这些处理器的控制平面,定义和处理好数据平面和控制平面的接口,丰富的开源和商业应用软件和电信级别的高可能性要求是目前需要解决的重要问题。 关键字:网络处理器,嵌入式Linux,多核处理器 引言:     在最近的24个月中,由于供应商组织面临经济状况不景气,加上IntelIXP和IBMPowerNP,Raza,Cavium,Xilinx等公司的网络处理器(多核处理器)的出现,使得Linux在基于这些处理器设备的控制和管理层面上有了更多的用武之地。 控制层面包括逻辑和物理接口,它们
[工业控制]
嵌入式Linux系统小型化技术
作者Email: zhh@httc.cn 介绍了Linux在嵌入式领域中的应用和宿主机、目标机开发模式,详细地给出了精简内核的实现过程。分析了glibc系统库和ELF文件格式的结构和其中的共享库裁剪技术的原理,提出并实现了一种库裁剪方案。 关键词 嵌入式;Linux;小型化 一、 概述 嵌入式Linux一般是指对标准Linux发行版本进行小型化裁剪处理之后,适合于特定嵌入式应用场合的专用Linux操作系统。嵌入式系统通常是资源受限的系统,无论是处理器计算能力还是RAM或其他存储器容量都比较“小”。因此,如何创建一个小型化的Linux作为操作系统开发成为首先需要考虑的问题。嵌入式Linux系统中普遍采用三层结构
[应用]
在MIPS开发板上建立Linux系统及开发环境
作者Email: jie_yi@sohu.com 介绍了利用已有的MIPS版Linux源文件包,编译可运行的Linux内核的方式;讨论了在主机上建立相关服务器,通过NFS服务从主机上下载内核,在无盘开发板上启动Linux的方法。 关键词:MIPS NFS 启动Linux 一、引言 大多数基于MIPS处理器的平台上都没有提供软、硬盘接口,一般情况下,也没有在板载Flash里烧入可使用的操作系统。没有操作系统,用户的使用就会受到很多限制,应用程序的开发、调试过程就会变得相当复杂。因此,建立合适的操作系统及开发环境,可以方便用户,简化开发调试过程。本文以RedHat Linux 为例,说明在RM7000A处理器无盘
[嵌入式]
PORT.H头文件下载-AVR定时器通用IO端口定义
/***************************************************************************** 文件名称:PORT.H 文件标识:_PORT_H_ 摘 要:AVR定时器通用IO端口定义 *****************************************************************************/ #ifndef _PORT_H_ #define _PORT_H_ #define P(X,Y) (BYPORT(X)*256+BYBIT(Y)) //合并引脚信息 #defin
[单片机]
ARM和LINUX系统下嵌入式USB主机的设计
1 引言 通用串行总线(Universal Serial Bus,即USB)是一种新型的高速串行总线,作为一种快速、灵活的总线接口,已经在电子产品中广泛应用。同时人们对USB的期望也越来越高,希望USB能应用在各种计算机领域中。但是,由于USB通信模型是一种Host/Slave主从式结构,经由USB总线进行通信的双方必须有一方在通信控制中担当主机,而两个USB设备之间则无法直接进行基于USB的数据交换。这就大大限制了USB在嵌入式设备中的应用。 ARM( Advanced RISC Machine)作为一种低功耗、高性能的32位嵌入式微处理器,在中高端嵌入式设备开发中有着广泛的应用。有些ARM芯片的生产厂商在以ARM为内核的微
[单片机]
ARM和<font color='red'>LINUX系统</font>下嵌入式USB主机的设计
基于2.6.19内核的小型Linux系统制作与移植
引言 ARM9 S3C2410微处理器与Linux的结合越来越紧密,逐渐在嵌入式领域得到广范的应用。目前,在便携式消费类电子产品、无线设备、汽车、网络、存储产品等都可以看到S3C2410与Linux相结合的身影。 S3C2410微处理器是一款由Samsung公司为手持终端设计的低价格、低功耗、高性能,基于ARM920T核的微处理器。它带有内存管理单元(MMU),采用0.18mm工艺和AMBA新型总线结构,主频可达203MHz。同时,它支持Thumb 16位压缩指令集,从而能以较小的存储空间获得32位的系统性能。 在众多嵌入式操作系统中,Linux目前发展最快、应用最为广泛 。性能优良、源码开放的Linux具有体积小、内核可裁
[应用]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved