1.说明
移植rt-smart到最新的板子上具体需要注意哪些细节,哪些才是移植rt-smart的关键点?本文从树莓派3b上移植rt-smart的角度,从头分析rt-smart移植的关键细节。为了简化系统,这里只做了rt-smart的最小系统的移植,启用了rt-smart最基本的特性。
下面来描述一下移植的基本过程了思路,这种思路也可以借鉴移植到其他的类似的带有mmu的系统平台上,可以在不同的开发板上体验rt-smart的开发过程。
2.rt-smart移植整体思路
由于rt-smart是具有用户态和内核态的区别的,所以从内核系统层面来看,所有的操作系统的使用的内存地址都是在高地址位,也就是0xc0000000
处。而应用程序是单独编译的,在低地址处执行。
为了移植和调试方便,我依次按照下面的步骤进行
(1)编译uboot,可以从tftp服务器上loader固件(rt-thread非rt-smart)到内存执行。
(2)将程序链接地址改为0x100000
,并让uboot加载到该处,并正常执行。
(3)分离出bsp代码,放到rt-samrt SDK包中,可以用musl库gcc编译通过。
(4)串口可正常输出信息。
(5)中断可正常产生。
(6)调度可正常运转,rt-smart可正常执行起来。
2.1 树莓派的启动问题
其中前(1)、(2)都是为了准备调试和运行环境。这里就需要细致的说一下树莓派的启动问题。
树莓派启动首先是需要加载SD卡中的start.elf
文件,该程序会读取同样位于sd卡中的config.txt
文件,config.txt
中记录了一些配置信息,比如是否设置hdmi,启动地址,启动文件名称等等。如果没有设置启动地址和启动文件,则默认会加载kernel8.img
文件,该文件是aarch64编译的程序,启动地址0x80000
,如果没有找到kernel8,img
,则会去找kernel7.img
文件,该程序是32位程序,启动地址为0x8000
。
例如:
enable_uart=1
#rt-smart
#kernel=kernel7.img
#kernel_address=0x100000
#uboot
kernel=u-boot.bin
下面的示例就很好的描述了相关的启动设置。
为什么要将rt-smart的地址设置为0x100000
?这是因为我想通过uboot的tftp启动rt-smart,方便调试,省去每次插拔sd卡的烦恼,当然,如果不怕麻烦,那直接在config.txt
文件中设置kernel_address=0x100000
效果也一样。
因为uboot的启动地址为0x8000
,我预留了一些内存空间,所以将rt-samrt的启动地址设置为0x100000
。
2.2 SDK编译问题
在第(3)步的过程中,需要重构rt-thread版本的架构,主要原因是需要适配rt-smart的编译方式,两者最大的区别在于换了musl库的gcc,同时集成了一些系统调用的接口。其他差别不大,具体rt-smart的编译方式可以参考下面的文章描述。
2.3 串口和中断
第(4)、第(5)步这里涉及到rt-smart一些很关键的部分,就是地址空间映射到内核态地址空间的问题,这里需要进行转换,将实际的物理地址转换成虚拟地址可以供内核程序使用,这里是在内核态写驱动程序需要注意的地方。
2.4 程序执行
这里已经开启了lwp,也就是可以加载单独编译应用程序,具体可以参考rt-smart的其他文档,编译成app,通过romfs加载到程序中编译,直接运行即可。
3.树莓3b rt-smart编译体验
在描述实现细节之前,首先描述一下如何编译树莓派3b。
3.1 下载rt-smart sdk
编译rt-smart最方便的是下载rt-smart sdk
由于在Windows环境下编译,所以下载下面两个文件。
`xhttps://realthread.cowtransfe...
`
下载sdk集成环境和交叉编译工具链。
下载完成后,将gcc解压到rt-smart\tools\gnu_gcc
目录下。
然后进入rt-smart\kernel\bsp\raspberry-pi
:
`git clone https://gitee.com/bigmagic/ra...
`
在rt-smart
目录打开env,输入.\smart-env.bat
。
3.2 存放树莓派3b的boot文件
这里直接从下面的网址上下载固件存放在sd卡中
`https://gitee.com/bigmagic/ra...
`
插上网线,设置tftp服务器,可以直接从服务器上获取代码然后执行,如果不具有网络条件,可以自行修改文件,让其直接从sd卡中boot。
4.实现细节分析
4.1 启动地址细节
看程序启动地址首先看链接地址
这里就有点疑惑了,启动的地址是0x100000
,为什么链接地址是0xc0000000
,那这样程序可以运行么。这是可行的,rt-smart前面有一段位置无关代码,会将地址进行重定位,让其变成符合内核态运行的地址。
这里配置物理地址和虚拟地址偏移量,0xc00000000+0x40100000
在32位模式,得到实际的启动地址为0x100000
,这里就是实际的启动地址。
如果更加细致的细节,可详见libcpu/start_gcc.S
的启动代码的实现细节。
4.2 外设地址空间管理
系统启动起来了,接下来就要分析设备地址该如何处理了,根据rt-thread的启动流程,板级最底层的驱动初始化工作一般在具体的bsp的board.c
文件下。从rt_hw_board_init
开始分析。
前面的page初始化以及设备的初始化代码基本都一样,对于arm 32位平台基本不用修改。而具体的转换函数其实就是rt_hw_kernel_phys_to_virt
,该程序会将实际的物理地址转换成内核态可以访问的虚拟地址空间。
只需要注意这一点,串口驱动就很好处理了,因为串口操作本质上也是访问一系列的寄存器。
4.3 树莓派3b的中断
树莓派3b的中断是属于bcm特定的中断控制器,一般目前arm上使用比较多的是gic,而树莓派4b也是gic,只需要将gic地址转换成内核态可以访问的地址空间即可。对于树莓派3b,中断直接出来的,所以需要排除gic处理。
这里不属于rt-smart的特性,移植其他的平台不需要过多的考虑,只是树莓派3b需要注意一下。具体的文件实现细节可以参考libcpu\arm\cortex-a\interrupt.c
。
中断的分发和处理,除了gic的处理比较通用,将gic_dist_base
与gic_cpu_base
转换一下即可。树莓派3b上转换的是irq_base
。
4.4 RT\_USING\_USERSPACE约束
查看rt-smart的代码,往往都会看到这个宏定义,其原因是预留适配切换开关,也就是如果不开启lwp情况下,预先可以用rt-thread去编译代码的。这样可以切换选择,对于用户空间和内核空间的区别其实本质上就是使用好mmu的一些特性,进行隔离和转换的过程。或许有一天,rt-smart与rt-thread可以无缝的结合到一起,只需进行menuconfig就可以选择了,我猜想大概就是目前rt-smart的代码在rt-thread的分支的原因吧。
5.总结
rt-smart从我角度上来看不见得那么复杂与神秘,就是使用好mmu的一些特性,掌握好内核空间与用户空间隔离的概念即可。程序的权限理清楚,各司其职,各行其事。操作系统本质上就是合理的管理硬件资源,同时给用户提供好的使用体验,其上层软件资源与生态的重要性不言而喻。
看树莓派3b上最基本的rt-smart的移植过程与注意细节点,其实和rt-thread的差别并不大,其实还有一些我没有提及,比如syscall、比如crt、比如musl库等等,这些通用的东西我没有单独的提出来,一是移植不需要关注这些,另外就是这些都是和linux的机制大同小异,懂得人自然懂,不懂这篇文章也说不清楚。
推荐阅读
【20210312期AI简报】用树莓派DIY激光枪、30天吃掉那只 TensorFlow2.0
RT-Thread针对不同架构芯片移植的方法
RT-Thread隐藏的宝藏之completion
原文链接:RTThread物联网操作系统
作者: RTThread物联网操作系统