关闭

IO端口和IO内存访问

标签: 内核
330人阅读 评论(0) 收藏 举报
分类:

回顾:总线(Bus)是各个功能部件传送信息的公共干线,是导线束。在常见PC主机体系结构中,CPU和NB(北桥如P31MCH)相连,再将NB和内存,PCI图形设备相连。这都属于I-BUS(内总线),相应的还有片内的C-BUS(片总线),
如图(from 百度百科):
connections

由于可以传递三种信息(数据、地址、控制信息),所以一组导线所组成的总线按照逻辑功能分为数据总线(Data Bus)、地址总线(Address Bus)、控制总线(Control Bus)。32位X86架构是指个人电脑的地址总线是32位的,而这32bit所能表示的地址不但要分给内存使用,还需要分配给设备寄存器等。(有的数据和地址总线有复用,如51单片机。)
更多相关:[CPU,内存那些事]

在通过物理的总线连接CPU和外设后,具体如何访问外设呢?

外设连接到总线后,可以从内存空间访问外设,对于x86架构处理器也可以通过IO空间访问外设,IO空间有属于自己的IO地址集,不占用32bit能寻址的范围(i386 64K IO寻址,单个PCI不超过256),通过inout函数访问。
外部设备常常会提供一组寄存器:控制寄存器、数据寄存器、状态寄存器。(通常会连续编址)当寄存器物理地址位于x86的IO空间时,这个空间称为IO端口(IO Port);位于内存空间时,对应内存称为IO内存(IO Memory)。

如一个视频采集卡设备,有自己的寄存器,被挂到IO Port上,设备自己的memory被挂到IO Memory。
虽然设备有自己的Memory但是CPU不能像访问内存一样直接访问设备Memory。需要通过remap做映射。同样也可以将IO Port映射后当作一个内存空间来访问达到访问寄存器的目的。

如果CPU是ARM或者PowerPC架构
没有单独的IO空间,IO Port和IO Memory共用地址寻址空间。虽然也可以用看似和i386相同的IO Port的in out方式访问,但是内核已经做了处理。Linux提供对二者的支持。
RISC指令系统CPU通常只有一个物理空间CPU可以像访问一个Memory地址一样访问一个原本IO Port对应的寄存器,不需要用专门的IO指令。

外设的IO内存资源的物理地址是已知的,硬件设计决定。但是OS不会与定义访问他们的虚拟地址映射,所以需要驱动程序来自己做好映射再做进一步访问。

1 Kernel对IO Port和IO Mem的操作

对IO端口操作
Kernel提供函数访问IO空间的端口port。函数的端口号高度依赖硬件平台。有inb(),outb()等。

对IO内存操作

  1. 映射物理地址
    Kernel访问IO内存前先将设备的物理地址做一个映射。
    ioremap()(io.h),建立新的页表,返回一个特殊虚拟地址,用来存取特定的物理地址范围。相应的通过iounmap()释放。

  2. 得到映射后通过地址直接读写过通过函数读写
    读:ioread8(),ioread16(),ioread8_rep()。写:iowrite8()iowrite16(),iowrite8_rep()。复制IO内存:memcpy_fronio(),memcpy_toio()

  3. IO内存的设置
    memset_io()

  4. IO端口映射到内存空间
    void *ioport_map(unsigned long port, unsigned int count);将port开始的连续count个IO端口“重映射”一段内存空间,然后像访问IO内存一样访问IO端口。用ioport_unmap()撤销映射。看源码可知,并没有映射到内核虚拟地址,只是为了让工程师可以统一用IO内存访问接口访问IO端口。

  5. 用户空间的映射
    当用mmap()映射一个设备,意味着使得用户空间的一段地址关联到内存上。设备文件的地址被映射到进程地址空间后,进程可以像访问普通内存一样对其进行访问,不必再调用read(),write()等操作。但注意对内存的访问可能会被编译器优化掉。(之后有更具体描述)

2 驱动申请释放IO端口和IO内存资源

IO Port申请/释放
struct resource *request_region(unsigned long first, unsigned long n, const char *name)向内核申请IO空间n个端口。从first开始,name参数为设备名。申请失败返回NULL。申请后可以在cat /proc/ioports中看到。这样的等级完成后就可以用inb(),outb()系列的函数访问IO Port了。
使用完后用void release_region(unsigned long start, unsigned long n)释放。

IO Memory资源申请/释放
申请:struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
释放:void release_mem_region(unsigned long start, unsigned long len);
这两个虽然不是必须,但是建议使用,在申请的时候会检查申请的资源是否可用,如果可用就会成功返回并标志已使用。
很多设备在申请之前就访问是不安全的。

3 IO Port和IO Memory的访问流程

内核IO Port访问

直接在内核空间使用IO Port

  1. 在moudle的加载或者open()函数中,request_region()登记IO Port。
  2. 设备驱动初始化、write()read()ioctl()中使用inb(),outb()系列函数,对设备操作。
  3. 在module卸载或者release()中对空间释放release_region()

将IO Port映射为IO Mem再在内核空间使用

  1. 在moudle的加载或者open()函数中,request _region()申请资源。
  2. 并将寄存器地址通过ioport_map()映射到内核空间虚拟地址。
  3. 设备驱动初始化、write()read()ioctl()中使用ioread8(),iowrite8()系列函数,对设备操作。
  4. 在module卸载或者release()中对映射释放,ioport_unmap()
  5. 并内存空间释放release_region()

内核IO Memory访问

在内核空间使用IO Memory

  1. 在moudle的加载或者open()函数中,request _mem_region()申请资源。
  2. 并将寄存器地址通过ioremap()映射到内核空间虚拟地址。
  3. 设备驱动初始化、write()read()ioctl()中使用ioread8(),iowrite8()系列函数,对设备操作。
  4. 在module卸载或者release()中对映射释放,iounmap()
  5. 并内存空间释放release_mem_region()

设备地址向用户空间的映射

通过mmap()函数实现的映射(需要以page为单位):将用户空间的一段内存与设备内存关联,当用户访问用户空间的地址范围,就会转化为对设备的访问。
比如在访问显示设备显存的时候,如果可以让用户空间直接访问显存节省拷贝时间就可以快很多。

在file_operaions中对mmap的定义是:

int (*mmap)(struct file *, struct vm_area_struct*);

驱动实现这个函数,将在用户调用系统调用mmap的时候被最终调用。struct vm_area_struct用于描述一块虚拟内存区域(VMA)。VMA的操作被定义在其中*vm_ops 所指向的struct vm_operations_struct结构体中,其中定义了对于VMA的openclosetypepopulate…操作函数。

VMA:进程用到的虚拟空间不连续,各部分属性也可能不同,所以用单链表连接的一系列VMA来描述。假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面page。
比如一个文件通两个VMA映射相应的页,分别时0–4069和5*4096-7*4096那么两个VMA的vm_pgoff是0和5。和物理地址并没有直接的关系。

系统调用mmap()的原型和file_operations中用的mmap差别很大:

caddr_t mmap(caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);

caddr_t 就是void*
addr是指定文件映射到用户空间的起始地址。一般是NULL,由Kernel选择。
len是从映射文件开始,offset个字节开始算起,要映射到用户空间的空间字节数。
fd是文件描述符。一般由open()返回。如果fd是-1,指定flags:MAP_ANON,进行匿名映射。

我的理解:如果open的是驱动设备,那么vma->vm_file就指向被映射的设备文件file结构,vma->vm_pgoff也就是代表该设备的内存的所在的物理地址的页。(还在继续查看资料)

prot设定权限,可以用或运算:PROT_READ可读,PROT_WRITE可写,PROT_EXEC可执行,PROT_NONE不可访问。(linux/mm.h)

用户应用程序调用系统调用的mmap()内核会做以下工作:

  1. 在进程查找一块VMA(virtual memory area)
  2. 将VMA映射
  3. 如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,就调用mmap()
  4. 将VMA插入进程VMA链表

这样应用程序就能获得一块VMA,接下来需要驱动程序将物理设备文件操作区域映射到VMA。同时确保空间不会他用,添加保护等。参考驱动总结之mmap函数实现

所以驱动程序相应实现file_operations的mmap(如xxx_mmap())这个功能需要:

  1. 标记、保护区域。(示例未给出) 保护是由于和设备建立的对应关系不希望在物理内存映射整体不够用时被打断,把物理内存用给其它进程,想长期保护这种映射关系的存在。

    有文件中写到:Remap-pfn-range will mark the range VM_IO and VM_RESERVED

  2. remap_pfn_range创建页表,建立物理页与虚拟页之间的关系.

  3. 显示调用需要的VMA的open()函数。
static int xxx_mmap(struct file *filp, struct vm_area_struct *vma)
{
    if(remap_pfn_range(vma, vma->vm_start, vma  ->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot));
    //remap_pfn_range创建页表,用内核根据用户请求来自己填充的vma结构体作为参数。得到虚拟地址范围 vma->vm_start到vma->vm_end
    vma->vm_ops = &xxx_remap_vm_ops;
    //填充vma的operation结果体指针(再后面定义)
    xxx_vma_open(vma);//显式调用vma的open函数。
}

关于remap_pfn_range:

int remap_pfn_range(structvm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)

vma是用户申请的虚拟内存区域;
addr是虚拟内存起始地址,即vma->vm_start;
pfn(page frame number页帧号)是物理地址的页号,一般通过物理地址得到其页号virt_to_phys(dev->data)>>PAGE_SHIFT即,先将虚拟地址转到物理内存,再算出页号。若PAGE_SIZE是4KB,则PAGE_SHITF=12,因为有关系PAGE_SIZE = 1 << PAGE_SHIFT。利用vma ->vm_pgoff其实是用了的驱动设备文件的特性。
关于调用open():当内核生成一个VMA,会自动调用这个VMA的open()。但是对于驱动自己的VMA需要的一些操作,用户系统调用mmap()后,VMA先产生,没有调用自己VMA的open()。所以驱动就需要在自己file_operations的mmap()中显示调用自己VMA的open()来做一些需要的操作。


notification
source: 《Linux设备驱动开发详解》(第二版),内容为读书笔记和网络资料,有些资料原始来源不详,分享为了方便自己和他人查阅。如有侵权请及时告知,对于带来的不便非常抱歉。转载请注明来源。Terrence Zhou.

reference
[1] CPU,内存,http://www.cnblogs.com/xkfz007/archive/2012/10/08/2715163.html
[2] IO端口和IO内存,http://www.360doc.com/content/10/1011/07/1317564_60018145.shtml
[3] mmap相关,http://blog.chinaunix.net/uid-20937170-id-3042479.html
[4] vma, remap_pfn_page,http://blog.csdn.net/skyflying2012/article/details/8691880; http://blog.csdn.net/hshl1214/article/details/8789507

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:12053次
    • 积分:284
    • 等级:
    • 排名:千里之外
    • 原创:15篇
    • 转载:1篇
    • 译文:2篇
    • 评论:6条
    天宫里的那些大神
    文章分类
    最新评论