IMX6ULL 从头开始,笔记一:获得一个烧写镜像

硬件

用的正点原子的IMX6ULL mini 开发版,价格还是比较合适的。

开发环境

Ubuntu18.04 / Ubuntu20.04 都没啥问题。
多折腾折腾系统,还是有收益的。

推荐纯 Ubuntu 开发。
推荐使用 Ubuntu 实体机而不是虚拟机。
如果之前搞单片机比较习惯 Windows 的话,那更应该坚持纯 Ubuntu 开发;不习惯归不习惯,有困难解决困难。
当习惯 Ubuntu 了就明白了。
并不是说 Ubuntu 比 Windows 好,而是开发 Linux 更合适。
搞 STM32 的话,我绝对不会使用 Ubuntu 的,虽然之前也搞通过 Ubuntu 下的 STM32 开发;但是还是 Keil 和 ARMCC 更香。

《鸟哥》多看两遍。
最好是全看,不要把《鸟哥》当做中文版的命令手册。
他讲了很多 Linux 的原理。比如说零号进程,比如说终端的运行环境,比如说文件系统。

工具链

linaro components
linaro 是首选,ARM 牵头,各大芯片厂商一起合作做出来的工具链。
包括 Nvidia 的 SDK 都是用的 linaro —— 这年头搞 Linux 谁不是奔着 AI 去的呢。
并且代码开源,后期想在开发板上折腾编译什么的,也是可以实现的。
进入它官网的 releases,然后路径:
toolchain -> binares -> 选个版本 -> arm-linux-gnueabihf -> gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz 或者 gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf.tar.xz
x86_64 是 64位系统,i686 是 32 位系统。(现在应该都是 64 位系统了吧)。

下载解压

如果下载完压缩包后剪切或复制到 ~/bin/ 下面。
这是个人习惯,在自己的家目录下建个 bin 文件,把一些自己用的东西比如说 repo 放到这里,不会干扰到 root 空间。
然后解压压缩包,如果不知道解压命令是啥,也可以在窗口文件夹中右键 -> “Extract Here”.
没必要鄙视窗口鼠标操作。
然后会得到 ~/bin/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/ 文件夹。

解压命令:
tar xvJf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

环境变量操作

cd ~/bin/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/
echo '#!/bin/bash' > source_this.sh
echo "PATH=\$PATH:$(pwd)" >> source_this.sh
chmod a+x source_this.sh

没必要啥都加 sudo.
这样就能得到一个 source_this.sh 脚本。
随便在其他地方建个文件夹,比如 ~/workspace/imx_study.
如果输入:

xxx@xxx-desktop:imx_study$ arm [tab][tab]

arm 加两个 tab 的话,会出一堆补全,但是不会出现 arm-linux-gnueabihf-xxx 的补全:

xxx@xxx-desktop:imx_study$ arm
arm2hpdl                  arm-none-eabi-gcc-6.3.1   arm-none-eabi-nm
arm-none-eabi-addr2line   arm-none-eabi-gcc-ar      arm-none-eabi-objcopy
arm-none-eabi-ar          arm-none-eabi-gcc-nm      arm-none-eabi-objdump
arm-none-eabi-as          arm-none-eabi-gcc-ranlib  arm-none-eabi-ranlib
arm-none-eabi-c++         arm-none-eabi-gcov        arm-none-eabi-readelf
arm-none-eabi-c++filt     arm-none-eabi-gcov-dump   arm-none-eabi-size
arm-none-eabi-cpp         arm-none-eabi-gcov-tool   arm-none-eabi-strings
arm-none-eabi-elfedit     arm-none-eabi-gprof       arm-none-eabi-strip
arm-none-eabi-g++         arm-none-eabi-ld          
arm-none-eabi-gcc         arm-none-eabi-ld.bfd 

然后把上面的那个脚本复制过来,并 source 一下的话。

xxx@xxx-desktop:imx_study$ cp ~/bin/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/source_this.sh ./
xxx@xxx-desktop:imx_study$ source source_this.sh

会神奇的发现:

xxx@xxx-desktop:imx_study$ arm
arm2hpdl                           arm-linux-gnueabihf-gfortran       arm-none-eabi-g++
arm-linux-gnueabihf-addr2line      arm-linux-gnueabihf-gprof          arm-none-eabi-gcc
arm-linux-gnueabihf-ar             arm-linux-gnueabihf-ld             arm-none-eabi-gcc-6.3.1
arm-linux-gnueabihf-as             arm-linux-gnueabihf-ld.bfd         arm-none-eabi-gcc-ar
arm-linux-gnueabihf-c++            arm-linux-gnueabihf-ld.gold        arm-none-eabi-gcc-nm
arm-linux-gnueabihf-c++filt        arm-linux-gnueabihf-nm             arm-none-eabi-gcc-ranlib
arm-linux-gnueabihf-cpp            arm-linux-gnueabihf-objcopy        arm-none-eabi-gcov
arm-linux-gnueabihf-dwp            arm-linux-gnueabihf-objdump        arm-none-eabi-gcov-dump
arm-linux-gnueabihf-elfedit        arm-linux-gnueabihf-ranlib         arm-none-eabi-gcov-tool
arm-linux-gnueabihf-g++            arm-linux-gnueabihf-readelf        arm-none-eabi-gprof
arm-linux-gnueabihf-gcc            arm-linux-gnueabihf-size           arm-none-eabi-ld
arm-linux-gnueabihf-gcc-7.5.0      arm-linux-gnueabihf-strings        arm-none-eabi-ld.bfd
arm-linux-gnueabihf-gcc-ar         arm-linux-gnueabihf-strip          arm-none-eabi-nm
arm-linux-gnueabihf-gcc-nm         arm-none-eabi-addr2line            arm-none-eabi-objcopy
arm-linux-gnueabihf-gcc-ranlib     arm-none-eabi-ar                   arm-none-eabi-objdump
arm-linux-gnueabihf-gcov           arm-none-eabi-as                   arm-none-eabi-ranlib
arm-linux-gnueabihf-gcov-dump      arm-none-eabi-c++                  arm-none-eabi-readelf
arm-linux-gnueabihf-gcov-tool      arm-none-eabi-c++filt              arm-none-eabi-size
arm-linux-gnueabihf-gdb            arm-none-eabi-cpp                  arm-none-eabi-strings
arm-linux-gnueabihf-gdb-add-index  arm-none-eabi-elfedit              arm-none-eabi-strip

arm-linux-gnueabihf-xxx 已经有了。

不过每次使用前都需要执行一下这个脚本。
虽然每次执行看起来麻烦,但是个人认为这是一个比较轻量化的实现,没必要把这个环境变量加到全局环境变量中。
尤其是如果你需要同时开发多个平台、会使用多个工具链时。
ROS 的官方做法也是每次都 source 下环境配置脚本。

其他

然后装下依赖:

sudo apt-get install lsb-core lib32stdc++6

验证下版本:

arm-linux-gnueabihf-gcc -v

OK了。


在开心的搞例程写代码之前,感觉最好搞清楚两件事情:

  • 启动也就是 boot 的大概流程
  • 启动镜像的结构和程序链接结构

启动的流程巨复杂,并且知道不知道都是那样操作;如果不关系硬件开发的话,可以先不细究。
下面仔细研究下启动镜像的结构和程序链接结构。

Program Image

Program Image 主要由四部分组成:Image vector table、Boot data、Device configuration data、User code and data.

基于不同的启动介质,IVT 在存储介质中的起始地址和 Initial Load Region 是约定好的。可以结合下面两张图片看:

  • 图1:在这里插入图片描述
  • 图2:
    在这里插入图片描述

上图左侧描述的是 Program Image 在存储介质中的状态,右侧描述的是 Program Image 在运行内存中的状态。
要注意到,在存储介质中的起始地址和在运行内存中的起始地址不是一个概念。
存储介质中,Offset 的第一个位置放的是 header,header 的前面基于约定是预留的;比如说 SD 卡启动,前面预留的就是 1K.
没想明白要预留 1K 的原因,暂时不想了。
由上图可知,Initial Load Region 是包含预留区域的;所以,比如说 SD 卡启动,实际可以放东西的空间就 3K.
Initial Load Region Size 中包含 Image vector table、Boot data、Device configuration data.

下图是借用的正点原子讲义里的一个烧写镜像:

  • 图3
    在这里插入图片描述

将这个镜像内容整理一下:

  • 表1
    在这里插入图片描述

其中,entry、dcd、boot data、self、csf、start 里面存放的是指针 —— 当把数据加载到 RAM 中时所需要的指针。
这里再解释一下,上面的镜像是烧写到存储介质中的镜像。片上 ROM 里的程序会解析镜像的内容,按照 entry、dcd、boot data、self、csf、start 等指针的值,把相应的镜像资源加载到 DDR 中的指定位置。
目前还没有实操,但是猜测,IVT、Boot Data、DCD 在存储介质镜像中的排布是连续的,是没有空洞的;Application 是在 Initial Load Region 之后的,具体到 SD 卡启动的话,会在存储介质的前 4K 空间之后。

Image 里面的指针

下面分析下 entry、dcd、boot data、self、csf、start 这六个指针的值。
csf 这个可以暂时不管,重点看 entry、dcd、boot data、self、start 这五个。

目前还没实操,下面的内容还是猜测,后期实践完后,再回来补正。
重点看图2.

start 指针

start 指向的是在内存中的起始地址,所以 start 应该是这五个指针中最小的。
IMX6ULL 的内存map映射截图:
请添加图片描述
DDR 的起始地址就是 0x8000_0000,所以 start 不能小于 0x8000_0000.

self 指针

self 指向的是 start 经过 IVT Offset 偏移后的值(SD卡启动的话是 1K = 0x400)。
所以 self = start + 0x400

boot data 指针

Boot Data 数据在存储介质中是紧随 IVT 之后的;但是,在加载到 DDR 中时,它的地址应该是比较自由的,不一定是 self + 0x20.
其中 0x20=32,是 IVT 的大小。(从 header 到 reserved2)
但是,没有必要搞这种骚操作;所以 boot data = self + 0x20 还是比较推荐的。

dcd 指针

DCD Body 在存储介质中是紧随 Boot Data 之后的;但是,在加载到 DDR 中时,它的地址应该是比较自由的,不一定是 boot data + 0x0C.
其中,0x0C=12,是 Boot Data 的大小。
但是,没有必要搞这种骚操作;所以 dcd = boot data + 0x0C 还是比较推荐的。

entry 指针

Application 在存储介质中是紧随 Initial Load Region 之后的;但是,在加载到 DDR 中时,它的地址应该是比较自由的,不一定是 start + Initial Load Region Size.
其中,如果是 SD 卡启动的话,Initial Load Region Size = 4K = 0x1000 = 4096.
这个有必要的话,还是可以定制的,但是最好要比 start + Initial Load Region Size 更大。

小结

基于图3 引用的镜像进行分析:
start = 0x877F_F000,这个可以是无条件定义的。
self = start + 0x400 = 0x877F_F400,加了个约定的 offset
boot data = self + 0x20 = 0x877F_F420,加了个 IVT
dcd = boot data + 0x0C = 0x877F_F42C,加了个 boot data 的大小
entry = start + 0x1000 = 0x8780_0000,

其中,entry 就是编译链接时,链接的起始地址;换句话说,链接地址和镜像里的 entry 必须保持一致。
片上 ROM 里的引导程序加载并运行完成后,应该是会使 PC 指针无条件跳转到 entry 处,app 开始正式运行。
有一部分是猜测,后期会尝试修改下指针地址以证实。

链接

到链接环节了。
链接,描述的是数据在运行内存中的分布,确认这一点很重要。
裸机程序,链接里指明的地址,是物理地址。

简单粗暴的指明起始地址,剩下的部分让链接器自己排布:

arm-linux-gnueabihf-ld -Ttext 0X87800000 -o <target>.elf $^

或者写个可控性更高的链接脚本:

arm-linux-gnueabihf-ld -Timx6ul.lds -o <target>.elf $^
SECTIONS{
    . = 0X87800000;
    .text :
    {
        start.o
        main.o
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)}
    .data ALIGN(4)   : { *(.data) }
    __bss_start = .;
    .bss ALIGN(4)  : { *(.bss)  *(COMMON) }
    __bss_end = .;
}

总之,链接的起始地址,要和 IVT 里面的 entry 指针的值对应起来:这里都是 0x87800000。
因为程序运行起来后,全局变量什么,是按绝对地址进行寻址的(也有办法是相对寻址,但是那是另一个话题)。如果 code 加载的地方(也就是 entry 指定的地方)和 .bin 里要寻址的地方(比如在链接环节确认的全局变量的地址)不匹配,程序运行必然会出错。

感谢读完,觉得有用的话,点个赞支持下。
有条件的同学可以收藏个。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值