admin 管理员组

文章数量: 1132215

前言

1. 本篇的主要目的是熟悉瑞芯微Linux sdk的目录结构和需要关注的文件。并完成基本的构建流程。

2. 本篇考虑到我自己的水平一般且大多数群友对这方面也了解不多,所以会偏向基础。

3. 本篇流程在泰山派buildroot构建文档的基础上,夹杂了一部分我自己的理解,肯定是有错误的,遇到了希望能在评论区或私信指正。

4. 本篇同样是折腾deepseek的前置篇。行文较乱,写到哪算哪。我不喜欢段前缩进,所以每次换行就是新段了。

5. 如果没有明说,那么后面操作默认就是在sdk根目录下的。

6. 参考关键词:

交叉编译工具链 --build/--host/--target sysroot 文件系统 根文件系统 posix 系统调用 glibc busybox 
binutils buildroot uboot spl/tpl rcS Kconfig defconfig

基本构建流程

1. 下载、解压

下载目标sdk压缩包。建议用这个,别用那个带repo的。可以看到文件后缀是tar.gz这是一个压缩包。tar是tar打包工具的后缀,gz是gzip的后缀。Linux中打包和压缩是分开的。写成tar.gz是习惯而已。Linux系统本身没有后缀名的区分。一般是给人看的。当然,有些场景也会用于识别,比如gcc用后缀识别源文件,makefile用文件名和后缀识别makefile文件。

下完之后,可以使用scp命令或者winscp工具,或者mobaxterm,将它上传到虚拟机里。用户目录即可。

我使用scp。pwd是显示当前目录。vagrant是虚拟机里用户名,192.168.4.47是虚拟机ip,/home/vagrant/是虚拟机中的目标目录。也就是我的用户空间。速度还是挺快的。

在虚拟机终端中,使用tar命令解压文件。

其中,z表示gzip格式的文件,x表示解压缩,v表示显示解压过程,f表示指定文件。ls命令表示显示当前目录下的文件和目录。

tar zxvf tspi_linux_sdk_20230916.tar.gz

这里31G那个就是解压出来的sdk文件夹了。原来的tar.gz压缩包可以删了。

2. 下载基础工具包和依赖库。

sudo apt-get install git ssh make gcc libssl-dev liblz4-tool expect g++ patchelf chrpath gawk texinfo chrpath diffstat binfmt-support qemu-user-static live-build bison flex fakeroot cmake gcc-multilib g++-multilib unzip device-tree-compiler ncurses-dev
sudo apt install vim
sudo apt install python2.7 bc time file rsync bzip2

前面的是文档里要求,vim是我要用的。第三行是不安装后面会报错的。

3. 编译及问题处理

这步的所有命令都以Release文件夹为基础目录。(就build.sh那个文件夹)

选择板级配置,选名字里带tspi的那个。

cd Release
./build.sh lunch

全部编译。

./build.sh all

遇到第一个错误。缺少python2。

先看下python2这个命令是否存在。然后看下python命令默认调用的哪个。再看下python3是否存在。最后确认下系统内和python相关的命令都有哪些。

python2 --version
python --version
python3 --version
ls /usr/bin/python*

这里的ls命令是列出目录下的子目录和文件,/usr/bin/python* 是指以python为前缀的所有文件和目录,*在这里表示通配符。/usr目录是通常我们安装软件的默认路径,bin表示二进制可执行文件目录。

这里可以看到,ubuntu22.04系统里,默认装的是python3,且不存在一个名叫python的可执行文件。但是我们的构建脚本需要用它,所以就报错了。

手动装一个吧。就用python2.7版本吧。然后创建名为python的软链接,类似于Windows下的快捷方式。软链接指向python2.7。

在/usr/bin下创建文件需要root权限。ln表示创建链接文件,-s 软链接。前面那个路径是目标文件,后面那个路径是新文件。

sudo apt install python2.7
sudo ln -s /usr/bin/python2.7 /usr/bin/python2

现在python2命令已经默认代表python2.7了。

继续构建。遇到第二个问题。缺少bc命令。

./build.sh all

直接装,装完继续构建。

sudo apt install bc
./build.sh all

 遇到第三个问题,这次是python命令找不到了。

这里就像第一个问题一样,创建名为python的新软链接,这次指向python3。

sudo ln -s /usr/bin/python3.10 /usr/bin/python

这里问题的原因是,tspi文档中是以Ubuntu18.04为例的,18.04中默认的python是python2.7,而我们现在用的python22.04默认用的是python3.10。python2到python3有大改动。部分语法不兼容。tspi的sdk是由多个不同的项目组合而成的,比如这里用于构建根文件系统和标准库文件的buildroot项目、用于构建引导启动程序的uboot项目、用于Linux核心支持的Linux-kernel项目。这里面python主要作脚本,不同模块需要的python版本可能不一致,所以脚本编写者用python2和python(指python3)来做区分。

搞完继续构建。遇到第四个报错。还是缺命令。装。装完继续构建。

./build.sh all
sudo apt install time
./build.sh all

好。这里遇到了第一个稍微复杂点的错误。

从这里看不出什么明显痕迹,往上翻,找到第一行标红的地方。

打开这个日志文件,看下详细报错。

缺个文件,下载。下完继续编。

sudo apt install file
./build.sh all

又说缺rsync,下,下完继续编。

sudo apt install rsync -y
./build.sh all

出现这样的画面就是正在下载三方库(软件)源码了。这个过程会很漫长。

又报错了,还是缺命令。下,下完继续。查了下,这命令是bzip2里的,不是bzcat。

sudo apt instal bzip2 -y
./build.sh all

唉......有报错,浏览器搜一下,有现成的解决方法。

Ubuntu22编译rk3566 buildroot报错 multiple definition of `fwriter_buffer‘; mksquashfs.o:(.bss+0x200180):_host-squashfs 3de1687d7432ea9b302c2db9521996f506c1-CSDN博客

照着做吧。下面diff开头的复制到这个patch文件里。然后把里面的host-squashfs目录删了。

然后继续编。

vim buildroot/package/squashfs/0001-multiple-definition.patch
rm buildroot/output/rockchip_rk3566/build/host-squashfs-3de1687d7432ea9b302c2db9521996f506c140a3 -rf
./build.sh all
diff -ruN squashfs-3de1687d7432ea9b302c2db9521996f506c140a3/squashfs-tools/mksquashfs.h squashfs-3de1687d7432ea9b302c2db9521996f506c140a3-patch/squashfs-tools/mksquashfs.h
--- squashfs-3de1687d7432ea9b302c2db9521996f506c140a3/squashfs-tools/mksquashfs.h       2015-12-07 09:42:03.000000000 +0800
+++ squashfs-3de1687d7432ea9b302c2db9521996f506c140a3-patch/squashfs-tools/mksquashfs.h 2025-01-02 16:59:52.994326895 +0800
@@ -133,7 +133,7 @@
 #define BLOCK_OFFSET 2
 
 extern struct cache *reader_buffer, *fragment_buffer, *reserve_cache;
-struct cache *bwriter_buffer, *fwriter_buffer;
+extern struct cache *bwriter_buffer, *fwriter_buffer;
 extern struct queue *to_reader, *to_deflate, *to_writer, *from_writer,
        *to_frag, *locked_fragment, *to_process_frag;
 extern struct append_file **file_mapping;

这样就是编好了。

4. 打包

创建固件,打包成img。

到这,就结束了。生成的文件在rockdev目录下,update.img就是生成的镜像文件。同级目录下还有别的分区文件的软链接。

介绍及拓展

目录及部分文件介绍

sdk根目录下有这些目录:

顶层不太需要关注的目录及组件

app 目录

主要存放官方给的各种应用层的app。和external类似吧。

external 目录

主要存放各种应用层的软件,部分是和系统交互的。比如播放器、浏览器、WiFi驱动模块什么的。这个目录需要关注。构建根文件系统的时候会根据配置编译这里面的组件,然后拷贝到文件系统中。比如,rkwifibt里存放的是WiFi驱动模块和源码。update_engine是系统ota升级工具。uvc_app是usb摄像头的app demo。rockit是多媒体处理框架。

tools 目录

这里面存放的是一些辅助工具,Linux、Windows、mac平台的都有,比如烧录工具,打包/解包工具。这个目录里的工具会被在构建的时候被调用。比如,build.sh firmware 和build.sh updateimg这两个命令就需要。里面的有些工具是不开源的。可以不关注,没事也不需要去动它。

需要重点关注的目录。

prebuilts 目录

这个目录里存放的是交叉编译工具链。就是你电脑上装的gcc那套工具。但是有点差异,叫交叉编译是因为,工具本身运行的平台和用它编译出来的程序运行的平台不一致。比如,这个工具运行在x86_64上,编出来的app运行在arm64上。

一个工具链用来编译uboot和kernel。另一个用于编译应用层的app、根文件系统、三方库等。

我们通常不需要动里面的东西。但是,当你要编译的源码,用到的特性需要更高版本的gcc支持时,你可能需要自己构建工具链了。折腾大模型推理的时候就需要改这个。

uboot 目录

这个目录存放的就是uboot源码,运行在MiniLoader之后,这里说的mini loader就是update.img中的那个bin文件,它是瑞芯微官方提供的,不开源。uboot有两个作用,一是做引导,启动kernel,并传递内核启动参数。二是实现rkusb功能。

buildroot和debian 目录

这俩都是创建文件系统的。debian这个我不太熟悉,就不讲了。buildroot也是一个单独的项目。主要用于创建根文件系统并编译一些三方库和工具进去。同时,它也会把external中的驱动和模块一起打包放进去。

package目录下是各种三方库、三方工具的编译脚本、补丁。fs目录下是文件系统类型的构建脚本和编译配置,比如ext4、ext2等。configs目录下是rootfs和recovery的defconfig配置文件。board下是板级配置、脚本和补丁。这里补丁的意思是,文件系统构建后期,会把补丁给拷贝到生成的根文件系统中。所以你有啥需要预先打包进系统的文件,就存这里。折腾deepseek要用这个目录。dl目录是下载的三方库的源码。buildroot会在第一次构建的时候,根据configs下的defconfig或当前目录下的隐藏文件.config中,参考package下面的编译脚本从网站上自动下载对应的软件包源码。然后现场配置和编译。一方面是它默认用的境外的网站,另一方面是它每次都要重新编译那些软件包和库,所以很慢。output是生成目录,构建的中间过程文件也在里面。rootfs和recovery的都在。如果你想看看最终系统里都有什么,可以到这里面找target目录。其他的目录我没注意过。不看了。

device 目录

主要是整个sdk的构建脚本,比如你用的build.sh脚本。还有板级配置的文件。分区表。这里重点说俩。

 1. device/rockchip/common 这里是前面说的构建脚本。还有两个目录oem和userdata,这俩是存放了生成rootfs时拷贝到oem和userdata目录下的补丁。和buildroot中board路径下的作用类似,区别是这俩只会拷贝到oem和userdata下。原因是有些系统它需要区分去写权限。就是其他目录都是只读的,烧进去啥样就啥样,用户数据放在userdata或者oem下。在这种情况下,buildroot里打包进去的,系统启动后删不了,这里面的可以。

 2. device/rockchip/rk356x 这里是前面说的板级配置文件。你执行build.sh lunch,选的就是这里面的配置文件。BoardConfig-rk3566-tspi-v10.mk 这个文件,指定了一些板级配置,比如uboot、rootfs、kernel都是用的哪个defconfig。文件系统类型用的啥,分区表用的哪个,编译线程数,拷贝到oem和userdata目录下的补丁路径。

其他目录和文件。

其他目录我都没注意过。br.log是你编译出错的日志文件。别动就行。

update.img 解包

我们下来解包看看吧。先到工具目录下添加个环境变量吧,等下到其他目录就能方便得使用里面工具了。$PWD是当前的工作目录。$PATH就是系统环境变量目录。你执行命令的时候,系统会自动去$PATH代表的目录下找。多个目录以冒号分隔。Windows下也有类似的概念。unpack.sh就是我们要用的解包脚本。那个rk356x-mkupdate.sh就是打包脚本,这俩实际上也是调用当前目录下的其他程序完成的。我们不管它内部调用。那个rk356x-package-file就是打包说明文件。

cd tools/linux/Linux_Pack_Firmware/rockdev
export PATH=$PWD:$PATH

然后到sdk前一级目录,新建个临时目录,用来测试。并把生成的update.img拷贝过去。du -sh是查看文件大小。mkdir是创建目录。cd是进入目录。cp是复制,前一个参数是源文件,后一个是目标目录。点是表示当前目录。可以看到,生成的固件包是499MB,这太大了。

cd ..
mkdir img_test
cd img_test/
cp ../Release/rockdev/update.img .

我们来看看它里面都有啥。直接执行unpack.sh脚本,解包它。报错了,说需要的某个文件没找到。./表示执行当前目录下的程序。该程序实际上是在之前的脚本同级目录下的,现在的目录肯定是没有的,所以报错了。

unpack.sh

我们直接改下脚本。用vim编辑脚本,这几行改成这样。意思是,当前目录下存在unpack.sh就使用当前目录下命令,不存在就从系统环境变量里找命令。

vim /home/vagrant/Release/tools/linux/Linux_Pack_Firmware/rockdev/unpack.sh
if [ -f "unpack.sh" ];then
    ./rkImageMaker -unpack update.img output || pause
    ./afptool -unpack output/firmware.img output || pause
else
    rkImageMaker -unpack update.img output || pause
    afptool -unpack output/firmware.img output || pause
fi

重新解包,生成了output目录。

unpack.sh

进入目录,首先是打包文件。它定义了系统都有哪些分区,以及分区文件名。左边那一列是分区名,和parameter中对应,右边是分区文件位置。#开头是注释。cat命令查看文件内容。

然后到Image下,里面是具体的分区文件。du -sh 查看每个文件的大小,*是通配符,匹配当前目录下每个文件和目录。file是查看文件类型。

MiniLoaderAll.bin就是从芯片内置rom到uboot的引导,不开源。parameter.txt是分区文件。boot包含kernel和dts。misc是用于临时存储启动参数的分区,系统启动时会读取其中的参数,然后判断是正常启动还是ota升级过程中进入recovery模式。oem和userdata都是用户或厂商自定义分区。要不要都行。recovery是系统升级或系统恢复时用的。里面也包含了kernel和dts,以及一个小的根文件系统。rootfs就是根文件系统,包含我们系统内需要的各种库和命令文件。uboot是从mini loader到kernel之间的引导。

cd Image
du -sh *
file *

可以看到,rootfs是ext4文件系统,也就是说我们可以手动挂载并修改里面的内容。来试试。先创建一个空文件夹,用作挂载点,然后使用mount挂载。里面就是我们板子系统的实际结构了。所以,不构建也能直接往里面加文件。这些目录文件的实际大小是216M,也就是还有冗余空间。

mkdir temp
sudo mount rootfs.img temp
ls temp/
sudo du -sh temp/

使用resize2fs和truncate可以缩小文件系统和镜像大小。但是不建议这么做。这是我之前搞升级包裁剪时走的弯路。

打包我就不演示了,是解包的逆操作。用这个脚本即可rk356x-mkupdate.sh。记得改里面的命令路径。

系统启动流程和各模块的职责。

1. 系统启动。

从上电开始,芯片内自带了一小段rom程序,它上电时自行从0x4000之前某个位置加载mini loader引导程序。就是我们固件包里那个bin文件。如果找不到,芯片就进入maskrom模式。mini loader加载后,会自行查找同在0x4000之前的parameter.txt文件,读取它来获取后面每个分区的起始地址和分区大小。0x4000之前只有这两个文件。所以文档里也提到了其他分区只能在0x4000之后。mini loader会引导启动uboot。uboot会读取dts,并初始化某些控制器。比如usb控制器。uboot后续会有三种走向。第一种,进入loader模式,rk平台的loader模式是在uboot中,基于usb gadget设备驱动实现的。所以,存储器中没有uboot就无法进入loader模式。第二种,打开串口终端,把后续控制权交给用户。第三种,引导启动kernel,同时将启动参数传递给kernel。之后,kernel初始化核心模块,读取dts,初始化各个外设,挂载根文件系统。启动后期,kernel开启init进程。从内核空间切换到用户空间。init进程读取/etc/fstab挂载文件,挂载各个系统内分区,比如oem、userdata、tmpfs等。同时,读取/etc/init.d下的rcS文件,执行各种外设初始化脚本。比如,udev配置,网卡驱动加载和配置。如果你需要开机自启程序,就可以定义到rcS中。

2. 系统的依赖关系。

我理解的系统依赖关系。uboot和kernel可以视作大型裸机程序,独立实现他们自身的全部功能。其中uboot承载着引导kernel的任务。它自身在运行时并没有初始化全部的驱动和外设。

kernel不依赖外部库文件,管理所有的硬件资源,并以C接口的形式提供系统调用给应用层。要使用系统调用我们就需要内核的接口头文件。也就是kernel/include目录下的那些。我们在应用层编程,完全可以直接使用系统调用完成所有的功能。但是不同系统提供的接口并不相同,有些程序也并不需要和内核交互。为了编程方便和移植性,我们通常使用C标准库来完成。

glibc就是gnu的C标准库实现。类似的还有uclibc、musl、bionic等。所以,我们通常将Ubuntu等Linux系统称为GNU/Linux。glibc屏蔽底层细节,为应用程序提供较统一的接口。虽然glibc的部分功能并不需要kernel的支持,但是我觉得仍然可以称它依赖于kernel。

Linux有“一切皆文件”的设计理念。kernel和用户空间的交互,习惯通过文件来实现。由文件系统来提供支持。根文件系统中包含了C标准库文件,链接器,加载器,C运行时等,将这些和固定的目录结构打包起来,组成用户程序运行的最小环境。这就是sysroot。

3. 关于交叉编译工具链。

前面提到了uboot和kernel像是裸机程序,不依赖于其他库和实现。而应用空间程序的实现依赖glibc。所以,就需要两套编译链。一个用于编译uboot和kernel,一个用于用户程序和文件系统。

这里目录名中的版本是gcc版本。

6.3版本的只是一些必要实现。且通常用静态链接库,在编译时直接将需要的功能链进去。

9.3版本的库更多,并且确实包含了sysroot。库也多是动态链接库,反正最后系统里都会有一份的。

除此之外,工具链中还应该包含内核头文件,包含系统调用定义。

注意事项

1. 养成习惯,路径尽量不要带中文。

2. ssh工具建议使用mobaxterm。

3. 文中出现adir、adls、go等命令是我自定义的几个shell函数,是对cd命令的封装,用于标记目录和快速跳转。可参考主页的第一篇。

4. 编译顺序为uboot、kernel、rootfs、recovery。编译到kernel时,可能会要求选择电压值,照着文档选就行了,原因是供电电源很重要,定义错了可能会上电就烧板,这一步的作用只是强制你确认一下,选错就停止编译而已。

本文标签: 泰山 流程 Buildroot SDK