文章目录
上篇文章:ARM 嵌入式 编译系列 7.1 – GCC 链接脚本中节区及各个段的详细介绍
下篇文章:ARM 嵌入式 编译系列 9-- GCC 编译符号表(Symbol Table)的详细介绍
1.1 RT-Thread Scons 编译介绍
SCons 是一个开源的构建工具,类似于 Make,但 SCons 使用 Python 脚本作为构建脚本。在 RT-Thread 实时操作系统中,SCons 用于构建和编译整个 RT-Thread 系统。
以下是一些常用的 SCons 命令:
-
scons
:执行 SConstruct 脚本,构建项目。如果没有指定目标,将构建默认目标。 -
scons
-c 或 scons --clean:清理已构建的目标。 -
scons --help
:显示描述当前 SCons 构建环境和可用选项的帮助信息。 -
scons -s
或scons --silent
:静默模式,不显示正在执行的命令。 -
scons -j N
:并行构建,N 是并发任务的数量,通常设置为 CPU 核心数。
需要注意:使用 SCons 构建 RT-Thread 时,需要在 RT-Thread 的根目录下创建一个名为 rtconfig.h
的配置文件,这个文件定义了 RT-Thread 的各种配置选项。
#ifndef RT_CONFIG_H__
#define RT_CONFIG_H__
/* Automatically generated file; DO NOT EDIT. */
/* RT-Thread Configuration */
/* RT-Thread Kernel */
#define RT_NAME_MAX 16
#define RT_ALIGN_SIZE 4
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 1000
#define RT_USING_OVERFLOW_CHECK
#define RT_USING_HOOK
#define RT_USING_IDLE_HOOK
#define RT_IDLE_HOOK_LIST_SIZE 4
#define IDLE_THREAD_STACK_SIZE 2048
#define RT_USING_TIMER_SOFT
#define RT_TIMER_THREAD_PRIO 4
#define RT_TIMER_THREAD_STACK_SIZE 2048
/* kservice optimization */
#define RT_DEBUG
...
1.1.1 Scons 编译层级设置
- 第一层目录下会看到类似下面内容的 SConscript 脚本,
# for module compiling
import os
from building import *
cwd = GetCurrentDir()
objs = []
list = os.listdir(cwd)
for d in list:
path = os.path.join(cwd, d)
if os.path.isfile(os.path.join(path, 'SConscript')):
objs = objs + SConscript(os.path.join(d, 'SConscript'))
Return('objs')
上面脚本的内容主要有以下几个方面:
1)导入 python 相关的模块
2)获取当前目录下子目录中的SConscript脚本
- 在子目录的脚本SConscript脚本中可以看到类似下面的内容:
from building import *
cwd = GetCurrentDir()
src = Glob('*.c')
CPPPATH = [cwd] //将当前目录累加到总的头文件路径
group = DefineGroup('DeviceDrivers', src, depend=[''], CPPPATH=CPPPATH)
Return('group')
上面内容主要有以下几方面内容:
1)获取当前目录路径;
2)使用Glob(*.c)
包含要编译的所有 c
文件;
3)将当前路径赋值给 CPPPATH
,用于将当前路径配置为全局include
路径;
4)当前路径编译配置,其中 depend = ['']
表示要编译当前路径中的内容所依赖的宏,在这个脚本中为空,也即不依赖任何宏。
1.1.2 Scons DependedSrc 的使用
在代码的编译过程中有时存在下面这种情况,也即编译某个 C
文件时需要依赖某一个宏,但是同一级目录下其他的C文件并不依赖这个宏,这个时候就可以用到 DependeSrc
这个函数了,如下脚本中的内容,表示编译 test.cc
时需要依赖宏 ”ARM_DEMO
“,如果 ARM_DEMO
没有定义,那么 test.cc
不会加入编译中,而其他文件不受影响。
from building import *
cwd = GetCurrentDir()
src = []
src += DependedSrc('ARM_DEMO', ['test.cc'])
group = DefineGroup('ARM_DEMO', src, depend = ['DEMO'])
Return('group')
1.1.3 Scons CCFLAGS 的使用
在代码编译过程中有时会增加自己需要的一些内容,比如定义一个宏,这个时候就可以使用到Scons SConscript中的
CCFLGAS
功能,如下内容为定义一个 DEMO_TEST
宏:
import rtconfig
from building import *
cwd = GetCurrentDir()
group = []
src = Glob('test1.c')
src += Glob('test2.c')
group += DefineGroup('DEMO', src, depend = [''], CCFLAGS = ' -DDEMO_TEST')
# add all SConscripts in sudir
if GetDepend(['DEMO_FOO']): #如果配置了DEMO_FOO 宏下面的内容才会走到
list = os.listdir(cwd)
for d in list:
sub_sconscript = os.path.join(cwd, d, 'SConscript')
if os.path.isfile(sub_sconscript):
vdir = BOARD_ROOT + "/build/" + os.path.relpath(cwd, start = TOP_ROOT) + "/" + d
group += SConscript(sub_sconscript, variant_dir = vdir, duplicate = 0)
Return('group')
1.1.4 Scons defconfig 介绍
在芯片开发的不同阶段需要建立不同的 profile(板级),比如在前期验证阶段只需要建立 fpga 相关的 profile,在芯片回来之前需要建立 udp 的 profile,不同的profile所使用的 config文件也不同,但是 SoC 级别的内容是不变的,所以SoC 相关的内容会独立出来。
比如在 rt-thread/bsp/vendor/
建立不同的 profile 目录:
fpga evb common
- fpga 为 fpga 验证阶段使用;
- evb 为回片后所使用;
- common 为 soc 内容,fpga 和 evb 共同都可以使用。
common
目录中通常会进行一些默认配置,在 rtthread 中,这些默认配置使用的 DEVICE_CONFIG_DEF
:
/**
* DEVICE_CONFIG_DEF - define some default devices in a platform
* @_compat: compatible flag, used to bind the device and driver
* @_type: device config structure (device descriptor)
* @...: body of device config array
*
* The parameter @_compat must be the same with it in macro DEVICE_INIT_CALL
* or DEVICE_XXX_INIT.
*
* This macro helps to define some default devices in a platform.
*
* A sample usage:
* DEVICE_CONFIG_DEF(sample, sample_type_t,
* { ... },
* { ... },
* ...
* );
*/
#define DEVICE_CONFIG_DEF(_compat, _type, ...) \
static _type _##_compat##_config_arr[] = { __VA_ARGS__ }; \
RT_WEAK void *_##_compat##_config_ptr = _##_compat##_config_arr;\
RT_WEAK int _##_compat##_config_step = sizeof(_type); \
RT_WEAK int _##_compat##_config_cnt = ARRAY_SIZE(_##_compat##_config_arr)
而在不同的 profile目录下的 device_config.c
中会使用 DEVICE_CONFIG
宏来进行配置:
/**
* DEVICE_CONFIG - define some real used devices in a BSP project
* @_compat: compatible flag, used to bind the device and driver
* @_type: device config structure (device descriptor)
* @...: body of device config array
*
* The parameter @_compat must be the same with it in macro DEVICE_INIT_CALL
* or DEVICE_XXX_INIT.
*
* This macro helps to define some real used devices in a BSP project, and
* will override the definitions generated by DEVICE_CONFIG_DEF (if existed)
* with the same @_compat.
*
* A sample usage:
* DEVICE_CONFIG(sample, sample_type_t,
* { ... },
* { ... },
* );
*/
#define DEVICE_CONFIG(_compat, _type, ...) \
static _type _##_compat##_config_arr[] = { __VA_ARGS__ }; \
void *_##_compat##_config_ptr = _##_compat##_config_arr; \
int _##_compat##_config_step = sizeof(_type); \
int _##_compat##_config_cnt = ARRAY_SIZE(_##_compat##_config_arr)
从上面的定义可以看出,DEVICE_CONFIG
宏会覆盖 DEVICE_CONFIG_DEF
所实现的内容,这个是因为不同 board 会使用不同的配置。
不同的场景需要使用不同的 .config
文件,我们知道.config
的生成是通过menuconfig
配置后生成,我们可以通过生成自己所需要的.config
,然后再将其命名为 xxx_defconfig
文件,在编译的时候可以直接使用下面命令即可完成自己所需配置:
$ scons --defconfig="xxx_defconfig"
再看下 scons --defconfig 的使用:
--stackanalysis thread stack static analysis
--genconfig Generate .config from rtconfig.h
--useconfig=USECONFIG make rtconfig.h from config file.
--defconfig=DEFCONFIG build with the .config from
'configs/xxx_defconfig'
--verbose print verbose information during build
--menuconfig make menuconfig for RT-Thread BSP
从上面的说明可以看到,这条命令会从 configs 目录下找 xxx_defconfig
, 然后根据其再生成 .config。
我们可以在 rtthread 代码中可以看到类似下面的代码:
demo/board/SConscript
from building import *
if GetDepend('DEVICE_CONFIG_NAME'):
device_config_file = GetConfigValue('DEVICE_CONFIG_NAME').strip('"') + '.c'
else:
device_config_file = 'device_config.c'
其中GetDepnd
函数中的参数就是“xxx
”, 在使用scons命令是如果后面跟有参数 “xxx
” 就会使用xxx_defconfig
的配置文件,如果没有参数,那么就使用默认的.config
文件。
1.2 Scons Kconfig 的使用
通常情况都是 SConscript 配合 Kconfig 一块使用,如下:
$: ~/demo/rt-thread/bsp/demo/libraries$ ls
drivers include kernel Kconfig SConscript
其中 libraries 目录下的 Kconfig 中会包含 其子目录下的 Kconfig 文件:
source "$LIB_DIR/drivers/Kconfig"
source "$LIB_DIR/kernel/Kconfig"
在执行 scons --menuconfig
的时候会遍历所有的 Kconfig 文件,并根据 Kconfig 文件中的默认配置来生成 .config,
在执行 scons 编译命令的时候就会使用 .config
中配置的宏,通常在生成 .config
的时候会自动生成一个 .h文件,我们当前项目中自动生成的.h文件叫做 rtconfig.h,其内容如下:
#ifndef RT_CONFIG_H__
#define RT_CONFIG_H__
/* Automatically generated file; DO NOT EDIT. */
/* RT-Thread Configuration */
/* RT-Thread Kernel */
#define RT_NAME_MAX 24
#define RT_ALIGN_SIZE 8
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 1000
#define RT_USING_OVERFLOW_CHECK
#define RT_USING_HOOK
#define RT_USING_IDLE_HOOK
#define RT_IDLE_HOOK_LIST_SIZE 4
#define IDLE_THREAD_STACK_SIZE 256
#define RT_USING_TIMER_SOFT
#define RT_TIMER_THREAD_PRIO 4
#define RT_TIMER_THREAD_STACK_SIZE 1024
/* kservice optimization */
#define RT_DEBUG
/* Inter-Thread communication */
#define RT_USING_SEMAPHORE
#define RT_USING_MUTEX
#define RT_USING_EVENT
#define RT_USING_MAILBOX
#define RT_USING_MESSAGEQUEUE
#define RT_USING_SIGNALS
...
.config 中所以配置生效的宏(配置為 “y” 或者配置為某個常数值)都会生成到 rtconfig.h中,所以这个.h
会动态生成,会跟着scons --menuconfig
的不同配置来生成的不同的rtconfig.h
文件,如果继续跟踪就会发现 在rt-thread/include/rtthread.h
中会包含 rtconfig.h
这个头文件,对于 rtthread.h
这个头文件我们都知道他的功能像linux中的 linux/module.h
,几乎所以地方都会用到。
1.2.1 Scons depend on 的使用
在编译代码的时候,有时一个模块A依赖另外一个模块B,如果B不编译的时候A也不能用,这个时候就需要一个依赖宏用来检查B模块是否参与了编译,如下:
menuconfig TEST_DEMO
bool " DEMO TEST"
defalult y
if TEST_DEMO
config TEST_DEMO1
bool "Enable test demo1"
depends on TEST_DEMO2
default y
endif
上面的内容含义是:
- 即便
TEST_DEMO1
的 默认值为y
, 但是如果没有配置TEST_DEMO
那么 就不会出现TEST_DEMO1
; - 即便
TEST_DEMO
定义了,如果TEST_DEMO2
没有定义,那么TEST_DEMO1
也无法生效。
上篇文章:ARM 嵌入式 编译系列 7.1 – GCC 链接脚本中节区及各个段的详细介绍
下篇文章:ARM 嵌入式 编译系列 9-- GCC 编译符号表(Symbol Table)的详细介绍