2017年10月1号
(1)外设IO寄存器地址独立编址的CPU,这时该称外设IO寄存器为IO端口,访问IO寄存器可通过ioport_map将其映射到虚拟地址空间,但实际上这是给开发人员制造的一个“”假象”,并没有映射到内核虚拟地址,仅仅是为了可以使用和IO内存一样的接口访问IO寄存器
例如:x86平台普通使用了名为内存映射的技术,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就如同访问内存一样。
(2)外设IO寄存器地址统一编址的CPU,这时应该称外设IO寄存器为IO内存,访问IO寄存器可通过ioremap将其映射到虚拟地址空间,然后在使用read/write接口访问
访问的方法:首先调用request()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址,之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放release()申请的io内存资源
2、动态映射和静态映射2017年12月18日补充
2.1 这里涉及到动态映射和静态映射,比如某个公司开发可一块板子,针对linux2.6.35移植内核,该公司在内核里面添加针对自己板子物理地址对应虚拟地址的静态映射表,也就是4G的虚拟地址的某个地址(如0xFDF000240)对应的实际板子IO寄存器物理地址(0xE0001234),这个虚拟地址被静态绑定对应的物理地址后,我们的内核代码使用动态映射时无法在使用这个虚拟地址(虚拟地址4G,实际物理可能只有256M,我们编写代码时,是想象有4G能使用,其实,很多时候是动态映射,如虚拟地址0xFF000000动态映射到物理地址0xEE000000,使用了这个地址的寄存器运算,因为我们写代码时想象有4G,所以虚拟地址用的很奢侈,0到4G的范围使用,就想在虚拟地址0xFFF0000F再对应一个新的物理地址,想一一对应,可是物理地址没有这么大,这个时候,0xFFF0000F对应的还是物理地址0xEE000000,上一个设备模块使用完后,释放了,映射关系被销毁),也有一些内存是直接一一对应映射的,虚拟等于物理,因为速度的要求和别的原因,因为建了动态映射,需要内核管理(申请,销毁等步骤),耗用时间,才换来了内存。静态映射只是绑定了虚拟地址到物理地址(动态映射是在这里耗费了时间,比如给一个物理地址你还要通过函数得到一个虚拟地址,之后都是一样的),并不是将虚拟地址空间同时分配占用了物理运行内存。
2.2 上面说到的蜂鸣器补充:蜂鸣器绑定了一个物理地址,无法改变,当我们的代码申请虚拟地址后(只有申请到的虚拟地址,才有对应物理内存给你运行代码,并对你要操作的蜂鸣器所对应的物理地址所在寄存器赋值,这时,这个虚拟地址没有释放前,就像静态映射一样,当然也占用着同等大小对应物理内存运行它,别人是无法通过其他虚拟地址及占用其他物理运行内存去操控这个蜂鸣器,这个虚拟地址所用到的运行内存在运算,还将最后的值绑定似的赋给蜂鸣器所在物理地址,当释放后,这个虚拟地址被释放,对应占用的物理内存空间也被释放。程序再次用蜂鸣器物理地址申请蜂鸣器IO时,ioremap()返回一个新的空闲的虚拟地址,当然会有相应的物理内存提供这个虚拟地址的代码去运行,再去操作。)
2.3 虚拟地址说的是虚拟地址空间,虚拟地址空间申请到后,会同时分配物理运行内存让其运行,这时可以理解虚拟地址空间就是物理运行内存空间,不同虚拟地址空间能在不同时刻对应相同的物理运行内存,这就是模拟出4G的原理。
3、内核的虚拟地址映射方法
3.1 为什么需要虚拟地址映射
(1)mmu单元打开:内核、应用层都使用虚拟地址,关闭:全使用物理地址
3.2 内核中有2套虚拟地址映射方法:动态和静态
3.2.1 静态映射方法的特点:
(1)内核移植时以代码的形式硬编码,如果要更改必须改源代码后重新编译内核,在内核启动时建立静态映射表,到内核关机时销毁,中间一直有效
(2)对于移植好的内核,你用不用他都在那里
(3)这个静态映射表就是我们平时说的直接映射,虚拟地址等于物理地址,就是我们制作开发板,官方将一些物理地址直接绑定一些功能
(4)比如led灯物理地址的绑定,对于移植好的内核,你用不用这套机制都在那里,你去使用那就效率高,缺点就是绑定物理地址,占用内存。led也可以建立虚拟地址映射之后再去应用层操作,效率相对低一些,但是使用完就释放,内存使用率高。
3.2.2 静态映射的硬编码代码:
(1)映射表具体物理地址和虚拟地址的值相关的宏定义
(2)映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
smdkc110_map_io
s5p_init_io
iotable_init
结论:经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
(3)开机时调用映射表建立函数
问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
start_kernel
setup_arch
paging_init
devicemaps_init
if (mdesc->map_io)
mdesc->map_io();
3.2.3 动态映射方法的特点:
1 驱动程序根据需要随时动态的建立映射、使用、销毁映射
(1)比如我们通过芯片手册知道了物理地址而不知道虚拟地址,调用内核给我们提供的动态映射的函数,我们就能得到内存给我们临时分配得虚拟地址,实现机制不用你管,用完之后你在释放它
(2)给你分配的地址就是给你一块可以运行的内存段去运行你的代码(运算、操作你的硬件过程),然后达到目的后,你在释放这段内存,让别人去运行。
2 先在芯片手册上找到硬件物理地址地址,通过下面函数得到虚拟地址,再去进行硬件操作
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
// 使用动态映射的方式来操作寄存器
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
return -EINVAL;
pGPJ0CON = ioremap(GPJ0CON_PA, 4); //函数返回值返回一个指针,指向寄存器定义的虚拟地址
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
*pGPJ0CON = 0x11111111;
*pGPJ0DAT = rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
4、如何选择虚拟地址映射方法
(1) 2种映射并不排他,可以同时使用
(2) 静态映射类似于C语言中全局变量,动态方式类似于C语言中malloc堆内存
(3) 静态映射的好处是执行效率高,坏处是始终占用虚拟地址空间;动态映射的好处是按需使用虚拟地址空间,坏处是每次使用前后都需要代码去建立映射&销毁映射(还得学会使用那些内核函数的使用)