linux内核启动以及文件系统的加载过程

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

当u-boot 开始执行 bootcmd 命令,就进入 Linux 内核启动阶段。普通 Linux 内核的启动过程也可以分为两个阶段。本文以项目中使用的 linux-2.6.37 版源码为例分三个阶段来描述内核启动全过程。第一阶段为内核自解压过程,第二阶段主要工作是设置ARM处理器工作模式、使能 MMU 、设置一级页表等,而第三阶段则主要为C代码,包括内核初始化的全部工作。

 

一、 Linux 内核自解压过程

在 linux 内核启动过程中一般能看到图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_flushbl    cache_offmov       r0, #0          @ must be zeromov       r1, r7          @ restore architecture numbermov       r2, r8          @ restore atags pointermov       pc, r4          @ call kernel

 

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

#ifdef CONFIG_AUTO_ZRELADDR@determine final kernel image addressmov       r4, pcand r4, r4, #0xf8000000add r4, r4, #TEXT_OFFSET
#elseldr   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=cpuidmovs    r10, r5                 THUMB( it eq )      @ force fixup-able long branch encodingbeq __error_p             @如果返回值r5=0,则不支持当前处理器'
  bl    __lookup_machine_type       @ 调用函数,返回值r5=machinfomovs    r8, r5        @ 如果返回值r5=0,则不支持当前机器(开发板)
THUMB( it   eq )         @ force fixup-able long branch encodingbeq __error_a             @ 机器码不匹配,转__error_a并打印错误信息bl    __vet_atags
#ifdef CONFIG_SMP_ON_UP    @ 如果是多核处理器进行相应设置bl    __fixup_smp
#endifbl    __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)  。

 

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

 从start_kernel函数开始       

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

                                                                                                   图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 时,也会试着驱动剩余的硬件设备。  r    c.sysinit 驱动的硬件设备包含以下几项:       

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

  b)       ISA PnP的硬件设备       

  c)       USB设备       

(7)初始化串行端口设备  

        Init服务会管理所有的串行端口设备,比如调制解调器、不断电系统、串行端口控制台等。Init  服务则通过rc.sysinit来初始化linux 的串行端口设备。当rc.sysinit 发现 linux 才能在这  /etc/rc.serial 时,才会执行 /etc/rc.serial ,借以初始化所有的串行端口设备。因此,你可以在 /etc/rc.serial 中定义如何初始化 linux所有的串行端口设备。       

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

(9)建立用户接口  

在执行完3个主要的 RC Script 后, init服务的最后一个工作,就是建立linux的用户界面,好让用户可以使用 linux  。此时init  服务会执行以下两项工作:       

(10)建立虚拟控制台  

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

              

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

 

转载于:https://www.cnblogs.com/ynxf/p/5322891.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/470905.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 2335. 装满杯子需要的最短总时长

文章目录1. 题目2. 解题1. 题目 现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。 给你一个下标从 0 开始、长度为 3 的整数数组 amount ,其中 amount[0]、amount[1] 和 amount[2] 分…

Linux实操篇——实用指令学习笔记(详解)

9.3帮助指令 9.3.1介绍 当我们对某个指令不熟悉时,我们可以使用Linux提供的帮助指令来了解这个指令的使用方法。 9.3.2man 获得帮助信息 基本语法 man[命令或配置文件](功能描述:获得帮助信息)应用实例 案例:查看1…

LeetCode 2249. 统计圆内格点数目

文章目录1. 题目2. 解题1. 题目 给你一个二维整数数组 circles ,其中 circles[i] [xi, yi, ri] 表示网格上圆心为 (xi, yi) 且半径为 ri 的第 i 个圆,返回出现在 至少一个 圆内的 格点数目 。 注意: 格点 是指整数坐标对应的点。 圆周上的…

Python实现自动发送邮件(详解)

Python实现自动发送邮件 1.开启SMTP服务 为了实现自动发送邮件的目的,我们需要在邮箱中开启SMTP服务: 这点很关键,别忘了去开启SMTP, 别忘了去开启SMTP,否则邮件是无法发送成功的 。然后你还需要点击下面生成授权…

React 组件:计时器例子

文章目录1. 将应用程序分解为组件2. 构建应用静态版本3. 哪些组件是有状态的4. 每个 state 应该在哪个组件里5. 硬编码初始化state6. 添加反向数据流7. 添加服务器通信learn from 《React全家桶:前端开发与实例详解》https://zh-hans.reactjs.org/tutorial/tutorial…

一、快速开始一个 MyBatis项目(详解)

1.0 概述 1.三层架构 界面层: 和用户打交道的, 接收用户的请求参数, 显示处理结果的。(jsp ,html ,servlet) 业务逻辑层: 接收了界面层传递的数据,计算逻辑,…

2013 ACM区域赛长沙 K Pocket Cube hdu 4801

题意:给了一个2*2的魔方..每步操作可以将任意一面翻转90度..现在问在N(<7)步内.最多能翻出几面相同的. 直接打表模拟每种翻转情况 1 #include<cstdio>2 #include<cstring>3 #include<algorithm>4 #include<iostream>5 using namespace std;6 7 int …

React 组件和服务器

文章目录1. 请求服务器数据2. 发送开始停止请求3. 发送创建、删除、更新请求learn from 《React全家桶&#xff1a;前端开发与实例详解》https://zh-hans.reactjs.org/tutorial/tutorial.htmlhttps://zh-hans.reactjs.org/docs/create-a-new-react-app.html#create-react-app服…

二、MyBatis常用对象分析 封装工具类

1.0 MyBatis 对象分析 &#xff08;1&#xff09; Resources 类 Resources 类&#xff0c;顾名思义就是资源&#xff0c;用于读取资源文件。其有很多方法通过加载并解析资源文件&#xff0c;返回不同类型的 IO 流对象。 &#xff08;2&#xff09; SqlSessionFactoryBuilder…

iOS图片拉伸技巧

纵观移动市场&#xff0c;一款移动app&#xff0c;要想长期在移动市场立足&#xff0c;最起码要包含以下几个要素&#xff1a;实用的功能、极强的用户体验、华丽简洁的外观。华丽外观的背后&#xff0c;少不了美工的辛苦设计&#xff0c;但如果开发人员不懂得怎么合理展示这些设…

LeetCode 2341. 数组能形成多少数对

文章目录1. 题目2. 解题1. 题目 给你一个下标从 0 开始的整数数组 nums 。在一步操作中&#xff0c;你可以执行以下步骤&#xff1a; 从 nums 选出 两个 相等的 整数从 nums 中移除这两个整数&#xff0c;形成一个 数对 请你在 nums 上多次执行此操作直到无法继续执行。 返…

三、MyBatis 使用传统 Dao 开发方式

1.0 使用 Dao 的实现类,操作数据库 1.0.1 Dao 开发 &#xff08;0&#xff09;定义接口StudentDao 及创建接口的映射文件StudentDao .xm package com.zep.dao;import com.zep.domain.Student;import java.util.List;public interface StudentDao {List<Student> selec…

LeetCode 2347. 最好的扑克手牌

文章目录1. 题目2. 解题1. 题目 给你一个整数数组 ranks 和一个字符数组 suit 。你有 5 张扑克牌&#xff0c;第 i 张牌大小为 ranks[i] &#xff0c;花色为 suits[i] 。 下述是从好到坏你可能持有的 手牌类型 &#xff1a; "Flush"&#xff1a;同花&#xff0c;五…

四、MyBatis 框架 Dao 动态代理

1.1 步骤 &#xff08;1&#xff09; 去掉 之前编写的Dao 接口实现类 &#xff08;2&#xff09; getMapper 获取代理对象 只需调用 SqlSession 的 getMapper()方法&#xff0c;即可获取指定接口的实现类对象。该方法的参数为指定 Dao接口类的 class 值。 不使用工具类&…

五、深入理解Mybatis中的参数parameterType (传递一个简单参数,传递多个参数:@Param、使用自定义对象、按位置、使用Map)

1.1 parameterType parameterType: 接口中方法参数的类型&#xff0c; 类型的完全限定名或别名。这个属性是可选的&#xff0c;因为 MyBatis可以推断出具体传入语句的参数&#xff0c;默认值为未设置&#xff08;unset&#xff09;。接口中方法的参数从 java 代码传入到mapper…

六、封装 MyBatis 输出结果resultType、resultMap 以及 数据库表中列名和返回对象属性名不一致的2种解决方案(详解)

1.1 resultType resultType: 执行 sql 得到 ResultSet 转换的类型&#xff0c;使用类型的完全限定名或别名。 注意&#xff1a;如果返回的是集合&#xff0c;那应该设置为集合包含的类型&#xff0c;而不是集合本身。resultType 和 resultMap&#xff0c;不能同时使用。 A、…

API 接口批量测试

ApiPost 创建接口 导入要测试的数据 测试结果 ApiFox 创建接口 导入接口 导入测试数据&#xff0c;可以直接编辑&#xff0c;粘贴进来 测试结果

LeetCode 2342. 数位和相等数对的最大和

文章目录1. 题目2. 解题1. 题目 给你一个下标从 0 开始的数组 nums &#xff0c;数组中的元素都是 正 整数。请你选出两个下标 i 和 j&#xff08;i ! j&#xff09;&#xff0c;且 nums[i] 的数位和 与 nums[j] 的数位和相等。 请你找出所有满足条件的下标 i 和 j &#xff…

软件项目管理-构建之法-四周总结

写在前面 课程名&#xff1a;软件项目管理 授课人&#xff1a;东北师范大学 杨贵福&#xff08; http://www.cnblogs.com/younggift/&#xff09; 教材&#xff1a;《构建之法 - 现代软件工程》 作者&#xff1a;邹欣老师 &#xff08;博客&#xff1a;http://www.cnblogs.com…

一、Vue基础语法学习笔记系列——插值操作(Mustache语法、v-once、v-html、v-text、v-pre、v-cloak)、绑定属性v-bind(绑定class、style)、计算属性

一、插值操作 1. Mustache 如何将data中的文本数据&#xff0c;插入到HTML中呢&#xff1f; 我们已经学习过了&#xff0c;可以通过Mustache语法(也就是双大括号)。 Mustache: 胡子/胡须. 我们可以像下面这样来使用&#xff0c;并且数据是响应式的 2. v-once 但是&#xff0…