随着智能终端功能的越来越庞大,与之,硬件配置越来越高,开机时间却越来越长。人们在享受强大功能的同时,对冗长的智能终端的开机时间却越来越缺乏耐心。
为了“取悦”用户,需要提供较好的用户体验,那么就必须使得智能终端尽可能短的进入“可工作”状态。
本文中的“尽可能短的开机时间”,“开机优化”、“开机时间优化”均表示同一个意思。
如果计划对linux系统进行开机时间优化,那么就必须熟悉linux启动流程,本小节主要从框架上对Linux启动做简单的介绍,并不会详细的描述。
如下图:
需要明确的是preloader、lk(Little Kernel)、kernel、android这些系统镜像文件是存储在nand flash中的,然后每个芯片都有个boot rom,在上电时刻,boot rom开始启动,boot rom加载preloader到内部SRAM中,preloader被加载完成之后,程序就从boot
rom跳转到preloader处开始执行,preloader初始化好外部RAM之后,preloader将lk(或uboot)加载外部RAM中,然后跳转到lk(或uboot)中去执行,lk(或uboot)紧接着就加载bootimage(包括kernel和ramdisk)到外部RAM中,然后去执行kernel部分。
1.1BootRom
ROM code是芯片厂商直接烧写到芯片中。
(1) 设备上电后,Boot ROM开始运行。
(2) BootROM初始化软件堆栈(software stack)、通信端口和可引导存储设备(比如NAND/EMMC)。
(3) BootROM通过UART/USB和flash工具握手。
(4) BootROM通过UART下载pre-loader镜像到NANDflash/EMMC中,然后重启。
(5) BootROM加载pre-loader到内部SRAM汇总,因为DRAM还没有初始化。
(6) BootROM跳转到pre-loader并执行。
1.2 BootLoader
BootLoader程序也有多种类型,不同的BootLoader程序,其引脚参数不同。
(1) SD方式
(2) NorFlash Rom方式
(3) NandFlash Rom + chip方式
一般情况下,发布的正式版本,引脚参数都是固定的,是不可改变的。
1.2.1 preLoader
preloader也就是BootLoader的第一部分。
pre-loader是内置的loader,它的主要功能如下:
(1) 负责在芯片组平台(chipset platform)上准备好可执行的环境
(2) 如果外部工具有效,它会试图通过UART或是USB来和外部工具握手。(比如USB升级)
(3) 从NAND/EMMC加载U-Boot,并跳转到U-Boot。
(4) 使用工具握手,设备能够触发进入下载模式来下载需要的镜像,或是进入工厂/测试模式,比如META模式和ATE工厂模式,在这些模式下可以测试模块,或是通过传递引导参数给U-Boot和linux内核来校准设备(devicecalibration)
1.2.2 U-Boot
U-Boot也就是BootLoader的第二部分。
1) 初始化本阶段使用的硬件设备
2) 检测系统内存映射
3) 将内核从Flash读取到RAM中
4) 为内核设置启动参数
U-Boot在执行过程中必须设置和初始化 Linux 的内核启动参数。Linux内核在启动过程中会根据该启动参数进行相应的初始化工作。
5) 调用内核映像
U-Boot完成的最后一项工作便是调用 Linux内核。如果 Linux 内核存放在 Flash 中,并且可直接在上面运行(这里的 Flash指 Nor Flash),那么可直接跳转到内核中去执行。但由于在 Flash 中执行代码会有种种限制,而且速度也远不及
RAM 快,所以一般的嵌入式系统都是将 Linux内核拷贝到 RAM 中,然后跳转到 RAM 中去执行。不论哪种情况,在跳到 Linux 内核执行之前 CUP的寄存器必须满足以下条件:r0=0,r1=处理器类型,r2=标记列表在 RAM中的地址。
智能终端在该阶段可能做了更多动作。
1.3 kernel
start_kernel是所有 Linux 平台进入系统内核初始化后的入口函数,它主要完成剩余的与硬件平台相关的初始化工作,在进行一系列与内核相关的初始化后,调用第一个用户进程-init
进程并等待用户进程的执行,这样整个 Linux 内核便启动完毕。该函数所做的具体工作有:
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,为各种内存调用机制提供缓存,包括;动态内存分配,虚拟文件系统(VirtualFile System)及页缓存。
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 内核启动完毕。
1.4 Init
init命令是Linux下的进程初始化工具,init进程是所有Linux进程的父进程,它的进程号为1。init命令是Linux操作系统中不可缺少的程序之一,init进程是Linux内核引导运行的,是系统中的第一个进程。
1.4.1 /etc/inittab
作为系统的第一个执行的程序“/sbin/init”。init程序做的工作相当多,除了利用设置文件“/etc/inittab”来获取运行等级之外,还会通过运行等级的设置值启动不同的服务项目。运行等级是指linux通过设置不同等级来规定系统用不同的服务来启动,让linux的使用环境不同。
"/etc/inittab"中有这么一句" si::sysinit:/etc/init.d/rcS",表明系统需要主动使用"rcS"这个shell脚本来设置系统环境。但这个文件的文件名在各个版本中基本上都是有区别的。
# /etc/inittab: init(8)configuration.
# $Id: inittab,v 1.91 2002/01/2513:35:21 miquels Exp $ # The default runlevel.
id:3:initdefault: 1:012345:respawn:/sbin/getty -n -LttyMT0 115200 vt100 -l /bin/sh # Boot-time systemconfiguration/initialization script.
# This is run first except whenbooting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS
1.4.2 rcS
1. source/etc/profile
2. 挂载 fstab 基本的文件系统 (/, proc, sys, devpts, tmpfs…)
3. 然后转 rc.local
#! /bin/sh
#设置环境变量
source /etc/profile # Don't use scripts. Just use mount-a for fast boot.
/bin/mount-avt nonfs,nosmbfs #if [ -e /etc/readahead_early ];then
# echo Start readahead /etc/readahead_early
# busybox readahead `cat /etc/readahead_early` &
#fi # Run /etc/rc.d/rc.local if it exists
[ -x /etc/rc.d/rc.local ] &&/etc/rc.d/rc.local
1.4.3 用户自定义引导程序(/etc/rc.d/rc.local)
一般来说,自定义的程序不需要执行上面所说的繁琐的建立shell增加链接文件的步骤,只需要将命令放在rc.local里面就可以了,这个shell脚本就是保留给用户自定义启动内容的。
1. 设置终端参数 stty
2. 设置基本网络相关参数 (lo, hosts, resolv.conf, …)
......