整个流程基本为从网上大牛的分享结合自己的理解所述,部分引用可能未粘贴链接。
Kernel启动流程中的Tips:
1、 Kernel一般会存在于存储设备上,比如FLASH\EMMC\SDCARD. 因此,需要先将kernel镜像加载到RAM的位置上,CPU才可以去访问到kernel。
但是注意,加载的位置是有要求的,一般是加载到物理RAM偏移0x8000的位置,也就是要在前面预留出32K的RAM。kernel会从加载的位置上开始解压,而kernel前面的32K空闲RAM中,16K作为boot params,16K作为临时页表
2、 Arch/arm/kernel/head.S(kernel的入口函数)
3、 bootloader需要通过设置PC指针到kernel的入口代码处(也就是kernel的加载位置)来实现kernel的跳转。
Some标记:
1)asmlinkage:表示函数不从寄存器传递参数,而是从栈传递
2)__visible:Tell the optimizer thatsomething else uses this function or variable
由__init*修饰的函数或者变量,只在开机阶段使用,占用的空间在系统完成后自行释放。
3)__init:标记内核启动时使用的初始化代码,内核启动完成后不再需要。以此标记的代码位于.init.text 内存区域
4)__initdata:标记内核启动时使用的初始化代码,内核启动完成后不再需要。以此标记的代码位于.init.data 内存区域
Start_Kernel():内核启动第二阶段入口 init/main.c
重要的点:
1、内核启动参数的获取和处理;
2、setup_arch(&command_line)函数;
3、内存管理的初化(从bootmem到slab);
4、rest_init()函数
asmlinkage __visible void__init start_kernel(void)
来自:https://www.cnblogs.com/yjf512/p/5999532.html
1、lockdep_init();kernel/kernel3.18/include/linux/lockdep.h
do{}while(0)什么都没做,作用暂未知。
2、set_task_stack_end_magic(&init_task);kernel/kernel3.18/kernel/fork.c
init_task:
1)为init_task_union结构体的成员,为系统的第一个进程,PID为0,唯一一个不用fork()创建的进程。其使用静态创建的方式生成,start_kernel
之前的汇编代码到start_kernel
执行,这里都会纳入idle进程的上下文(之前的汇编代码就是为了idle进程的执行做准备)。
2) 由宏“INIT_TASK(tsk)”初始化,方式:为task_struct结构体中的每个成员直接赋值
3)init进程,PID为1,在rest_init()中才被创建。
该函数主要用于指定init_task线程堆栈的结尾,设置标记STACK_END_MAGIC(防溢出操作)
3、smp_setup_processor_id();kernel/kernel3.18/arch/arm/kernel/setup.c
设置smp模型的处理器ID;SMP(多对称处理模型),指多个CPU之间地位平等,共享所有总线、内存、I/O,但也因此会造成抢占资源的问题。
4、boot_init_stack_canary();kernel/kernel3.18/arch/arm/asm/stackprotector.h
该函数也用于防止栈溢出(不是堆栈,是栈),该函数在局部变量和保存的指令指针之间设置一个标记位(canaryword)。当该位被修改后,即可探测到溢出,最后调用__stack_chk_fail函数,丢出错误退出进程。
参考:https://www.ibm.com/developerworks/cn/linux/l-cn-gccstack/
上图2中的EBP(堆栈帧指针,ESP为栈顶指针)和返回地址已经被覆盖,如果能在运行时检测出这种破坏,就有可能对函数栈进行保护。目前的堆栈保护实现大多使用基于 “Canaries” 的探测技术来完成对这种破坏的检测。详细见上述地址。
5、cgroup_init_early();kernel/kernel3.18/kernel/cgoup.c
参考:http://www.cnblogs.com/yjf512/p/6003094.html描述了Linux系统中的cgroup机制,部分截选如下:
我们把每种资源叫做子系统,比如CPU子系统,内存子系统。为什么叫做子系统呢,因为它是从整个操作系统的资源衍生出来的。然后我们创建一种虚拟的节点,叫做cgroup(controlgroup,一组进程的行为控制)。
进程分组css_set,不同层级中的节点cgroup也都有了。那么,就要把节点cgroup和层级进行关联,和数据库中关系表一样。这个事一个多对多的关系。为什么呢?首先,一个节点可以隶属于多个css_set,这就代表这这批css_set中的进程都拥有这个cgroup所代表的资源。其次,一个css_set需要多个cgroup。因为一个层级的cgroup只代表一种或者几种资源,而一般进程是需要多种资源的集合体。
Cgroup机制结构图如下:
结构体css_set中最重要的成员就是cgroup_subsys_statesubsys[]数组以及structcgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
task_struct->css_set->cgroup_subsys_state->cgroup反映了进程与cgroup之间的连接关系。
即该函数就是初始化这个cgroup机制。
6、local_irq_enable();关闭中断(底层调用汇编指令)
7、boot_cpu_init();初始化第一个CPU,并将当前CPU设置为激活状态
8、page_address_init();
页地址初始化的作用,未定义高端内存,代码为do{}while(0),不需要做任何操作,具体参考上述网址。
9、setup_arch(&command_line);
内核启动最重要的部分
参考:http://blog.csdn.net/qing_ping/article/details/17351541
http://www.it165.net/os/html/201409/9264.html
主要完成四个工作:
A)取得machine和processor的信息,然后赋值给kernel相应的全局变量
B)对boot_command_line和tags进行解析
C)memory和cache的初始化
D)为kernel后续运行请求资源
1. /*内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。
2. machine_desc结构体通过MACHINE_START宏来初始化,在代码中,
3. 通过在start_kernel->setup_arch中调用setup_machine_fdt来获取。*/
每种体系结构都有自己的setup_arch()函数,是体系结构相关的,具体编译哪个 体系结构的setup_arch()函数,由源码树顶层目录下的Makefile中的ARCH变量决定
{
1)setup_processor();
首先从寄存器中读取cpuid,之后调用lookup_processor_type来取得proc_info_list
lookup_processor_type定义在head-common.S中,其实就是调用
__lookup_processor_type
2)mdesc =setup_machine_fdt(__atags_pointer);
主要工作:获取machine_desc结构体以及uboot传来的参数。
__atags_pointer即Uboot调用kernel时传递的第三个参数DTB首地址。如何指向的?
通过搜索该关键字,可在head-common.S中找到其定义,在对象__mmap_switched_data中,该对象被__mmap_switched调用,具体操作是通过将r3表示地址(__mmap_switched_data)中的内容依次存入相应的寄存器(r4~r7)中,在代码中,__atags_pointer地址被存入r6,之后再将uboot传来的第三个参数(第三个参数,所以在r2中)寄存器r2中的值赋值给r6,即实现__atags_pointer指向DTB首地址。
str r2, [r6]
验证DTB的有效性,主要为FDT的magic(参考kernel启动流程第一阶段的__vet_args)
调用of_flat_dt_match_machine接口:先获取DTB的根节点,通过循环遍历后续的节点进行匹配,找出最接近machine_desc结构.
调用early_init_dt_scan_chosen接口:扫描“/chosen/”下的节点,包括获取initrd相关的信息;bootargs相关的信息存入变量boot_command_line。
调用early_init_dt_scan_root接口:依据属性“#size-cells”获取相关信息。
3)setup_machine_tags():
若步骤2返回的mdesc结构为空(只有在验证时就失败返回的情况),则以__machine_arch_type为依据重新进行一次查找mdesc和boot_command_line。若仍未找到,则陷入死循环。
参考:http://www.360doc.com/content/13/0401/19/11635678_275349091.shtml
4)结构struct mm_struct管理进程的虚拟地址空间,所有内核线程都使用共同的地址空间,因为他们都是用相同的地址映射,这个地址空间由init_mm来描述。此处对init_mm结构体进行初始化,其中_text和_etext表示内核镜像代码的起始位置和结束位置,_etext 到_edata之间是已初始化数据段。_edata到_end时未初始化数据段等。_end后便是堆区。ps:每一个任务都有一个mm_struct结构以管理内存空间,init_mm是内核自身的mm_struct
5)parse_early_param:
看代码parse_one()中param为“early option”而params为NULL,所以理应在parameq判断条件时就返回了,最后直接调用传进来的函数调用(do_early_param)。
parse_early_param===>parse_one===>do_early_param===>proc_info_list.setup_func()
内容及原理比较混乱。
6)early_paging_init:
核心就是调用之前找到的mdesc中的init_meminfo()成员。
7)setup_dma_zone:
设置DMA区域的大小
8)arm_memblock_init():
将所有内存块添加到全局变量memblock中,该变量为结构体structmemblock类型,结构体memblock中存在两种类型的结构体,分别用于存储已使用的内存区(类型为reserve)和未使用的内存区(类型为memory)。其中,将已使用的内存(长度:_end- _stext)添加到reserve区中。
参考:http://blog.csdn.net/modianwutong/article/details/53162142
Linux内核使用伙伴系统管理内存,那么在伙伴系统工作前,如何管理内存?答案是memblock;
memblock在系统启动阶段进行简单的内存管理,记录物理内存的使用情况;
9)sanity_check_meminfo():
扫描各个内存块,检测低端内存的最大值arm_lowmem_limit,设置高端内存起始值的虚拟地址high_memory
10)paging_init(mdesc);
内核创建页表,初始化自举分配器。
11)request_standard_resources(mdesc);
内核中将许多物理资源用struct resource结构来管理,该函数就是将IO内存作为resource注册到内核
12)unflatten_device_tree();
解析FDT创建设备树节点。
后续暂留
}
10、mm_init_cpumask(&init_mm)
初始化CPU屏蔽字
mm.owner = &init_task
11、setup_command_line();
对cmdline进行备份和保存,均为分配的内存:
1、保存未改变的comand_line到字符数组static_command_line[] 中。
2、保存 boot_command_line到字符数组saved_command_line[]中
3、 初始化initcall_command_line变量用于后续使用
其中,command_line由boot_command_line拷贝得到,在setup_arch中进行此操作。
12、setup_nr_cpu_ids();
参考:http://blog.csdn.net/yin262/article/details/46778013
nr_cpu_ids全局变量被声明为__read_mostly属性。
nr_cpu_ids保存的是所有可处于联机状态的CPU总数。
nr_cpu_ids具有当前系统能具备的CPU数的信息,默认值为NR_CPUS值,NR_CPUS是编译时用户可设置的常量值。NR_CPUS并非当前系统内存在的CPU