Linux内核和根文件系统引导加载程序

续博文《u-boot之u-boot-2009.11启动过程分析

Linux内核启动及文件系统载入过程

当u-boot開始运行bootcmd命令。就进入Linux内核启动阶段,与u-boot类似,普通Linux内核的启动过程也能够分为两个阶段,但针对压缩了的内核如uImage就要包含内核自解压过程了。本文以linux-2.6.37版源代码为例分三个阶段来描写叙述内核启动全过程。

第一阶段为内核自解压过程。第二阶段主要工作是设置ARM处理器工作模式、使能MMU、设置一级页表等。而第三阶段则主要为C代码,包含内核初始化的所有工作,以下是具体介绍。

/******************************************************************************************************************************************/

原创作品,转载时请务必以超链接形式标明文章原始出处:http://blog.csdn.net/gqb_driver/article/details/26954425,作者:gqb666

/******************************************************************************************************************************************/

一、Linux内核自解压过程

在linux内核启动过程中一般能看到图1内核自解压界面,本小节本文重点讨论内核的自解压过程。

图1 解压内核

内核压缩和解压缩代码都在文件夹kernel/arch/arm/boot/compressed。编译完毕后将产生head.o、misc.o、piggy.gzip.o、vmlinux、decompress.o这几个文件。head.o是内核的头部文件,负责初始设置。misc.o将主要负责内核的解压工作,它在head.o之后;piggy.gzip.o是一个中间文件。事实上是一个压缩的内核(kernel/vmlinux),仅仅只是没有和初始化文件及解压文件链接而已;vmlinux是没有(zImage是压缩过的内核)压缩过的内核,就是由piggy.gzip.o、head.o、misc.o组成的,而decompress.o是为支持很多其它的压缩格式而新引入的。

在BootLoader完毕系统的引导以后并将Linux内核调入内存之后。调用do_bootm_linux()。这个函数将跳转到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()将内核放于指定的位置。

以下简介一下解压缩过程,也就是函数decompress_kernel实现的功能:解压缩代码位于kernel/lib/inflate.c,inflate.c是从gzip源程序中分离出来的。包括了一些对全局数据的直接引用,在使用时须要直接嵌入到代码中。

gzip压缩文件时总是在前32K字节的范围内寻找反复的字符串进行编码, 在解压时须要一个至少为32K字节的解压缓冲区,它定义为window[WSIZE]。

inflate.c使用get_byte()读取输入文件。它被定义成宏来提高效率。

输入缓冲区指针必须定义为inptr,inflate.c中对之有减量操作。inflate.c调用flush_window()来输出window缓冲区中的解压出的字节串,每次输出长度用outcnt变量表示。在flush_window()中。还必须对输出字节串计算CRC而且刷新crc变量。在调用gunzip()開始解压之前,调用makecrc()初始化CRC计算表。

最后gunzip()返回0表示解压成功。我们在内核启动的開始都会看到这种输出:

UncompressingLinux...done, booting the kernel.

这也是由decompress_kernel函数输出的。运行完解压过程,再返回到head.S中的583行,启动内核

call_kernel: bl    cache_clean_flush
             bl    cache_off
             mov       r0, #0                   @ must be zero
             mov       r1, r7                   @ restore architecture number
             mov       r2, r8                   @ restore atags pointer
             mov       pc, r4                   @ call kernel

当中r4中已经在head.S的第180行处预置为内核镜像的地址。例如以下代码:

#ifdef CONFIG_AUTO_ZRELADDR
             @determine final kernel image address
             mov       r4, pc
             and r4, r4, #0xf8000000
             add r4, r4, #TEXT_OFFSET
#else
             ldr   r4, =zreladdr
#endif

这样就进入Linux内核的第一阶段。我们也称之为stage1。

二、Linux内核启动第一阶段stage1

承接上文。这里所以说的第一阶段stage1就是内核解压完毕并出现Uncompressing
Linux...done,booting the kernel.之后的阶段。该部分代码实如今arch/arm/kernel的 head.S中,该文件里的汇编代码通过查找处理器内核类型和机器码类型调用对应的初始化函数,再建 立页表,最后跳转到start_kernel()函数開始内核的初始化工作。

检測处理器类型是在汇编子函数__lookup_processor_type中完毕的,通过下面代码可实现对它的调用:bl__lookup_processor_type(在文件head-commom.S实现)。__lookup_processor_type调用结束返回原程序时。会将返回结果保存到寄存器中。当中r5寄存器返回一个用来描写叙述处理器的结构体地址。并对r5进行推断,假设r5的值为0则说明不支持这样的处理器,将进入__error_p。r8保存了页表的标志位,r9
保存了处理器的ID 号,r10保存了与处理器相关的struct
proc_info_list结构地址。

Head.S核心代码例如以下:

ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @设置SVC模式关中断
      mrc p15, 0, r9, c0, c0        @ 获得处理器ID。存入r9寄存器
      bl    __lookup_processor_type        @ 返回值r5=procinfo r9=cpuid
      movs      r10, r5
 THUMB( it eq )        @ force fixup-able long branch encoding
      beq __error_p                   @假设返回值r5=0,则不支持当前处理器'
      bl    __lookup_machine_type         @ 调用函数,返回值r5=machinfo
      movs      r8, r5            @ 假设返回值r5=0。则不支持当前机器(开发板)
THUMB( it   eq )             @ force fixup-able long branch encoding
      beq __error_a                   @ 机器码不匹配,转__error_a并打印错误信息
      bl    __vet_atags
#ifdef CONFIG_SMP_ON_UP    @ 假设是多核处理器进行对应设置
      bl    __fixup_smp
#endif
      bl    __create_page_tables  @最后開始创建页表

检測机器码类型是在汇编子函数__lookup_machine_type (相同在文件head-common.S实现)
中完毕的。

与__lookup_processor_type类似。通过代码:“bl __lookup_machine_type”来实现对它的调 用。该函数返回时,会将返回结构保存放在r5、r6 和r7三个寄存器中。

当中r5寄存器返回一个用来描写叙述机器(也就是开发板)的结构体地址,并对r5进行推断,假设r5的值为0则说明不支持这样的机器(开发板),将进入__error_a。打印出内核不支持u-boot传入的机器码的错误如图2。

r6保存了I/O基地址,r7 保存了 I/O的页表偏移地址。

当检測处理器类型和机器码类型结束后,将调用__create_page_tables子函数来建立页表。它所要做的工作就是将
RAM 基地址開始的1M 空间的物理地址映射到 0xC0000000開始的虚拟地址处。对本项目的开发板DM3730而言,RAM挂接到物理地址0x80000000处。当调用__create_page_tables 结束后 0x80000000
~ 0x80100000物理地址将映射到 0xC0000000~0xC0100000虚拟地址处。当全部的初始化结束之后,使用例如以下代码来跳到C 程序的入口函数start_kernel()处,開始之后的内核初始化工作: bSYMBOL_NAME(start_kernel) 。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3FiX2RyaXZlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

图2 机器码不匹配错误

三、Linux内核启动第二阶段stage2

从start_kernel函数開始

Linux内核启动的第二阶段从start_kernel函数開始。start_kernel是全部Linux平台进入系统内核初始化后的入口函数。它主要完毕剩余的与 硬件平台相关的初始化工作。在进行一系列与内核相关的初始化后,调用第一个用户进程- init 进程并等待用户进程的运行。这样整个 Linux内核便启动完毕。该函数位于init/main.c文件里,主要工作流程如图3所看到的:

 
                                                                图3 start_kernel流程图

该函数所做的详细工作有 :

1) 调用setup_arch()函数进行与体系结构相关的第一个初始化工作;对不同的体系结构来说该函数有不同的定义。

对于ARM平台而言。该函数定义在 arch/arm/kernel/setup.c。它首先通过检測出来的处理器类型进行处理器内核的初始化。然后 通过bootmem_init()函数依据系统定义的meminfo结构进行内存结构的初始化,最后调用 paging_init()开启MMU,创建内核页表,映射全部的物理内存和IO空间。

2) 创建异常向量表和初始化中断处理函数。

3) 初始化系统核心进程调度器和时钟中断处理机制;

4) 初始化串口控制台(console_init);

ARM-Linux 在初始化过程中一般都会初始化一个串口做为内核的控制台,而串口Uart驱动却把串口设备名写死了。如本例中linux2.6.37串口设备名为ttyO0。而不是经常使用的ttyS0。有了控制台内核在启动过程中就能够通过串口输出信息以便开发人员或用户了解系统的启动进程。

5) 创建和初始化系统cache,为各种内存调用机制提供缓存,包含;动态内存分配,虚拟文件系统(VirtualFile
System)及页缓存。

6) 初始化内存管理,检測内存大小及被内核占用的内存情况。

7) 初始化系统的进程间通信机制(IPC)。 当以上全部的初始化工作结束后。start_kernel()函数会调用rest_init()函数来进行最后的初始化,包含创建系统的第一个进程-init进程来结束内核的启动。

挂载根文件系统并启动init

Linux内核启动的下一过程是启动第一个进程init,但必须以根文件系统为载体,所以在启动init之前,还要挂载根文件系统。

四、挂载根文件系统

根文件系统至少包含下面文件夹:

/etc/:存储重要的配置文件。

/bin/:存储经常使用且开机时必须用到的运行文件。

/sbin/:存储着开机过程中所需的系统运行文件。

/lib/:存储/bin/及/sbin/的运行文件所需的链接库。以及Linux的内核模块。

/dev/:存储设备文件。

注:五大文件夹必须存储在根文件系统上。缺一不可。

以仅仅读的方式挂载根文件系统。之所以採用仅仅读的方式挂载根文件系统是由于:此时Linux内核仍在启动阶段。还不是非常稳定。假设採用可读可写的方式挂载根文件系统,万一Linux不小心宕机了,一来可能破坏根文件系统上的数据,再者Linux下次开机时得花上非常长的时间来检查并修复根文件系统。

挂载根文件系统的而目的有两个:一是安装适当的内核模块。以便驱动某些硬件设备或启用某些功能;二是启动存储于文件系统中的init服务,以便让init服务接手兴许的启动工作。

运行init服务

Linux内核启动后的最后一个动作。就是从根文件系统上找出并运行init服务。Linux内核会按照下列的顺序寻找init服务:

1)/sbin/是否有init服务

2)/etc/是否有init服务

3)/bin/是否有init服务

4)假设都找不到最后运行/bin/sh

找到init服务后,Linux会让init服务负责兴许初始化系统使用环境的工作,init启动后,就代表系统已经顺利地启动了linux内核。

启动init服务时,init服务会读取/etc/inittab文件,依据/etc/inittab中的设置数据进行初始化系统环境的工作。/etc/inittab定义init服务在linux启动过程中必须依序运行下面几个Script:

/etc/rc.d/rc.sysinit

/etc/rc.d/rc

/etc/rc.d/rc.local

/etc/rc.d/rc.sysinit基本的功能是设置系统的基本环境。当init服务运行rc.sysinit时
要依次完毕以下一系列工作:

(1)启动udev

(2)设置内核參数

运行sysctl –p,以便从/etc/sysctl.conf设置内核參数

(3)设置系统时间

将硬件时间设置为系统时间

(4)启用交换内存空间

运行swpaon –a –e,以便依据/etc/fstab的设置启用全部的交换内存空间。

(5)检查并挂载全部文件系统

检查全部须要挂载的文件系统。以确保这些文件系统的完整性。检查完成后以可读可写的方式挂载文件系统。

(6)初始化硬件设备

Linux除了在启动内核时以静态驱动程序驱动部分的硬件外,在运行rc.sysinit时,也会试着驱动剩余的硬件设备。rc.sysinit驱动的硬件设备包括下面几项:

a)定义在/etc/modprobe.conf的模块

b)ISA PnP的硬件设备

c)USB设备

(7)初始化串行port设备

Init服务会管理全部的串行port设备。比方调制解调器、不断电系统、串行port控制台等。Init服务则通过rc.sysinit来初始化linux的串行port设备。

当rc.sysinit发现linux才干在这/etc/rc.serial时。才会运行/etc/rc.serial。借以初始化全部的串行port设备。因此,你能够在/etc/rc.serial中定义怎样初始化linux全部的串行port设备。

(8)清除过期的锁定文件与IPC文件

(9)建立用户接口

在运行完3个基本的RC Script后,init服务的最后一个工作,就是建立linux的用户界面,好让用户能够使用linux。此时init服务会运行下面两项工作:

(10)建立虚拟控制台

Init会在若干个虚拟控制台中运行/bin/login。以便用户能够从虚拟控制台登陆linux。linux默认在前6个虚拟控制台。也就是tty1~tty6,运行/bin/login登陆程序。当全部的初始化工作结束后。cpu_idle()函数会被调用来使系统处于闲置(idle)状态并等待用户程序的运行。至此。整个Linux内核启动完成。

整个过程见图4。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3FiX2RyaXZlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

图4:linux内核启动及文件系统载入全过程

版权声明:本文博主原创文章,博客,未经同意不得转载。

时间: 2024-12-22 16:31:01

Linux内核和根文件系统引导加载程序的相关文章

debian下为stm32f429i-discovery编译uboot、linux内核和根文件系统

交叉编译器:arm-uclinuxeabi-2010q1 交叉编译器下载下来后解压,然后将其中bin文件夹路径加入到PATH变量中. 根据<debian下烧写stm32f429I discovery裸机程序>安装openocd. 安装genromfs,使用下面命令: sudo apt-get install genromfs 已经有人提供好脚本,只要执行几条命令即可.使用git下载文件并进行编译: git clone https://github.com/jserv/stm32f429-lin

GRUB(GRand Unified Boot loader)引导加载程序

http://hi.baidu.com/eao110/blog/item/b56177ec8c89afdc2f2e218f.html 一.GRUB简介 首先搞清楚与 GNU GRUB的关系. GNU GRUB 分为 GNU GRUB Legacy 和 GNU GRUB2 两代 .GNU GRUB Legacy 其实就是原来的 GNU GRUB 0.xx ,最新版是 2005 年发布的 GNU GRUB 0.97 .目前已停止开发,并改名为 GNU GRUB Lagecy .GNU GRUB2 是

引导加载程序之争: LILO 和 GRUB

在不考虑他们的工作或专业情况下,所有 Linux 用户都会使用的是哪个工具?引导加载程序.通过本文了解引导加载程序的工作原理,认识两个流行的引导加载程序 LILO(LInux LOader)和 GNU GRUB(GRand Unified Boot loader), 并研究两者各自的优点和缺点. 什么是引导加载程序? 最简单地讲,引导加载程序(boot loader) 会引导操作系统.当机器引导它的操作系统时,BIOS 会读取引导介质上最前面的 512 字节(即人们所知的 主引导记录(maste

引导加载程序之争:了解 LILO 和 GRUB PCV

从普通的桌面用户到 Linux? 系统管理员,大部分 Linux 用户都使用过一种名为引导加载程序的工具.此类工具的不同变种会提供不同层次的支持和功能.在很多情况下,Linux 发行版默认安装的引导加载程序并不总是适合需要:每个引导加载程序的默认设置也是如此.在本文中,Laurence Bonney 讨论了两个流行的引导加载程序 -- LILO 和 GRUB -- 的优点和缺点,并建议了很多配置,以充分发掘机器的潜力. 1评论: Laurence Bonney ([email protected

Win7下安装双系统Centos,并修复Centos引导加载程序安装在U盘上的问题

1.使用U盘安装Centos时,磁盘分区划分要注意:系统(包含Win7)只能4个主分区,所以只能在删除一个主分区或者在扩展分区的空闲分区内建立目录. 2.Centos在安装步骤的最后,引导加载程序的选项只有“安装在U盘”一项,注:不可轻易选择安装在MBR上,会覆盖掉Win7系统的引导加载程序. 所以我们先选择将引导加载程序安装在U盘上,然后再修复. 3.Centos修复grub启动加载程序 参考并转自:http://blog.csdn.net/llhwin2010/article/details

如何配置Ubuntu 16.04 GRUB 2引导加载程序

正如你所知,GRUB 2 是大多数 Linux 操作系统的默认引导加载程序.GRUB 是 GRand Unified Bootloader 的缩写,它是 Linux 启动时首先要加载的一个程序,此后它会负责载入并将控制权交给 Linux kernel,并由 Linux kernel 负责对操作系统的其它部分进行初始化.在本文中,我们将对 Ubuntu 16.04 LTS 桌面版 GRUB 2 引导加载程序的重要配置进行介绍,当然操作和配置也与其它 Linux 发行版中的 GRUB 配置通用. G

Linux内核与根文件系统的关系

开篇题外话:对于Linux初学者来说,这是一个很纠结的问题,但这也是一个很关键的问题!         一语破天机: “尽管内核是 Linux 的核心,但文件却是用户与操作系统交互所采用的主要工具.这对 Linux 来说尤其如此,这是因为在 UNIX 传统中,它使用文件 I/O 机制管理硬件设备和数据文件.” 一.什么是文件系统         文件系统指文件存在的物理空间,linux系统中每个分区都是一个文件系统,都有自己的目录层次结构.Linux文件系统中的文件是数据的集合,文件系统不仅包含

linux内核裁剪及编译可加载模块

一:linux内核裁剪: (1):编译内核源码: 在邦飞的课程学习已经接近尾声:这周的重点内容是内核驱动的编写,在编写驱动之前首先的了解linux内核源码,linux主要是由五个子系统组成:进程调度,内存管理,文件系统,网络接口以及进程间通信:下面是解压的linux内核源码文件: 下面对linux内核里面的文件进行简单的说明: arch目录中包含于体系结构有关的子目录和文件,arm的相关平台信息在arch/arm目录下. scripts目录中存放着对核心配置的脚本文件. crypto目录中包含着

系统引导加载器的简单实现

在实现引导加载器之前,首先我们先了解下在开机之后系统是怎么被引导的,这对于实现引导加载器来说是很重要的.只有知道原理,才能更好的实现嘛. 1 引导过程 1.1 当电源按钮按下时 当我们按下电源按钮是到底发生了什么?当这个按钮被按下后,连接到这个按钮的线缆会向主板发送一个电信号,主板简单的把这个信号转发给电源(PSU ).这个信号只包含1 比特信息.如果是0 ,表示没电(计算机关闭,主板不活动).如果是1 (活动信号),意味着系统已经加电.当PSU 收到这个活动信号,它开始向系统的其余部分供电.当