典型嵌入式linux软件部分由哪些模块组成?他们的功能及相互联系? Bootloader分为哪两阶段?分

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了典型嵌入式linux软件部分由哪些模块组成?他们的功能及相互联系? Bootloader分为哪两阶段?分相关的知识,希望对你有一定的参考价值。

二、 典型嵌入式linux软件部分由哪些模块组成?他们的功能及相互联系? 三、 Bootloader分为哪两阶段?分别实现了那些功能? 四、 简述嵌入式文件系统的种类和管理机制。 五、 如何理解消费类的电子开发产品的可剪裁性和可移植性,并以linux系统为例说明。 六、 详细描述嵌入式linux软件开发的编译开发环境和编译开发工具。 七、 基于S3C2410的嵌入式linux开发的逻辑空间和物理空间如何对应?详细描述之

参考技术A 典型的嵌入式系统,软件部分从下到上,分别是boot,kernel,rootfs,fsimg和上层应用。
起到的作用分别是,引导内核,启动内核,挂载根文件系统,挂载实际文件系统,开启上层应用主循环。
你问的这些问题,每一点都可以单独拿出来,长篇大论的讲很久了。建议去网上先看相关的资料。贪多求快是不好的,一个知识点一个知识点的掌握。
参考技术B 从软件硬件设计特点简单描述嵌入式产品开发设计流程。
项目论证阶段:项目的可行性分析并形成可行性研究报告。
系统方案阶段:对产品需求加以分析、细化,并抽象出需要完成的功能列表,明确定义所要完成的任务。
系统设计阶段:软件开发部分完成软件需求分析,形成软件总体设计方案,软件开发接口规范等;硬件部分完成硬件总体设计方案,接口定义及说明等。
产品详细设计阶段:完成软/硬件的详细设计,编制代码,形成软件各模块的设计说明;硬件部分各单板的原理图,PCB和料单,同时完成产品的结构设计。
制造联试阶段:完成产品的系统调试和可靠性测试,并形成相应的系统调试报告和可靠性测试报告。
典型嵌入式Linux软件部分由哪些模块组成?它的功能和相互关系是什么?
Bootloader、嵌入式Linux内核、嵌入式文件系统组成。Bootloader完成硬件设备的初始化以及引导内核加载,内核通过文件系统来管理对整个系统中的所有的数据和文件。
BootLoader分为哪两个阶段?分别实现了哪些功能?
stage1和stage2两个阶段。
完成的工作:
硬件设备初始化。
为加载Bootloader的stage2准备RAM空间。
拷贝Bootloader的stage2到RAM空间中。
设置堆栈。
跳转到stage2的C入口点。
stage2完成的工作:
初始化本阶段要使用到的硬件设备。
监测系统内存映射。
将内核映像和根文件系统映像从Flash设备上复制到RAM空间中。
设置内核启动参数。
调用启动内核。
简述嵌入式文件系统的种类和管理机制。
Ext2fs文件系统 2.基于Flash的文件系统 3.基于RAM的文件系统 4.网络文件系统。
Linux引入了虚拟文件系统vfs(virtual file system),为各类文件系统提供一个统一的应用编程接口。
如何理解消费类电子产品开发的可裁剪性和可移植性,并以Linux系统为例进行说明。
Linux来说,假如我们用不到以太网设备,我们可以将该设备的驱动程序以及相关库文件等都去掉以缩小体积。
Linux可以在不同架构的CPU平台上运行。
详细描述嵌入式Linux软件开发的编译开发环境和编译开发工具。
开发环境:首先宿主机上需要安装Linux操作系统。需要为这个Linux系统安装以下三个部分:
函数库(glibc):是Linux下C语言的主要函数库。
编译器(gcc):可以将C,C++,汇编源程序和目标程序编译、链接成可执行文件。
系统头文件(glibc_header):系统相关功能的头文件集合。
编译开发工具:编辑器有Vi和Emacs;编译器为GCC,是GUN推出的功能强大、性能优越的多平台编译器;调试器为GDB,可以方便的设置断点、单步跟踪等调试功能;项目管理器“make”,用来控制编译或者重复编译,自动管理软件编译内容、方式和时机。
基于S3C2410嵌入式Linux的开发的逻辑空间和物理空间如何对应?详细描述之。
在支持MMU的32位处理器平台上,Linux系统中的物理存储空间和虚拟存储空间的地址范围分别都是从0x00000000到0xFFFFFFFF,共4GB,但物理存储空间与虚拟存储空间布局完全不同。Linux运行在虚拟存储空间,并负责把系统中实际存在的远小于4GB的物理内存根据不同需求映射到整个4GB的虚拟存储空间中。
n 物理存储空间布局
Linux的物理存储空间布局与处理器相关,详细情况可以从处理器用户手册的存储空间分布表(memory map)相关章节中查到,我们这里只列出嵌入式处理器平台Linux物理内存空间的一般布局,如图18-4所示。

图18-4 Linux物理内存空间一般布局示意图
说明:
1)最大node号n不能大于MAX_NUMNODES-1。
2)MAX_NUMNODES表示系统支持的最多node数。在ARM系统中,Sharp芯片最多支持16个nodes,其他芯片最多支持4个nodes。
3)numnodes是当前系统中实际的内存node数。
4)在不支持CONFIG_DISCONTIGMEM选项的系统中,只有一个内存node。
5)最大bank号m不能大于NR_BANKS-1。
6)NR_BANKS表示系统中支持的最大内存bank数,一般等于处理器的RAM片选数。在ARM系统中,Sharp芯片最多支持16个banks,其他芯片最多支持8个banks。
7)mem_init()函数会将所有节点的页帧位码表所占空间、孔洞页描述符空间及空闲内存页都释放掉。

n虚拟存储空间布局
在支持MMU的系统中,当系统做完硬件初始化后就使能MMU功能,这样整个系统就运行在虚拟存储空间中,实现虚拟存储空间到物理存储空间映射功能的是处理器的MMU,而虚拟存储空间与5路存储空间的映射关系则是由Linux内核来管理的。32位系统中物理存储空间占4GB空间,虚拟存储空间同样占4GB空间,Linux把物理空间中实际存在的远远小于4GB的内存空间映射到整个4GB虚拟存储空间中除映射I/O空间之外的全部空间,所以虚拟内存空间远远大于物理内存空间,这就说同一块物理内存可能映射到多处虚拟内存地址空间上,这正是Linux内存管理职责所在。图18-5列出了Linux内核中虚拟内存空间的一般布局(其实I/O空间也在其中,通常占用高端内存空间,在此未标出)。

图18-5 Linux系统虚拟内存空间一般布局示意图
说明:
1)线性地址空间:是指Linux系统中从0x00000000到0xFFFFFFFF整个4GB虚拟存储空间。
2)内核空间:内核空间表示运行在处理器最高级别的超级用户模式(supervisor mode)下的代码或数据,内核空间占用从0xC0000000到0xFFFFFFFF的1GB线性地址空间,内核线性地址空间由所有进程共享,但只有运行在内核态的进程才能访问,用户进程可以通过系统调用切换到内核态访问内核空间,进程运行在内核态时所产生的地址都属于内核空间。
3)用户空间:用户空间占用从0x00000000到0xBFFFFFFF共3GB的线性地址空间,每个进程都有一个独立的3GB用户空间,所以用户空间由每个进程独有,但是内核线程没有用户空间,因为它不产生用户空间地址。另外子进程共享(继承)父进程的用户空间只是使用与父进程相同的用户线性地址到物理内存地址的映射关系,而不是共享父进程用户空间。运行在用户态和内核态的进程都可以访问用户空间。
4)内核逻辑地址空间:是指从PAGE_OFFSET(3G)到high_memory(物理内存的大小,最大896)之间的线性地址空间,是系统物理内存映射区,它映射了全部或部分(如果系统包含高端内存)物理内存。内核逻辑地址空间与图18-4中的系统RAM内存物理地址空间是一一对应的(包括内存孔洞也是一一对应的),内核逻辑地址空间中的地址与RAM内存物理地址空间中对应的地址只差一个固定偏移量(3G),如果RAM内存物理地址空间从0x00000000地址编址,那么这个偏移量就是PAGE_OFFSET。
5)低端内存:内核逻辑地址空间所映射物理内存就是低端内存(实际物理内存的大小,但是小于896),低端内存在Linux线性地址空间中始终有永久的一一对应的内核逻辑地址,系统初始化过程中将低端内存永久映射到了内核逻辑地址空间,为低端内存建立了虚拟映射页表。低端内存内物理内存的物理地址与线性地址之间的转换可以通过__pa(x)和__va(x)两个宏来进行,#define __pa(x) ((unsignedlong)(x)-PAGE_OFFSET) __pa(x)将内核逻辑地址空间的地址x转换成对应的物理地址,相当于__virt_to_phys((unsigned long)(x)),__va(x)则相反,把低端物理内存空间的地址转换成对应的内核逻辑地址,相当于((void *)__phys_to_virt((unsigned long)(x)))。
6)高端内存:低端内存地址之上的物理内存是高端内存(物理内存896之上),高端内存在Linux线性地址空间中没有没有固定的一一对应的内核逻辑地址,系统初始化过程中不会为这些内存建立映射页表将其固定映射到Linux线性地址空间,而是需要使用高端内存的时候才为分配的高端物理内存建立映射页表,使其能够被内核使用,否则不能被使用。高端内存的物理地址于现行地址之间的转换不能使用上面的__pa(x)和__va(x)宏。
7)高端内存概念的由来:如上所述,Linux将4GB的线性地址空间划分成两部分,从0x00000000到0xBFFFFFFF共3GB空间作为用户空间由用户进程独占,这部分线性地址空间并没有固定映射到物理内存空间上;从0xC0000000到0xFFFFFFFF的第4GB线性地址空间作为内核空间,在嵌入式系统中,这部分线性地址空间除了映射物理内存空间之外还要映射处理器内部外设寄存器空间等I/O空间。0xC0000000~high_memory之间的内核逻辑地址空间专用来固定映射系统中的物理内存,也就是说0xC0000000~high_memory之间空间大小与系统的物理内存空间大小是相同的(当然在配置了CONFIG_DISCONTIGMEMD选项的非连续内存系统中,内核逻辑地址空间和物理内存空间一样可能存在内存孔洞),如果系统中的物理内存容量远小于1GB,那么内核现行地址空间中内核逻辑地址空间之上的high_memory~0xFFFFFFFF之间还有足够的空间来固定映射一些I/O空间。可是,如果系统中的物理内存容量(包括内存孔洞)大于1GB,那么就没有足够的内核线性地址空间来固定映射系统全部物理内存以及一些I/O空间了,为了解决这个问题,在x86处理器平台设置了一个经验值:896MB,就是说,如果系统中的物理内存(包括内存孔洞)大于896MB,那么将前896MB物理内存固定映射到内核逻辑地址空间0xC0000000~0xC0000000+896MB(=high_memory)上,而896MB之后的物理内存则不建立到内核线性地址空间的固定映射,这部分内存就叫高端物理内存。此时内核线性地址空间high_memory~0xFFFFFFFF之间的128MB空间就称为高端内存线性地址空间,用来映射高端物理内存和I/O空间。896MB是x86处理器平台的经验值,留了128MB线性地址空间来映射高端内存以及I/O地址空间,我们在嵌入式系统中可以根据具体情况修改这个阈值,比如,MIPS中将这个值设置为0x20000000B(512MB),那么只有当系统中的物理内存空间容量大于0x20000000B时,内核才需要配置CONFIG_HIGHMEM选项,使能内核对高端内存的分配和映射功能。什么情况需要划分出高端物理内存以及高端物理内存阈值的设置原则见上面的内存页区(zone)概念说明。
8)高端线性地址空间:从high_memory到0xFFFFFFFF之间的线性地址空间属于高端线性地址空间,其中VMALLOC_START~VMALLOC_END之间线性地址被vmalloc()函数用来分配物理上不连续但线性地址空间连续的高端物理内存,或者被vmap()函数用来映射高端或低端物理内存,或者由ioremap()函数来重新映射I/O物理空间。PKMAP_BASE开始的LAST_PKMAP(一般等于1024)页线性地址空间被kmap()函数用来永久映射高端物理内存。FIXADDR_START开始的KM_TYPE_NR*NR_CPUS页线性地址空间被kmap_atomic()函数用来临时映射高端物理内存,其他未用高端线性地址空间可以用来在系统初始化期间永久映射I/O地址空间。
http://blog.21ic.com/user1/8499/archives/2012/90535.html本回答被提问者采纳
参考技术C 闷农雪 参考技术D 捉腾歪 第5个回答  2012-12-23 菠脱失

嵌入式Linux之uboot移植

一、嵌入式Linux系统组成

嵌入式Linux系统一般由以下几部分组成:

  • 引导加载程序,包括固化在固件中的boot代码和BootLoader两大部分。有些CPU在运行BootLoader之前会先运行一段固化程序,比如x86的CPU会先运行BIOS中的固件,然后才运行硬盘的第一个分区(MBR)中的BootLoader,但是在大多的嵌入式系统中,是没有固件的,BootLoader就是上电后执行的第一个程序。 

  • linux内核:特定于嵌入式板子的定制内核以及内核的启动参数,内核的启动参数可以是内核默认的,也可以是由BootLoader传递给它的。

  • 文件系统: 包括根文件系统和建立于Flash内存设备上的文件系统,里面包含了linux系统能够运行所必须的应用程序、库等等,比如可以给用户提供LInux的控制界面shell程序、动态链接的程序时所需要的glibc或uClibc库等。

  • 用户应用程序    特定于用户的应用程序,它们也存储在文件系统中,有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面,常用的嵌入式GUI有:Qtopia和MinGUI等。显然,在嵌入式系统的固态存储设备上有相应的分区来存储它们。

在Flash存储器中,它们的分布一般如下:

 二、BootLoader

Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。

对于嵌入式系统,Bootloader是基于特定硬件平台来实现的。因此,几乎不可能为所有的嵌入式系统建立一个通用的Bootloader,不同的处理器架构都有不同的Bootloader。Bootloader不但依赖于CPU的体系结构,而且依赖于嵌入式系统板级设备的配置。对于2块不同的嵌入式板而言,即使它们使用同一种处理器,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,一般也都需要修改Bootloader的源程序。 反过来,大部分Bootloader仍然具有很多共性,某些Bootloader也能够支持多种体系结构的嵌入式系统。

例如,U-Boot就同时支持PowerPC、ARM、MIPS和X86等体系结构,支持的板子有上百种。通常,它们都能够自动从存储介质上启动,都能够引导操作系统启动,并且大部分都可以支持串口和以太网接口。

本节将对各种Bootloader总结分类,分析它们的共同特点。以U-Boot为例,详细讨论Bootloader的设计与实现。

2.1 u-boot工程简介

最早,DENX软件工程中心的Wolfgang Denk基于8xxrom的源码创建了PPCBOOT工程,并且不断添加处理器的支持。后来,Sysgo Gmbh把ppcboot移植到ARM平台上,创建了ARMboot工程。然后以ppcboot工程和armboot工程为基础,创建了U-Boot工程。

现在U-Boot已经能够支持PowerPC、ARM、X86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强并且开发最积极的开放源码BootLoader。目前仍然由DENX的Wolfgang Denk维护。

u-boot的源码包可以从sourceforge网站下载,还可以订阅该网站活跃的U-Boot Users邮件论坛,这个邮件论坛对于U-Boot的开发和使用都很有帮助。

u-boot软件包下载网站:https://ftp.denx.de/pub/u-boot/

DENX相关的网站:http://www.denx.de/re/DPLG.html

u-boot git仓库:https://gitlab.denx.de/u-boot/u-boot

2.2 U-Boot源码结构

从网站上下载得到u-boot源码包,例如:u-boot-2016.05.tar.bz2 (最新的u-boot版本已经不支持s3c2440)。

解压就可以得到全部u-boot源程序。在顶层目录下有20个子目录:

分别存放和管理不同的源程序。这些目录中所要存放的文件有其规则,可以分为3类。 

  1. 第1类目录与处理器体系结构或者开发板硬件直接相关; ·
  2. 第2类目录是一些通用的函数或者驱动程序; 
  3. 第3类目录是U-Boot的应用程序、工具或者文档。

下面列出了U-Boot顶层目录下各级目录存放原则:

  • api: 硬件无关的功能函数的API。uboot移植时基本不用管,这些函数是uboot本身使用的。
  • arch: 与体系结构相关的代码。比如 arm、 avr32、 m68k 等,我们现在用的是 ARM 芯片,所以只需要关心 arm 文件夹即可。
  • board:存放电路板相关的目录文件,主要包含SDRAM、FLASH驱动,例如:samsung/smdk2410、samsung/smdk5250等目录。
  • cmd:命令相关代码。
  • common:common是普遍的普通的,这个文件夹下放的是一些与具体硬件无关的普遍适用的一些代码。譬如控制台实现、crc校验的。但是更多的主要是两类:一类是cmd开头的,是用来实现uboot的命令系统的;另一类是env开头的,是用来实现环境变量的。
  • cpu: 这个目录是SoC相关的,里面存放的代码都是SoC相关初始化和控制代码(譬如CPU的、中断的、串口等SoC内部外设的,包括起始代码start.S也在这里)。里面很多子文件夹,每一个子文件夹就是一个SoC系列。
  • configs:此文件夹为 uboot 配置文件, uboot 是可配置的,但是你要是自己从头开始一个一个项目的配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。我们可以在这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的配置文件统一命名为“xxx_defconfig”, xxx 表示开发板名字,这些 defconfig 文件都存放在 configs文件夹。
  • disk: 磁盘有关的。
  • doc:文档目录,里面存放了很多uboot相关文档,这些文档可以帮助我们理解uboot代码。
  • drivers:顾名思义,驱动。这里面放的就是从linux源代码中扣出来的原封不动的linux设备驱动,主要是开发板上必须用到的一些驱动,如网卡驱动、Inand/SD卡、NandFlash等的驱动。要知道:uboot中的驱动其实就是linux中的驱动,uboot在一定程度上移植了linux的驱动给自己用。但是linux是操作系统而uboot只是个裸机程序,因此这种移植会有不同,让我说:uboot中的驱动其实是linux中的驱动的一部分。
  • dts:设备树。
  • examples:示例代码。
  • fs:filesystem,文件系统。这个也是从linux源代码中移植过来的,用来管理Flash等资源。
  • include:头文件目录。uboot和linux kernel在管理头文件时都采用了同一个思路,就是把所有的头文件全部集中存放在include目录下,而不是头文件跟着自己对应的c文件。所以在uboot中头文件包含时路径结构要在这里去找。
  • lib:架构相关的库文件。这类文件夹中的内容移植时基本不用管。
  • Licenses:许可证相关文件。
  • net: 网络相关的代码,譬如uboot中的tftp nfs ping命令 都是在这里实现的。
  • post:加电自检程序。
  • scripts:脚本文件。
  • test:测试代码。
  • tools:里面是一些工具类的代码。譬如mkimage。

U-Boot的源代码包含对几十种处理器、数百种开发板的支持。可是对于特定的开发板,配置编译过程只需要其中部分程序。这里具体以S3C2440 ARM920T处理器为例,具体分析S3C2410处理器和开发板所依赖的程序,以及U-Boot的通用函数和工具。

2.3 U-Boot编译

我们将下载的U-Boot通过samba上传到ubuntu服务器上:

复制uboot到/word/hardware路径下,解压u-boot-2016.tar.bz2:

cd /work/sambashare
mv u-boot-2016.05.tar.bz2 /work/hardware
cd /work/hardware
sudo apt-get install  bzip2
tar -jxvf   u-boot-2016.05.tar.bz2
cd  u-boot-2016.05

uboot的编译分为两步:配置、编译:

(1) 配置选择所要使用的board ,我调试使用的是S3C2440,但是configs目录下没有smdk2440_defconfig这个文件,只有smdk2410_defconfig,因此执行如下命令,生成.config文件:

make smdk2410_defconfig

(2) 编译、执行make命令,生成u-boot:

 make ARCH=arm CROSS_COMPILE=arm-linux-

CROSS-COPILE是在Makefile文件中定义的变量,是用来指定交叉工具链,ARCH用来指定处理器架构。此外,我们可以在u-boot的顶层Makefile中定义:

CROSS_COMPILE=arm-linux-
ARCH=arm

这样就省去了每次编译都要在控制台输入的麻烦。

编译成功后会生成一个u-boot.bin,可以烧写到开发板上,不过一般是用不起来的,需要进一步修改。

三、u-boot编译过程目标依赖分析

3.1 u-boot目标依赖关系图

根据以上目标依赖关系图,为了分析方便,将整个目标依赖图分为三部分:_all、u-boot、prepare。

3.2 目标_all

从顶层的Makefile开始查找,首先找到的目标为_all,128行:

# That\'s our default target when none is given on the command line
PHONY := _all
_all:

在Makefile中,.PHONY后面的target表示的也是一个伪造的target, 而不是真实存在的文件target,注意Makefile的target默认是文件。

紧接着会对_all伪目标添加all伪目标依赖,194行:

# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

当我们定义了M变量或者SUBDIRS时表示编译一个外部模块,显然我们并没有编译一个外部模块。因此伪目标_all依赖al。

all自身依赖于$(ALL-y),803行:

all:        $(ALL-y)
ifneq ($(CONFIG_SYS_GENERIC_BOARD),y)
    @echo "===================== WARNING ======================"
    @echo "Please convert this board to generic board."
    @echo "Otherwise it will be removed by the end of 2014."
    @echo "See doc/README.generic-board for further information"
    @echo "===================================================="
endif
ifeq ($(CONFIG_DM_I2C_COMPAT),y)
    @echo "===================== WARNING ======================"
    @echo "This board uses CONFIG_DM_I2C_COMPAT. Please remove"
    @echo "(possibly in a subsequent patch in your series)"
    @echo "before sending patches to the mailing list."
    @echo "===================================================="
endif

3.3 目标$(ALL-y)

$(ALL-y)定义了最终需要生成的所有文件,731行

# Always append ALL so that arch config.mk\'s can add custom ones
ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check

ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
ifeq ($(CONFIG_SPL_FSL_PBL),y)
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
else
ifneq ($(CONFIG_SECURE_BOOT), y)
# For Secure Boot The Image needs to be signed and Header must also
# be included. So The image has to be built explicitly
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
endif
endif
ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
ifeq ($(CONFIG_SPL_FRAMEWORK),y)
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
endif
ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
ifneq ($(CONFIG_SPL_TARGET),)
ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
endif
ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi

ifneq ($(BUILD_ROM),)
ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
endif

# enable combined SPL/u-boot/dtb rules for tegra
ifeq ($(CONFIG_TEGRA)$(CONFIG_SPL),yy)
ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb-tegra.bin
endif

# Add optional build target if defined in board/cpu/soc headers
ifneq ($(CONFIG_BUILD_TARGET),)
ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
endif

以上的$(ALL-y)目标中看起来很复杂,但是除了第一行的通用目标外,其余的目标都只有在特殊条件下才生成,这里略去不提,只分析通用目标依赖:

ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check

3.4 目标u-boot.bin

(1) 在$(ALL-y)的通用目标中,先分析u-boot.bin,826行

ifeq ($(CONFIG_OF_SEPARATE),y)
u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
    $(call if_changed,cat)

u-boot.bin: u-boot-dtb.bin FORCE
    $(call if_changed,copy)
else
u-boot.bin: u-boot-nodtb.bin FORCE
    $(call if_changed,copy)
endif

由于没有定义CONFIG_OF_SEPARATE选项,所以我们关心else中定义的依赖关系。

if_changed函数检测依赖文件是否有更新或目标文件是否不存在, 然后echo打印传入的参数,最后执行传入的命令copy。面贴出 scripts/Kbuild.include文件中的部分内容,大家可以自行分析

u-boot.bin: u-boot-nodtb.bin FORCE
    echo COPY    $@; cp $<  $@      #输出 COPY   u-boot.bin, 然后执行cp u-boot-nodtb.bin u-boot.bin

(2) 目标u-boot-nodtb.bin

分析867行,u-boot-nodtb.bin依赖u-boot:

u-boot-nodtb.bin: u-boot FORCE
    $(call if_changed,objcopy)
    $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
    $(BOARD_SIZE_CHECK)

(3) 目标u-boot

分析1191行:

u-boot:    $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
    $(call if_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
    $(call cmd,smap)
    $(call cmd,u-boot__) common/system_map.o
endif

(4) u-boot-init,u-boot-main

u-boot-init := $(head-y)
u-boot-main := $(libs-y)

在linux上搜索head-y这个目标: grep \'head-y\' * -nR 得到下面这个结果: arch/arm/Makefile:74:head-y := arch/arm/cpu/$(CPU)/start.o 所以head-y指的是start.S.

在顶层目录Makefile中搜索libs-y可以发现其包含许多目录 比如:

libs-y += drivers/mtd/ 

另:

libs-y := $(patsubst %/, %/built-in.o, $(libs-y))

这条规则使libs-y中每个条目的最后一个斜杠替换成 /built-in.o,比如 drivers/mtd/ 会变为 drivers/mtd/built-in.o

可见libs-y最后指向各个文件夹的built-in.o,而这些built-in.o则由 Kbuild Makefile 将obj-y所包含的各个文件编译而成,

具体可研究 scripts/Kbuild.include 和 scripts/Makefile.build。

3.5 %config

一般我们在执行make之前都需要配置一下开发板参数,比如make smdk2410_config,在顶层目录Makefile有:

# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic
    $(Q)rm -f .tmp_quiet_recordmcount

# To avoid any implicit rule to kick in, define an empty command.
scripts/basic/%: scripts_basic ;

build 在 scripts/Kbuild.include定义:

build := -f $(srctree)/scripts/Makefile.build obj

展开就是:

@make -f scripts/Makefile.build obj=scripts/basic

第476行:

config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

%config: scripts_basic outputmakefile FORCE
    $(Q)$(MAKE) $(build)=scripts/kconfig $@

展开就是:

@make -f scripts/Makefile.build obj=scripts/kconfig %config

scripts/Makefile.build中:

scripts/Makefile.build中:

12: prefix := spl
13: src := $(patsubst $(prefix)/%,%,$(obj))         #obj=scripts/kconfig, src := scripts/kconfig

57: kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))                    
  #kbuild-dir := $(srctree)/scripts/kconfig

58: kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)   
#由于没有scripts/kconfig/Kbuild这个文件,所以 kbuild-file := $(srctree)/scripts/kconfig/Makefile

59: include $(kbuild-file)  #在这里将scripts/kconfig/Makefile 添加进来了

%config这个目标的生成规则在scripts/kconfig/Makefile中:

15: SRCARCH := ..    
113:%_defconfig: $(obj)/conf
114:    $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)         
      #执行:@scripts/kconfig/conf --defconfig=arch/../configs/%_defconfig Kconfig      (没有进入arch目录)
      #即:  @scripts/kconfig/conf --defconfig=configs/%_defconfig Kconfig 
115116:# Added for U-Boot (backward compatibility)
117:%_config: %_defconfig
118:    @:

分析到这里,我们可以看出配置单板相关的参数是在 scripts/kconfig/conf 中进行的, 传入的参数是 --defconfig=configs/%_defconfig Kconfig 可以推测conf会从configs目录下读取对应的defconfig文件进行解析然后保存参数,但我看了一下scripts/kconfig/conf.c 的代码,发现实在是过于复杂,恕博主不继续深入分析,如果以后有时间会再继续研究。

3.6 分析if_changed规则

   scripts/Kbuild.include:
     7:squote  := \'

    30:escsq = $(subst $(squote),\'$(squote)\',$1)

   217echo-cmd = $(if $($(quiet)cmd_$(1)),
   218echo \'  $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)\';)

   234:ifneq ($(KBUILD_NOCMDDEP),1)
   235:# Check if both arguments has same arguments. Result is empty string if equal.
   236:# User may override this check using make KBUILD_NOCMDDEP=1
   237:arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) 
   238:                    $(filter-out $(cmd_$@),   $(cmd_$(1))) )
   239else
   240:arg-check = $(if $(strip $(cmd_$@)),,1)
   241:endif

   249make-cmd = $(call escsq,$(subst #,\\#,$(subst $$,$$$$,$(cmd_$(1)))))

   253:any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)

   257:if_changed = $(if $(strip $(any-prereq) $(arg-check)),              
   258:    @set -e;                                                            
   259:    $(echo-cmd) $(cmd_$(1));                                            
   260:    printf \'%s
\' \'cmd_$@ := $(make-cmd)\' > $(dot-target).cmd)

   314echo-why = $(call escsq, $(strip $(why)))

这里简单分析一下if_changed规则:

  • 首先any-prereq, arg-check目标是检测目标和依赖是否存在或有更新;
  • set -e 发生错误后立即停止,此处是为了不让错误像滚雪球一样越来越多;
  • echo-cmd 由于是”安静模式”下进行编译的,所以这个参数会被展开成: echo \' quiet_cmd_cmd(1) \';此处忽略why, 编译的时候也没出现why信息, 估计是被屏蔽了(KBUILD_VERBOSE不等于2);
  • 所以259行可以展开成: echo \' quiet_cmd_cmd(1) \'; $(cmd_$(1)); 即先打印信息,然后执行命令;

大家分析命令的时候可以搜索 quiet_cmd_xxx, 还有cmd_xxx, “xxx”是调用call函数时传入的命令参数.

3.7 总结

依赖关系如下:

all -> u-boot.bin -> u-boot-nodtb.bin -> u-boot |-> u-boot-init -> head-y (start.o)    -> start.S
                                                |-> u-boot-main -> libs-y (built-in.o) -> obj-y ...

参考文章:

[1] bootloader 详细介绍

[2] 新版u-boot移植到s3c2440开发板(一)--建立单板

[3] UBOOT2016.07移植(第一篇)初步分析(部分转载)

[4] 【u-boot-2018.11】make编译过程目标依赖分析

[5]U-Boot工程目录介绍

 

以上是关于典型嵌入式linux软件部分由哪些模块组成?他们的功能及相互联系? Bootloader分为哪两阶段?分的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式接口复习资料

机器视觉系统是由哪些部分组成的

什么是嵌入式系统的软硬件协同设计

嵌入式系统综述

这几款嵌入式操作系统,你用过哪些?

Linux内核模块文件组成介绍