分类: LINUX
2012-02-21 11:59:51
2011-03-18 12:23:19| 分类: Linux |字号
void __init bootmem_init(void)
{
struct meminfo *mi = &meminfo;
unsigned long memend_pfn = 0;
int node, initrd_node;
/*
* Locate which node contains the ramdisk image, if any.
*/
initrd_node = check_initrd(mi);
/*
* Run through each node initialising the bootmem allocator.
*/
//遍历所有节点,为每个节点调用bootmem_init_node()完成指定节点内存的映射创建
for_each_node(node) {
unsigned long end_pfn = bootmem_init_node(node, mi);
.......
}
//bootmem_init_node():为指定节点的主内存创建映射
static unsigned long __init bootmem_init_node(int node, struct meminfo *mi) {
unsigned long start_pfn, end_pfn, boot_pfn;
unsigned int boot_pages;
pg_data_t *pgdat;
int i;
start_pfn = -1UL;
end_pfn = 0;
/*
* Calculate the pfn range, and map the memory banks for this node.
*/
for_each_nodebank(i, mi, node) {
struct membank *bank = &mi->bank[i];
unsigned long start, end;
start = bank_pfn_start(bank);
end = bank_pfn_end(bank);
if (start_pfn > start)
start_pfn = start;
if (end_pfn < end)
end_pfn = end;
map_memory_bank(bank); //为指定节点类型的Bank创建映射
}
.......
}
bootmem_init_node()遍历整个meminfo结构,为指定节点类型的Bank创建映射。为一个Bank创建映射是通过函数map_memory_bank()实现的。这个函数也在arch/arm/mm/init.c中:
static inline void map_memory_bank(struct membank *bank) //为一个Bank创建映射
{
#ifdef CONFIG_MMU
struct map_desc map;
map.pfn = bank_pfn_start(bank); //要映射的物理起始页面号
map.virtual = __phys_to_virt(bank_phys_start(bank)); //映射到的虚拟地址
map.length = bank_phys_size(bank); //映射长度
map.type = MT_MEMORY; //映射类型
create_mapping(&map); //为一个物理存储空间创建映射,实际上就是填充页表
#endif
}
map_memory_bank()调用create_mapping()完成映射的创建工作。create_mapping()函数用于为一个物理存储空间创建映射,实际上就是填充页表。
需要重点关注的是create_mapping()的参数map_desc结构,它在arch/arm/include/asm/mach/map.h中定义,描述了一个映射区间:
struct map_desc {
unsigned long virtual; //映射到的虚拟地址
unsigned long pfn; //要映射的物理起始页面号
unsigned long length; //映射长度
unsigned int type; //映射类型,MT_xxx,决定了相应映射页面的访问权限
};
// type的取值
/* types 0-3 are defined in asm/io.h */
#define MT_UNCACHED 4
#define MT_CACHECLEAN 5
#define MT_MINICLEAN 6
#define MT_LOW_VECTORS 7
#define MT_HIGH_VECTORS 8
#define MT_MEMORY 9
#define MT_ROM 10
#define MT_MEMORY_NONCACHED 11
其中type成员决定了页面的访问权限,这是通过全局数组struct mem_type mem_types[]实现的(type是这个数组的下标):
arch/arm/mm/mm.h
struct mem_type {
unsigned int prot_pte;
unsigned int prot_l1;
unsigned int prot_sect;
unsigned int domain;
};
arch/arm/mm/mmu.c
static struct mem_type mem_types[] = {
[MT_DEVICE] = { /* Strongly ordered / ARMv6 shared device */
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |
L_PTE_SHARED,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE | PMD_SECT_S,
.domain = DOMAIN_IO,
},
[MT_DEVICE_NONSHARED] = { /* ARMv6 non-shared device */
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_NONSHARED,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE,
.domain = DOMAIN_IO,
},
[MT_DEVICE_CACHED] = { /* ioremap_cached */
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_CACHED,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE | PMD_SECT_WB,
.domain = DOMAIN_IO,
},
[MT_DEVICE_WC] = { /* ioremap_wc */
.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_WC,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PROT_SECT_DEVICE,
.domain = DOMAIN_IO,
},
[MT_UNCACHED] = {
.prot_pte = PROT_PTE_DEVICE,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN,
.domain = DOMAIN_IO,
},
[MT_CACHECLEAN] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN,
.domain = DOMAIN_KERNEL,
},
[MT_MINICLEAN] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_XN | PMD_SECT_MINICACHE,
.domain = DOMAIN_KERNEL,
},
[MT_LOW_VECTORS] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_EXEC,
.prot_l1 = PMD_TYPE_TABLE,
.domain = DOMAIN_USER,
},
[MT_HIGH_VECTORS] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_USER | L_PTE_EXEC,
.prot_l1 = PMD_TYPE_TABLE,
.domain = DOMAIN_USER,
},
[MT_MEMORY] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
.domain = DOMAIN_KERNEL,
},
[MT_ROM] = {
.prot_sect = PMD_TYPE_SECT,
.domain = DOMAIN_KERNEL,
},
[MT_MEMORY_NONCACHED] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
.domain = DOMAIN_KERNEL,
},
};
再回到map_memory_bank() 代码中, 虚拟地址设成了__phys_to_virt(bank_start) 。
__phys_to_virt()和__virt_to_phys()是两个宏定义,用于在虚拟地址和物理地址之间互相转换。
这两个宏定义在arch/arm/include/asm/memory.h 中:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
其
中PHYS_OFFSET 是物理内存起点, PAGE_OFFSET 是内核虚拟地址空间起点(0xC0000000,
3GB)。所以内核的虚拟地址和物理地址之间只是有一个固定的偏移量。对于AT91SAM9260EK 平台,PHYS_OFFSET
为0x20000000,有以下转换关系:
PA = VA -(0xC0000000 – 0x20000000) = VA – 0xA0000000
VA = PA + (0xC0000000 – 0x20000000) = PA + 0xA0000000
也就是说, AT91SAM9260EK 板上的64MB SDRAM 的物理地址范围是
0x20000000~0x24000000-1,映射后的虚拟地址范围是: 0xC0000000~0xC4000000-1。