处理器是一种统称,内部一般包含CPU、片上内存、片上外设接口等不同的硬件逻辑。
Linux启动过程关乎处理器配置、内存配置、外围硬件配置、而不同的处理器和硬件系统会采用不同的策略,从而具体的启动过程会有所差异。但无论差异如何,从计算机系统的角度来看,启动过程一般分为三个步骤。开机并执行Bootloader程序,操作系统内核初始化,执行第一个应用程序。
第一步是开机,开机就是给系统供电,此时硬件电路会产生一个确定的复位时序,保证CPU是最后一个被复位的器件。为什么CPU要最后被复位呢?因为,如果CPU第一个被复位,则当CPU复位后开始运行时,其他硬件内部的寄存器状态可能还没有准备好,比如磁盘或者内存,那么就可能出现外围硬件初始化错误。
当正确完成复位后,CPU开始执行第一条指令,该指令所在的内存地址是固定的,这由CPU的制造者制定。不同的CPU可能会从不同的地址获取指令,但这个地址必须是固定的,这个固定地址所保存的程序往往被称为“引导程序”(Bootloader),因为其作用是装载真正的用户程序。
至于如何装载,则是一个策略问题,不同的CPU会提供不同的装载方式,比如有的是通过普通的并口存储器,有的则是通过SD卡,还有的是通过RS232接口。无论硬件上使用何种接口装载,装载过程必须提供:从哪里读取用户程序?用户程序的长度是多少?装载完用户程序后应该跳转到哪里,即用户程序的执行入口在哪里?对于ARM处理器,当复位完毕后,处理器首先执行其片上ROM中的一小块程序。这块ROM的大小一般只有几KB,这段程序就是Bootloader程序,这段程序执行时会根据处理器上一些特定引脚的高低电平状态,选择从何种物理接口上装载用户程序,比如USB口,串口,SD卡,并口Flash等。多数基于ARM的实际硬件系统,会从并口NAND Flash芯片中的0x00000000地址出装载程序。对于一些小型嵌入式系统而言,该地址中的程序就是最终要执行的用户程序,而对于Android而言,该地址中的程序还不是Android程序,而是一个叫做uboot或者fastboot的程序,其作用是初始化硬件设备,比如网口,SDRAM,RS232等,并提供一些调试功能,比如想NAND Flash 中写入新的数据,这可用于开发过程中的内核烧写,升级等。当uboot(fastboot)被装载后便开始运行,它一般会先检测用户是否按下了某些特别按键,这些特别按键是uboot在编译时预先约定好的,用于进入调试模式。如果用户没有按下这些特别的按键,则uboot会从nand flash中装载linux内核,装载的地址是在编译uboot时预先约定好的。
第二步是执行内核程序,这里所说的内核程序在上一步中指的是“用户程序”。因为从CPU的角度来看,除Bootloader外的所有程序都是用户程序,只是从软件的角度来看,用户程序被分为内核程序和应用程序。Linux内核被装载后,就开始进行内核初始化的过程。内核程序初始化时执行的操作包括,初始化各种硬件,包括内存、网络接口、显示器、输入设备,然后建立各种内部数据结构,这些数据结构将用于多线程调度及内存的管理等。Linux内核的启动步骤:
函数名称 |
所在内核源码路径 |
描述 |
start() |
./arch/XXX/boot/head.S |
汇编语言,进行一些cpu寄存器的配置,并调用startup_32()函数 |
startup_32() |
./arch/xxx/boot/compress/head.S |
汇编语言,配置堆栈,并对BSS段进行清空 |
decompress_kernel() |
./arch/XXX/boot/compress/misc.c |
C语言函数,用于解压内核,Linux Kernel的映像文件是一个zlib压缩格式的数据,因此需要先解压 |
startup_32() |
./arch/XXX/kernel/head.S |
进行CPU的页表配置,主要用于虚拟内存,并检测该CPU是否有浮点处理单元(FPU)支持,此时该进程为系统进程,即0号进程 |
start_kernel() |
./init/main.c |
内核内部数据初始化,配置中断向量表,并挂载ramdisk,最后调用kernel_thread()方法 |
kernel_thread() |
./arch/XXX/kernel/process.c |
该方法会根据ramdisk中的一个叫init.rc的文本文件的配置内容,启动不同的应用程序,第一个启动的程序就是地一个用户级的程序 |
cpu_idle() |
./init/main.c |
此时,内核已经可以按照进程的优先级进行调度了,当没有其他进程运行时,就调用该方法 |
init.rc的内容格式类似于一种脚本,但是它却不是标准的Linux脚本,而是仅用于启动的脚本。当内核程序初始化完毕后,就开始运行具体的应用程序了。在一般情况下,习惯于将第一个应用程序称为“Home程序’。
第三步就是运行Home程序,比如Windows系统的桌面就是一个典型的Home程序。之所以称为HOME程序,是因为通过该程序可以方便地启动其他应用程序。而传统的Linux系统启动后,第一个运行程序一般是一个Terminal,尽管它表面上就像一个Dos界面,但它也可以被称为Home程序,因为Home程序设计的目标是提供一个入口,用户可以通过该入口启动其他应用程序。