linux内核启动过程学习总结

Posted 风雨田

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核启动过程学习总结相关的知识,希望对你有一定的参考价值。

下面是学习linux内核启动过程的记录
平台是:powerpc mpc8548 + linux2.6.23 内核
 
 
 
 
 

通用寄存器的作用
r0
:在函数开始时使用
r1
:存放堆栈指针,相当于ia32架构中的esp寄存器
r2
:存放当前进程的描述符的地址
r3
:存放第一个参数和返回地址
r4-r10
:存放函数的参数
r11
:用在指针的调用和当前一些语言的环境指针
r12
:用于存放异常处理
r13
:保留做为系统线程ID
r14-r31
:作为本地变量,具有非易失性

Linux启动过程描述

第一步:使用Boot Loader(一般是U-boot)加载Linux内核映像到内存,并负责目标系统的基本初始化过程,并搜集这个系统的基本信息,比如内存大小、处理器主频、外设的使用情况等一系列信息。然后把这些信息传递给linux内核。然后Boot loaderlinux内核复制到从0x0000 0000 开始的物理内存处(虚拟地址一般为0xc000 0000处)开始执行。

备注:这一部分内容,本文不做重点介绍。请参考《uboot启动过程学习总结.doc

 

*

 

*******************************************************************************

记录2linux kernel 链接文件、入口函数和相关宏定义等

Bootstraploader过程:从文件\arch\powerpc\boot\zImage.lds中可以看出,bootstraploader的入口为_zimage_start。在代码arch\powerpc\boot\crt0.S

D:\virtual_machine\share_folder\linux-2.6.23\arch\powerpc\boot\zImage.lds中定义的入口地址为4MB,见下面

SECTIONS

{

  . = (4*1024*1024);

  _start = .;

  .text      :

进入linux内核:vmlinux.lds看到,内核入口为_stext,通过段.text.head 将代码定位到0xc0000000处。

在代码arch/powerpc/kernel/head_32.S_stext之后紧接着是_start,他们之间没有代码,他们表示相同的地址。

vmlinux.lds中将.text.head规划为.text的第一个字段(保证了地址定位到0xc0000000)。

*******************************************************************************

 

 

 

 

第二步:Linux系统的初始化

1、  bootstraploader 过程

注意:需要知道从uboot跳到此处时,r3寄存器的内容,以及其他register的内容

如果运行地址和链接地址不同,则修正got表中各个函数的指针

清零BSS

调用platform_init(),保存bd__res,初始化ppc_mdppc module)中的各个函数。

调用arch\powerpc\boot\main.c中的start()

start()中:

1.1将命令行拷贝到cmdline

1.2调用open函数打开串口

1.3解压缩kernel代码

1.4解压缩ramdisk image

1.5最终初始化设备树

1.6跳到内核代码中执行

有两个调用语句,应该是运行了语句:kentry(ft_addr, 0, NULL); need confirm

2、  进入linux内核

入口:arch/powerpc/kernel/head_32.S中的_start

2.1 early_init() arch/powerpc/kernel/setup_32.c

计算运行地址和链接地址的差值。根据cpu型号调用do_feature_fixups函数来对__ftr_fixup段进行修复处理。

例如若HIDO寄存器的HIGH_BAT_EN位置位,另外的4组寄存器 IBATs (4–7) 4 DBATs (4-7) 将会被激活,__ftr_fixup段中对这8组寄存器进行初始化的代码就会生效;否则__ftr_fixup中的这段代码就会被nop指令所代替!

early_init()函数调用identify_cpu()函数通过cpu中的pvr寄存器存放的CPU核的版本号在全局数组cpu_specs中寻找到当前cpu的详细信息,identify_cpu()函数在找到之后,会调用setup_cpu_spec()函数把上述cpu的信息所在的链接地址赋值给cur_cpu_spec变量

 

2.2 mmu_off() 关闭mmu

2.3 flush_tlbs() TLB中移除页表

2.4 call_setup_cpu()call_setup_cpu()位于misc_32.S文件中

2.5 relocate_kernel():把内核代码拷贝到链接地址指向的位置

2.6 turn_on_mmu():映射了256MB内存,就可以避免调用reloc_offset()函数来显式得把虚拟地址映射到物理地址!

2.7跳到start_here()函数中运行,可以认为是真正内核开始运行。。。

2.7.1加载0号线程上下文,全局变量init_task

注:0号线程优先级为120,从#define INIT_TASK(tsk)中可以看出。

init_task是进程0使用的进程描述符,也是Linux系统中第一个进程描述符,该进程的描述符在arch/powerpc/kernel/init_task.c中定义,代码片段如下:

struct  task_struct  init_task = INIT_TASK(init_task);

init_task描述符使用宏INIT_TASKinit_task的进程描述符进行初始化,宏INIT_TASKinclude/linux/init_task.h文件中

init_taskLinux内核中的第一个线程,它贯穿于整个Linux系统的初始化过程中,该进程也是Linux系统中唯一一个没有用kernel_thread() 函数创建的进程!在init_task进程执行后期,它会调用kernel_thread()函数创建第一个核心进程kernel_init,同时init_task进程继续对Linux系统初始化。在完成初始化后,init_task会退化为cpu_idle进程,当Core 0的就绪队列中没有其它进程时,该进程将会获得CPU运行。新创建的1号进程kernel_init将会逐个启动次CPU,并最终创建用户进程!

备注:core0上的idle进程由init_task进程退化而来,而APidle进程则是BSP在后面调用fork()函数逐个创建的,我们会在后面详细讨论。

init_task进程使用init_thread_union数据结构描述的内存区域作为该进程的堆栈空间,并且和自身的thread_info参数公用这一内存空间空间,其数据结构的定义如下(linux- 2.6.38/include/linux/sched.h)

2.7.2 调用machine_init()分析OF树的结构,获得当前处理器的内存使用情况, 创建LMB结构,同时获得当前CPUOF树中的硬件信息;保存命令行等,从cmd_line拷贝uboot引导kernel时使用的命令行参数到boot_command_line,并并使用parse_early_param()函数分析这些命令行参数。

 

2.7.3 调用MMU_init(),为LinuxPowerPC建立MMU地址映射,区分memory normal 区域和高端区域。(768M896M为分界线)

注:会把也映射信息更新到init_mm. pgd,即swapper_pg_dir指向的页表中。最终应该会把init_mm填到init1 线程任务结构的mm_struct中。需要后续验证***********************

*****************************************************************************************************************************************

2.7.4 调用load_up_mmu()重新装载MMU相关的寄存器,开启MMU并跳到start_kernel

再次让CPU进入是实地址模式,去运行load_up_mmu()函数。这样做的目的是让core 0在实模式下调用load_up_mmu()函数来重新装载MMU相关的寄存器,比如SDR1BAT寄存器等。之所以要重新转载,是因为我们在<11>bl initial_bats,创建的临时BAT块地址映射,只是启动的第一阶段用到的临时映射,现在这个临时地址映射需要舍弃了,我们需要重新初始化MMU,来建立正式的MMU地址映射。

 

注:从__start()start_here()再到调用start_kernel(),主要的工作与当前目标板的硬件结构密切相关,包含对一些底层硬件进行最基本初始化操作等等,从start_kernel()开始的初始化操作与处理器的类型基本无关了。

 

3 start_kernel

本阶段也是有0号线程init_task中调用的,将完成Linux内核核心数据结构的初始化,最终创建1号线程kernel_init,最后由1号内核线程启动1号用户进程。需要后续确认***

 

3.1 关中断

3.2 调用tick_init(),初始化系统时钟滴答链

3.3 调用page_address_init(),将高端内存组织在一起。应该是为buddy初始化做准备,需要进一步确认

3.4 setup_arch()setup_arch()函数是以上是关于linux内核启动过程学习总结的主要内容,如果未能解决你的问题,请参考以下文章

《Linux内核分析》课程第七周学习总结

Linux内核设计第六周学习总结 分析Linux内核创建一个新进程的过程

《Linux内核分析》第六周学习总结

《Linux内核分析》第四周学习总结

LINUX内核分析第一周学习总结——计算机是如何工作的

Linux根文件系统学习总结