静态映射和动态映射

Posted 正在起飞的蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了静态映射和动态映射相关的知识,希望对你有一定的参考价值。

1、为什么需要映射

在内核启动过程中会开启MMU,建立虚拟映射表,以后内核使用的都是虚拟地址。但是我们查询数据手册得到I/O寄存器地址都是物理地址,于是需要将物理地址转换到虚拟地址,这样才能在内核空间去访问I/O寄存器。物理地址转换到虚拟地址就叫做映射,映射分为静态映射和动态映射。

2、动态映射和静态映射的比较

(1)建立映射的时机:静态映射是在内核启动过程中就建立好,并且在整个内核的生命周期都存在;动态映射是在需要的时候才进行,使用完后要解除映射;
(2)占用资源:静态映射是在内核启动就建立,所以会一直占用地址资源;动态映射只在映射的时候才占用地址资源,解除映射后就不再占用地址资源;
(3)访问速度:静态映射因为是一开始就映射好的,所以访问速度快;动态映射使用的时候要向申请再映射,所以访问速度稍慢一点;
(4)一般会把常访问的I/O地址设置成静态映射,因为访问速度更快;但是不能把太多物理地址设置成静态映射,因为静态映射会一直占用资源;

3、静态映射

3.1、struct machine_desc结构体

每个开发板都对应一个struct machine_desc结构体,因为嵌入式设备都是高度定制的,结构体里是针对该开发板的描述。其中成员变量map_io是个函数指针,每个开发板的静态映射就是该函数指针对应的函数完成的。struct machine_desc结构体的详细介绍见博客:《内核启动过程中机器码的确定》

3.2、静态映射的建立过程

	start_kernel
		setup_arch
			paging_init
				devicemaps_init
					if (mdesc->map_io)	// Ask the machine support to map in the statically mapped devices.s
						mdesc->map_io();

在devicemaps_init函数里去调用struct machine_desc结构体的map_io函数指针,也就是每个开发板特定的静态映射建立函数。从上面的代码还可以知道,静态映射不是必须的,在添加开发板对应的struct machine_desc结构体时,可以将map_io函数指针赋值为NULL,表示此开发板不需要静态映射。

3.3、struct map_desc结构体

	
		unsigned long virtual;	 /* 映射后的虚拟地址 */
		unsigned long pfn;		 /* I/O资源物理地址所在的页帧号 */
		unsigned long length;	 /* I/O资源长度 */
		unsigned int type;		 /* I/O资源类型 */
	;

struct map_desc结构体是用来描述如何建立静态映射的,需要指明物理地址、映射后的虚拟地址、要映射的长度、映射的资源类型;其中物理地址传的是页帧号,因为内核建立的虚拟映射是以叶为单位的。

3.4、以X210开发板的map_io函数为例

#define S3C_ADDR_BASE	(0xFD000000)

#define S3C_ADDR(x)	(S3C_ADDR_BASE + (x))

#define S5P_VA_CHIPID		S3C_ADDR(0x00700000)
#define S5P_VA_GPIO			S3C_ADDR(0x00500000)

static struct map_desc s5p_iodesc[] __initdata = 
	
		.virtual	= (unsigned long)S5P_VA_CHIPID,
		.pfn		= __phys_to_pfn(S5P_PA_CHIPID),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	, 
		.virtual	= (unsigned long)S3C_VA_SYS,
		.pfn		= __phys_to_pfn(S5P_PA_SYSCON),
		.length		= SZ_64K,
		.type		= MT_DEVICE,
	, 
		.virtual	= (unsigned long)S3C_VA_UART,
		.pfn		= __phys_to_pfn(S3C_PA_UART),
		.length		= SZ_4K,
		.type		= MT_DEVICE,
	, 
		.virtual	= (unsigned long)VA_VIC0,
		.pfn		= __phys_to_pfn(S5P_PA_VIC0),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	, 
		.virtual	= (unsigned long)VA_VIC1,
		.pfn		= __phys_to_pfn(S5P_PA_VIC1),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	, 
		.virtual	= (unsigned long)S3C_VA_TIMER,
		.pfn		= __phys_to_pfn(S5P_PA_TIMER),
		.length		= SZ_16K,
		.type		= MT_DEVICE,
	, 
		.virtual	= (unsigned long)S5P_VA_GPIO, //开发板的GPIO映射后的虚拟地址的基地址
		.pfn		= __phys_to_pfn(S5P_PA_GPIO), //开发板GPIO的物理地址基地址
		.length		= SZ_4K,	//要映射的长度					
		.type		= MT_DEVICE,	//资源类型
	,
;

void __init iotable_init(struct map_desc *io_desc, int nr)

	int i;

	for (i = 0; i < nr; i++)
		create_mapping(io_desc + i);


void __init s5p_init_io(struct map_desc *mach_desc,
			int size, void __iomem *cpuid_addr)

	unsigned long idcode;

	/* initialize the io descriptors we need for initialization */
	iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));
	if (mach_desc)
		iotable_init(mach_desc, size);

	idcode = __raw_readl(cpuid_addr);
	s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));


static void __init smdkv210_map_io(void)

	s5p_init_io(NULL, 0, S5P_VA_CHIPID);
	s3c24xx_init_clocks(24000000);
	s3c24xx_init_uarts(smdkv210_uartcfgs, ARRAY_SIZE(smdkv210_uartcfgs));

3.4.1、函数调用顺序:

	smdkv210_map_io
		s5p_init_io
			iotable_init
				create_mapping

3.4.2、函数调用说明

(1)X210开发板的map_io函数是smdkv210_map_io,这是在构建struct machine_desc结构体时指定的,见博客:《内核启动过程中机器码的确定》
(2)s5p_iodesc是struct map_desc类型的数组,每个struct map_desc类型的元素代表要进行静态映射的虚拟地址、物理地址已经映射的长度;
(3)create_mapping函数是真正构建静态映射的函数,将对s5p_iodesc数组里的每个元素进行解析,构建静态映射;

3.4.3、如何添加静态映射

 
		.virtual	= (unsigned long)S5P_VA_GPIO, //开发板的GPIO映射后的虚拟地址的基地址
		.pfn		= __phys_to_pfn(S5P_PA_GPIO), //开发板GPIO的物理地址基地址
		.length		= SZ_4K,	//要映射的长度					
		.type		= MT_DEVICE,	//资源类型
	,

(1)在s5p_iodesc结构体数组中多添加一个struct map_desc 结构体成员,里面说明要映射的物理地址和虚拟地址;
(2)以s5p_iodesc结构体数组的最后一个成员为例,就是将物理地址S5P_PA_GPIO(0xE0200000)映射到虚拟地址S5P_VA_GPIO(0xFD500000)。物理地址就是从数据手册里查到的地址,映射后就可以在操作系统中用虚拟地址去访问那些物理地址。

4、动态映射

4.1、涉及的函数

//参数:物理地址、地址长度、名字
//返回值:成功、失败
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)
//参数:要映射的物理地址、地址长度
//返回值:映射得到的虚拟地址
#define ioremap(cookie,size)           __ioremap(cookie,size,0) 
//参数:之前映射得到的虚拟地址
#define iounmap(cookie)			__iounmap(cookie)
//参数:之前申请的物理地址、地址长度
#define release_mem_region(start,n)	__release_region(&iomem_resource, (start), (n))

4.2、函数的调用次序

(1)request_mem_region:向内核申请物理地址资源;
(2)ioremap:将物理地址映射成虚拟地址;
(3)iounmap:解除物理地址到虚拟地址的映射;
(4)release_mem_region:通知内核释放物理地址;

4.3、示例代码

#define GPJ0CON_PA	0xe0200240
#define GPJ0DAT_PA 	0xe0200244

unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;

// 申请物理地址
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
	return -EINVAL;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
	return -EINVAL;
	
//进行虚拟地址映射
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);

// 解除映射
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);

以上是关于静态映射和动态映射的主要内容,如果未能解决你的问题,请参考以下文章

从库函数解析STM32地址映射

驱动学习之静态映射和动态映射

静态映射和动态映射

5.2.14.静态映射操作LED1

浅谈Cisco ASA应用NAT

Elasticsearch常用操作:映射篇