开发者社区> xumaojun> 正文

内存映射(IO地址和内存地址)

简介: ARM体系结构下面内存和i/o映射区别 (1)关于IO与内存空间:    在X86处理器中存在着I/O空间的概念,I/O空间是相对于内存空间而言的,它通过特定的指令in、out来访问。
+关注继续查看

ARM体系结构下面内存和i/o映射区别<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

1)关于IO与内存空间:
   
X86处理器中存在着I/O空间的概念,I/O空间是相对于内存空间而言的,它通过特定的指令inout来访问。端口号标识了外设的寄存器地址。Intel语法的inout指令格式为:
    IN
累加器, {端口号DX}
    OUT {
端口号DX},累加器
   
目前,大多数嵌入式微控制器如ARMPowerPC等中并不提供I/O空间,而仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和其他数据都存在于内存空间中。
    即便是在X86处理器中,虽然提供了I/O空间,如果由我们自己设计电路板,外设仍然可以只挂接在内存空间。此时,CPU可以像访问一个内存单元那样访问外设I/O端口,而不需要设立专门的I/O指令。因此,内存空间是必须的,而I/O空间是可选的。

2inboutb

Linux设备驱动中,宜使用Linux内核提供的函数来访问定位于I/O空间的端口,这些函数包括:
· 
读写字节端口(8位宽)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
· 
读写字端口(16位宽)
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
· 
读写长字端口(32位宽)
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
· 
读写一串字节
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
·  insb()
从端口port开始读count个字节端口,并将读取结果写入addr指向的内存;outsb()addr指向的内存的count个字节连续地写入port开始的端口。
· 
读写一串字
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
· 
读写一串长字
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
上述各函数中I/O端口号port的类型高度依赖于具体的硬件平台,因此,只是写出了unsigned

3readbwriteb:
在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是工程师宜使用Linux内核的如下一组函数来完成设备内存映射的虚拟地址的读写,这些函数包括:
· 
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);

4)把I/O端口映射到内存空间”:
void *ioport_map(unsigned long port, unsigned int count);
通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段内存空间。然后就可以在其返回的地址上像访问I/O内存一样访问这些I/O端口。当不再需要这种映射时,需要调用下面的函数来撤消:
void ioport_unmap(void *addr);
实际上,分析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制造的一个假象,并没有映射到内核虚拟地址,仅仅是为了让工程师可使用统一的I/O内存访问接口访问I/O端口。

 

 

 

<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />11.2.7 I/O 空间的映射

很多硬件设备都有自己的内存,通常称之为I/O空间。例如,所有比较新的图形卡都有几MBRAM,称为显存,用它来存放要在屏幕上显示的屏幕影像。

1.地址映射

    根据设备和总线类型的不同,PC体系结构中的I/O空间可以在三个不同的物理地址范围之间进行映射:

1)对于连接到ISA总线上的大多数设备

I/O空间通常被映射到从0xa00000xfffff的物理地址范围,这就在640K1MB之间留出了一段空间,这就是所谓的“洞”。

2)对于使用VESA本地总线(VLB)的一些老设备

    这是主要由图形卡使用的一条专用总线:I/O空间被映射到从0xe000000xffffff的地址范围中,也就是14MB16MB之间。因为这些设备使页表的初始化更加复杂,因此已经不生产这种设备。

3)对于连接到PCI总线的设备

    I/O空间被映射到很大的物理地址区间,位于RAM物理地址的顶端。这种设备的处理比较简单。

2.访问I/O空间

    内核如何访问一个I/O空间单元?让我们从PC体系结构开始入手,这个问题很容易就可以解决,之后我们再进一步讨论其他体系结构。

    不要忘了内核程序作用于虚拟地址,因此I/O空间单元必须表示成大于PAGE_OFFSET的地址。在后面的讨论中,我们假设PAGE_OFFSET等于0xc0000000,也就是说,内核虚拟地址是在第4G

    内核驱动程序必须把I/O空间单元的物理地址转换成内核空间的虚拟地址。PC体系结构中,这可以简单地把32位的物理地址和0xc0000000常量进行或运算得到。例如,假设内核需要把物理地址为0x000b0fe4I/O单元的值存放在t1中,把物理地址为0xfc000000I/O单元的值存放在t2中,就可以使用下面的表达式来完成这项功能:

 

    t1 = *((unsigned char *)(0xc00b0fe4));

    t2 = *((unsigned char *)(0xfc000000));

 

    在第六章我们已经介绍过,在初始化阶段,内核已经把可用的RAM物理地址映射到虚拟地址空间第4G的最初部分。因此,分页机制把出现在第一个语句中的虚拟地址0xc00b0fe4映射回到原来的I/O物理地址0x000b0fe4,这正好落在从640K1MB的这段“ISA洞”中。这正是我们所期望的。

    但是,对于第二个语句来说,这里有一个问题,因为其I/O物理地址超过了系统RAM的最大物理地址。因此,虚拟地址0xfc000000就不需要与物理地址0xfc000000相对应。在这种情况下,为了在内核页表中包括对这个I/O物理地址进行映射的虚拟地址,必须对页表进行修改:这可以通过调用ioremap( )函数来实现。ioremap( )vmalloc( )函数类似,都调用get_vm_area( ) 建立一个新的vm_struct描述符,其描述的虚拟地址区间为所请求I/O空间区的大小。然后,ioremap( )函数适当地更新所有进程的对应页表项。

因此,第二个语句的正确形式应该为:

 

    io_mem = ioremap(0xfb000000, 0x200000);

    t2 = *((unsigned char *)(io_mem + 0x100000));

 

    第一条语句建立一个2MB的虚拟地址区间,从0xfb000000开始;第二条语句读取地址0xfc000000的内存单元。驱动程序以后要取消这种映射,就必须使用iounmap( )函数。

 

    现在让我们考虑一下除PC之外的体系结构。在这种情况下,把I/O物理地址加上0xc0000000常量所得到的相应虚拟地址并不总是正确的。为了提高内核的可移植性,Linux特意包含了下面这些宏来访问I/O空间

    readb, readw, readl

   分别从一个I/O空间单元读取12或者4个字节

   writeb, writew, writel

   分别向一个I/O空间单元写入12或者4个字节

   memcpy_fromio, memcpy_toio

   把一个数据块从一个I/O空间单元拷贝到动态内存中,另一个函数正好相反,把一个数据块从动态内存中拷贝到一个I/O空间单元

   memset_io

   用一个固定的值填充一个I/O空间区域

对于0xfc000000 I/O单元的访问推荐使用这样的方法:

    io_mem = ioremap(0xfb000000, 0x200000);

    t2 = readb(io_mem + 0x100000);

    使用这些宏就可以隐藏不同平台访问I/O空间所用方法的差异。

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
c++ windows 获取mac地址
Windows平台下用C++代码取得机器的MAC地址并不是一件简单直接的事情。到目前为止,作者尚未发现有任何一个通用的100%的适用于所有Windows平台的方法可以稳定的取得MAC地址。而有些应用(比如MMORPG)则需要稳定的得到机器的MAC地址,解决方案往往是通过多种方法依次使用来提高成功率。
2219 0
iOS夯实:ARC时代的内存管理
iOS夯实:ARC时代的内存管理 什么是ARC Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations [^1] [^1]: Transitioning to ARC Release Notes ARC提供是一个编译器的特性,帮助我们在编译的时候自动插入管理引用计数的代码。
990 0
内存数据库
        内存数据库,顾名思义就是将数据放在内存中直接操作的数据库。相对于磁盘,内存的数据读写速度要高出几个数量级,将数据保存在内存中相比从磁盘上访问能够极大地提高应用的性能。同时,内存数据库抛弃了磁盘数据管理的传统方式,基于全部数据都在内存中重新设计了体系结构,并且在数据缓存、快速算法、并行操作方面也进行了相应的改进,所以数据处理速度比传统数据库的数据处理速度要快很多,一般都在10
1305 0
android内存优化
Android的应用被限制为最多占用16m的内存,至少在T-Mobile G1上是这样的(当然现在已经有几百兆的内存可以用了——译者注)。它包括电话本身占用的和开发者可以使用的两部分。即使你没有占用全部内存的打算,你也应该尽量少的使用内存,以免别的应用在运行的时候关闭你的应用。Android能在内存中保持的应用越多,用户在切换应用的时候就越快。作为我的一项工作,我仔细研究了Android应
940 0
理解 iOS 和 macOS 的内存管理
本文将会介绍 iOS 和 macOS 应用开发过程中,如何进行内存管理,以及介绍一些内存管理使用的场景,帮助大家理解内存方面的问题
7359 0
+关注
xumaojun
乐于学习与分析
1079
文章
95
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载