一,IO端口与IO内存
独立编址:处理器将IO地址在独立的IO地址空间编排(具有独立的操作指令,指令短访问速度快)——设备寄存器与设备内存被映射到IO地址空间称为IO映射;
统一编址:处理器将IO地址和主内存在一个地址空间编排(具有统一的操作指令,操作内存的指令也可以操作设备寄存器与设备内存,操作种类多,指令长访问速度慢)————设备寄存器与设备内存被映射到内存空间称为内存映射;
IO端口:设备寄存器与设备内存映射到IO地址空间称为IO端口;
IO内存:设备寄存器与设备内存映射到物理内存地址空间称为IO内存;
端口:处理器访问设备寄存器或设备内存的设备地址,端口也成为寄存器;由于端口<------>地址<-------->寄存器三者一一对应,所以寄存器地址即就是端口地址也成为端口;
二,IO端口与内存
区别:IO端口访问具有边际效应,内存没有边际效应;
IO端口 访问需要解决的问题:边际效应,优化(缓存,指令序列)
优化问题的解决方案 :缓存优化——禁用缓存;
指令序列优化的解决方案:设置内存屏障-达到避免指令序列被优化而重新排序的目的;
linux内核实现的内存屏障函数:
<linux/kernel.h>
barrier();
通知编译器添加内存屏障,对硬件没有任何影响,屏障前后的指令读写都是从内存中直接读取或写入,处理器对屏障前后的指令不进行优化重新排序;
<asm/system.h>
mb(); //读写内存屏障;
wmb(); //写内存屏障;
rmb(); //读内存屏障;
read_barrier_depends();
这几个内存屏障是添加硬件内存屏障,都是barrier()的超集;
多处理版本:
smp_mb();
smp_wmb();
smp_rmb();
smp_read_barrier_depends();
二,IO端口访问;
Linux内核对IO端口访问提供了两种方式,这里是第一种:
1,struct resources *request_region(unsigned long first_port , unsigned n, char *name);
在对设备端口进行操作之前一定要取得设备的独占访问;
这个函数是申请设备从first_port 端口开始的n个端口(地址),name为设备名称;
first_port 为设备端口地址改地址是可以直接访问设备的物理地址;
函数返回值为NULL则申请失败,非NULL则申请成功;
2,IO端口操作函数:
linux内核多端口的操作分为三类,按照端口的位宽来分;
读取:
unsigned char inb(unsigned long port); //一个字节--8位端口;
unsigned short inw(unsigned long port);//一个字长(双字节)--16位;
unsigned inl(unsigned long port); //双子长(四个字节)--32位;
写入:
void outb(char val,unsigned long port);
void outw(unsigned short val,unsigned long port);
void outl(unsigned val,unsigned long port);
串IO操作——对某个端口(读取或 )写入一个数据序列(数据序列的单位是一次能够读写的数据位宽)-与单次操作的区别:单次读取或写入可以调整字节序,而数据序列操作不能调整字节序:
//in
void insb(unsigned char port,void *addr,unsigned long count);
void insw(unsinged short port,void *addr,unsigned long count);
void insl(unsigned port,void *addr,unsigned long count);
//out
void outsb(unsigned long port,void *addr,unsigned long count);//将地址addr处开始的count个字节写入端口;
void outsw(unsigned long port,void *addr,unsigned long count);
void outsl(unsigned long port,void *addr,unsigned long count);
3,对设备端口独占访问释放:
release_region(unsigned long first_port,unsigned long n)
;
三,IO内存访问;
1,struct resources *request_mem_region(unsigned long addr,unsigned long size,char *name);
申请从IO地址addr处开始的size大小的IO内存块;
返回值为NULL申请失败,非NULL则申请成功;
void *ioremap(unsigned long addr,unsigned long size);
由于内核使用的是处理器的虚拟地址,所以将IO内存的物理地址映射到Linux内核虚拟地址空间中,便于Linux内核访问IO内存空间;
无论是否存在MMU单元,为了代码的可移植性,都不要对函数返回的虚拟地址直接操作,而是要用Linux内核提供的安全的,经过优化的,平台统一的操作函数;
2,IO内存操作函数:
同IO端口一样分为三类:
读取:
unsigned char read8(void *addr);
unsigned short read16(void *addr);
unsigned read32(void *addr);
写入:
void write8(unsigned char val,void *addr);
void write16(unsigned short val,void *addr);
void write32(unsigned val,void *addr);
数据序列操作--上述函数的重复版本(repeat):
//read;
void ioread8_rep(void *dest,void *source,unsigned long count);
void ioread16_rep(void *dest,void *source,unsigned long count);
void ioread32_rep(void *dest,void *source,unsigned long count);
//write;
void iowrite8_rep(void *dest,const void *source,unsigned long count);
void iowrite16_rep(void *dest,const void *source,unsigned long count);
void iowrite32_req(void *dest,const void *source,unsigned long count);
将从原地址source开始的count个数据单位写入同一个目标地址dest处,数据单位是一次性写入IO内存的数据位宽;
3,释放IO内存到内核虚拟地址空间的映射:
void iounmap(void *addr,unsigned long count);
4,释放IO内存独占访问:
void release_mem_region(unsigned long addr, unsigned long size);
四,IO端口的第二种方位方式——采用和IO内存一样的操作函数——从操作层面淡化IO端口与IO内存之间的区别;