下面代码以高通智驾平台为例。
1 QNX应用程序编译原理
在高通提供的qnx开发包中,qnx的内核已经由qnx所提供,所以qnx的编译,其实就是大量应用程序的编译,以及最后利用buildfile文件,把内核,库文件以及应用程序打包在一起的过程。
1.1 qnx的工程目录
应用程序的编译,可以利用最常见的makefile规则,来指定生成目标所需要的依赖文件;也可以利用qnx提供的编译机制来生成对应的可执行文件或者库文件。文本着重介绍后者,如何利用qnx的编译机制来生成应用程序。
如果要利用qnx的编译机制,来生成可执行文件,首先要构建如下所示的文件目录结构:
qnx规则对文件目录进行了比较详细的规范,要求开发者尽量遵循上述规则来构建适应多os,多架构的构建。这样构建目录的好处是开发者可以尽可能把通用的代码放到上层目录,尽可能的减少代码的可重复性。同时,每一层级都会有makefile文件,用户可以在任何目录输入make命令,make 会递归每一级目录来编译相应的内容。根据开发经验,上述目录并不是必选的,但是CPU level和variant level是开发过程中需要构建的目录。
这边尤其要注意的是variant level目录的名字,决定着最后编译出来的文件是可执行文件,动态库,还是静态库。
该名字可以包含如下的一些组合,并用(.),(-)或者(/)来进行分割。
名字中包含a字符:说明该文件需要构建成静态库。
名字中包含so字符:说明该文件需要构建成动态库。
名字中包含g字符:说明该文件需要构建成debug的版本,该版本会包含调试信息,可以供gdb调试器使用。
名字中包含be, le字符:说明该目标文件是大端或者是小端。
比如说variant level的目录名为g.le,说明需要构建的目标文件是一个debug的版本的小端的可执行程序。不过有一点需要注意的是,向.a, .so, or .o这些选项最好不要放在名字的结尾,不然会和现有的文件类型产生混淆。
1.2 qnx的makefile
用qnx的规则来构建makefile,可以看到除了最底层variant level,其他层的makefile文件所包含的内容基本都是类似的:
LATE_DIRS=boards
include recurse.mk
recurse.mk 文件用来通知makefile向更底层的目录遍历。
在同级别如果有多个目录,并且不同目录中的文件构建有先后的依赖关系,我们可以使用EARLY_DIRS和LATE_DIRS来构建。需要更早构建的目录放在EARLY_DIRS,需要更晚构建的目录,放在LATE_DIRS中,EARLY_DIRS和LATE_DIRS中可以放置多个目录,但各个目录之间构建的时候没有先后关系。
而在最底层的variant level中,makefile文件的内容基本是这样的形式:
include ../../common.mk
common.mk文件通常放在project目录下面。common.mk文件中的内容是整个编译的核心,通常放着编译的标志,头文件位置,源文件位置,以及需要链接的库等。
common.mk中的内容大概如下:
ifndef QCONFIG
QCONFIG=qconfig.mk
endif
include $(QCONFIG)
# Preset make macros go here
include $(MKFILES_ROOT)/qtargets.mk
# Postset make macros go here
qconfig.mk宏通常包含着一些编译中使用的编译工具的宏定义,命令的宏定义,如(CP_HOST
,LN_HOST)qnx建议在编译的时候使用这些宏,而不是直接去引用某个绝对路径,或者某个host中的命令。在Preset 的位置,用户可以用来定义一些宏,比如如何进行链接,包含了哪些头文件或者源文件,以及目标文件的名字等。Postset可以用来定义一些哄,用来覆盖qtargets.mk中的定义。qtargets.mk用来包含安装路径以及文件链接。
qrules.mk用来放置编译过程中会用到的一些宏,如目标文件,源文件,编译标志,对于可写属性的宏,用户可以进行更改。
一些编译过程中会经常用到的宏:
INSTALLDIR:目标文件安装路径
CCFLAGS:编译标志位
LDFLAGS:链接标志位
LIBS:需要链接的库文件
NAME:指定目标文件的名字
EXTRA_INCVPATH:编译过程中需要搜索的头文件路径
EXTRA_SRCVPATH:编译过程中的源文件,通常源文件不在当前工程文件下时,需要设置该变量。
EXTRA_LIBVPATH:指定编译过程中搜索依赖库的路径
2 QNX image构建原理
在上述利用通用makefile规则或者qnx的makefile规则,编译出相应的可执行程序以后,就需要把这些应用程序打包,构建启动文件。qnx 利用buildfile文件来构建整个image。buildfile制定了qnx的启动文件,启动脚本,以及启动过程中需要打包的库文件以及应用程序。
可以先看一下qnx的启动时序:
最一开始的PLL是硬件相关的,这边不做讨论。
IPL阶段,全称是initial program loader,这是引导qnx系统的第一个阶段。通常用来做一些最基本的硬件初始化工作,当然也有可能会对startup程序进行内存拷贝(如果这一部分工作没有在bootloader完成的话)。
Startup阶段,完成后续的硬件初始化工作,包括内存页表映射,内核调用函数链接,然后把接下来的工作交给procnto(内核),拷贝os image到ram中。
Base system包含内核,以及系统启动的一些必要文件。
Boot script阶段,系统启动中优先级高的任务,可放在该阶段执行。
SLM阶段,一般的应用程序,可放在该阶段执行。
而由startup program,base system和boot script等组成的image,又称作Image Filesystem(IFS)
上述描述了整个qnx系统启动时的一个大概的时序,而buildfile所做的工作,基本能和上述所描述的元素所吻合。看一下buildfile的一些基本要素:
[virtual=x86_64,bios] .bootstrap = {
startup-x86
# PATH is the *safe* path for executables (confstr(_CS_PATH...))
# LD_LIBRARY_PATH is the *safe* path for libraries
# (confstr(_CS_LIBPATH)). That is, it's the path searched for libs
# in setuid/setgid executables.
# The module=aps enables the adaptive partitioning scheduler.
[module=aps] PATH=/proc/boot:/bin:/usr/bin:/sbin:/usr/sbin \
LD_LIBRARY_PATH=/proc/boot:/lib:/lib/dll:/usr/lib \
procnto-smp-instr
}
# Start-up script
[+script] .script = {
# Create some adaptive partitions during system startup:
# - IOPKT with a 20% budget
# - QCONN with a 20% budget
# NOTE: To specify a critical budget of 5 ms, use sched_aps as seen below
# when the filesystem on the disk is available.
sched_aps IOPKT 20 5
sched_aps QCONN 20 5
。。。。。。。
# Start the main shell
reopen /dev/con1
[+session] sh &
}
[perms=0777]
# Include the current "libc.so". It will be created as a real file
# using its internal "SONAME", with "libc.so" being a symlink to it.
# The symlink will point to the last "libc.so.*" so if an earlier
# libc is needed (e.g. libc.so.5) add it before the this line.
libc.so
libgcc_s.so.1
/usr/lib/ldqnx-64.so.2=ldqnx-64.so.2
libelfcore.so.1
libslog2.so
#libusbdi.so
devu-hcd-ehci.so
virtual=x86_64,bios描述的是IPL,startup-x86描述的是startup程序,procnto-smp-instr 是procnto程序,.script 中描述的是启动脚本需要完成的事情。最下面的内容是IFS中包含的一些系统启动所需要的库文件以及应用程序。通过buildfile文件,描述了整个IFS构建所需要的文件以及系统的启动流程。
3 以一个例子介绍高通基线应用程序的编译
这边以qcarcam_test为例,介绍一下高通如何利用qnx的规则,来生成对应的应用程序。
qcarcam_test 的目录结构如下所示:
源文件放在src目录中,build目录就是之前第一章所描述的,利用qnx的规则所创建的目录结构,build 目录可以理解为project level,aarch64目录和arm目录为cpu level,o-le和o-le-v7为variant level。
除了variant level的makefile中引用了common.mk:
其他的makefile文件都只是包含了recurse.mk,用来递归搜索下层目录:
目标文件编译规则由common.mk来描述:
ifndef QCONFIG
QCONFIG=qconfig.mk
endif
include $(QCONFIG)
include $(AMSS_ROOT)/amss_defs.mk
include ../../../../../build/qnx/overrides.mk
NAME=qcarcam_test
#===== INSTALLDIR - Subdirectory where the executable or library is to be installed.
INSTALLDIR=$(CAMERA_OUT_BIN)/$(NAME)
ifeq ($(CPULIST),aarch64)
cpulist=aarch64le
else
cpulist=armle-v7
endif
#===== USEFILE - the file containing the usage message for the application.
USEFILE=$(PROJECT_ROOT)/../src/qcarcam_test.use
#===== PINFO - the file containing the packaging information for the application.
define PINFO
PINFO DESCRIPTION=QCarCam test application
endef
#===== EXTRA_SRCVPATH - a space-separated list of directories to search for source files.
EXTRA_SRCVPATH+= \
$(PROJECT_ROOT)/../src \
$(PROJECT_ROOT)/../../test_util/src \
$(PROJECT_ROOT)/../../test_util/src/qnx
#===== EXTRA_INCVPATH - a space-separated list of directories to search for include files.
EXTRA_INCVPATH+= \
$(QCX_ROOT)/utils/inc \
$(QCX_ROOT)/utils/inc/os \
$(PROJECT_ROOT)/../../test_util/inc \
$(AMSS_INC) \