Fs2410开发板基于三星的S3C2410芯片,主频203MHZ,S3C2410的存储空间由8个组(bank)组成,每个组的大小是128M,所以128MX8=1G。其中bank0到bank5用于ROM或者SRAM;bank6到bank7可用于ROM, SRAM, SDRAM。Fs2410开发板的外部存储器有内存:64M字节,NOR flash:2M字节,NAND flash:64M字节。其中根据跳线的不同,系统启动时可能选择不同的启动方式,如从NOR启动,还是NAND启动,不同的启动方式不在本文的讨论范围之内,可以参考另一篇文章:http://blog.csdn.net/lieye_leaves/article/details/8849777
其中64M内存的起始地址是从bank6开始的,地址是0x3000 0000, 结束地址0x3400 0000
选择分析的linux的版本是linux-2.6.26.5,FS2410的体系结构代码选择的是linux/arch/arm/mach-s3c2410下的该体系下的代码,该体系下系统启动时,linux内核被加载到地址0x3000 8000。
在编译内核时会产生文件system.map,在该文件中会保存内核的各个函数的地址,在该文件中有如下的变量(我选择了我编译的某个版本的system.map文件)
c0008000 T stext
该部分为内核代码
c01b3000 A _etext
该部分为内核数据
c01c8408 D _edata
该部分为用于初始化的数据
c01e5208 B _end
以上部分的地址为内核的虚拟地址,减去0xc000 0000,即为物理地址。
在linux的启动过程中和启动完成后采用的内存分配的方式是不一样的,在启动过程中使用位图分配方式,在启动完成后采用伙伴系统。在start_kernel()中setup_arch()中先将系统初始化成位图的方式,此后的内存分配采用位图分配方式,其中伙伴系统的页描述符数组mem_map[]就是采用在此处采用位图分配的方式分配的,函数执行,直到mem_init()将不需要的位图分配中的内存释放到伙伴系统,系统内存逐步转化成伙伴系统。
Linux位图分配方式的初始化过程,代码的大体位置如下:
Start_kernel/setup_arch/paging_init/bootmem_init/bootmem_init_node
FS2410的内存配置只有一个bank,数据如下:
struct membank {
unsigned long start; // 0x3000 0000
unsigned long size; // 0x0400 0000
int node;
};
内存采用页框的方式,每个页框的大小为4k,内存从0x3000 0000----0x3400 0000
4.1.1 位图总数
在位图内存分配时,每个页框用一个位来表示,
start = bank->start >> PAGE_SHIFT; //PAGE_SHIFT = 12,即为4k的大小
end = (bank->start + bank->size) >> PAGE_SHIFT;
if (start_pfn > start)
start_pfn = start;
if (end_pfn < end)
end_pfn = end;
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
函数详细如下:
pages = end_pfn – start_pfn
mapsize = (pages+7)/8;
mapsize = (mapsize + ~PAGE_MASK) & PAGE_MASK;
//#define PAGE_SIZE (1UL << PAGE_SHIFT)
//#define PAGE_MASK (~(PAGE_SIZE-1))
mapsize >>= PAGE_SHIFT;
// mapsize= 0x1000
// mapsize= 0x1
return mapsize;
//boot_pages = mapsize
由以上的计算可知,使用一个页框的位图可以表示所有的页框的分配状态
//////////////////////////////////////
4.1.2 位图地址
find_bootmap_pfn(int node, struct meminfo *mi, unsigned int bootmap_pages)
start_pfn = PAGE_ALIGN(__pa(&_end)) >> PAGE_SHIFT;
//内核数据的结尾4K对齐
bootmap_pfn = 0;
start = mi->bank[bank].start >> PAGE_SHIFT;
end = (mi->bank[bank].size +mi->bank[bank].start) >> PAGE_SHIFT;
//本开发板只有一个bank,所以start 为0x3000 0000
if (end < start_pfn)
continue;
if (start < start_pfn)
start = start_pfn;
if (end <= start)
continue;
if (end - start >= bootmap_pages) {
bootmap_pfn = start;
break;
}
}
if (bootmap_pfn == 0)
BUG();
return bootmap_pfn;
//返回值即为位图所在的地址
/////////////////////////////////////////
4.1.3 位图初始化
init_bootmem_node/init_bootmem_core
初始化数据结构:
typedef struct bootmem_data {
unsigned long node_boot_start; // 0x3000 0000
unsigned long node_low_pfn; // 0x34000
void *node_bootmem_map; // phys_to_virt(bootmap_pfn << PAGE_SHIFT); 位图所在的地址;
unsigned long last_offset;
unsigned long last_pos;
unsigned long last_success; /* Previous allocation point. To speed
* up searching */
struct list_head list;
} bootmem_data_t;
//////////////////////////////////
4.1.4 用位图分配页描述符
比较重要的函数:
free_area_init_node/ alloc_node_mem_map
/ free_area_init_core
Alloc_node_mem_map()
size = (end - start) * sizeof(struct page);
//size =( (0x3000 0000 >>PAGE_SHIFT) - (0x3400 0000>>PAGE_SHIFT))*sizeof(page)
//sizeof(page) = 32, 所以总数为0x80000
map = alloc_bootmem_node(pgdat, size);
pgdat->node_mem_map = map + (pgdat->node_start_pfn - start);
if (pgdat == NODE_DATA(0)) {
mem_map = NODE_DATA(0)->node_mem_map;
mem_map即为所有内存描述符的数组
free_area_init_core()
该函数初始化pglist_data *pgdat->zone的各个字段;
init_currently_empty_zone()/ zone_init_free_lists()
int order, t;
for_each_migratetype_order(order, t) {
INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
zone->free_area[order].nr_free = 0;//初始化空闲链表,此时伙伴系统内存为空
}
位图分配方式的退出在函数start_kernel/mem_init()中实现,系统内存分配的方式转到伙伴系统。
mem_init()
free_unused_memmap_node
free_all_bootmem_node()/ free_all_bootmem_core()释放位图那部分内存
free_unused_memmap_node()
/* 该函数的作用
*If we had a previous bank, and there is a space
* between the current bank and the previous, free it.
*/
free_all_bootmem_node()
{
register_page_bootmem_info_node(pgdat);//此处该函数为空
return free_all_bootmem_core(pgdat);//该函数完成两件事情
1.遍历位图表,释放内存至伙伴系统
2.释放位图表本身,将该表占用的内存释放到伙伴系统
}
FS2410开发板的内存空间是物理地址 0x3000 0000 --0x3400 0000,每个页框的大小为4k(即12位), 那么总的页框个数是 (0x3400 0000 - 0x3000 0000)>>PAGE_SHIFT = 0x400 0000 >> 12 = 0x4000;所以页描述符数组mem_map[]的总长度是 0x4000*sizeof(page)=0x4000*32=0x80000,该mem_map描述了从内存地址开始0x3000 0000到内存地址0x3400 0000结束的所有页框,也描述了已经被内核所占用的空间。
虚拟地址与物理地址之间的转换
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
// 0x3000 0000 0xc000 0000
#define __pa(x) __virt_to_phys((unsigned long)(x))
#define __va(x) ((void *)__phys_to_virt((unsigned long)(x)))
#define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT)
#define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
#define page_to_phys(page) (page_to_pfn(page) << PAGE_SHIFT)
struct page *pfn_to_page(unsigned long pfn)
{
return __pfn_to_page(pfn);
}
unsigned long page_to_pfn(struct page *page)
{
return __page_to_pfn(page);
}
#define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
#define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)
// ARCH_PFN_OFFSET = (PHYS_OFFSET >> PAGE_SHIFT)