本博客主要对学习嵌入式Linux进行总结,学习视频参考正点原子。开发板使用Imx6ull芯片。
1.、Uboot启动Linux系统
1.0 从网络启动linux
1.使用tftp命令将zImage下载到DDR的0x800000地址处
tftp 80800000 zImage
2.将设备树imx6ull_mynand.dtb下载到DDR的0x83000000
tftp 83000000 imx6ull_mynand.dtb
3.bootz启动linux内核
bootz 80800000 - 83000000
2.0 EMMC中启动linux
1.使用fatload命令将zImage下载到DDR的80800000
fatload mmc 1:1 80800000 zImage
2.使用fatload命令将设备树下载到DDR的83000000
fatload mmc 1:1 83000000 imx6ull_mynand.dtb
3.bootz启动linux内核
bootz 80800000 - 83000000
3.0 设置bootcmd环境变量进行网络启动linux
1.设置环境变量
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull_mynand.dtb; bootz 80800000 - 83000000'
2.保存环境变量
saveenv
3.启动linux
boot
2、Linux内核编译过程
对于linux编译需要安装lzop软件,使用sudo apt-get install lzop进行安装
2.1 首先创建编译脚本文件
1.将arch/arm/configs/目录下的imx_v7_mfg_defconfig文件拷贝并命名为imx_mynand_defconfig,这是linux默认配置文件
2.创建imx6ull_mynand.sh文件,给该文件赋予权限,使用sudo chmod 777 ./imx6ull_mynand.sh,在该文件中添加如下指令,并执行./mx6ull_alientek_emmc.sh
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean # 清除工程中的一些文件
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_mynand_defconfig # 该名称与之前创建的默认配置文件名称相同
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig # 图形配置界面
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16 # 编译
3.当弹出图行界面配置时不用理会,直接两下esc退出即可
4.当编译完成后,最终会生成/arch/arm/boot/zImage镜像文件镜像和arch/arm/boot/dts/imx6ull-mynand.dtb设备树文件。
总结:Linux 的编译过程基本和 uboot 一样,都要先执行“make xxx_defconfig”来配置一下,然后再执行“make”进行编译。如果需要使用图形界面配置的话就执行“make menuconfig”。
2.2 顶层Makefile分析
Linux 的顶层Makefile 和 uboot 的顶层 Makefile 非常相似,因为 uboot 参考了Linux,前602行几乎一样
在第258和259行定义ARM架构和编译器
2.2.1 make xxx_defconfig执行过程
当执行该指令时,与顶层Makefile中的第548行目标“%config”匹配相匹配,“%config”依赖scripts_basic、outputmakefile 和 FORCE,“%config”真正有意义的依赖就只有scripts_basic。
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@ # 执行该程序
其中scripts_basic规则为:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
build 定义在文件scripts/Kbuild.include 中,值为 build := -f $(srctree)/scripts/Makefile.build obj
---> 命令展开就是:@make -f ./scripts/Makefile.build obj=scripts/basic
其中$(Q)$(MAKE) $(build)=scripts/kconfig $@
--->命令展开就是: @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
总结:执行make xxx_defconfig指令会对应执行如下命令
@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
1.分析@make -f ./scripts/Makefile.build obj=scripts/basic指令
打开Makefile.build文件分析
将 kbuild-dir 展开后为: kbuild-dir=./scripts/basic
将 kbuild-file 展开后为: kbuild-file= ./scripts/basic/Makefile
include $(kbuild-file)展开后为: include ./scripts/basic/Makefile
@make -f ./scripts/Makefile.build obj=scripts/basic该命令没有指定目标,所以会使用到默认目标__build
则_build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
目标__build 有 5 个依赖,其中只有 always 有效
故__build: scripts/basic/fixdep scripts/basic/bin2c
scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 和 scripts/basic/bin2c 这两个软件
2.分析@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig指令
此命令会使用到的各个变量值
src= scripts/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile
由此可得知Makefile.build 会读取 scripts/kconfig/Makefile 中的内容,该文件中的代码段为
%_defconfig: $(obj)/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
目标%_defconfig 与 xxx_defconfig 匹配,会执行这条规则,将该代码段展开为
%_defconfig: scripts/kconfig/conf
@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig
%_defconfig 依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c 生成conf 这个软件。
此conf 软件就会将%_defconfig 中的配置输出到.config 文件中,最终生成 Linux kernel 根目录下的.config 文件
2.2.2 make命令执行过程
使用命令“make xxx_defconfig”配置好Linux内核以后就可以使用“make”或者“make all”命令进行编译
_all:…… # _all是默认目标,如果使用命令“make”编译 Linux 的话此目标就会被匹配
_all: all # 默认目标_all依赖all。
all: vmlinux # 目标all依赖vmlinux
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
KBUILD_LDS = arch/arm/kernel/vmlinux.lds
KBUILD_VMLINUX_INIT = $(head-y) $(init-y)
KBUILD_VMLINUX_MAIN = $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux 的依赖为:scripts/link-vmlinux.sh、$(head-y) 、$(init-y)、$(core-y) 、$(libs-y) 、$(drivers-y) 、$(net-y)、arch/arm/kernel/vmlinux.lds 和 FORCE
head-y = arch/arm/kernel/head.o
init-y = init/built-in.o
drivers-y = drivers/built-in.o sound/built-in.o firmware/built-in.o
net-y = net/built-in.o
libs-y = arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o
core-y = usr/built-in.o arch/arm/vfp/built-in.o \
arch/arm/vdso/built-in.o arch/arm/kernel/built-in.o \
arch/arm/mm/built-in.o arch/arm/common/built-in.o \
arch/arm/probes/built-in.o arch/arm/net/built-in.o \
arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o \
arch/arm/mach-imx/built-in.o kernel/built-in.o\
mm/built-in.o fs/built-in.o \
ipc/built-in.o security/built-in.o \
crypto/built-in.o block/built-in.o
head-y 、init-y、core-y 、libs-y 、drivers-y 和 net-y 这 6 个变量都是一些 built-in.o 或.a 等文件,这个和 uboot 一样,都是将相应目录中的源码文件进行编译,然后在各自目录下生成 built-in.o 文件,有些生成了.a 库文件。
最终将这些 built-in.o 和.a 文件进行链接(使用vmlinux.lds链接脚本,其中vmlinux-deps依赖KBUILD_LDS且KBUILD_LDS等于vmlinux.lds)即可形成ELF格式的可执行文件,也就是vmlinux
1 但是链接是需要链接脚本的,vmlinux 的依赖arch/arm/kernel/vmlinux.lds 就是整个Linux 的链接脚本arch/arm/kernel/vmlinux.lds。
2 顶层Makefile文件中的+$(call if_changed,link-vmlinux) 表示将$(call if_changed,link-vmlinux)的结果作为最终生成 vmlinux 的命令,前面的“+”表示该命令结果不可忽略
其中link-vmlinux 是函数 if_changed 的参数,函数 if_changed 定义在文件 scripts/Kbuild.include 中,该文件中有命令$(echo-cmd) $(cmd_$(1));
3 $(echo-cmd)用于打印命令执行过程,$(cmd_$(1))中的$(1)表示参数,也就是 link-vmlinux,$(cmd_$(1))表示执行 cmd_link-vmlinux 的内容
4 cmd_link-vmlinux 会调用scripts/link-vmlinux.sh这个脚本来链接出vmlinux
至此我们基本理清了 make 的过程,重点就是将各个子目录下的 built-in.o、.a 等文件链接在一起,最终生成vmlinux 这个ELF 格式的可执行文件
各个目录下的built-in.o 文件编译生成过程
在每个目录下均会生成一个built-in.o文件,这些文件通过vmlinux.lds脚本进行链接,最终调用link-vmlinux.sh脚本输出vmlinux镜像文件,最后被压缩成zImage文件,了解built-in-o如何编译。
1.vmliux 依赖 vmlinux-deps,而 vmlinux-deps= $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
其中KBUILD_LDS是链接脚本,这里不考虑,剩下的KBUILD_VMLINUX_INIT 和KBUILD_VMLINUX_MAIN 就是各个子目录下的 built-in.o、.a 等文件。
最终 vmlinux-deps 的值如下
vmlinux-deps = arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o \
init/built-in.o usr/built-in.o \
arch/arm/vfp/built-in.o arch/arm/vdso/built-in.o \
arch/arm/kernel/built-in.o arch/arm/mm/built-in.o \
arch/arm/common/built-in.o arch/arm/probes/built-in.o \
arch/arm/net/built-in.o arch/arm/crypto/built-in.o \
arch/arm/firmware/built-in.o arch/arm/mach-imx/built-in.o \
kernel/built-in.o mm/built-in.o \
fs/built-in.o ipc/built-in.o \
security/built-in.o crypto/built-in.o\
block/built-in.o arch/arm/lib/lib.a\
lib/lib.a arch/arm/lib/built-in.o\
lib/built-in.o drivers/built-in.o \
sound/built-in.o firmware/built-in.o \
net/built-in.o
在顶层Makefile文件中有如下命令可以编译链接
$(sort $(vmlinux-deps)): $(vmlinux-dirs);
vmlinux-dirs主要和目录有关,此变量保存着生成 vmlinux 所需源码文件的目录,,vmlinux-dirs的值为
vmlinux-dirs = init usr arch/arm/vfp \
arch/arm/vdso arch/arm/kernel arch/arm/mm \
arch/arm/common arch/arm/probes arch/arm/net \
arch/arm/crypto arch/arm/firmware arch/arm/mach-imx\
kernel mm fs \
ipc security crypto \
block drivers sound \
firmware net arch/arm/lib \
lib
$(vmlinux-dirs): prepare scripts 目标 vmlinux-dirs 依赖 prepare 和 scripts
$(Q)$(MAKE) $(build)=$@ 展开为make -f ./scripts/Makefile.build obj=$@
其中$@表示目标文件,也就是vmlinux-dirs的值,将vmlinux-dirs中的这些目录全部带入到命令中如下:
@ make -f ./scripts/Makefile.build obj=init
@ make -f ./scripts/Makefile.build obj=usr
@ make -f ./scripts/Makefile.build obj=arch/arm/vfp
@ make -f ./scripts/Makefile.build obj=arch/arm/vdso
@ make -f ./scripts/Makefile.build obj=arch/arm/kernel
@ make -f ./scripts/Makefile.build obj=arch/arm/mm
@ make -f ./scripts/Makefile.build obj=arch/arm/common
@ make -f ./scripts/Makefile.build obj=arch/arm/probes
@ make -f ./scripts/Makefile.build obj=arch/arm/net
@ make -f ./scripts/Makefile.build obj=arch/arm/crypto
@ make -f ./scripts/Makefile.build obj=arch/arm/firmware
@ make -f ./scripts/Makefile.build obj=arch/arm/mach-imx
@ make -f ./scripts/Makefile.build obj=kernel
@ make -f ./scripts/Makefile.build obj=mm
@ make -f ./scripts/Makefile.build obj=fs
@ make -f ./scripts/Makefile.build obj=ipc
@ make -f ./scripts/Makefile.build obj=security
@ make -f ./scripts/Makefile.build obj=crypto
@ make -f ./scripts/Makefile.build obj=block
@ make -f ./scripts/Makefile.build obj=drivers
@ make -f ./scripts/Makefile.build obj=sound
@ make -f ./scripts/Makefile.build obj=firmware
@ make -f ./scripts/Makefile.build obj=net
@ make -f ./scripts/Makefile.build obj=arch/arm/lib
@ make -f ./scripts/Makefile.build obj=lib
具体每个命令的生成不再介绍,最终在每个目录下的所有.o 文件链接在一起,最终形成每个目录下的built-in.o,再由vmlinux.lds脚本进行链接,最终调用link-vmlinux.sh脚本输出vmlinux镜像文件,最后被压缩成zImage文件。
Linux编译基本和 uboot 的顶层 Makefile 一样,重点在于vmlinux 的生成。最后将 vmlinux 压缩成我们最常用的 zImage 或 uImage 等文件
3、linux启动流程
分析linux启动过程,首先对linux源码进行编译(见上一节)
1. linux内核链接脚本文件为arch/arm/kernel/vmlinux.lds,在该文件中ENTRY(stext) 为整个linux内核入口,入口为stext
2. 分析stext,定义在文件arch/arm/kernel/head.S中88行
safe_svcmode_maskall r9 # 第100行,确保CPU 处于 SVC 模式,并且关闭了所有的中断。safe_svcmode_maskall 定义在文件arch/arm/include/asm/assembler.h 中
mrc p15, 0, r9, c0, c0 # 第102行,读处理器ID,ID 值保存在r9 寄存器中
bl __lookup_processor_type # 第103行,调用函数__lookup_processor_type 检查当前系统是否支持此 CPU,如果支持就获取 procinfo 信息
bl __vet_atags # 第130行,调用函数__vet_atags 验证 atags 或设备树(dtb)的合法性。函数__vet_atags 定义在文件arch/arm/kernel/head-common.S 中。
bl __create_page_tables # 第139行,调用函数__create_page_tables 创建页表
ldr r13, =__mmap_switched # 第149行,将函数__mmap_switched 的地址保存到r13 寄存器中。__mmap_switched 定义在文件 arch/arm/kernel/head-common.S,__mmap_switched 最终会调用 start_kernel 函数。
1: b __enable_mmu # 第159行,调 用 __enable_mmu 函 数 使 能 MMU , __enable_mmu 定 义 在 文 件arch/arm/kernel/head.S 中。__enable_mmu 最终会通过调用__turn_mmu_on 来打开 MMU,__turn_mmu_on 最后会执行 r13 里面保存的__mmap_switched 函数。
3.执行stext程序,最终会执行第149行__mmap_switched函数(定义在文件 arch/arm/kernel/head-common.S),通过分析该函数,最终会执行b start_kernel程序来启动linux内核(start_kernel 函数定义在文件 init/main.c中)