这里以uboot 2014年11月的主线代码为例分析从uboot到linux的全过程。之所以写这篇文章,是由于网上的资料多数都很陈旧,诸如start_armboot之类的函数在新的代码里根本找不到了。由于uboot支持的CPU以及Board非常的多,所以本文仅以Samsung exynos为例来介绍这个过程。
从上电到uboot启动:
1./arch/arm/cpu/armv7/start.S: reset——uboot的汇编入口
2./arch/arm/lib/crt0.S: _main
3./arch/arm/lib/board.c: board_init_f——初始化第一阶段
4./arch/arm/lib/board.c: board_init_r——初始化第二阶段
5./common/main.c: main_loop——uboot主循环
uboot启动Linux
1.uboot中有个bootd的命令选项,执行该命令会进入/common/cmd_bootm.c: do_bootd
2.common/cli.c: run_command,传入bootcmd命令作为参数。
3.common/cmd_bootm.c: do_bootm
4.arch/arm/lib/bootm.c: do_bootm_linux
5.arch/arm/lib/bootm.c: do_jump_linux——跳转到Linux内核的入口地址
uImage格式是专为uboot开发的格式,主要解决了uboot和linux在嵌入式设备的存储上共存的问题。
从main_loop到命令处理:
1./common/main.c: main_loop
2./common/cli.c: cli_loop
3./common/cli_simple.c: cli_simple_loop
4./common/cli.c: run_command_repeatable
5./common/cli_simple.c: cli_simple_run_command
6./common/cli_simple.c: cmd_process
7./common/command.c: cmd_call
上面的流程仅是主循环如何调用命令回调函数的过程。下面介绍一下命令是如何声明、存储和查询的。
首先查看链接脚本,uboot使用的链接脚本文件名为u-boot.lds。根据cpu和board的不同,u-boot.lds也有所差异。例如Samsung exynos所用的u-boot.lds在arch\arm\cpu下。
其中有个.u_boot_list
段就是用来存储命令数据的。它的表述如下所示:
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
命令的声明,通常使用U_BOOT_CMD宏。这个宏最终展开为:
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))
这也就是.u_boot_list*
的来历了。
可以使用/common/command.c: find_cmd函数在命令列表中,根据名称查找命令数据。
/common/cmd_nvedit.c: setenv–这个函数用于设置环境变量的值。它的原理是:
1.首先在环境变量数组default_environment中,更改相应内容的值。
2.然后调用saveenv,保存default_environment的值,到具体的硬件中。例如NAND设备的代码在/common/env_nand.c中。
在linux内核层面也可以修改uboot的环境变量。通常的做法步骤如下:
1.uboot代码中有个tools/env文件夹。编译改代码可以得到fw_printenv文件。编译的命令是:
make env
2.将fw_printenv放到linux系统的/usr/sbin路径下,并创建符号链接fw_setenv。此处的符号链接并不是可有可无的,这里有个编程小技巧:如何用同一个可执行文件执行不同的功能呢?
除了最常用的使用参数区分的方法之外,还可以采用如下方法:
int main(int argc, char *argv[])
这是main函数的声明,其中argv是参数数组,而argv[0]是输入的命令本身,因此可以使用这个作为判断依据,来区分不同的用途。这时候符号链接也就派上用场了。
有些uboot提供了tftpsrv的功能,用于从网口传输文件(主要是烧写用的镜像文件)。
该tftpsrv默认监听的ip地址保存在uboot的ip环境变量中。如果需要的话,可进行必要的修改并重启。
客户端传输镜像文件时,需要采用二进制模式。命令如下:
tftp 10.3.9.161 -m binary -c put <file name>
这是源代码直接生成的镜像文件。以x86平台为例:
arch\x86\kernel\vmlinux.lds.S–这是链接脚本的源代码,经过C语言的宏预处理之后会生成vmlinux.lds,使用这个脚本,链接即可得到vmlinux,其过程与普通应用程序并无太大区别,也就是个elf文件罢了。
vmlinux使用objcopy处理之后,生成的不包含符号表的镜像文件。这是linux默认生成的结果。
zImage = 使用gzip压缩后的image + GZip自解压代码。使用make zImage
或者make bzImage
创建。两者的区别是zImage只适用于大小在640KB以内的内核镜像。
uImage = uImage header + zImage。使用uboot提供的mkimage工具创建。
以上的这些镜像文件的关系可参见:
http://www.cnblogs.com/armlinux/archive/2011/11/06/2396786.html
http://www.linuxidc.com/Linux/2011-02/32096.htm
一般来说,一个完整的linux系统,不仅包括内核,还包括bootloader和若干分区。这些镜像文件散布,不利于批量生产的进行。这时就需要将之打包,并生成一个可直接用于生产烧写的Flash镜像。
可使用mtd-utils库中的ubinize工具生成Flash镜像。
mtd-utils的官网是:
http://www.linux-mtd.infradead.org/
安装方法:
sudo apt install mtd-utils
mtd-utils还可用于烧写分区。例如如下命令:
mtd write xyz.uimage linux
其中xyz.uimage
是镜像文件名,linux
是分区名称。
参考:
http://blog.csdn.net/andy205214/article/details/7390287
利用mkfs.ubifs和ubinize两个工具制作UBI镜像
从代码来查看板子的MTD分区方案,主要是搜索mtd_partition类型的使用定义。比如mini2440板子的分区方案可在mini2440_default_nand_part数组中查到。
MTD(memory technology device):内存技术设备,是linux用于描述ROM,NAND,NOR等内存设备的子系统的抽象。
除了MTD之外,常用的还有SPI Flash方案。
Linaro虽然名义上是一家非营利性质的开放源代码软件工程公司。然而,它所提供的ARM工具链,基本上已经是ARM御用级别的了。
官网:
https://www.linaro.org/
Yocto是一个开源协作项目,它提供了一些模板、工具和方法来支持面向嵌入式产品的自定义Linux系统。
官网:
https://www.yoctoproject.org/
它也提供了一套工具链,依附于旗下的子项目:Poky Linux。似乎NXP用的较多。
参考:
https://www.ibm.com/developerworks/cn/linux/l-yocto-linux/index.html
使用Yocto Project构建自定义嵌入式Linux发行版
大致的原则是:
1.使用对应的bit的工具链。
2.链接的库也需要是对应bit的。
32bit float:
-march=armv7-a -mfloat-abi=hard -mfpu=neon
这里主要参考以下文档:
http://ju.outofmemory.cn/entry/176659
为防遗失,摘录如下:
上图为OpenWrt的网络接口图。
有线网络配置文件:/etc/config/network
无线网络配置文件:/etc/config/wireless
pppoe-wan:虚拟设备,拨号上网接口。
lo:虚拟设备,自身的回环网设备。
ra0 rai0:无线设备,它们各自对应一个SSID,分别是2.4G和5G。
iptables是Openwrt的防火墙软件命令,可参见:
http://wiki.openwrt.org/doc/howto/netfilter
这里将主要用法示例如下:
iptables -I zone_wan_input 4 -p tcp --dport 22 -j ACCEPT # allow SCP on WAN port
类似的东西还有nftables和一些基于eBPF技术的软件包。
大多数情况下,我们并不需要亲手编写OpenWrt模块,因为日常使用中的绝大部分功能,OpenWrt项目都提供了相应的扩展包。因此,如何寻找、安装扩展包,就成为一个经常性的问题。
官方扩展包可以在代码根目录下的feeds.conf.default文件中找到。
从这里可以看出,OpenWrt项目早期的代码托管在openwrt.org下,而最近的开发已经迁移至github。因此,如果有的包在源代码中没有看到的话,可以到openwrt.org下碰碰运气。
一个软件包可以生成独立的ipk安装文件(Modularizes),也可以直接打包进img中(Built-in)。这个生成选项在make menuconfig
的选项菜单中,选择Y就是Built-in,选择M就是Modularizes。
由于完整的GDB尺寸太大(~1.5MB),因此通常使用GDBServer进行调试。两者的代码都在gdb软件包中。
参考文档:
http://wiki.openwrt.org/doc/devel/gdb
http://h4x3rotab.github.io/blog/2014/02/27/openwrtxia-de-gdbyuan-cheng-diao-shi/
OpenWRT下的GDB远程调试
除了上面列出的内容之外,我还遇到了一个问题:我所用平台的SDK将-Os
作为全局的编译选项。这在平时自然没什么,但调试的时候就有问题了。如何将-Os
换成-O0
呢?可参见以下示例:
TARGET0_CFLAGS:=$(filter-out -Os,$(TARGET_CFLAGS))
TARGET_CFLAGS:= -O0 $(TARGET0_CFLAGS) -ggdb3
这里解释一下:
1.filter-out是make提供的过滤函数,可去除字符串A中包含的特定字符串B。
2.定义TARGET0_CFLAGS的原因在于:make不支持变量的递归定义,需要中间变量暂存之。
思科曾有个巨大的失误,就是其终端路由器WRT54G错误地选用了Linux,因GPL协议的原因被迫公布了源代码,这导致所有公司一下子都会做普通路由器了。如果思科当时像苹果MacOS那样,选FreeBSD,恐怕还能多赚很多钱。
ImmortalWrt和Lean’s LEDE是两个国内比较流行的OpenWRT的发行版。
https://www.zhihu.com/question/635802944
为什么简体中文社交网络上大家更推荐Lean’s LEDE而不是官方的OpenWrt主线?
diff/patch这对工具在数学上来说,diff是对2个集合求差,patch是求和。
diff -uNr A B > C #生成A和B的diff文件C,-uNr为最常用的选项
patch A C #给A打上diff文件得到B
patch -R B C #B还原为A
patch -p1 <1.patch
这种情况适合1.patch中包含对多个文件的修改时。
有的时候,patch不是一个patch文件,而是一个目录中的若干个patch文件。这时可用如下办法:
find . -name "*.patch">1.txt
sort 1.txt | xargs cat >2.patch
patch -p1 <2.patch
参考:
https://mp.weixin.qq.com/s/VlQnDAD4dGQpmHNil7uPuQ
如何在Linux上使用xargs命令
https://mp.weixin.qq.com/s/HchAIWJmqPNTqEEwb3TbAg
Linux下xargs命令
您的打赏,是对我的鼓励