Linux系统下IO Memory和IO Port的区别
2014年07月06日
⁄ Linux知识⁄ 共 721字 ⁄ 字号
小 中 大
下面以x86架构为例,其它的架构可能并不适用。
先说一下本人对IO Memory和IO Port的理解。
IO Port叫做IO端口,主要用来指外设(如网卡、HBA卡)等的寄存器空间,而IO Memory叫做IO内存,有些外设可能不光有寄存器,还有其他的内存空间,这部分内存空间叫做IO内存,IO端口就相当于主板上CPU里面的寄存器,IO内存就相当于主板上的内存条。可以参考一下下面这张图:
IO内存作为系统内存的一部分,外设的IO内存资源的物理地址是已知的,由硬件的设计决定,可以通过cat /proc/iomem命令查看外设的IO内存物理地址分布情况,访问IO内存前,必须先通过ioremap方式建立内存映射表(页表)后CPU才能够访问这些IO空间(访问系统内存前不也是需要建立映射表么,只不过不是通过ioremap方式建立的),因为CPU是不可以直接访问物理地址的,所以在访问前必须先为这些物理地址空间建立映射后CPU通过访问映射后的虚拟地址去访问这些IO内存。
IO端口即外设的寄存器空间,CPU访问外设的寄存器有两种方式,一种是直接将这些寄存器当成普通的IO内存来看待,这样CPU访问这些寄存器就跟访问IO内存方式是一样的了;另外一种方式是通过类似将这些寄存器进行编号(即IO端口号),然后通过特殊的端口访问方式来访问这些寄存器。系统的IO端口分布情况可以通过cat /proc/ioport方式查看,这里面列出了系统所有的IO端口分布情况,注意这边看到的地址不是物理地址,而是IO端口号的分布情况,跟物理地址没有关系,CPU访问外设寄存器就是通过传入这些端口号来访问外设寄存器的,而cat /proc/iomem这个里面看到的地址是物理地址的分布情况。
操作IO端口
对I/O端口的操作需按如下步骤完成:
1、申请
2、访问
3、释放
申请I/O端口:内核提供了一套函数来允许驱动申请它需要的I/O端口,其中核心的函数是:
struct resource *request_region(unsigned long first, unsigned long n, const char *name)
这个函数告诉内核,你要使用从first开始的n个端口,name参数是设备的名字。如果申请成功,返回非NULL,申请失败,返回NULL。
系统中端口的分配情况记录在/proc/ioports中(展示)。如果不能分配需要的端口,可以来这里查看谁在使用。
访问I/O端口:I/O端口可分为8-位,16-位,和32-位端口。linux内核头文件(体系依赖的头文件<asm/io.h>)定义了下列内联函数来访问I/O端口:
unsigned inb(unsigned port)
读字节端口(8位宽)
void outb(unsigned char byte, unsigned port)
写字节端口(8位宽)
unsigned inw(unsigned port)
void outw(unsigned short word, unsigned port)
存取16-位端口
unsigned inl(unsigned port)
void outl(unsigned longword, unsigned port)
存取32-位端口
释放I/O端口:
当用完一组I/O端口(通常在驱动卸载时),应使用如下函数把它们返还给系统:
void release_region(unsigned long start, unsigned long n)
操作I/O内存
对I/O内存的操作需按如下步骤完成:
1、申请
2、映射
3、访问
4、释放
申请I/O内存:内核提供了一套函数来允许驱动申请它需要的I/O内存,其中核心的函数是:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name)
这个函数申请一个从start开始,长度为len字节的内存区。如果成功,返回非NULL;否则返回NULL,所有已经在使用的I/O内存在/proc/iomem中列出。
映射I/O内存:在访问I/O内存之前,必须进行物理地址到虚拟地址的映射,ioremap函数具有此功能:
void *ioremap(unsigned long phys_addr, unsigned long size)
访问I/O内存:访问I/O内存的正确方法是通过一系列内核提供的函数:
从I/O内存读,使用下列之一:
unsigned ioread8(void *addr)
unsigned ioread16(void *addr)
unsigned ioread32(void *addr)
写I/O内存,使用下列之一:
void iowrite8(u8 value, void *addr)
void iowrite16(u16 value, void *addr)
void iowrite32(u32 value, void *addr)
老版本的I/O内存访问函数:
从I/O内存读,使用下列之一:
unsigned readb(address)
unsigned readw(address)
unsigned readl(address)
写I/O内存,使用下列之一:
unsigned writeb(unsigned value, address)
unsigned writew(unsigned value, address)
unsigned writel(unsigned value, address)
释放I/O内存
I/O内存不再需要使用时应该释放,步骤如下:
1、void iounmap(void * addr)
2、void release_mem_region(unsigned long start, unsigned long len)
对I/O空间的访问
1、访问I/O内存(I/O内存必须映射到内存空间)的流程是:
request_mem_region() -> ioremap() -> ioread8()/iowrite8() -> iounmap() -> release_mem_region() 。
前面说过,IO内存是统一编址下的概念,对于统一编址,IO地址空间是物理主存的一部分,对于编程而言,我们只能操作虚拟内存,所以,访问的第一步就是要把设备所处的物理地址映射到虚拟地址,Linux2.6下用ioremap(): void *ioremap(unsigned long offset, unsigned long size); ioremap()用来将IO资源的物理地址映射到内核虚地址空间(3GB - 4GB)中,参数addr是指向内核虚地址的指针。然后,我们可以直接通过指针来访问这些地址,但是也可以用Linux内核的一组函数来读写: ioread8(), iowrite16(), ioread8_rep(), iowrite8_rep()......
2、访问I/O端口
访问IO端口有2种途径(对于独立编址系统体系):
I/O映射方式(I/O-mapped)、内存映射方式(Memory-mapped)。
前一种途径不映射到内存空间,直接使用 intb()/outb()之类的函数来读写IO端口;
后一种MMIO是先把IO端口映射到IO内存(“内存空间”),再使用访问IO内存的函数来访问 IO端口。 void ioport_map(unsigned long port, unsigned int count); 通过这个函数,可以把port开始的count个连续的IO端口映射为一段“内存空间”,然后就可以在其返回的地址是像访问IO内存一样访问这些IO端口。