linux启动流程学习与总结

       前言

        无论是linux还是uboot,首先首先要看的就是其配置文件,而linux各种配置文件,例如xxx_defconfig、make menuconfig(Kconfig)、.config等,最终最终都是服务于makefile的。

        先看配置文件吧,且看下面这个图,网课截的

总结起来就是,配置文件要么在架构-平台的configs文件夹里,找一个和你的板子相近的xxx_defconfig来作为蓝本,以此为基础进行修改,要么就用开发板厂家提供的xxx_defconfig或者xxx_config覆盖.config。

上面xxx_defconfig和.config的关系就是,make xxx_defconfig之后,.config就会有xxx_defconfig提及的配置项。

而make menuconfig配置完后,也是改变了.config这个配置文件。

.config这个文件,就直接决定了各级makefile有哪些宏被打开。

配置语法合理性及配置示例

好,现在举个例子

以DM9000为例,如果我们要linux内核支持该网卡,该怎么做呢?

首先收索CONFIG_DM9000可以知道哪类文件与其配置相关,且看下图

由图可知,共有圆序号1~4涉及了DM9000的配置,直接说结论,

.config被某些脚本工具解析,生成autoconf.h和auto.conf

autoconf.h用于将宏define为1,使得c语言代码可以识别宏开关

auto.conf用于将将宏的y,m转换为适应makefile语法的y,m

到了这里宏的配置同c的语法和makefile语法的关系我们就自圆其说了。

        实际上我们日常配置只需要关注和DM9000相关的底层的makefile即可(driver/net/makefile),因为如果我们要的网卡内核没有,那我们移植网卡时,是需要修改这里的(以及底层的Kconfig)。如果是内核提供了的驱动,我们仅仅只需要在make menuconfig里将其配置项选上即可。

知道了配置文件是makefile了,现在该去分析它了

分析makefile

可以得知-- -->第一个文件(顺藤摸瓜了解启动流程),链接脚本(内核放在哪个地址,代码段数据段等等是如何排布的)

下图是makefile的类型,可作工具查阅

下面挑一些makefile中最核心的语句

首先第一个要点,顶层makefile第一个包含的makefile是

架构的makefile

这个呢一般都是固定的,例如arm在linux内核中有一些必要配置,x86有一些必要的配置,它们会根据ARCH来找到对应的架构makefile将相关的必要的配置项选上。

然后我们知道我们编译出内核镜像是zImage或者uImage

这个在makefile中肯定是最终的目标文件,我们需要顺藤摸瓜查一查它的依赖是什么

可见,内核镜像依赖于vmlinux,所以经常听到网课啊或者博客人家说vmlinux是真正的内核,就是这么个事。

再看这个vmlinux是依赖于什么(从哪些文件的来的)

图片后面有些看不清没关系,下面有文字描述

vmlinux依赖于以下这些,首先是链接脚本vmlinux-lds,然后是vmlinux-init、vmlinux -main

将它们剖析

vmlinux-init是:

vmlinux -main是:

也就是说,vmlinux是由head-y,init-y等等这些个变量里面,保存的.o对应的源文件构成的。我们要清楚,make编译的时候,编译的顺序就是以上这些.o排放的顺序,因此因此,我们看第一个程序是啥?从vmlinux-init我们可以得知,第一个程序是head-y变量里存放的head.S。

知道了原材料(依赖),下面再看怎么炒菜(编译指令)

上图是紧跟着vmlinux依赖的编译指令,分析起来太复杂了,直接看make uImage v=1的结果,里面就有这段makefile脚本展开的语句,上面的v=1的意思是让编译过程的打印信息更详细的意思。

上图就是,编译指令的展开,看不清楚没关系,下面有文字的解释

首先用arm-linux-ld工具进行链接

-o指定输出的是vmlinux

-T 指定链接脚本vmlinux.lds,即设置程序段"应该"在哪个地址(可以浏览一下,不过表面是看不出什么的,得后面根据源码来分析才知道哪些段是干嘛的)

指定连接脚本之后就是指定一堆原材料,原材料就是一堆的.o目标文件()

 下面简单浏览一下链接脚本

链接脚本:vmlinux.lds

在没有编译过的源码里grep就知道,它是由vmlinux.lds.S的来的,是/arch/arm/kernel里的代码

.指令内核放在哪里(虚拟地址)

首先放所有文件的.text.head段,这所谓“所有文件”的.text.head段放置的顺序由链接时候.o的顺序决定

然后就是.init.text段 ...等等

确实看不出有什么帮助,可能只有开头的那个点有一些初步价值。

至此,由Makefile我们知道了,程序执行的第一个文件是head.S还有链接脚本

网课总结到这张图里了,接下来分析内核启动

分析uboot和内核启动之间的这个过程

uboot是负责启动linux内核的,先看一下uboot在最后的阶段做了什么事情:

uboot的最后一条代码是:

uboot在某个地址写了环境变量,然后设置r0(0) r1(机器ID) r2(环境变量地址) 然后启动内核

theKernel是函数指针,其地址是内核在内存中的地址,展开细说就是下图圈起来的部分

而简单的说就是,uboot在某个地址写了一堆的启动信息,然后调用的最后一条函数,这个函数呢,是个指针函数,其地址就是kernel程序的加载地址。这个函数的参数分别对应着r0,r1,r2,r1和r2又分别存在机器ID和启动信息的地址。

重点说一下这个指针函数,我们说它的地址就是kernel的加载地址,举个例子就明白了,我们的uboot,都要设置一个bootcmd,它是uboot加载模式下(你没按按键的时候)它自动执行的指令,一般是这样的形式(不同板子块设备读写指令不一样,但是意思是一个意思)

bootcmd 'movi read kernel 40008000;movi read rootfs 40df0000 100000;bootm 40008000 40df0000'

意思是将内核从固态存储设备加载到0x4008000的地址处,这个0x4008000就是uboot设置的指针函数的地址,重点来了,要让linux内核成功地被uboot启动,在编译linux内核时,也要指定其加载地址与uboot设置的指针函数地址一致,否则可能会启动失败。

怎么办呢?其实很简单,因为编译uImage时是可以指定的,例如:

make uImage LOADADDR=0x40007000

这样uboot和uImage一起约定好的地址就不会有错了。

下面继续分析内核启动流程

上面我们说到,uboot将机器ID,启动参数传到了uboot的最后一条函数,当执行这最后一条函数的时候,pc指针也就指向了这个指针函数所在的地址,然后去指向其代码了,我们很容易理解,其实这个地方,就是linux内核的代码所在了,uboot这个逻辑程序的任务也就此完结,后面都是linux的工作了。

跳转过来的地方,当然就是前面所说的linux的第一个程序head.S啦,它是一个汇编程序

        或许有人会疑惑,这是传统的启动方式,现在有了设备树,又是怎么回事呢?其实也很简单,设备树在uboot加载命令bootcmd中被加载到了内存的某个地址,uboot完全可以在启动信息中,告诉内核设备树在哪里,这样内核就能够将设备树展开并获取里面的信息了。实际上,设备树的加入,对原来的内核其实还是很兼容的,许多许多地方是不变的。新的内核设备树展开的部分在本文不会提到,后续应该会再出个帖子分析新的内核,这个帖子只是记录网课学习,涉及的只有没有设备树版本的内核。

分析内核第一个程序head.S

        总的来说是先处理传过来的参数:判断是否支持该cpu,判断是否支持该机器id

创建页表的原因是链接地址0xc000000(虚拟)开始的,而物理内存(SDRAM/DDR)是从0x30000000(根据自己的板子)开始的,所以要建一个页表以便后面启动MMU。

在这里先插入详细分析一下__lookup_machine_type这个汇编函数,再往下继续

如果想保持内核启动源码理解的连续性,可以跳过这一小节,因为这个函数内部是比较复杂的,看完思路都飞走了。

分析__lookup_machine_type

这个函数的作用是检测机器ID,说白了就是看linux支不支持这个soc,从编程的角度,无非就是一个数字放到一个数表里比较一下,数表里有的就支持,没有的就不支持,来看看是怎么写的。

先了解一下相关ARM汇编语法

ldmia指令    连续加载内容到寄存器    

示例:ldmia r1 ,[r2,r3,r4] 将r1地址开始的内容放到r2,r3,r4中

adr指令 

示例:adr r3 , 3b 将3b标签的地址赋给r3

好,来看代码

再次铺垫一些知识:

看上面那个红框

3:        这是一个标签,可以理解为它这个东东就代表了一个地址

3:        .long        .

这个.long后面的这个        “.”        点这个东西表示的是程序应该处于的地址,是虚拟地址。细品这句话

__arch_info_begin和__arch_info_end这连个东西的值,也是虚拟地址

但是adr r3,3b        里面的这个3b是物理地址

凭什么呢?因为当前程序,还没有开启MMU,根本没有所谓的虚拟地址,3b是个标签是随时就会被跳转然后运行的,现在没有开启MMU,它的地址当然就是物理地址啦,而".",__arch_info_begin和__arch_info_end这三个东西,可以理解为是一个写死的常量,里面保存的是虚拟地址也就是常量的值保存着虚拟地址,和当时开没开其MMU半毛钱关系都没有。

接下来再看这张图

真正开始看这个函数里的代码和注释了

注意看第二个红框下面的sub和add语句

那一句sub实际是:同一个位置的物理地址(r3,”3b”这个标签的地址)---同一个位置的虚拟地址(r4,“.”的地址),得到偏移值(是负数)

可见,r5存着的,就是__arch_info_begin的物理地址了,也就是现在没有开启MMU的情况下,pc指针一指过去就会执行的地址。这个__arch_info_begin明显是一个段,里面是什么名堂呢?我们需要结合链接文件和linux源文件来分析(链接文件终于登场了)

vmlinux.lds.S中是这样写的

可以分析一下这个MACHINE_START宏是干了什么,是被谁、在哪里使用

可见,这个宏是被arch/arm/mach-xxx/Mach-xxx.c使用,,从名字和参数我们不难猜出,这个段,大概率是存着SOC的信息,再回想我们一路追寻到这里,最初的目标,就是检测机器ID,所以这个东西,大概率就是某个板级文件调用了这个宏,就能够将机器ID注册到linux内核中,如果uboot传过来的机器ID与linux内核的机器id匹配,这个所谓的检测机器ID就合格了。反过来说,这个段存着很多机器的信息(机器ID,启动参数地址等),这个检测机器ID的函数,其实更像在匹配,我们到底用这个段中那么多板级文件arch/arm/mach-xxx/Mach-xxx.c那么多板级信息的哪一个板级信息。所以机器ID的匹配是至关重要的。

解析这个宏:先说结论

这个宏是用来造结构体的,这个结构体的特殊之处在于能够把结构体的信息保存在.arch.info.init段中。这也解释的通为什么检测机器ID这个函数和链接脚本扯上了关系,因为linux中保存机器ID信息的结构体,它就是存在.arch.info.init段中的。

且看这个宏,展开和具体示例。下面红框就是这个宏的展开的样子,上面绿框是从某个具体的实例截取的。

将示例替换到定义,可得如下这个结构体(也就是说,它这个宏被调用之后,在代码中真正的样子),包含了一系列信息,并且是保存在arch.info.init段中的

里面的".nr"成员,实际就是保存着机器ID

也就是说,如果我们linux要对一个板子新加支持,要在内核新加一个这样包含MACH_TYPE_XXX的结构体(用宏定义),并且这个信息要与Uboot传进来r1的匹配。

检测机器ID这个函数就解释到这里,下面继续linux第一个程序head.s的分析

继续分析head.S(实际上要跳转到start_kernel函数了)

head.S的最后一条函数是然后跳转到start_kernel (内核的第一个c函数,我们早知道,uboot传来的东西中,机器id处理了,启动参数还没处理呢)

由下面这个图可以知道head.S程序和start_kernel函数具体干了什么事情

其实可以理解为start_kernel之后,就一直在做两件事,一是解析uboot传来的环境变量参数,二是初始化一堆“基础设施”,例如终端呀,打印信息之类的,下面细说。

按顺序分析:进入start_kernel之后

具体内核怎么解析呢?

我们先不提,反正我们知道,它从uboot那里拿到了bootargs的内容(即cmdline命令行参数)还有其他一些信息,看下面,就把uboot存在某个地址的tags信息都取了出来。

好,大家肯定有疑惑为什么mdesc->boot_params里面就是启动的信息的地址呢,这其实和上面讲的检测机器ID里的那个段有关,那个 段是

我们说,这个段里面的内容实际是一堆这样的东西:

里面赫然存放着一个启动参数的地址,和我们前面前面提到的,uboot传给指针函数的第三个参数也就是启动参数地址一模一样。其实按我的理解,我们知道从指针函数拿到第三个参数,就可以去取出启动参数那些信息了,为何linux的arch.info.init还要保存一个机器的信息呢?无法理解,可能是为了安全?总之我们得到了启动参数里面的内容。

继续分析start_kernel

后面就是把启动参数tag里面的东西全部取出来存在变量里,以备后用

这些启动参数里,要重点关注的是命令行参数(即uboot的bootargs参数),form是一个地址,里面存放着linux默认的cmdline(make menuconfig配置的)。下面来看这个

cmdline解析函数里,简言之就是把bootargs参数里,root=xxx,这个所谓的xxx取出来,这个xxx是某个分区的名字,和后面要挂载的根文件系统有关。这个参数里还要init=xxx,这个xxx是指某个程序,和后面挂载根文件系统后要运行的第一个程序有关。

这个__setup宏又是一个结构体,里面包含字符串、函数指针等成员

插讲一下内核分区的概念

在flash里没有分区表,那root=/dev/mtdblock3,内核怎么知道呢?内核其实也不知道,是内核在代码里写死了

启动内核时,内核会打印出分区信息

那内核在哪里写死了呢?当然是在板级文件,由我们来写啦,不然怎么会和uboot烧录的位置一样呢。在板级文件中定义了

继续分析start_kernel函数

解析完启动参数后,start_kernel的最后一个函数是rest_init()

先挂一个图,说明start_kernel函数做的事情,以及rest_init()做的事情

rest_init()又是一堆初始化,里面追踪进去是有mount_root即挂接根文件系统的函数,挂接完事后,调用init_post(),打开中断,内核启动的事情就完事了,接下来是执行应用程序了~

如果我们uboot的bootargs参数有指定init=xxx,那么就会执行这个xxx程序,如果没有指定,会执行红框里/sbin/init程序,如果这个程序不存在就继续往下执行,如果都不存在就报错。值得注意的是,这个run_init_process一旦执行成功就不会再返回了。

因此,内核启动流程其实也没多少东西,下面是图总结:

总结:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux安装设置是指在计算机上安装Linux操作系统并进行相关配置的过程。这个过程包括选择适合自己的Linux发行版、下载安装介质、进行分区、设置用户账户、安装必要的软件包、配置网络等。Linux安装设置需要一定的技术知识和经验,但是通过学习和实践,可以轻松掌握。 ### 回答2: Linux Setup是指在计算机上安装和配置Linux操作系统的过程。以下是一个简单的步骤指导来完成Linux Setup。 首先,你需要选择一个适合你需求的Linux发行版。常见的发行版有Ubuntu、Fedora、Debian等。每个发行版都有不同的特点和用户群体,你可以根据自己的喜好和需求来选择。 接下来,你需要下载所选发行版的ISO镜像文件。你可以在官方网站或者镜像站点上找到相应的下载链接。确保选择适合你计算机架构的版本,一般有x86、x86_64、ARM等。 一旦你下载了ISO镜像文件,你可以通过刻录成光盘或者创建一个启动盘来准备安装媒介。如果你选择的发行版支持UEFI引导,你可以使用工具如Rufus或者Etcher来创建一个启动盘。 接下来,你需要在你的计算机上设置引导顺序,以使它从安装介质启动。这通常需要在计算机启动时按下特定的按键(如F2或者Delete键)来进入BIOS设置界面,然后在启动选项中选择你的安装介质。 一旦你成功启动了安装媒介,你将进入Linux安装程序。根据程序提示,在适当的时候选择语言、时区、键盘布局等选项。 接下来,你需要选择磁盘分区方案。如果你是第一次使用Linux,推荐选择自动分区选项,让安装程序自动为你分配磁盘空间。如果你有特定的需求,你可以选择手动分区。 在安装过程中,你还需要设置一个用户账户和密码。确保你设置一个安全且容易记住的密码。 最后,等待安装过程完成。一旦安装完成,你将可以重新启动计算机并进入全新的Linux系统。 总的来说,Linux Setup过程并不复杂,但需要一些基本的计算机知识。通过遵循上述步骤,你将能够成功地在你的计算机上安装和配置Linux操作系统。 ### 回答3: Linux是一款开源的操作系统,可以在各种计算机设备上使用。为了安装Linux,我们首先需要准备一个安装介质,可以是光盘、USB驱动器或者镜像文件。 在安装之前,我们需要确定计算机硬件的兼容性,即了解操作系统版本所支持的硬件。这可以通过查看硬件规格和要求来确认。一旦我们确定硬件支持,就可以开始安装过程了。 首先,我们需要插入安装介质到计算机,然后重新启动系统。在开机时,我们需要进入BIOS设置界面,一般通过按下DEL键、F2键或者ESC键可以进入。在BIOS设置界面中,我们需要调整启动顺序,以便使计算机从安装介质启动。 完成上述设置后,我们会看到Linux安装程序的启动界面。我们需要按照指示选择安装语言、区域和时区。然后,我们需要选择硬盘分区。对于新用户来说,可以选择默认分区选项,系统会自动为我们进行分区。 接下来,我们需要为Linux设置一个用户名和密码。这个用户名和密码会成为我们登录系统时的凭据。安装程序还会询问我们是否需要创建其他用户账户,以及是否需要加密我们的家目录。 最后,我们等待安装程序完成安装过程。这个过程可能需要一些时间,取决于硬件性能和操作系统版本。安装完成后,我们就可以享受Linux操作系统的各种功能和特性了。 总结起来,Linux安装过程相对简单,只需要通过BIOS设置启动顺序,按照指示进行语言、区域和硬盘分区的选择,设置用户名和密码,等待安装完成即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值