内核启动流程分析
精简讲述linux内核启动过程
精簡講述linux內核啟動過程1. Linux內核啟動過程概述一個嵌入式 Linux 系統從軟件角度看可以分為四個部分:引導加載程序(Bootloader ),Linux 內核,文件系統,應用程序。
其中 Bootloader是系統啟動或reset以後執行的第一段代碼,它主要用來初始化處理器及外設,然後調用 Linux 內核。
Linux 內核在完成系統的初始化之後需要掛載某個文件系統做為根文件系統(Root Filesystem)。
根文件系統是 Linux 系統的核心組成部分,它可以做為Linux 系統中文件和數據的存儲區域,通常它還包括系統配置文件和運行應用軟件所需要的庫。
應用程序可以說是嵌入式系統的「靈魂」,它所實現的功能通常就是設計該嵌入式系統所要達到的目標。
如果沒有應用程序的支持,任何硬件上設計精良的嵌入式系統都沒有實用意義。
2. Bootloader啟動過程Bootloader在運行過程中雖然具有初始化系統和執行用戶輸入的命令等作用,但它最根本的功能就是為了啟動 Linux 內核。
2.1 Bootloader的概念和作用Bootloader是嵌入式系統的引導加載程序,它是系統上電後運行的第一段程序,其作用類似於 PC 機上的 BIOS。
在完成對系統的初始化任務之後,它會將非易失性存儲器(通常是Flash或DOC等)中的Linux 內核拷貝到 RAM 中去,然後跳轉到內核的第一條指令處繼續執行,從而啟動 Linux 內核。
由此可見,Bootloader 和 Linux 內核有著密不可分的聯繫,要想清楚的瞭解 Linux內核的啟動過程,我們必須先得認識 Bootloader的執行過程,這樣才能對嵌入式系統的整個啟動過程有清晰的掌握2.2 Bootloader的執行過程不同的處理器上電或reset後執行的第一條指令地址並不相同,對於 ARM 處理器來說,該地址為 0x00000000。
對於一般的嵌入式系統,通常把 Flash 等非易失性存儲器映射到這個地址處,而 Bootloader就位於該存儲器的最前端,所以系統上電或reset 後執行的第一段程序便是Bootloader。
Linux 内核启动分析
Linux 内核启动分析1. 内核启动地址1.1. 名词解释ZTEXTADDR解压代码运行的开始地址。
没有物理地址和虚拟地址之分,因为此时MMU处于关闭状态。
这个地址不一定时RAM的地址,可以是支持读写寻址的flash等存储中介。
Start address of decompressor. here's no point in talking about virtual or physical addresses here, since the MMU will be off at the time when you call the decompressor code. Y ou normally call the kernel at this address to start it booting. This doesn't have to be located in RAM, it can be in flash or other read-only or read-write addressable medium.ZRELADDR内核启动在RAM中的地址。
压缩的内核映像被解压到这个地址,然后执行。
This is the address where the decompressed kernel will be written, and eventually executed. The following constraint must be valid:__virt_to_phys(TEXTADDR) == ZRELADDRThe initial part of the kernel is carefully coded to be position independent.TEXTADDR内核启动的虚拟地址,与ZRELADDR相对应。
一般内核启动的虚拟地址为RAM的第一个bank地址加上0x8000。
=5内核启动流程之(init_post())[在rest_init()中被调用]
韦东山342页init进程是由内核启动的第一个(也是唯一一个)用户进程(进程号ID=1),它根据配置文件决定启动哪些程序,比如执行某些脚本、启动shell、运行用户指定的程序等。
Init进程是后续所有进程的发起者,也是后续进程的父进程。
比如在init进程启动/bin/sh程序后,才能够在控制台上输入各种命令。
init进程的执行程序通常是/sbin/init程序,上面讲述的init进程的作用只不过是/sbin/init 这个程序的功能。
我们完全可以编写自己的/sbin/init,或者传入命令行参数“init=xxxxx”指定某个程序作为init进程运行。
一般而言,在Linux系统有两种init程序:BSD init和System V init。
BSD和System V 是两种版本的UNIX系统。
这两种init程序各有优缺点,现在大多Linux的发行版本使用System V init。
但是在嵌入式领域,通常使用BusyBox集成的init程序,下面基于它进行讲解。
【busybox-1.7.0也是一套源码树,进入后可以执行make menuconfig调用Config.in文件进行配置,然后便已安装到指定的目录下[你做的根文件系统目录]。
】1 内核如何启动init进程[第771行---第774行来个四选一]内核启动的最后一步就是启动init进程,代码在[busybox-1.7.0/init/main.c]文件中,如下所示:顺便罗列一下内核启动流程:/arch/arm/boot/compressed/head.S:Start:Decompressed_kernel()//位于/arch/arm/boot/compressed/misc.c[解压缩内核]Call_kernel()Stext:/init/main.cStart_kernel()Setup_arch()…Rest_init()Init()Do_basic_setup()Prepare_namespace()看到了这里,我已激动得说不出话了,因为来到我与挂载根文件系统最重要的接口函数。
简析linux内核的内核执行流程
简析linux内核的执行流程----从bootsect.s到main.c(内核版本0.11)Linux启动的第一阶段(从开机到main.c)3个任务:A、启动BIOS,准备实模式下的中断向量表和中断服务程序。
B、从启动盘加载操作系统程序到内存。
C、为执行32的main函数做过渡准备。
内存变化如下:①、0xFE000到0xFFFFF是BIOS启动块,其中上电后第一条指令在0xFFFF0。
②、而后0x00000到0x003FF总共1KB存放中断向量表,而接下去的地址到0x004FF共256B存放BIOS数据,从0x0E05B开始的约8KB的内存中存放中断服务程序。
③、利用BIOS中断0x19h把硬盘的第一扇区bootsect.s的代码加载到内存中,即0x07c00处,后转到该处执行。
④、将bootsect.s的代码复制到0x90000处。
⑤、利用中断0x13h将setup.s程序加载到内存0x90200处。
⑥、再将剩余的约240个扇区的内容加载到0x10000~0x2EFFF处。
⑦、开始转到setup.s处执行,第一件事就利用BIOS提供的中断服务程序从设备上获取内核运行的所需系统数据并存在0x90000的地址处,这时将原来bootsect.s的代码覆盖得只剩2Byte的空间。
⑧、关中断并将系统代码复制到0x00000处,将原来放在这里的中断向量表与BIOS数据区覆盖掉,地址范围是0x00000~0x1EFFF。
同时制作两表与两寄存器。
⑨开地址线A20,寻址空间达到4GB,后对8259重新编程,改变中断号。
⑩、转到head.s(大小是25K+184B)执行,执行该程序完后是这样的:0x00000~0x04FFF:页目录与4个页表,每一项是4KB,共20KB;0x05000~0x05400:共1KB的空间是软盘缓冲区;0x05401~0x054b8:共184B没用;0x054b9~0x05cb8:共2KB的空间存中断描述符表;0x05cb9~0x064b8:共2KB的空间存全局描述符表;之后就是main函数的代码了!第二阶段、从main.c函数到系统准备完毕阶段。
linux内核启动流程总结#精选.
X86体系结构内核启动分析一、硬件检测当机器加电后它首先执行BIOS(基本输入输出系统)中的代码,BIOS首先执行加电自检程序(POST),当自检通过程便完成了硬件的启动。
当自检完成后BIOS按照系统COMS中设置的启动顺序搜寻有效的启动驱动器(这里我们以硬盘为例),并读入系统引导扇区,并将系统控制权交给引导程序。
二、加载和执行引导程序系统引导程序主要是把系统内核装载到内存,启动盘必须在第一个逻辑磁道上包含引导记录。
这512个字节的扇区又被称作是引导扇区,在系统完成加电自检后,BIOS从启动盘中将引导扇区读入到内存中。
一旦引导记录加载完毕,BIOS就交出系统的执行控制权,跳转到引导程序的头部执行。
有关linux pc的引导程序lilo和grub,lilo和grub可以引导多个系统,嵌入式系统上,最常见的bootloader是UBOOT,如果机器上要装多系统的话一般都会用到它们,这一引导程序也储存在引导扇区中或者存放在主引导记录中(MBR),lilo和grub都许允用户自己配置,它们在系统安装时建立了关于系统内核占用磁盘数据块的位置对照表。
比如,grub程序就非常强大。
Gurb运行后,将初始化设置内核运行所需的环境。
然后加载内核镜像。
grub磁盘引导全过程:stage1: grub读取磁盘第一个512字节(硬盘的0道0面1扇区,被称为MBR(主引导记录),也称为bootsect)。
MBR由一部分bootloader的引导代码、分区表和魔数三部分组成。
stage1_5: 识别各种不同的文件系统格式。
这使得grub识别到文件系统。
stage2: 加载系统引导菜单(/boot/grub/menu.lst或grub.lst 根据grub版本不同文件位置会有所不同),加载内核vmlinuz和RAM磁盘initrd。
有时候基本引导装载程序(stage1)不能识别stage2所在的文件系统分区,那么这时候就需要stage1.5来连接stage1和stage2了假设有如下grub配置代码root (hd0,0)//grub分区kernel /vmlinuz‐2.6.35.10‐74.fc14.i686 ro root=/dev/ram0 //linux分区initrd /initramfs‐2.6.35.10‐74.fc14.i686.img要搞清楚上面两个root的关系,root (hd0,0)中的root是grub命令,它用来指定boot所在的分区作为grub的根目录.而root=/dev/ram0是kernel的参数,它告诉操作系统内核加载完毕之后,真实的文件系统所在的设备.要注意grub的根目录和文件系统的根目录的区别。
kernel启动流程分析02-解压内核
decompress
调用gunzip执行解压
decompress
cache_clean_flush
unzip
RAM
zImage
0x50C00000
分为call_cache_fn __avmv4_mmu_cache_flush
Image
16KB
Page Directory (4096 Entries)
* This routine must preserve:
* r0, r4, r5, r6, r7
*/
cache_clean_flush:
* r5 = start of this image
* r2 = end of malloc space (and therefore this image)
* We basically want:
* r4 >= r2 -> OK
* r4 + image length <= r5 -> OK
*/
cmp
mov
r0, #0
@ must be zero
mov
r1, r7
@ restore architecture number 结构编号
mov
r2, r8
@ restore atags pointer 输入atags指针
mov
pc, r4
@ call kernel 调用内核
这个地方就是最终我们从zImage跳转到Image的伟大一跳了,跳之前准备好r0,r1,r2
cmp
r0, r5
bls
wont_overwrite
zImage 的起始地址大于 vmlinux 的目标起始地址加上 vmlinux 大小( 4M )的地址, 所以将 zImage 直接解压到 vmlinux 的目标地址
内核启动过程(根据自己心得所整理 最全)
内核启动过程总结之前配置编译过内核源代码,在交叉编译源代码后产生了三个文件(还有其他文件)分别是vmlinuz、vmlinux、vmlinux32,其中vmlinuz是可引导的、压缩了的内核,将该内核拷贝到系统文件/boot目录下,再配置下/boot/boot.cfg 文件,将启动时选择内核的信息和加载内核的地方写入就可以实现内核的移植。
其实移植过程和正常内核启动过程的原理是一样的。
系统加电启动后,MIPS处理器默认的程序入口时0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是0x1FC00000,即CPU从0x1FC00000开始取第一条指令,内核是系统引导程序把内核加载到内存中的,如果内核是经过压缩的,那么首先执行/arch/mips/boot/compressed的head.S 文件去建立堆栈并解压内核映像文件,然后去执行/arch/mips/kernel下的head.S,如果是没有压缩的内核则直接去执行该head.S。
linux内核启动的第一阶段就是从kernel文件夹下的head.S开始的,kernel_entry()函数就是内核启动的入口函数,这个函数是与体系结构相关的汇编语言编写的,它首先初始化内核堆栈段,来为创建系统的第一个进程0进程作准备,接着用一段循环将内核映像的未初始化数据段bss段清零,最后跳转到/init/main.c中的start_kernel()初始化硬件平台相关的代码。
kernel_entry() - arch/mips/kernel/head.STLB初始化,Cache初始化清除BSS段准备参数 argc/argp/envp设置栈jal start_kernel (init/main.c)怎么为第一个进程0进程作准备?第一个进程涉及到init_thread_union,这个结构体在include/linux/sched.h得到定义extern union thread_union init_thread_union;union thread_union{struct thread_info thread_info;unsigned long stack[THREAD_SIZE/sizeof(long)];};THREAD_SIZE是一个宏定义#define THREAD_SIZE (2*PAGE_SIZE) #define PAGE_SIZE (_AC(1,UL)<<PAGE_SHIFT) PAGE_SHIFT=13 算出PAGE_SIZE=2^12=4096;则THREAD_SIZE=8192内核把进程存放在任务队列的双向循环链表中,链表中的每一项都是类型为task_struct、称为进程描述符的结构,进程描述符中包含一个具体进程的所有信息,linux通过slab分配器分配task_struct结构,每个任务都有一个thread_info结构,它在内核栈的尾部分配,结构中的task域中存放的是指向该任务实际task_struct的指针,thread_info结构在文件<asm/thread_info.h>中定义。
【内核】linux内核启动流程详细分析
【内核】linux内核启动流程详细分析Linux内核启动流程 arch/arm/kernel/head-armv.S 该⽂件是内核最先执⾏的⼀个⽂件,包括内核⼊⼝ENTRY(stext)到start_kernel间的初始化代码, 主要作⽤是检查CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函数。
在执⾏前,处理器应满⾜以下状态:r0 - should be 0r1 - unique architecture numberMMU - offI-cache - on or offD-cache – off1/* 部分源代码分析 */2/* 内核⼊⼝点 */3 ENTRY(stext)4/* 程序状态,禁⽌FIQ、IRQ,设定SVC模式 */5 mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode6/* 置当前程序状态寄存器 */7 msr cpsr_c, r0 @ and all irqs disabled8/* 判断CPU类型,查找运⾏的CPU ID值与Linux编译⽀持的ID值是否⽀持 */9 bl __lookup_processor_type10/* 跳到__error */11 teq r10, #0 @ invalid processor?12 moveq r0, #'p' @ yes, error 'p'13 beq __error14/* 判断体系类型,查看R1寄存器的Architecture Type值是否⽀持 */15 bl __lookup_architecture_type16/* 不⽀持,跳到出错 */17 teq r7, #0 @ invalid architecture?18 moveq r0, #'a' @ yes, error 'a'19 beq __error20/* 创建核⼼页表 */21 bl __create_page_tables22 adr lr, __ret @ return address23 add pc, r10, #12 @ initialise processor24/* 跳转到start_kernel函数 */25 b start_kernel1. start_kernel()函数分析 下⾯对start_kernel()函数及其相关函数进⾏分析。
ARM-Linux内核移植之(一)——内核启动流程分析
ARM-Linux内核移植之(一)——内核启动流程分析Y-Kee转载请注明来自于衡阳师范学院08电2 Y-Kee /ayangke, QQ:843308498内核版本:2.6.22 为什么要采用这样一个较低的版本进行移植了,因为韦东山大牛说了,低版本的才能学到东西,越是高版本需要移植时做的工作量越少,学的东西越少。
内核启动分为三个阶段,第一是运行head.S文件和head-common.S,第三个阶段是允许第二是运行main.c文件对于ARM的处理器,内核第一个启动的文件是arc/arm/kernel下面的head.S文件。
当然arc/arm/boot/compress下面也有这个文件,这个文件和上面的文件略有不同,当要生成压缩的内核时zImage时,启动的是后者,后者与前者不同的时,它前面的代码是做自解压的,后面的代码都相同。
我们这里这分析arc/arm/kernel下面的head.S文件。
当head.S所作的工作完成后它会跳到init/目录下跌的main.c的start_kernel函数开始执行。
第一阶段:首先截取部分head.S文件,将后面重点要分析的代码高亮显示。
ENTRY(stext)msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode@ and irqs disabledmrc p15, 0, r9, c0, c0 @ get processor idbl __lookup_processor_type @ r5=procinfo r9=cpuidmovs r10, r5 @ invalid processor (r5=0)?beq __error_p @ yes, error 'p'bl __lookup_machine_type @ r5=machinfomovs r8, r5 @ invalid machine (r5=0)?beq __error_a @ yes, error 'a'bl __create_page_tables/** The following calls CPU specific code in a position independent* manner. See arch/arm/mm/proc-*.S for details. r10 = base of* xxx_proc_info structure selected by __lookup_machine_type* above. On return, the CPU will be ready for the MMU to be* turned on, and r0 will hold the CPU control register value.*/ldr r13, __switch_data @ address to jump to after@ mmu has been enabledadr lr, __enable_mmu @ return (PIC) address第一步,执行的是__lookup_processor_type,这个函数是检查处理器型号,它读取你的电路板的CPU型号与内核支持的处理器进行比较看是否能够处理。
Linux内核的启动过程
Linux内核的启动过程在 bootloader将 Linux 内核映像拷贝到 RAM 以后,可以通过下例代码启动 Linux 内核:call_linux(0, machine_type,kernel_params_base)。
其中,machine_tpye 是 bootloader检测出来的处理器类型,kernel_params_base 是启动参数在 RAM 的地址。
通过这种方式将Linux 启动需要的参数从 bootloader传递到内核。
Linux 内核有两种映像:一种是非压缩内核,叫 Image,另一种是它的压缩版本,叫zImage。
根据内核映像的不同,Linux 内核的启动在开始阶段也有所不同。
zImage 是 Image经过压缩形成的,所以它的大小比 Image 小。
但为了能使用 zImage,必须在它的开头加上解压缩的代码,将zImage 解压缩之后才能执行,因此它的执行速度比 Image 要慢。
但考虑到嵌入式系统的存储空容量一般比较小,采用 zImage 可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的。
所以一般的嵌入式系统均采用压缩内核的方式。
对于 ARM 系列处理器来说,zImage 的入口程序即为arch/arm/boot/compressed/head.S。
它依次完成以下工作:开启 MMU 和 Cache,调用 decompress_kernel()解压内核,最后通过调用call_kernel()进入非压缩内核 Image 的启动。
下面将具体分析在此之后 Linux 内核的启动过程。
(1)Linux内核入口Linux 非压缩内核的入口位于文件/arch/arm/kernel/head-armv.S 中的 stext 段。
该段的基地址就是压缩内核解压后的跳转地址。
如果系统中加载的内核是非压缩的Image,那么bootloader将内核从 Flash中拷贝到 RAM 后将直接跳到该地址处,从而启动 Linux 内核。
linux启动流程分析---内核解压缩过程
linux启动流程分析---内核解压缩过程内核压缩和解压缩代码都在目录kernel/arch/arm/boot/compressed,编译完成后将产生vmlinux、head.o、misc.o、head-xscale.o、piggy.o这几个文件,head.o是内核的头部文件,负责初始设置;misc.o将主要负责内核的解压工作,它在head.o之后;head-xscale.o文件主要针对Xscale的初始化,将在链接时与head.o合并;piggy.o是一个中间文件,其实是一个压缩的内核(kernel/vmlinux),只不过没有和初始化文件及解压文件链接而已;vmlinux是(没有--lw:zImage是压缩过的内核)压缩过的内核,就是由piggy.o、head.o、misc.o、head-xscale.o组成的。
在BootLoader完成系统的引导以后并将Linux内核调入内存之后,调用bootLinux(),这个函数将跳转到kernel的起始位置。
如果kernel没有压缩,就可以启动了。
如果kernel压缩过,则要进行解压,在压缩过的kernel头部有解压程序。
压缩过得kernel入口第一个文件源码位置在arch/arm/boot/compressed/head.S。
它将调用函数decompress_kernel(),这个函数在文件arch/arm/boot/compressed/misc.c中,decompress_kernel()又调用proc_decomp_setup(),arch_decomp_setup()进行设置,然后使用在打印出信息“Uncompressing Linux...”后,调用gunzip()。
将内核放于指定的位置。
以下分析head.S文件:(1)对于各种Arm CPU的DEBUG输出设定,通过定义宏来统一操作。
(2)设置kernel开始和结束地址,保存architecture ID。
Linux系统启动流程分析-电脑资料
Linux系统启动流程分析-电脑资料一系统上电和启动ROMNOR Flash作为启动ROM的系统启动过程NOR Flash开头处存放启动代码,程序从NOR Flash开始处启动,。
配置EMI寄存器,设置好各存储器的地址和存取规则。
配置电源管理模块,各模块上电。
启动代码将位于NOR Flash中的正式执行代码复制到内存中,以提高执行效率。
设置PC指针,指向NOR Flash中固定地址。
设置地址映射,用0地址映射到内存RAM空间。
设置PC指针,指向RAM中初始化代码,开始执行代码。
NAND Flash作为启动ROM的系统启动流程上电初始,DMA默认设置将存储在NAND Flash中第一页的数据搬运到内部RAM中,然后设置PC到内部RAM开始处的地址,开始执行代码,电脑资料《Linux系统启动流程分析》(https://www.)。
在启动代码中设置中断向量和硬件配置等。
将执行代码搬运到外部SDRAM或DDR=RAM,留出启动代码的位置。
将启动代码搬运到SDRAM或DDR-RAM中首址。
设置Remap,将0地址重新映射到SDRAM或DDR-RAM首地址。
设置PC指针,开始执行正式的执行代码。
二 Bootloader引导三 Linux内核引导非压缩内核:Image压缩内核:zImage内核初始化设备初始化启动内核挂载文件系统启动用户空间进程四 init初始化系统服务初始化log系统解析/init.rc和/init.%hardware%.rc文件,执行early-init,并执行解析出的init动作、early-boot动作、boot动作和execute property动作。
进行设备初始化,属性服务器初始化并开启属性服务。
进入无线循环以等待属性设置或子进程退出事件。
了解计算机操作系统的启动流程和功能
了解计算机操作系统的启动流程和功能计算机操作系统是一种管理和控制计算机硬件和软件资源的软件系统。
它负责启动计算机、分配资源、管理文件系统、提供安全保护等。
了解计算机操作系统的启动流程和功能,对我们理解计算机工作原理、诊断和解决问题都具有重要意义。
本文将详细介绍操作系统的启动流程和功能。
一、计算机操作系统的启动流程:1. 加电启动- 用户按下计算机的电源按钮,计算机开始供电。
- 供电后,计算机的BIOS(基本输入输出系统)开始工作。
- BIOS进行自检,检测内存、硬盘、显示器等硬件设备是否正常。
- 自检完成后,BIOS读取硬盘上的引导扇区信息。
2. 引导加载- 引导扇区包含引导程序,也称为引导加载器(Boot Loader)。
- 引导加载器的主要功能是加载操作系统内核到内存中,并将控制权转交给内核。
- 引导加载器通常会提供一个可选择的菜单,供用户选择不同的操作系统。
3. 内核启动- 引导加载器将控制权转交给操作系统内核。
- 内核启动后,会进行系统初始化,包括配置内存管理、设备驱动、创建进程等。
- 内核将初始化后的系统状态保存到内存中的数据结构中。
4. 用户空间载入- 内核加载完成后,会运行第一个用户程序,通常是init进程。
- init进程是用户空间的第一个进程,负责启动其他用户进程和系统服务。
- 用户空间程序包括各种应用程序、服务、Shell等。
二、计算机操作系统的功能:1. 进程管理- 进程管理是操作系统的核心功能之一,负责创建、调度和终止进程。
- 操作系统通过分时调度算法,使多个进程能够共享CPU时间片。
- 进程管理还包括进程间通信、进程同步等。
2. 内存管理- 内存管理是操作系统的另一个重要功能,负责管理计算机的内存资源。
- 内存管理包括内存分配、内存保护、内存回收等。
- 操作系统通过虚拟内存技术,将物理内存扩展到逻辑上更大的地址空间。
3. 文件系统- 文件系统是操作系统用于管理和组织文件的一种机制。
=5内核启动流程(函数列表)(未标记函数作用)
linux内核启动过程分析嵌入式linux系统从软件角度来看可分为四部分:bootloader,linux内核,文件系统和应用程序。
在这里我选取的内核版本是linux2.6.28,硬件平台选择smdk6410。
Bootloader是系统启动或复位后首先被执行的代码,它的主要作用是初始化处理器,初始化ram,初始化相应的外设(uart,usb等等),下载内核映像(或文件系统)到ram相应的位置,然后跳转到内核下载地址c0008000,将控制权交给linux内核。
Linux内核下载到ram 中的映像一般是zImage。
这是压缩版本的内核,首先要进行解压操作。
调用decompress_kernel()(位于arch/arm/boot/compressed/misc.c)进行解压缩操作,然后再次跳到c0008000,进行真正的内核初始化操作。
我们重点放在讲解内核映像解压之后linux内核的启动过程。
内核初始化启动过程如下:1) __lookup_processor_type() 查找处理器类型。
2) __lookup_machine_type() 查找机器类型。
3) __vet_atags()。
函数实现的就是判断r2是否是有效的tag列表指针,如果不是,就将零指针赋值给r2。
4) __create_page_tables() 创建页表。
5) __enable_mmu(),使能MMU。
6) __mmap_switched(),拷贝数据,清BBS。
7) start_kernel(),进入真正的内核初始化函数。
【开始进入】8) smp_setup_processor_id(); 设备处理器的ID号。
9) unwind_init();10)lockdep_init();11) debug_objects_early_init();12) cgroup_init_early();13) local_irq_disable(); 【关闭中断】14) early_boot_irqs_off();15) early_init_irq_lock_class();16) lock_kernel(); 【锁定内核】17) tick_init();18) boot_cpu_init(); 【CPU启动初始化】19) page_address_init(); 【页表地址初始化】20) setup_arch(&command_line);21) mm_init_owner(&init_mm, &init_task);22) setup_command_line(command_line);23) unwind_setup();24) setup_per_cpu_areas();25) setup_nr_cpu_ids();26) smp_prepare_boot_cpu();27) sched_init();28) preempt_disable();29) build_all_zonelists();30) page_alloc_init();31) parse_early_param();33) trap_init();34) rcu_init();35) init_IRQ(); 【中断初始化】36) pidhash_init();37) init_timers(); 【定时器初始化】38) hrtimers_init();39) softirq_init(); 【软中断初始化】40) timekeeping_init();41) time_init(); 【时间初始化】42) sched_clock_init();43) profile_init();44) early_boot_irqs_on();45) local_irq_enable(); 【本地中断使能】46) console_init(); 【控制台初始化】【是不是从此开始对外打印信息?】47) lockdep_info();48) locking_selftest();49) vmalloc_init();50) vfs_caches_init_early();51) cpuset_init_early();52) page_cgroup_init();53) mem_init(); 【内存初始化】54) enable_debug_pagealloc();55) cpu_hotplug_init();56) kmem_cache_init();57) debug_objects_mem_init();58) idr_init_cache();59) setup_per_cpu_pageset();60) numa_policy_init();61) if (late_time_init)62) late_time_init();63) calibrate_delay();64) pidmap_init();65) pgtable_cache_init();66) prio_tree_init();67) anon_vma_init();68) thread_info_cache_init();69) fork_init(num_physpages);70) proc_caches_init();71) buffer_init(); 【缓冲器初始化】72) key_init();73) security_init();74) vfs_caches_init(num_physpages); 【虚拟文件系统缓存初始化】76) signals_init();77) page_writeback_init();78) proc_root_init();79) cgroup_init();80) cpuset_init();81) taskstats_init_early();82) delayacct_init();83) check_bugs();84) acpi_early_init();85) ftrace_init();86) rest_init(); 【最后的初始化操作】87)cpu_idle(); 【函数会被调用来使系统处于闲置(idle)状态并等待用户程序的执行。
Linux内核启动过程和Bootloader(总述)
Linux内核启动过程和Bootloader(总述)1.Linux内核启动过程概述一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序。
其中 Bootloader是系统启动或复位以后执行的第一段代码,它主要用来初始化处理器及外设,然后调用 Linux 内核。
Linux 内核在完成系统的初始化之后需要挂载某个文件系统做为根文件系统(Root Filesystem)。
根文件系统是Linux 系统的核心组成部分,它可以做为Linux 系统中文件和数据的存储区域,通常它还包括系统配置文件和运行应用软件所需要的库。
应用程序可以说是嵌入式系统的“灵魂”,它所实现的功能通常就是设计该嵌入式系统所要达到的目标。
如果没有应用程序的支持,任何硬件上设计精良的嵌入式系统都没有实用意义。
2. Bootloader启动过程Bootloader在运行过程中虽然具有初始化系统和执行用户输入的命令等作用,但它最根本的功能就是为了启动 Linux 内核。
2.1 Bootloader的概念和作用Bootloader是嵌入式系统的引导加载程序,它是系统上电后运行的第一段程序,其作用类似于 PC 机上的 BIOS。
在完成对系统的初始化任务之后,它会将非易失性存储器(通常是Flash或DOC等)中的Linux 内核拷贝到 RAM 中去,然后跳转到内核的第一条指令处继续执行,从而启动 Linux 内核。
由此可见,Bootloader 和 Linux 内核有着密不可分的联系,要想清楚的了解 Linux内核的启动过程,我们必须先得认识 Bootloader的执行过程,这样才能对嵌入式系统的整个启动过程有清晰的掌握2.2 Bootloader的执行过程不同的处理器上电或复位后执行的第一条指令地址并不相同,对于 ARM 处理器来说,该地址为 0x00000000。
对于一般的嵌入式系统,通常把 Flash 等非易失性存储器映射到这个地址处,而 Bootloader就位于该存储器的最前端,所以系统上电或复位后执行的第一段程序便是Bootloader。
一文搞懂内核的启动
一文搞懂内核的启动vmlinux 属于ELF 文件,要想了解如何启动vmlinux,首先需要知道 ELF 的格式。
精灵•文本段代码段,通常是指用来存放程序执行代码的一块内存区域。
这部分区域的大小在程序运行前就已经确定。
•data段数据段,通常是指用来存放程序中已初始化的全局变量的一块内存区域。
数据段属于静态内存分配。
•bss段通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。
BSS段属于静态内存分配。
•初始化段linux定义的一种初始化过程中才会用到的段,一旦初始化完成,那么这些段所占用的内存会被释放掉,后续会继续说明。
vmlinux 入口:第一行运行的代码Linux启动,会启动内核编译后的文件 vmlinux,vmlinux 是一个ELF 文件,按照./arch/arm64/kernel/vmlinux.lds 设定的规则进行链接,vmlinux.lds 是vmlinux.lds.S 编译之后生成的。
所以为了确定vmlinux 内核的起始地址,首先通过vmlinux.lds.S 链接脚本进行分析。
如下所示:$ readelf -h vmlinuxELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: AArch64 Version: 0x1 Entry point address: 0xffff800010000000 Start of program headers: 64 (bytes into file) Start of section headers:494679672 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 5 Size of section headers: 64 (bytes) Number of section headers: 38 Section header string table index: 37$ readelf -l vmlinuxElf file type is DYN (Shared object file)Entry point 0xffff800010000000There are 5 program headers, starting at offset 64Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000010000 0xffff800010000000 0xffff800010000000 0x0000000001beacdc 0x0000000001beacdc RWE 10000 LOAD 0x0000000001c00000 0xffff800011c00000 0xffff800011c00000 0x00000000000c899c 0x00000000000c899c R E 10000 LOAD 0x0000000001cd0000 0xffff800011cd0000 0xffff800011cd0000 0x0000000000876200 0x0000000000905794 RW 10000 NOTE 0x0000000001bfaca0 0xffff800011beaca0 0xffff800011beaca0 0x000000000000003c 0x000000000000003c R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 Section to Segment mapping: Segment Sections...00 .head.text .text .got.plt .rodata .pci_fixup __ksymtab __ksymtab_gpl __ksymtab_strings __param __modver __ex_table .notes 01 .init.text .exit.text .altinstructions 02 .init.data .data..percpu .hyp.data..percpu .rela.dyn .data __bug_table .mmuoff.data.write .mmuoff.data.read .pecoff_edata _padding .bss 03 .notes 04通过上面的查询可知,此vmlinux 为一个aarch64 架构平台的ELF 可执行文件,其程序入口的地址为 0xffff800010000000,此段对应的section 为.head.text .text .got.plt......,所以vmlinux 的入口在.head.text文本段。
kernel启动流程分析01-准备解压
@ turn off interrupts to 读出cpsr寄存器的值放到r2中
orr
r2, r2, #0xc0
@ prevent angel from running 中断关闭位置位
msr
cpsr_c, r2
@ 把r2的值重新写回到cpsr寄存器中,关闭中断
#else
teqp
pc, #0x0c000003
mov
r0, #0x17
@ angel_SWIreason_EnterSVC 向SWI中传递参数
swi
0x123456
@ angel_SWI_ARM 让CPU从用户空间调到SVC空间,
@ 这个会从前面的0x0008处重新执行。
not_angel:
/* 表示非用户模式,可以直接关闭中断 */
mrs
r2, cpsr
/* 读入地址表。因为代码可以在任何地址执行,即位置无关代码(PIC),所以需要加上一个偏移量。*/ /* 链接的时候,这里可以插入一些体系结构相关的代码,但是应该保留r7, r8, r9 */
.text adr ldmia subs
beq
r0, LC0 r0, {r1, r2, r3, r4, r5, r6, ip, sp} r0, r0, r1
Processor Type Table Architecture Number Magic Number xxx_mmu_cache_on xxx_mmu_cache_off
Offset
0 4 8 12 16
ห้องสมุดไป่ตู้
64 KB Malloc
Flush I/D TLBs
I-Cache Enable/RR Replacement Enable
Linux内核分析:Linux内核启动流程分析
Linux内核分析:Linux内核启动流程分析(注:本⽂参考资料:朱有鹏嵌⼊式课程、。
本⽂为个⼈学习记录,如有错误,欢迎指正。
内核版本:九⿍公司移植的2.6.35.7)1. Linux内核⾃解压过程uboot完成系统引导以后,执⾏环境变量bootm中的命令;即,将Linux内核调⼊内存中并调⽤do_bootm函数启动内核,跳转⾄kernel的起始位置。
如果内核没有被压缩,则直接启动;如果内核被压缩过,则需要进⾏解压,被压缩过的kernel头部有解压程序。
压缩过的kernel⼊⼝第⼀个⽂件源码位置在/kernel/arch/arm/boot/compressed/head.S。
它将调⽤decompress_kernel()函数进⾏解压,解压完成后,打印出信息“Uncompressing Linux...done,booting the kernel”。
解压缩完成后,调⽤gunzip()函数(或unlz4()、或bunzip2()、或unlz())将内核放于指定位置,开始启动内核。
P.S.:。
2. Linux内核启动准备阶段由内核链接脚本/kernel/arch/arm/kernel/vmlinux.lds可知,内核⼊⼝函数为stext(/kernel/arch/arm/kernel/head.S)。
内核解压完成后,解压缩代码调⽤stext函数启动内核。
P.S.:内核链接脚本vmlinux.lds在内核配置过程中产⽣,由/kernel/arch/arm/kernel/vmlinux.lds.S⽂件⽣成。
原因是,内核链接脚本为适应不同平台,有条件编译的需求,故由⼀个汇编⽂件来完成链接脚本的制作。
ENTRY(stext)setmodePSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode@ and irqs disabledmrcp15, 0, r9, c0, c0 @ 获得处理器ID,并存储在r9寄存器中bl__lookup_processor_type @ 结果返回:描述处理器结构体的地址 r5=procinfo ,处理器ID号 r9=cpuidmovsr10, r5 @ invalid processor (r5=0)?判断内核是否⽀持该处理器beq__error_p @ yes, error 'p'bl__lookup_machine_type @结果返回:描述机器(开发板)的结构体地址 r5=machinfomovsr8, r5 @ invalid machine (r5=0)?判断内核是否⽀持该机器(开发板)beq__error_a @ yes, error 'a'bl__vet_atags @检查uboot给内核的传参ATAGS格式是否正确bl__create_page_tables @建⽴虚拟地址映射页表ldrr13, __switch_data @ address to jump to after(1)关闭IRQ、FIQ中断,进⼊SVC模式。
ARM Linux内核启动流程
ARM Linux 内核启动流程操作ARM Linux 内核主要分为哪几个步骤,下面杭州硕数就为大家讲解一下这个启动的流程。
ARM Linux 内核综述启动分两步,首先是硬件相关部分,入口是arch/arm/kernel/head.S 代码文件中的ENTRY(stext)函数;然后是硬件无关部分,入口函数是init/main.c 代码文件中的start_kernel 函数。
ARM Linux 内核硬件相关部分 1. 我们平台是arm,自然硬件相关部分代码在arch/arm/里面。
2. 平台相关部分的核心代码自然在arch/arm/kernel/里面。
3. 找入口函数,必须从对应目录的Makefile 和链接脚本*.lds 入手!# ls arch/arm/kernel/得到一个Makefile 文件和一个vmlinux.lds 文件# vim arch/arm/kernel/Makefile看到有一句extra-y := $(head-y) init_task.o vmlinux.lds,则说明其使用链接脚本为vmlinux.lds还有一句head-y := head$(MMUEXT).o# vim arch/arm/kernel/vmlinux.lds看到有一句ENTRY(stext),则说明入口函数为stext4. 结合上面3 句关键的代码,可以推测stext 函数在arch/arm/kernel/head*.S 或arch/arm/kernel/head*.c# ls arch/arm/kernel/head*得到arch/arm/kernel/head-common.S arch/arm/kernel/head-nommu.S arch/arm/kernel/head.o arch/arm/kernel/head.S再结合Makefile 的默认规则%.o : %.S 或%.o : %.c可以知道arch/arm/kernel/head.o 与arch/arm/kernel/head.S 对应。
内核启动过程分析(自解压后到start_kernel)
内核启动过程分析在学习过程中“行走的流云”的博文给了我很大的帮助,我也复制了很多他的总结,在这里感谢他!本文件的代码主要是讲内核开始工作前的准备工作,其工作主要有一下几点:1.进入管理模式,判断处理器ID是否匹配,判断机器ID是否匹配,判断atags指针是否合法。
2.建立页表,实现虚拟地址和物理地址的映射。
3.关闭Icache、Dcache、清空write buffer、使TLB无效4.使能MMU、使能cache跳转到start kernel开始真正的内核运行。
对于经过压缩的内核(zIm age),先运行解压缩decom press_kernel,然后还需要重定位,然后调用内核,就象跳到未压缩的内核中的开始处,内核的s tartup在arch/arm/kernel/head.S中,进行页表初始化和处理器缓存初始化等工作,然后跳到C代码init/main.c中的s tart_kernel,接下来的事情就是大众化工作了,在这里,我们继续分析解压缩后的工作,即是arch/arm/kernel/head.S的工作内容。
首先先说下内核的启动条件:1. CPU必须处于SVC(s upervis or)模式,并且IRQ和FIQ中断都是禁止的;2. MMU(内存管理单元)必须是关闭的, 此时虚拟地址对物理地址;3. 数据cache(Data cache)必须是关闭的4. 指令cache(Ins truction cache)可以是打开的,也可以是关闭的,这个没有强制要求;5. CPU 通用寄存器0 (r0)必须是0;6. CPU 通用寄存器1 (r1)必须是ARM Linux m achine type7. CPU 通用寄存器2 (r2) 必须是kernel param eter lis t 的物理地址/** linux/arch/arm/kernel/head.S** Copyright (C) 1994-2002 Russell King* Copyright (c) 2003 ARM Limited* All Rights Reserved** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.** Kernel startup code for all 32-bit CPUs*/#include <linux/linkage.h>#include <linux/init.h>#include <asm/assembler.h>#include <asm/domain.h>#include <asm/ptrace.h>#include <asm/asm-offsets.h>#include <asm/memory.h>#include <asm/thread_info.h>#include <asm/system.h>#if (PHYS_OFFSET & 0x001fffff)#error "PHYS_OFFSET must be at an even 2MiB boundary!"//boundary(分界线)#endif#define KERNEL_RAM_V ADDR (PAGE_OFFSET + TEXT_OFFSET)#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)/** swapper_pg_dir is the virtual address of the initial page* We place the page tables 16K below KERNEL_RAM_V ADDR. Therefore, we must* make sure that KERNEL_RAM_V ADDR is correctly set. Currently, we expect* the least significant 16 bits to be 0x8000, but we could probably* relax this restriction to KERNEL_RAM_V ADDR >= PAGE_OFFSET + 0x4000.*/swapper_pg_dir 是初始页表的虚拟地址.我们将页表放在KERNEL_RAM_VADDR以下16K的空间中.因此我们必须保证KERNEL_RAM_VADDR已经被正常设置.当前,我们期望的是这个地址的最后16 bits为0x8000,但我们或许可以放宽这项限制到KERNEL_RAM_VADDR >=PAGE_OFFSET + 0x4000.#if (KERNEL_RAM_V ADDR & 0xffff) != 0x8000#error KERNEL_RAM_V ADDR must start at 0xXXXX8000 //内核的起始地址必须是32k对齐#endif.globl swapper_pg_dir //定义一个全局变量.equ swapper_pg_dir, KERNEL_RAM_V ADDR - 0x4000/*页表的起始地址,大小为16kb*/.macro pgtbl, rd /*定义一个宏,在建页表时用到*/ldr \rd, =(KERNEL_RAM_PADDR - 0x4000).endm#ifdef CONFIG_XIP_KERNEL#define KERNEL_ST ART XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)#define KERNEL_END _edata_loc#else#define KERNEL_ST ART KERNEL_RAM_V ADDR#define KERNEL_END _end /*内核镜像的结束地址(虚拟地址,在vmlinux.lds.S 中定义)*/#endif/** Kernel startup entry point.* ---------------------------*这些参数同通常是由解压代码传进来的* This is normally called from the decompressor code. The requirements* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,* r1 = machine nr, r2 = atags pointer.** This code is mostly position independent, so if you link the kernel at* 0xc0008000, you call this at __pa(0xc0008000).** See linux/arch/arm/tools/mach-types for the complete list of machine* numbers for r1. r1中放的是机器ID** We're trying to keep crap to a minimum; DO NOT add any machine specific* crap here - that's what the boot loader (or in extreme极度的, well justified合理的* circumstances事件,详细事件,zImage) is for.不要随意的添加机器ID,要和bootloard 提供的相匹配。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
linux内核的启动过程:
在bootloader将linux内核映像拷贝到RAM以后,启动linux内核:
call_linux(0,machine_type, kernel_params_base)。
其中,machine_tpye是bootloader检测出来的处理器类型,kernel_params_base
是启动参数在RAM的地址。
通过这种方式将Linux启动需要的参数从bootloader传递到内核。
linux内核有两种映像:一种是非压缩内核,叫Image,另一种是它的压缩版本,叫zImage。
根据内核映像的不同,linux内核的启动在开始阶段也有所不同。
zImage是Image经过压缩形成的,所以它的大小比Image小。
但为了能使用zImage,必须在它的开头加上解压缩的代码,将zImage解压缩之后才能执行,
因此它的执行速度比Image要慢。
但考虑到嵌入式系统的存储空容量一般比较小,采用zImage可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的。
所以一般的嵌入式系统均采用压缩内核的方式。
对于ARM系列处理器来说,zImage的入口程序即为
arch/arm/boot/compressed/head.s。
它依次完成一下工作:开启MMU和cache,调用decompress_kernel()解压内核,最后通过调用call_kernel()进入非压缩内核Image的启动。
下面将具体分析在此之后Linux内核的启动过程。
2:linux内核入口
Linux非压缩内核的入口位于文件/arch/arm/kernel/head-armv.S中的stext段。
该段的基地址就是压缩内核解压后的跳转地址。
如果系统中加载的内核是非压缩的Image,那么bootloader将内核从Flash中拷贝到RAM后将直接跳到该地址处,从而启动Linux内核。
不同体系结构的Linux系统的入口文件是不同的,而且因为该文件与具体体系结构有关,所以一般均用汇编语言编写[3]。
对基于ARM处理的Linux系统来说,该文件就是head-armv.S。
该程序通过查找处理器内核类
型和处理器类型调用相应的初始化函数,再建立页表,最后跳转到start_kernel()函数开始内核的初始化工作。
检测处理器内核类型是在汇编子函数__lookup_processor_type中完成的。
通过以下代码可实现对它的调用:bl__lookup_processor_type。
__lookup_processor_type
调用结束返回原程序时,会将返回结果保存到寄存器中。
其中r8保存了页表的
标志位,r9保存了处理器的ID号,r10保存了与处理器相关的struproc_info_list 结构地址。
检测处理器类型是在汇编子函数__lookup_architecture_type中完成的。
与
__lookup_processor_type类似,它通过代码:“bl__lookup_processor_type”来实现
对它的调用。
该函数返回时,会将返回结构保存在r5、r6和r7三个寄存器中。
其中r5保存了RAM的起始基地址,r6保存了I/O基地址,r7保存了I/O的页表偏移地址。
当检测处理器内核和处理器类型结束后,将调用__create_page_tables 子函数来建立页表,它所要做的工作就是将RAM基地址开始的4M空间的物理地址映射到0xC0000000开始的虚拟地址处。
对笔者的S3C2410开发板而言,RAM连接到物理地址0x30000000处,当调用__create_page_tables结束后
0x30000000~0x30400000物理地址将映射到0xC0000000~0xC0400000虚拟地址处。
当所有的初始化结束之后,使用如下代码来跳到C程序的入口函数start_kernel()处,开始之后的内核初始化工作:
bSYMBOL_NAME(start_kernel)
3:start_kernel函数
start_kernel是所有的linux平台进入系统内核初始化的入口函数,它主要完成剩余的与硬件平台相关的初始化工作,在进行一系列与内核相关的初始化之后,调用第一个用户进程—init进程并等待用户进程的执行,这样整个linux内核便启动完毕该函数所做的具体工作有[4][5]
:
1)调用setup_arch()函数进行与体系结构相关的第一个初始化工作;
对不同的体系结构来说该函数有不同的定义。
对于ARM平台而言,该函数定义在arch/arm/kernel/Setup.c。
它首先通过检测出来的处理器类型进行处理器内核的初始化,然后通过bootmem_init()函数根据系统定义的meminfo结构进行内存结构的初始化,最后调用paging_init()开启MMU,创建内核页表,映射所有的物理内存和IO空间。
2)创建异常向量表和初始化中断处理函数;
3)初始化系统核心进程调度器和时钟中断处理机制;
4)初始化串口控制台(serial-console);
ARM-Linux在初始化过程中一般都会初始化一个串口做为内核的控制台,这样内核在启动过程中就可以通过串口输出信息以便开发者或用户了解系统的启动进程。
5)创建和初始化系统cache,为各种内存调用机制提供缓存,包括;动态内存分配,虚拟文件系统(VirtualFileSystem)及页缓存。
6)初始化内存管理,检测内存大小及被内核占用的内存情况;
7)初始化系统的进程间通信机制(IPC);
当以上所有的初始化工作结束后,start_kernel()函数会调用rest_init()函数来进行最后的初始化,包括创建系统的第一个进程-init进程来结束内核的启动。
Init
进程首先进行一系列的硬件初始化,然后通过命令行传递过来的参数挂载根文件系统。
最后init进程会执行用户传递过来的“init=”启动参数执行用户指定的命令,或者执行以下几个进程之一:
execve("/sbin/init",argv_init,envp_init);
execve("/etc/init",argv_init,envp_init);
execve("/bin/init",argv_init,envp_init);
execve("/bin/sh",argv_init,envp_init)。
当所有的初始化工作结束后,cpu_idle()函数会被调用来使系统处于闲置(idle)状态并等待用户程序的执行。
至此,整个Linux内核启动完毕。