了解完kernel发动曾经的汇编之后咱们来看看正式的c言语发动代码,也便是咱们的start_kernel函数了。start_kernel适当大,里边每一个调用到的函数都满足咱们伤脑筋了,我这儿仅仅浅尝辄止的描绘一下函数的功用,从而对kernel发动的进程有一个比较直观的了解。许多函数真实了解需要对linux相关体系有很深的了解,暂时没有时刻深化,留下今后了。
说实话发动的代码看到现在仅有的感觉便是kernel的全局变量实在太多了,要了解一个进程盯梢一个变量的值的改变适当苦楚啊,不过耐性看下来,收成仍是比较丰富的,对许多概念都有了一个比较直观的了解。闲话就不多说了,直接来上代码~~
smp_setup_processor_id();
//这个函数现在是空的;
lockdep_init();
//Runtime locking correctness validator, see Documentation/lockdep_design.txt
debug_objects_early_init();
cgroup_init_early();
//Control group, read Documentation/cgroup.txt
local_irq_disable();
//运用armcpsid i指令来制止IRQ
early_boot_irqs_off();
early_init_irq_lock_class();
/* 根本上面几个函数便是初始化lockdep和cgroup,然后制止IRQ,为后续的运转创造条件 */
lock_kernel();
/* 看这个函数的之前咱们首要来了解一段常识,linux kernel默许是支撑preemption(抢占)的。在SMP环境下为了完成kernel的确定,kernel运用了一个BKL(big kernel lock)的概念,在初始化的进程中先确定这个BKL,然后再继续进行其他发动或许初始化进程,这样就能够避免发动进程中被中止,履行到res_init今后,kernel会开释这个锁,这样就保证了整个start_kernel进程都不会被抢占或许中止。由此咱们能够看到这首要是针对多处理器而言的,实践上假如只要一个处理器的话它也不会被中止或许被抢占。 */
/* 下面咱们来看看这个函数的履行进程,在这儿边能学到许多东西的:
int depth = current->lock_depth+1;
这个current实践上是一个宏,界说在arch/arm/include/asm/current.h里边,它实践是一个task_struct的结构体,这个结构体描绘了这个task的一系列信息(task应该是linux进程调度的一个根本单位?)。linux下每个进程都有一个相对应的task_struct,这个task_struct有几个咱们常常能看到的信息,一个便是PID,然后便是comm进程的姓名,然后便是mm_struct,它界说了跟这个进程相关的一切恳求的memory的办理。这个curren_thread_info()也是个很有意思的函数,直接读取SP来取得当时线程的结构体信息thread_info。thread_info和task_struct这两个结构体应该就代表了当时线程的一切信息。
初始化的lock_depth是-1,只要init task的lock_depth是0。
if (likely(!depth))
__lock_kernel();
这儿判别是不是init task,假如是就会履行__lock_kernel();这个__lock_kernel首要制止抢占,然后试着取得BKL,假如成功则直接回来,假如不成功首要判别是否制止抢占成功了,假如成功了就用自旋锁去锁BKL。假如制止抢占没有成功则在抢占有用的状况下去等候BKL,直到取得BKL。由于QC的片子不是SMP,一切这儿第一次try的时分就直接成功了。
current->lock_depth = depth;
这个就没什么好说的了 */
/* 根本上来说这个lock_kernel便是制止抢占,然后取得BKL,干了这么件事 */
tick_init();
//和时钟相关的初始化,好像是注册notify事情,没有细心研讨过
boot_cpu_init();
//这个实践上是在SMP环境下挑选CPU,这儿直接CPUID挑选的是0号cpu
page_address_init();
//初始化high memory,在arm环境下实践上这个函数是空的,也便是说arm不支撑high memory
printk(KERN_NOTICE);
printk(linux_banner);
//这儿的KER_NOTICE是字符串<5>,不太理解它的意思。。。后边的linux_banner界说在kernel/init/version.c里边,这儿的printk是门深邃的学识,今后看console的时分会细心剖析
setup_arch(&command_line);
/* 这是一个重量级的函数了,会比较细心地剖析一下,首要完成了4个方面的作业,一个便是取得MACHINE和PROCESSOR的信息然或将他们赋值给kernel相应的全局变量,然后呢是对boot_command_line和tags接行解析,再然后呢便是memory、cach的初始化,最终是为kernel的后续运转恳求资源。 */
/* 咱们来细心看看这个函数的完成:
setup_processor();
这个函数首要从arm寄存器里边取得cpu ID,然后调用lookup_processor_type来取得proc_info_list这个结构体。这个进程实践上和咱们在head-common.S里边的进程是相同的,不知道这儿为什么不直接去读switch_data里边现已保存好的数据反而又查询一遍是为什么?取得proc_info_list今后,将里边的内容一个个赋值给相应的全局变量,然后将CPU的信息打印出来。然后它会从arm寄存器里边取得cache的信息,并将cache的信息打印出来。最终它会调用cpu_proc_init()的函数,这个函数实践上界说在proc-v6.S里边,没有做任何事情。
mdesc = setup_machine(machine_arch_type);
首要这个machine_arch_type界说在生成的./include/asm-arm/mach-types.h里边,这个setup_macine实践上也是和上面的processor相似,都是调用head-common.S里边的函数,依据machine ID来取得Machine的信息,并将Machine name打印出来。
if (mdesc->soft_reboot)
reboot_setup(“s”);
设置reboot方法,默许是硬发动;
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
这儿首要判别head-common.S里边界说的__atags_pointer是不是为空,不为空的话阐明bootloader设置了初始化参数,将参数的物理地址转化为虚拟地址,这儿有一个掩盖,便是说能够在Machine desc里边对初始化参数的物理地址从头定位。
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
首要判别是不是正确的atag格局,假如是曾经老版别的param_struct格局会首要将其转化成tag格局,假如转化今后仍是不对,则运用默许的init_tags,这儿判别的进程都是依据结构体第一个值是不是ATAG_CORE.
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
这儿首要判别fixup函数指针,这儿一般为空,假如不为空就会地用fixup来从头修正memory map,meminfo这个结构体界说在arch/arm/include/asm/setup.h里边,描绘了内存块的信息,内存块的个数,每个内存块的开始地址和巨细,假如修正了memory map则需要从atag参数里边去掉bootloader传过来的的memory map信息,然后是保存一下atag,这个保存函数在这儿实践上是空的,没有做任何操作,最终是对atag参数进行解析。这儿要阐明一下这儿的tags实践上是一个tag的数组或许说行列,里边有多个tag结构体,每一个结构体都是一个header加一个参数,详细的结构咱们能够看看setup.h。对ATAG参数的解析悉数界说在arch/arm/kernel/setup.c里边,首要在setup.c里边界说了一个相似于这样__tagtable(ATAG_CORE, parse_tag_core)的宏,这个宏实践上是声明晰一个放在__tagtable_begin和__tagtable_end段之间结构体,这个结构体界说了这个一个参数类型,和对这个参数类型进行解析的函数。一切的参数解析咱们都能够从setup.c里边找到相对应的函数,比方说对boot_commad_line的解析,从config文件得到的default_commad_line就会被ATAG里边取得commad_line给替换掉;再比方ramdisk,就会将ATAG里边的ramdisk的信息赋值给rd_image_start, rd_size等体系的全局变量。
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
这就便是对init_mm结构体进行赋值,详细不了解这些东西咋用的,可是便是将text和data段给赋值了。
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = /0;
parse_cmdline(cmdline_p, from);
这儿的boot_command_line来自于与config文件里边的CONFIG_CMDLINE,也有或许被ATAG里边的boot参数给掩盖,取得command_line今后对command_line进行解析。这个解析的进程也是在setup.c里边进行的,它首要查找.early_param.init段里边注册的结构体,经过__early_param将early_param结构体注册到这些段里边,实践这个early_param很简单一个便是相似于”initrd=”的字符串,用来与command_line里边的字符进行匹配,假如匹配到了就履行early_param里边的那个函数,并将匹配到的字符作为参数传给函数。举个比如比方说咱们现在的commadline里边有一句initrd=0x11000000,然后咱们首要在early_param.init段里边查找时分有没有early_param->arg和这个initrd=匹配,找到了就履行它里边的func然后将这个initrd=的值作为参数传进去。
paging_init(mdesc);
这个函数是个大函数,详细内容没有细心看,需要对arm MMU了解比较深,这儿只贴一下source里边关于这个函数的注释:
/*
* paging_init() sets up the page tables, initialises the zone memory
* maps, and sets up the zero page, bad page and bad page tables.
*/
request_standard_resources(&meminfo, mdesc);
这个函数用来恳求一些应该是内存资源,详细的内容没有细心研讨,看不大懂。。
cpu_init();
初始化CPU,这儿首要是对arm寄存器cpsr的操作
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
这儿将体系结构相关的几个函数,中止,初始化,定时器之类的赋值给kernel全局变量;
conswitchp = &vga_con;
这儿设置了关于console的一个变量,详细不知道怎样用的,今后看console的时分再细心剖析
early_trap_init();
不知道这个函数详细做什么用的。。。 */
/* 根本上咱们能够总结出setup_arch首要将一些体系结构的相关信息来赋值给kernel的全局变量,包含cpu啊,machine啊,memory,cahce啊,然后kernel再依据这些函数或许变量来做相应的作业,并且很明显地能够看出来这个setup_arch和前面的head.S,head-common.S,proc-v6.S,board-msm7x27.c是紧密联系在一起的 */