我是靠谱客的博主 知性摩托,最近开发中收集的这篇文章主要介绍物理内存映射,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

问题1:在系统启动是,arm linux内核如何知道系统中有多大的内存空间?

        物理内存大小定义在设备树的memory节点中,系统启动时会通过dtb解析memory节点,从而获得内存大小。

内存大小定义

在arm vexpress平台中,内存定义在vexpress-v2p-ca9.dts中。起始地址为0x60000000,大小为0x40000000(1G)。设备树语法参考链接Device Tree 详解 - 魅族内核团队

start_kernel->setup_arch->setup_machine_fdt->early_init_dt_scan_nodes->of_scan_flat_dt->early_init_dt_scan_memory

在函数early_init_dt_scan_memory会解析memory节点

/**
 * early_init_dt_scan_memory - Look for an parse memory nodes
 */
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
				     int depth, void *data)
{
	..........................
	while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
		u64 base, size;
		..............................................
		printk(KERN_EMERG "base %llx, size %llxn", base, size);
		early_init_dt_add_memory_arch(base, size);
	}
	return 0;
}

 输出的刚好是dts里面定义的起始地址和大小

sudo qemu-system-arm -M vexpress-a9 -m 1024M。如果-m 512M

则会输出base 60000000, size 20000000(512M)

感觉是二者取较小值

 物理内存映射

page_init来完成系统分页机制的初始化工作,建立页表,从而内核可以完成虚拟地址到物理地址的映射关系

void __init paging_init(const struct machine_desc *mdesc)
{
	void *zero_page;
	printk(KERN_EMERG "rnpaging_init beginn");
	/* 给静态全局变量mem_types赋值 */
	build_mem_type_table();
	prepare_page_table();//将页表项清零
	map_lowmem();//初始化页表,真正创建页表,重新建立从物理地址起始点到high_mem的起始点的一一映射
	/* 建立DMA映射表 */
	dma_contiguous_remap();
	/* 为设备IO空间和中断向量表创建页表,并刷新TLB和缓存 */
	devicemaps_init(mdesc);
	/* 进行永久内存映射的初始化,存储在pkmap_page_table中 */
	kmap_init();
	/* TCM初始化,TCM是一个固定大小的RAM,紧密地耦合至处理器内核,提供与cache相当的性能 */
	tcm_init();

	top_pmd = pmd_off_k(0xffff0000);

	/* allocate the zero page. */
	zero_page = early_alloc(PAGE_SIZE);
	/* bootmem_init初始化内存管理 */
	bootmem_init();

	/* 分配一个0页,该页用于写时复制机制。 */
	empty_zero_page = virt_to_page(zero_page);
	__flush_dcache_page(NULL, empty_zero_page);
}

prepare_page_table:把页表项清零

static inline void prepare_page_table(void)
{
	unsigned long addr;
	phys_addr_t end;

	/*
	 * Clear out all the mappings below the kernel image.
	 */
	for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
		pmd_clear(pmd_off_k(addr));

#ifdef CONFIG_XIP_KERNEL
	/* The XIP kernel is mapped in the module area -- skip over it */
	addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK;
#endif
	for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
		pmd_clear(pmd_off_k(addr));

	/*
	 * Find the end of the first block of lowmem.
	 */
	end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
	if (end >= arm_lowmem_limit)
		end = arm_lowmem_limit;

	/*
	 * Clear out all the kernel space mappings, except for the first
	 * memory bank, up to the vmalloc region.
	 */
	for (addr = __phys_to_virt(end);
	     addr < VMALLOC_START; addr += PMD_SIZE)
		pmd_clear(pmd_off_k(addr));
	printk(KERN_EMERG "rn MODULES_VADDR %lx, PAGE_OFFSET %lx, VMALLOC_START %lxn", 
			(unsigned long)MODULES_VADDR, (unsigned long)PAGE_OFFSET, (unsigned long)VMALLOC_START);
}

 对三段地址调用pmd_clear()清除一级页表的内容

0x0--MODULES_VADDR ; 

MODULES_VADDR--PAGE_OFFSET; 

arm_lowmem_limit--VMALLOC_START

arm_lowmem_limit a0000000

 MODULES_VADDR 7f000000, PAGE_OFFSET 80000000, VMALLOC_START c0800000
 暂时不知道这几个地址是什么意思,难道这个地址范围内都是页表??

PAGE_OFFSET:对于内核空间,给定一个虚拟地址x,其物理地址为x-PAGE_OFFSET。1、感觉这个也只是针对线性映射区吧。2、那这个不说明内核虚拟地址起始位置为PAGE_OFFSET=0x80000000(2G)??。那4G地址空间就是2:2进行划分的

 map_lowmem:初始化页表

static void __init map_lowmem(void)
{
	struct memblock_region *reg;
	unsigned long kernel_x_start = round_down(__pa(_stext), SECTION_SIZE);
	unsigned long kernel_x_end = round_up(__pa(__init_end), SECTION_SIZE);
	printk(KERN_EMERG "rn arm_lowmem_limit %lx", arm_lowmem_limit);
	printk(KERN_EMERG "rn kernel_x_start %lx,kernel_x_end %lx", kernel_x_start, kernel_x_end);
	/* Map all the lowmem memory banks. */
	for_each_memblock(memory, reg) {
		phys_addr_t start = reg->base;
		phys_addr_t end = start + reg->size;
		struct map_desc map;

		if (end > arm_lowmem_limit)
			end = arm_lowmem_limit;
		if (start >= end)
			break;
		printk(KERN_EMERG "rn start %lx, end %lx, size %lxn", start, end, reg->size);
		if (end < kernel_x_start || start >= kernel_x_end) {
            /* 将物理地址转为物理page number, 这里是4k为一页 */
			map.pfn = __phys_to_pfn(start);
			map.virtual = __phys_to_virt(start);
			map.length = end - start;
			map.type = MT_MEMORY_RWX;

            /* create_mapping进行线性映射 */
			create_mapping(&map);
		} else {
			/* This better cover the entire kernel */
			if (start < kernel_x_start) {
				map.pfn = __phys_to_pfn(start);
				map.virtual = __phys_to_virt(start);
				map.length = kernel_x_start - start;
				map.type = MT_MEMORY_RW;

				create_mapping(&map);
			}
            /* 映射kernel image区域 */
			map.pfn = __phys_to_pfn(kernel_x_start);
			map.virtual = __phys_to_virt(kernel_x_start);
			map.length = kernel_x_end - kernel_x_start;
			map.type = MT_MEMORY_RWX;

			create_mapping(&map);
            /* 书上说映射低端内存,为什么是低端内存呢 */
			if (kernel_x_end < end) {
				map.pfn = __phys_to_pfn(kernel_x_end);
				map.virtual = __phys_to_virt(kernel_x_end);
				map.length = end - kernel_x_end;
				map.type = MT_MEMORY_RW;

				create_mapping(&map);
			}
		}
	}
}

 只循环了一次,memblock只有一个。起始地址0x60000000, 长度0x40000000

#define for_each_memblock(memblock_type, region)					
	for (region = memblock.memblock_type.regions;				
	     region < (memblock.memblock_type.regions + memblock.memblock_type.cnt);	
	     region++)

可以看到这里的 map_lowmem函数里for_each_memblock(memory, reg)就是在使用之前memblock子系统里面的动态内存部分。在关于memblock子系统部分,关于memory_type为memory,即动态内存的部分,也只加入了一个区域,即dts里面定义的memory节点。因此这里也只循环了一次

end = start+size = 0xa0000000(这里的地址究竟是啥地址哦,虚拟地址or物理地址??).刚好是dts里面定义的memory节点。__phys_to_pfn感觉这里的地址都是物理地址。

但是物理内存只有1G,物理地址范围不是0-1G嘛?为什么会有0x60000000这种物理地址呢

我看网上说,memory节点里面的reg = <0x60000000 0x40000000>;表示物理内存起始地址,以及大小。因此物理地址范围[0x60000000,0xa0000000]。看来物理地址不一定是从0x0开始

低端内存:32位CPU能够访问4G地址空间。linux将4G地址空间为了用户和内核地址空间,一般是3:1(这个比例可以调整2:2)。内核只能访问1G的地址空间(3G-4G)。

如果物理内存大于1G.那么内核需要如何管理地址内存呢?

因此linux把内核1G的虚拟地址空间分为了两个部分。线性映射区,和非线性映射区。线性映射的物理内存,也成为了低端内存,剩下的内存被称为高端内存。线性区的内存是提前映射好了。高端内存是在使用时动态映射,使用非线性映射区去管理大于(1G,896或者说,线性映射区管理不了的高端内存)。因此,内存的前896M(物理内存)(低端内存)被一一映射,即线性映射,到了内核地址空间3G--3G+896M(虚拟地址空间)的部分,剩下的128M的虚拟地址空间,用kmap动态映射,管理超过1G的高端内存

static void __init map_lowmem(void)
{
	....................
		if (end < kernel_x_start || start >= kernel_x_end) {
           /*不走这里*/.................
		} else {
			/* This better cover the entire kernel */
			if (start < kernel_x_start) {
                /* 也不走这里 */................................
			}
            /* 映射kernel image区域 */
			map.pfn = __phys_to_pfn(kernel_x_start);
			map.virtual = __phys_to_virt(kernel_x_start);
			map.length = kernel_x_end - kernel_x_start;
			map.type = MT_MEMORY_RWX;

			create_mapping(&map);
            /*
             书上说映射低端内存,为什么是低端内存呢?
             因为这部分内存是进行线性映射的,因此属于低端内存(zone_dma & zone_normal )
             */
			if (kernel_x_end < end) {
				map.pfn = __phys_to_pfn(kernel_x_end);
				map.virtual = __phys_to_virt(kernel_x_end);
				map.length = end - kernel_x_end;
				map.type = MT_MEMORY_RW;

				create_mapping(&map);
			}
		}
	}
}

map_lowmem:

如上面代码所示,该函数会对两个区域进行映射

区间1:[kernel_x_start, kernel_x_end](物理地址)

物理地址:kernel_x_start(0x60000000)-- kernel_x_end (0x60600000) 大小共6M

虚拟地址:0x80000000-0x80600000

区间2:[kernel_x_end, end](物理地址)

物理地址:kernel_x_end (0x60600000)--end(0xa0000000)。共(1018M)

这里感觉和书上写的不一样。那这1G的物理内存全部都拿去做线性映射了,没有所谓的高端内存

下图里面的lowmem刚好也是1024M

书上的arm_lowmem_limit=0x8f800000,意味着低端内存大小为0x8f800000-0x60000000=760M

那高端内存还有1024-760 = 264M.

arm_lowmem_limit(是个物理地址):那这个值感觉就是低端内存的上界。高于arm_lowmem_limit都是属于高端内存。

最后

以上就是知性摩托为你收集整理的物理内存映射的全部内容,希望文章能够帮你解决物理内存映射所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(52)

评论列表共有 0 条评论

立即
投稿
返回
顶部