源码声明:基于Linux kernel 3.08
1. 在kernel/arch/mips/kernel/head.S中会做一些特定硬件相关的初始化,然后会调用内核启动函数:start_kernel;
2. start_kernel是通用的内核启动函数,但是在初始化内核过程中,必然有一些参数是特定于硬件体系结构的,这些特定于硬件体系结构的设置通过调用函数setup_arch函数;
3. 我们看看MIPS架构的setup_arch函数做了哪些特定于MIPS的设置:
/* kernel/arch/mips/kernel/setup.c */ void __init setup_arch(char **cmdline_p) { cpu_probe(); prom_init(); #ifdef CONFIG_EARLY_PRINTK setup_early_printk(); #endif cpu_report(); check_bugs_early(); #if defined(CONFIG_VT) #if defined(CONFIG_VGA_CONSOLE) conswitchp = &vga_con; #elif defined(CONFIG_DUMMY_CONSOLE) conswitchp = &dummy_con; #endif #endif /* 这个是内存管理相关的,我们只关注该函数,其他的看看函数名,意淫一下就可以了 */ arch_mem_init(cmdline_p); resource_init(); plat_smp_setup(); }
4. arch_mem_init
/* 这么关键的注释,一定要仔细阅读 */
/* * arch_mem_init - initialize memory management subsystem(初始化 memory management subsystem) * * o plat_mem_setup() detects the memory configuration and will record detected * memory areas using add_memory_region.
* plat_mem_setup():探测memory配置,然后使用add_memory_region记录探测到的内存区域【低端内存,高端内存,DMA的区域】。 * * At this stage the memory configuration of the system is known to the * kernel but generic memory management system is still entirely uninitialized.
* 此时,kernel知道memory的配置,但是generic memory management sysetm依然没有被初始化。 * * o bootmem_init() * o sparse_init() * o paging_init() * * At this stage the bootmem allocator is ready to use.
* 此时,bootmem allocator可以使用了。 * * NOTE: historically plat_mem_setup did the entire platform initialization. * This was rather impractical because it meant plat_mem_setup had to * get away without any kind of memory allocator. To keep old code from * breaking plat_setup was just renamed to plat_setup and a second platform * initialization hook for anything else was introduced.
*
* NOTE:从历史上看,plat_mem_setup完成整个platform的初始化。
* 这是不切实际啊的,因为这意味着plat_mem_setup必须在不使用memory allocator时完成所有工作。......
*/ static int usermem __initdata; static int __init early_parse_mem(char *p) { unsigned long start, size;
/* 该函数会被调用2次:
* 第一次:p 是 "224M@0x0"
* 第二次:p 是 "256M@0x30000000"
* early_parse_mem的参数是如何传递的需要深入了解两个函数:early_parm和parse_eraly_param,这个还是有些复杂,我们就不去深入了解了。但是我们可以大体猜测出这些函数的目的,就是:
* 从commandline中根据关键字"mem"提取物理内存的信息,即起始地址和大小。
* 这里我们看到该函数被调用两次,是由于我们的command line包含两个"mem"信息,这是由于我们的板子使用是4+8的EMCP,那么RAM大小是512M,刚好是两个256M。
* 第一个变成了224M,可能是由于RAM不是精确的512M,也可能是uboot占用了一部分。具体就不太了解了。
*/ /* * If a user specifies memory size, we * blow away any automatically generated * size. */ if (usermem == 0) { boot_mem_map.nr_map = 0; usermem = 1; } start = 0; size = memparse(p, &p); if (*p == '@') start = memparse(p + 1, &p); // 解析”mem“字符串,提取memory信息 add_memory_region(start, size, BOOT_MEM_RAM); // 记录memory信息,就是对boot_mem_map结构体的赋值,代码我就不粘了 return 0; } early_param("mem", early_parse_mem); #ifdef CONFIG_ANDROID_PMEM #ifdef CONFIG_SOC_4775 //unsigned long start, size; static unsigned long g_pmem_total_size=0; static unsigned long g_pmem_start=0; /* called in arch/mips/xburst/soc-4775/common/setup.c */ unsigned long set_reserved_pmem_total_size(unsigned long size) { g_pmem_total_size = size; return 0; } unsigned long get_reserved_pmem_size(void) { return g_pmem_total_size; } unsigned long get_reserved_pmem_start(void) { return g_pmem_start; } #endif /* CONFIG_SOC_4775 */ #endif /* CONFIG_ANDROID_PMEM */ static void __init arch_mem_init(char **cmdline_p) { extern void plat_mem_setup(void); /* call board setup routine */ plat_mem_setup(); pr_info("Determined physical RAM map: "); strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE); strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
// arcs_cmdline : console=tty3,115200n8 mem=224M@0x0 mem=256M@0x30000000 ip=off root=/dev/ram0 rw rdinit=/init rd_start=0x81A00000 rd_size=0x0012E720
// 这个command line是由uboot传递过来的一些启动参数,具体如何过来的不再深入追了,最后kernel会从这里提取内存信息,串口信息,其他信息(不懂)
*cmdline_p = command_line; parse_early_param(); // 内核一种看起来很牛逼的函数调用,具体流程忽略,最后结果就是调到了:early_parse_mem if (usermem) { pr_info("User-defined physical RAM map: "); print_memory_map(); // 打印内存信息,如下
/*
memory: 0e000000 @ 00000000 (usable)
memory: 10000000 @ 30000000 (usable)
第一个字段是是size,第二个字段是起始地址
*/
bootmem_init(); device_tree_init(); sparse_init(); plat_swiotlb_setup(); paging_init(); }
5. plat_mem_setup
该函数是针对每种不同的CPU进行的配置。/* kernel/arch/mips/xburst/soc-m200/common/setup.c *//* 针对m200这种mips架构的CPU,进行的配置 */
void __init plat_mem_setup(void) { /* jz mips cpu special */ __asm__ ( "li $2, 0xa9000000 " "mtc0 $2, $5, 4 " "nop " ::"r"(2)); /* use IO_BASE, so that we can use phy addr on hard manual * directly with in(bwlq)/out(bwlq) in io.h. */ set_io_port_base(IO_BASE); // kernel/arch/mips/include/asm/mach-generic/spaces.h: #define IO_BASE _AC(0xa0000000, UL)
/*
kernel/kernel/resouce.c
struct resource ioport_resource = {
.name = "PCI_IO",
.start = 0,
.end = IO_SPACE_LIMIT,
.flags = IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);
*/
// 记录 io resource ioport_resource.start = 0x00000000; ioport_resource.end = 0xffffffff; iomem_resource.start = 0x00000000; iomem_resource.end = 0xffffffff; setup_init(); // 一些硬件相关的初始化 init_all_clk(); // 初始化所有的始终。如:CCLK:1200MHZ L2CLK:300MHZ H0CLK:200MHZ H2CLK:200MHz PCLK:100MHZ #ifdef CONFIG_ANDROID_PMEM /* reserve memory for pmem. */ board_pmem_setup(); #endif return; }
set_io_port_base:
/* * Gcc will generate code to load the value of mips_io_port_base after each * function call which may be fairly wasteful in some cases. So we don't * play quite by the book. We tell gcc mips_io_port_base is a long variable * which solves the code generation issue. Now we need to violate the * aliasing rules a little to make initialization possible and finally we * will need the barrier() to fight side effects of the aliasing chat. * This trickery will eventually collapse under gcc's optimizer. Oh well. */
/*
* GCC会在每个函数的调用之后,产生Code来加载mips_io_port_base的值,这有时完全是浪费的。因此,我们不完全按照手册。我们告诉gcc,mips_io_port_base是一个长整型变量,这将会解决code generation问题。
* 现在,我们需要违反aliasing(别名)规则,来完成初始化,最后我们需要barrier()来fight side effects of the aliasing chat.
* 这种欺诈行为最终将会随着gcc的优化而崩塌。Oh well。
*/
static inline void set_io_port_base(unsigned long base) { * (unsigned long *) &mips_io_port_base = base; // 简单地赋值语句 barrier(); // 内存屏障,下边我们简单看看 }
/* kernel/include/linux/compiler-gcc.h */ /* Optimization barrier */ /* The "volatile" is due to gcc bugs */ #define barrier() __asm__ __volatile__("": : :"memory")
/*
* 1. __asm__用于指示编译器在此插入汇编语句
* 2. __volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
* 3. memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
* 4. "":::表示这是个空指令。
*/
6. print_memory_map
再次回顾我们的主线:start_kernel—>setup_arch—>arch_mem_init。
刚才我们分析了arch_mem_init调用的第一个函数:plat_mem_setup。
该函数就是和具体的CPU相关,该函数做了这么几件事:1. set_io_port_base: mips_io_port_base = 0xa0000000.
2. 记录io resource.
3. 一些硬件初始化和始终初始化
接着,我们继续看arch_mem_init调用的第二个函数:print_memory_map。
很明显,这个函数是打印当前的内存信息,但是我们也详细去看看,因为内部有很重要的数据结构。
static void __init print_memory_map(void) { int i; const int field = 2 * sizeof(unsigned long); for (i = 0; i < boot_mem_map.nr_map; i++) { // boot_mem_map:是一个类型为struct boot_mem_map的全局变量 printk(KERN_INFO " memory: %0*Lx @ %0*Lx ", field, (unsigned long long) boot_mem_map.map[i].size, field, (unsigned long long) boot_mem_map.map[i].addr); switch (boot_mem_map.map[i].type) { case BOOT_MEM_RAM: printk(KERN_CONT "(usable) "); break; case BOOT_MEM_ROM_DATA: printk(KERN_CONT "(ROM data) "); break; case BOOT_MEM_RESERVED: printk(KERN_CONT "(reserved) "); break; default: printk(KERN_CONT "type %lu ", boot_mem_map.map[i].type); break; } } }
没错,我们说的重要的数据结构就是:boot_mem_map.
/* kernel/arch/mips/include/asm/bootinfo.h */ /* * A memory map that's built upon what was determined * or specified on the command line. */ struct boot_mem_map { int nr_map; struct boot_mem_map_entry { phys_t addr; /* start of memory segment */ phys_t size; /* size of memory segment */ long type; /* type of memory segment */ } map[BOOT_MEM_MAP_MAX]; };
7. 在arch_mem_init我们还有bootmem_init之后的几个调用,具体分析见下篇文章