内核启动流程分析
精简讲述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()中被调用]
![=5内核启动流程之(init_post())[在rest_init()中被调用]](https://img.taocdn.com/s3/m/7ba8c1d1d15abe23482f4d34.png)
韦东山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()函数及其相关函数进⾏分析。
- 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内核启动完毕。