嵌入式linux的开机顺序
上图是一个嵌入式Linux系统的典型结构,划分成了4个区:
uboot区域存储的是uboot类似于windows的BIOS,做的东西就是 嵌入式系统最基础的硬件初始化,驱动,内核加载,参数传递.
参数传递就是uboot将内核启动的基本参数传递给内核,最常见的就是bootargs ,console=ttyS0 ,就是传递的内核的终端是从串口0输出的,这个参数会在printk.c里面获取,这个比较简单,需要自己去跟一下代码,之后会具体讲解一下bootargs传递参数代表的意思是什么。
内核镜像区域存放的是内核的镜像的压缩,当他被解压后放到内存里面,作为嵌入式设备的系统。
文件系统区域存放的是经过压缩的文件系统,他会被linux内核解压后并且挂载,并且做为各种应用程序,文件的载体。
uboot的启动流程
uboot的stage1完成的主要任务
基本的硬件初始化,这是uboot一开始就应该执行的操作,主要谜底是为了stage2的执行以及随后的kernel的执行准备给好一些基本的硬件唤醒,它通常包括以下步骤:
禁用中断(因为中断服务通常是设备驱动程序的责任)
设置时钟,CPU的速度
RAM初始化
关闭CPU的Cache(因为通常情况下CPU对数据的读取和写入都是通过CR15在通过CACHE对数据读取,但是这个是后DDR或者外部SRAM并没有初始化,所以这个时候去cache里面读出来的数据就错误的,并不是你想要的。)
复制stage2到RAM中
复制的时候需要确定两点:stage2的可执行镜像在固态存储设备的存放起始和结束地址,以及RAM空间的起始地址
设置堆栈指针SP,堆栈指针的设置是为了执行C语言代码做好准备
跳转到stage2的C入口地址。一般都是bl ...
uboot的stage2完成的主要任务
stage2的基本上都是C语言实现的,原因是C语言更加的好阅读,更容易实现复杂的功能,但是并没有是用glibc库中的任何函数,哪些比较常见的字符串操作和内存操作函数都是需要自己实现的,但是uboot都做好了。
初始化现在需要使用的硬件设备,比如串口,usb,网络,主要是用来下载和打印log的,比如输出一些版本号,和调试信息。
加载内核镜像,从mmc或者flash中读取镜像到RAM中。
设置内核的启动参数,具体之后单独写一下。
调用内核,一般都是直接跳转到内核的第一条指令处,在跳转的时候,一定要满足以下几个条件:
CPU寄存器的设置:
R0 = 0
R1=机器类型ID;我们直接看linux/arch/arm/tools/mach-types就可以了
R2=启动参数标记列表在RAM中起始及地址;
CPU模式设置:
必须禁用中断
必须处于SVC模式
Cache和mmu的设置:
MMU必须关闭
CACHE也要关闭
关闭为啥大家看一下詹荣开写的《嵌入式系统Bootloader技术内幕》这本书。
linux内核初始化
内核自解压
设置工作模式,以及使能MMU,设置一级页表啥的
内核自检,一般来说找到你的体系架构下的arch/arm(或者你的体系架构)/kernel 里面有一个head-xxx.S文件中
主要作用是检查CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函数,在执行前,处理器应满足以下状态:
r0 - should be 0
r1 - unique architecture number
MMU - off
I-cache - on or off
D-cache – off
运行C代码,内核启动的全部工作。
Linux内核启动的第二阶段从start_kernel函数开始。start_kernel是所有Linux平台进入系统内核初始化后的入口函数,它主要完成剩余的与 硬件平台相关的初始化工作,在进行一系列与内核相关的初始化后,调用第一个用户进程- init 进程并等待用户进程的执行,这样整个 Linux内核便启动完毕。该函数位于init/main.c文件中,主要工作流程如图所示:
该函数所做的具体工作包括以下几项:
调用setup_arch()函数进行与体系结构相关的第一个初始化工作,具体函数在/arch/xxx/kernel/setup-xxx.c里面,做的工作基本上都是一个套路,根据系统定义的meminfo结构进行内存结构的初始化,最后调用 paging_init()开启MMU,创建内核页表,映射所有的物理内存和IO空间。
创建异常向量表和初始化中断处理函数
初始化系统核心进程调度器和时钟中断处理机制;
初始化串口控制台(console_init),但是不是说在上面这些操作中就不能够打印log了,linux内核的打印模块实际上也是很有意思的,他有一个缓存buf,会将这些log信息放到这里面然后等到串口初始化后从这个buf中取出来打印。
创建和初始化系统cache,为各种内存调用机制提供缓存,包括;动态内存分配,虚拟文件系统(VirtualFile System)及页缓存
初始化内存管理,检测内存大小及被内核占用的内存情况;初始化系统的进程间通信机制(IPC);当以上所有的初始化工作结束后,start_kernel()函数会调用rest_init()函数来进行最后的初始化,包括创建系统的第一个进程-init进程来结束内核的启动。
挂载根文件系统并启动init
Linux内核启动的下一过程是启动第一个进程init,但必须以根文件系统为载体,所以在启动init之前,还要挂载根文件系统;
文件系统引导
实际上就是kernel将一个存储介质,U盘或者flash等等,挂载为根文件系统,就像我们mount一个设备为一个文件夹一样。
那kernel在执行init之后做的是什么,如图