你知道io端口与io内存有哪些区别吗?.doc
你知道io端口与io内存有哪些区别吗?(一)地址的概念物理地址:2)总线地址:总线的地址线或在地址周期上产生的信号。外设使用的是总线地址,CPU使用的是物理地址。,这是因为它们共享相同的地址空间这句话有点难理解,详见下面的“独立编址”。在其他平台上,可能需要转换/映射。比如:CPU需要访问物理地址是0xfa000的单元,那么在x86平台上,会产生一个PCI总线上对0xfa000地址的访问。因为物理地址和总线地址相同。3)虚拟地址:现代操作系统普遍采用虚拟内存管理(VirtualMemory Management)机制,这需要MMU(MemoryManagement Unit)的支持。MMU通常是CPU的一部分,如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(物理内存)接收,这称为物理地址(Physical Address),如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。 外设都是通过读写设备上的统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”(很多外设有自己的内存、缓冲区,外设的寄存器和内存统称“I/O空间”)。 如,S独立编址(单独编址):IO地址与存储地址分开独立编址,I/0端口地址不占用存储空间的地址范围,这样,在系统中就存在了另一种与存储地址无关的IO地址,CPU也必须具有专用与输入输出操作的IO指令(IN、OUT等)和控制逻辑。独立编址下,地址总线上过来一个地址,设备不知道是给IO端口的、还是给存储器的,于是处理器通过MEMR/MEMW和IOR/IOW两组控制信号来实现对I/O端口和存储器的不同寻址。如,CPU内存和I/O是一起编址的,就是说内存一部分的地址和I/O地址是重叠的。 独立编址也称为“I/O端口”方式,外设寄存器位于“I/O(地址)空间”。 对于x86架构来说,通过IN/OUT指令访问。PC架构一共有65536个8bit的I/O端口,组成64K个I/O地址空间,编号从00xFFFF,有16位,80x86用低16位地址线A0-A15来寻址。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个 32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如I/O地址空间为64K,一个32bit的CPU物理地址空间是4G。如,在Intel 8086+Redhat9.0 下用“more/proc/ioports”可看到:0000-001f : (三)不同体系结构编址方式总结几乎每一种外设都是通过读写设备上的寄存器来进行的。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/Omapped),另一种是内存映射方式(Memorymapped)。而具体采用哪一种则取决于CPU的体系结构。内存映射方式”(Memorymapped)。这是一个与CPU地RAM物理地址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。CPU通过设立专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元(也即I/O端口)。这就是所谓的“I/O映射方式”(I/Omapped)。与RAM物理地址空间相比,I/O地址空间通常都比较小,如x86 CPU的I/O空间就只有64KB(00xffff)。这是“I/O映射方式”的一个主要缺点。(四)IO端口与IO内存区别1. CPU是i386架构的情况2. CPU是ARM 或PPC架构的情况X86体系中,具有两个地址空间:IO空间和内存空间,而IO空间:X86特有的一个空间,与内存空间彼此独立的地址空间,32位X86有64K的IO空间。IO端口:当寄存器或内存位于IO空间时,称为IO端口。一般寄存器也俗称I/O端口,或者说I/Oports,这个I/O端口可以被映射在MemorySpace,也可以被映射在I/IO内存:当寄存器或内存位于内存空间时,称为IO内存。(五)在Linux下对IO端口与IO内存访问方式总结1)在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端口有2种途径:I/O映射方式(I/Omapped)、内存映射方式(Memorymapped)。前一种途径不映射到内存空间,直接使用 intb()/outb()之类的函数来读写IO端口;后一种MMIO是先把IO端口映射到IO内存(“内存空间”),再使用访问IO内存的函数来访问 IO端口。1、I/O映射方式资源表示I/O端口地址的一个范围。每个资源对应的信息存放在resource数据结构中:struct resource resource_size_t start;/ 资源范围的开始 resource_size_t end;/ 资源范围的结束 const char *name; /资源拥有者的名字 unsigned long flags;/ 各种标志 struct resource *parent, *sibling, *child;/ 指向资源树中父亲,兄弟和孩子的指针 2、内存映射方式 将IO端口映射为内存进行访问,在设备打开或驱动模块被加载时,申请IO端口区域并使用ioport_map()映射到内存,之后使用IO内存的函数进行端口访问,最后,在设备关闭或驱动模块被卸载时释放IO端口并释放映射。在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是宜使用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);2)Linux下访问IO内存而这里的start为I/O物理地址,name为设备的名称。注意,如果分配成功,则返回非NULL,否则,返回NULL。另外,可以通过/proc/iomem查看系统给各种设备的内存范围。申请一组I/O内存后,调用ioremap()函数:将一个I/O地址空间映射到内核的虚拟地址空间上(通过requset _mem_region()申请到的)3)ioremap和ioport_mapvoid _iomem *ioport_map(unsigned long port, unsigned int nr) if (port > PIO_MASK) return NULL; return (void _iomem *) (unsigned long) (port + PIO_OFFSET); void ioport_unmap(void _iomem *addr) /* Nothing to do */ 这样portio的64k空间就被映射到虚拟地址的64k128k之间,而ioremap返回的虚拟地址则肯定在3G之上。ioport_map函数的目的是试图提供与ioremap一致的虚拟地址空间。分析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员unsigned int fastcall ioread8(void _iomem *addr) IO_COND(addr, return inb(port), return readb(addr); #define VERIFY_PIO(port) BUG_ON(port if (port VERIFY_PIO(port); port is_pio; else is_mmio; while (0) 展开: unsigned int fastcall ioread8(void _iomem *addr) unsigned long port = (unsigned long _force)addr; if( port BUG_ON( (port port return inb(port); else return readb(addr);