kernel启动流程第二阶段

整个流程基本为从网上大牛的分享结合自己的理解所述,部分引用可能未粘贴链接。



Kernel启动流程中的Tips:

1、 Kernel一般会存在于存储设备上,比如FLASH\EMMC\SDCARD. 因此,需要先将kernel镜像加载到RAM的位置上,CPU才可以去访问到kernel

但是注意,加载的位置是有要求的,一般是加载到物理RAM偏移0x8000的位置,也就是要在前面预留出32KRAMkernel会从加载的位置上开始解压,而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、内核启动参数的获取和处理;

2setup_arch(&command_line)函数;

3、内存管理的初化(从bootmemslab);

4rest_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子系统,内存子系统。为什么叫做子系统呢,因为它是从整个操作系统的资源衍生出来的。然后我们创建一种虚拟的节点,叫做cgroupcontrolgroup,一组进程的行为控制)。

        进程分组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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值