OpenHarmony下musl编译工具链普法
引言
欠的债总是要还的,这不前面欠的关于OpenHarmony下musl相关的还是要还的。这里我对其中的相关知识点,梳理,归纳重新消化下!
一.GCC/Clang/LLVM的区别与联系
说实话,这块我现在都木有搞清楚,GCC/Clang/LLVM之间的区别与联系!这里只能硬着头皮,捡一捡网上各路大神的总结,先理解一波有个基本概念!
先了解,后面再深入吗!先搞个普法,对概念先有个基本的认识!
1.1 GCC的简介
传统的编译器譬如gcc设计是分为三个部分,如下图所示:
-
前端, front end:解析源码,检查错误(词法分析(lexical analysis), 语法分析(syntax analysis), 语义分析(semantic analysis)),所以前端又称为分析阶段。并且构建一个特定于语言的抽象语法树(Abstract Syntax Tree,AST)来代表输入的代码。
-
优化器,optimizer :负责进行各种转换尝试,改善代码的执行时间,比如消除冗余计算。通常或多或少与语言及目标无关。
-
后端,back end:代码产生器,将代码映射到目标指令集,除了编写正确的代码外,它还负责生成利用所支持体系结构的不寻常特性的良好代码。一个编译器后端的通用部分包含指令选择,寄存器分配,及指令调度(instruction selection, register allocation, and instruction scheduling)
1.2 GCC架构的优点
GCC架构的优点总结为如下几点:
- 使用通用的中间代码,新增支持语言只需要新增一个前端,新增支持目标机器只需要新增一个后端
- 针对前后端,开发人员技术栈不同,开发人员只需要关注自身的技术栈,有利于让更多人员参与进来
总结来说,前端+优化器+后端的结构有三大好处:灵活程度高,组件复用率高,维护成本低
1.3 GCC架构的缺点
- GCC的三段式模块必须配套使用,很难做到部分重用
- 不能将 GCC 部分功能作为库重用的原因有很多,包括滥用全局变量、不可变变量不能更改限制不严、设计不良的数据结构、庞大的代码库、以及使用宏来防止代码库一次编译可以支持多个编译前端-目标机对。不过,最难解决的问题是其早期设计和那个时代所固有的架构设计。具体来说,GCC 饱受分层问题和抽象漏洞的困扰:编译后端遍历编译前端抽象语法树(AST)来生成调试信息(debug info),编译前端生成编译后端数据的结构,整个编译器依赖命令行设置的全局数据结构。
上面的这些优缺点,不是专业人士基本是体会不到相关差异的。我是木有体会到!
1.4 LLVM介绍
LLVM是开源的编译器(compiler)架构,以cpp编写而成,用于优化以随意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、执行时间(run-time)以及空暇时间(idle-time),对开发人员保持开放,并兼容已有脚本。LLVM的设计就采用了上述前端+优化器+后端的设计。如下图所示:
基于这个认知,我们可以认为 LLVM 包括了两个概念:一个广义的 LLVM 和一个狭义的 LLVM 。广义的 LLVM 指的是一个完整的 LLVM 编译器框架系统,包括了前端、优化器、后端、众多的库函数以及很多的模块;而狭义的 LLVM 则是聚焦于编译器后端功能的一系列模块和库,包括代码优化、代码生成、JIT 等。
整体的编译器架构就是 LLVM 架构:
- Clang 大致可以对应到编译器的前端,主要处理一些和具体机器无关的针对语言的分析操作;
- 编译器的优化器和后端部分就是之前提到的 LLVM 后端,即狭义的 LLVM。
此外,由于 LLVM 的命名最早源自于底层虚拟机(Low Level Virtual Machine) 的首字母缩写,但这个项目的范围并不局限于创建一个虚拟机,这个缩写导致了大量的疑惑。LLVM 成长之后已成为众多编译工具及低级工具技术的统称,使得这个名字变得更不贴切,所以开发者决定放弃这个缩写的涵义,现在 LLVM 已独立成为一个品牌,适用于 LLVM 下的所有项目,包括 LLVM 中介码、LLVM 除错工具、LLVM cpp 标准库等。
1.5 clang介绍
Clang 是一个 C、cpp、Objective-C 和Objective-cpp编程语言的编译器前端,采用底层虚拟机(LLVM)作为后端。
- Clang 采用的是 BSD 协议的许可证,而 GCC 采用的是 GPL 协议,显然前者更为宽松;
- Clang 是一个高度模块化开发的轻量级编译器,编译速度快、占用内存小、有着友好的出错提示
- Clang项目包括Clang前端和Clang静态分析器等,目的是输出代码对应的抽象语法树并将代码编译成LLVM bitcode
- 接着后端使用LLVM编译成平台相关的机器语言
1.6 gcc和clang的交叉编译
gcc和clang都支持交叉编译链,gcc这个就比较常见了像后面章节2.2所描述的就是gcc的一个交叉编译工具链!clang通过–target=基本选项是定义目标体系结构,triple 的一般格式为<sub>---,其中:
- arch = x86_64、i386、arm、thumb、mips等。
- sub = v5, v6m, v7a, v7m等。
- vendor = pc, apple, nvidia, ibm,等。
- sys = none, linux, win32, darwin, cuda等。
- abi = eabi, gnu, android, macho, elf等。
譬如:
--target=arm-linux-gnueabihf
并且通常一个大型的编译工程,不会是上面这么直接裸写的,一般会通过环境变量进行控制的。
export LINUX_ROOT="$HOME/OH_V3.2_BETA2/code-v3.2-Beta2"
export SYSROOT="${LINUX_ROOT}/OpenHarmony/out/rk3568/obj/third_party/musl"
export LLVM_BUILD_DIR="${LINUX_ROOT}/llvm"
export PATH=${LINUX_ROOT}/OpenHarmony/prebuilts/clang/ohos/linux-x86_64/llvm/bin:$PATH
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-ohos-
export HOSTCC=clang
export BUILD=debug
1.使用Clang作为编译器 —— 使用 Clang 交叉编译
2. 使用 Clang 交叉编译
3. How To Cross-Compile Clang/LLVM using Clang/LLVM
4. LLVM cross-compiled Linux From Scratch: C & cpp libraries
1.7 小结
前面说了这么多,其实我也还还不是很清楚!先不管吗,先把参考的链接放上!
这里我对gcc,clang和llvm的理解可以用一句话来概括就是: gcc = clang(前端) + 狭义的llvm后端
1.经典编译器组成(前端+优化器+后端)以及LLVM和Clang简介
2.【编译原理】GCC/Clang/LLVM的区别与联系
3.请问LLVM与GCC之间的关系
4.GCC和clang/LLVM
5.详解三大编译器:gcc、llvm 和 clang
二.musl你究竟是个啥
musl你是个啥,还得从它的的出生,发展历程出发一一道来。这里我们就从各个方面来捋一捋它!musl 是基于 Linux 系统调用 API 之上实现的 C 语言标准库(libc),历史可以追溯到 2005 年,但在 2011 年才正式确定了 MUSL 这个名称,开始对标 glibc、uClibc,从 2012 年开始,musl 开始使用 MIT License。
musl 致力于简单、高效,凭借 libc 优秀的松耦合性,做到了静态版本最小10kB,即使添加了线程等高级特性,也可以控制到 50kB,所以特别适合嵌入式设备。为了控制资源的使用, musl自己竟然不做动态内存分配。
2.1 glibc和musl-libc
这个我们分别来介绍下:
-
glibc: glibc是GNU C Library 是GNU项目(GNU Project),所实现的 C语言标准库(C standard library)。 目前,常见的桌面和服务器中的GNU/Linux类的系统中,都是用的这套C语言标准库。 其实现了常见的C库的函数,支持很多种系统平台,功能很全,但是也相对比较臃肿和庞大
-
musl-libc:C语言标准库Musl-libc项目发布了1.0版。Musl是一个轻量级的C标准库,设计作为GNU C library (glibc)、 uClibc或Android Bionic的替代用于嵌入式操作系统和移动设备。它遵循POSIX 2008规格和 C99 标准,采用MIT许可证授权,使用Musl的Linux发行版和项目包括sabotage,bootstrap-linux,LightCube OS等等
musl libc 和 glibc 是两个常见的 C 标准库实现,它们有一些差异。下面列出了其中一些主要的差异:
-
大小和速度:musl libc 要小得多,因为它没有像 glibc 那样提供大量的额外功能。相反,musl libc 专注于尽可能减少代码大小和函数调用开销,以提高性能。
-
兼容性:glibc 是 Linux 系统上最常见的 C 标准库,并且具有广泛的兼容性,支持许多架构和操作系统。相比之下,musl libc 对其他平台和操作系统的移植性较差。
-
实现方法:musl libc 是使用静态链接编译的,这使得它更易于构建和管理,并且不需要动态链接器。相反,glibc 使用动态链接器,这也使得它更灵活,因为它可以动态加载所需的库。
-
POSIX 标准:musl libc 更加严格地遵循 POSIX 标准,而glibc 则添加了一些扩展,以提供更多的功能和兼容性。
-
错误处理:musl libc 实现的错误处理更严格和更规范,而 glibc 则有更多的错误处理选项,并且支持不同的语言环境。
-
版权问题:由于采用了 BSD 许可证,musl libc 比 glibc 更容易以开源、商业和专有软件的形式使用。
综上所述,选择使用 musl libc 还是 glibc 取决于您的具体需求。如果您需要一个小巧且速度较快的 C 标准库,在 Linux 系统上使用,则可以考虑使用 musl libc。如果您需要更广泛的兼容性和功能,则可以使用 glibc。
- 额外补充:两个都是C++标准库。libc++是针对clang编译器特别重写的cpp标准库,那libstdc++自然就是gcc的事儿了。
- 无关的科普: libc.so.6是c运行时库glibc的软链接,而系统几乎所有程序都依赖C运行时库。程序启动和运行时,是根据libc.so.6软链接找到glibc库。删除libc.so.6将导致系统的几乎所有程序不能工作。如果程序编译的时候链接的libc库版本不在程序运行环境下的glibc库支持的libc版本之内,也会报错。于是,系统的所有命令 IS,cp,cd等等都无法使用了。
1.Musl libc:为什么我们会需要另一个 libc?
2.musl和glibc,性能区别到底有多大
3.理清gcc、glibc、libstdc++的关系
4.uclibc、eglibc、glibc、Musl-libc之间的区别和联系
5.如何紧急修复,当你在linux中干穿了libc丢了libc.so.6,ld-linux-x86-64.so.2其中一个
干穿了,我就提桶跑路,还修复个啥!
6.musl简介
7.glibc和musl libc的区别
8.在同一个进程中加载 musl libc.so 和 gcc libc.so?
9.musl 的 GCC 包装器与 musl 的交叉编译器有何不同?
10.gcc编译静态库到自己的程序 解决在不同linux下因libc版本问题而不能运行 版本兼容问题
11.glib和glibc的联系区别
12.libc、glibc 和 glib 的关系
13.glibc所包含的各个库
2.2 aarch64-linux-musl-xxx
前面搞完了一个musl-libc库,这里又来了一个aarch64-linux-musl-xxx。你们是逗比吗,来这么多。真的逗比,今天又联系补上!
这里我们对aarch64-linux-musl其简单介绍下:
-
aarch64 是随 ARMv8 ISA 一起引入的 64 位架构,用于执行 A64 指令的计算机。而且在 aarch64 状态下执行的代码只能使用 A64 指令集。,而不能执行 A32 或 T32 指令。但是,与 AArch32 中不同,在64位状态下,指令可以访问 64 位和 32 位寄存器。
-
aarch64-linux-musl 是一个交叉编译工具链,可以在其他架构的系统中,编译安装 64 位 arm 架构的程序。常用在嵌入式代码的移植中。aarch64-linux-musl 是由musl官网基于 GCC 推出的的 ARM 交叉编译工具。可用于交叉编译 ARMv8 64 位目标中的裸机程序、u-boot、Linux kernel、filesystem 和 App 应用程序。aarch64-linux-musl 交叉编译器必须安装在 64 位主机上,才能编译目标代码。
aarch64-linux-musl-xxx如下所示:
$/opt/musl/bin$ ls
aarch64-linux-musl-addr2line aarch64-linux-musl-gcc-9.4.0 aarch64-linux-musl-nm
aarch64-linux-musl-ar aarch64-linux-musl-gcc-ar aarch64-linux-musl-objcopy
aarch64-linux-musl-as aarch64-linux-musl-gcc-nm aarch64-linux-musl-objdump
aarch64-linux-musl-c++ aarch64-linux-musl-gcc-ranlib aarch64-linux-musl-ranlib
aarch64-linux-musl-cc aarch64-linux-musl-gcov aarch64-linux-musl-readelf
aarch64-linux-musl-c++filt aarch64-linux-musl-gcov-dump aarch64-linux-musl-size
aarch64-linux-musl-cpp aarch64-linux-musl-gcov-tool aarch64-linux-musl-strings
aarch64-linux-musl-elfedit aarch64-linux-musl-gprof aarch64-linux-musl-strip
aarch64-linux-musl-g++ aarch64-linux-musl-ld
aarch64-linux-musl-gcc aarch64-linux-musl-ld.bfd
这里我们可以把 aarch64-linux-musl-xxx和aarch64-linux-gnu-gcc做对比。它是一个gcc交叉编译工具链。
XXX:/opt/musl$ tree -L 1
.
├── aarch64-linux-musl
├── bin
├── include
├── lib
├── libexec
└── share#其中git仓库地址如下,对应的https://github.com/richfelker/musl-cross-make.git
https://gitee.com/stesen/musl-cross-make_for_openharmony.git
三.其它关于工具链知识的一些普法
工具链的知识真多,搞了一个又是一个!搞不完,记不住。但是还是木的办法,只能硬着头皮上!我不入地狱谁入地狱。我是超人!
3.1 什么是sysroot
sysroot 被称为逻辑根目录,只在链接过程中起作用,作为交叉编译工具链搜索库文件的根路径,如配置–sysroot=dir,则dir作为逻辑根目录,链接器将在dir/usr/lib中搜索库文件。
export LINUX_ROOT="$HOME/xxx/linux"
export SYSROOT="${LINUX_ROOT}/sysroot-ubuntu-20.04-arm64"export LINUX_ROOT="$HOME/OH_V3.2_BETA2/code-v3.2-Beta2"
export SYSROOT="${LINUX_ROOT}/OpenHarmony/out/rk3568/obj/third_party/musl"
只有链接器开启了–with-sysroot选项,–sysroot=director才生效
1.编译链接实战(4)–sysroot和-isysroot选项对编译的影响
2.GCC编译选项和环境变量
3.sysroot位何物
3.2 pkg-config是个什么东东?
pkg-config的功能主要是在编译应用程序和库的时候作为一个工具来使用。pkg-config可以使用第三方库编译程序,指定库文件和头文件的位置。pkg-config就是通过.pc文件获取库的各种必要信息,包括版本信息、编译和链接需要的参数等。不同的系统的库文件的路径不同,用户也可以把库安装到不同的目录下
对于一个比较大第三方库,其头文件和库文件的数量是比较多的。如果我们一个个手动地写,那将是相当麻烦的。所以,pkg-config就应运而生了。大家应该都知道一般用第三方库的时候,就少不了要使用到第三方的头文件和库文件。我们在编译、链接的时候,必须要指定这些头文件和库文件的位置。对于一个比较大的第三方库,其头文件和库文件的数量是比较多的,如果我们一个个手动地写,那将是相当的麻烦的。因此,pkg-config就应运而生了。pkg-config能够把这些头文件和库文件的位置指出来,给编译器使用。pkg-config主要提供了下面几个功能:
- 检查库的版本号。 如果所需要的库的版本不满足要求,它会打印出错误信息,避免链接错误版本的库文件
- 获得编译预处理参数,如宏定义、头文件的位置
- 获得链接参数,如库及依赖的其他库的位置,文件名及其他一些链接参数
- 自动加入所依赖的其他库的设置
pkg-config能够把这些头文件和库文件的位置指出来,给编译器使用。如果你的系统装有libdrm,可以尝试一下下面的命令$pkg-config --cflags libdrm。可以看到其输出是gtk的头文件的路径。
我们平常都是这样用pkg-config的。
$gcc main.c `pkg-config --cflags --libs libdrm` -o main$pkg-config --cflags --libs libdrm
-I/usr/local/include -I/usr/local/include/libdrm -L/usr/local/lib -ldrm
上面的编译命令中,`pkg-config --cflags --libs libdrm`的作用就如前面所说的,把gtk的头文件路径和库文件列出来,让编译去获取。–cflags和–libs分别指定头文件和库文件。
命令中的`不是引号,而是数字1左边那个键位的那个符号。
其实,pkg-config同其他命令一样,有很多选项,不过我们一般只会用到–libs和–cflags选项。
$pkg-config --help
Usage:pkg-config [OPTION?]Help Options:-h, --help Show help optionsApplication Options:--version output version of pkg-config--modversion output version for package--atleast-pkgconfig-version=VERSION require given version of pkg-config--libs output all linker flags--static output linker flags for static linking--short-errors print short errors--libs-only-l output -l flags--libs-only-other output other libs (e.g. -pthread)--libs-only-L output -L flags--cflags output all pre-processor and compiler flags--cflags-only-I output -I flags--cflags-only-other output cflags not covered by the cflags-only-I option--variable=NAME get the value of variable named NAME--define-variable=NAME=VALUE set variable NAME to VALUE--exists return 0 if the module(s) exist--print-variables output list of variables defined by the module--uninstalled return 0 if the uninstalled version of one or more module(s) or their dependencies will be used--atleast-version=VERSION return 0 if the module is at least version VERSION--exact-version=VERSION return 0 if the module is at exactly version VERSION--max-version=VERSION return 0 if the module is at no newer than version VERSION--list-all list all known packages--debug show verbose debug information--print-errors show verbose information about missing or conflicting packages (default unless --exists or --atleast/exact/max-version given on the command line)--silence-errors be silent about errors (default when --exists or --atleast/exact/max-version given on the command line)--errors-to-stdout print errors from --print-errors to stdout not stderr--print-provides print which packages the package provides--print-requires print which packages the package requires--print-requires-private print which packages the package requires for static linking--validate validate a package's .pc file--define-prefix try to override the value of prefix for each .pc file found with a guesstimated value based on the location of the .pc file--dont-define-prefix don't try to override the value of prefix for each .pc file found with a guesstimated value based on the location of the .pc file--prefix-variable=PREFIX set the name of the variable that pkg-config automatically sets
3.2.1 pkg-config配置环境变量
事实上,pkg-config只是一个工具,所以不是你安装了一个第三方库,pkg-config就能知道第三方库的头文件和库文件的位置的。为了让pkg-config可以得到一个库的信息,就要求库的提供者提供一个.pc文件。默认情况下,比如执行如下命令:
# pkg-config --libs --cflags glib-2.0
-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lglib-2.0
pkg-config会到/usr/lib/pkconfig/目录下去寻找glib-2.0.pc文件。也就是说在此目录下的.pc文件,pkg-config是可以自动找到的。然而假如我们安装了一个库,其生成的.pc文件并不在这个默认目录中的话,pkg-config就找不到了。此时我们需要通过PKG_CONFIG_PATH环境变量来指定pkg-config还应该在哪些地方去寻找.pc文件。
我们可以通过如下命令来设置PKG_CONFIG_PATH环境变量:
# export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig/
这样pkg-config就会在/usr/local/lib/pkgconfig/目录下寻找.pc文件了。另外还需要注意的是,上述环境变量的设置只对当前的终端窗口有效。为了让其永久生效,我们可以将上述命令写入到/etc/bash.bashrc等文件中(配置环境变量有很多方法,不止这一种),以方便后续使用。
3.3 pc文件是个什么东东?
pc文件是个什么东东,这里我用一句话来概括就是pkg-config用来在PKG_CONFIG_PATH查找库相关依赖的,一种特殊格式文件!既然是OpenHarmony系统,这里我们就以OpenHarmony下开源mesa3d里面的pc文件为例来举例说明:
$pwd
/home/xxx/xxx/third_party/mesa3d
$cat ohos/pkgconfig_template/zlib.pc
prefix=ohos_project_directory_stub
exec_prefix=${prefix}
libdir=${prefix}/out/ohos-arm-release/obj/third_party/zlib
sharedlibdir=${libdir}
includedir=${prefix}/third_party/zlibName: zlib
Description: zlib compression library
Version: 1.2.12Requires:
Libs: -L${libdir} -L${sharedlibdir} -lz
下面我们简单描述一下pc文件中的用到的一些关键词:
-
Name: 一个针对library或package的便于人阅读的名称。这个名称可以是任意的,它并不会影响到pkg-config的使用,pkg-config是采用pc文件名的方式来工作的。
-
Description: 对package的简短描述
-
URL: 人们可以通过该URL地址来获取package的更多信息或者package的下载地址
-
Version: 指定package版本号的字符串
-
Requires: 本库所依赖的其他库文件。所依赖的库文件的版本号可以通过使用如下比较操作符指定:=,<,>,<=,>=
-
Requires.private: 本库所依赖的一些私有库文件,但是这些私有库文件并不需要暴露给应用程序。这些私有库文件的版本指定方式与Requires中描述的类似。
-
Conflicts: 是一个可选字段,其主要用于描述与本package所冲突的其他package。版本号的描述也与Requires中的描述类似。本字段也可以取值为同一个package的多个不同版本实例。例如: Conflicts: bar < 1.2.3, bar >= 1.3.0
-
Cflags: 编译器编译本package时所指定的编译选项,和其他并不支持pkg-config的library的一些编译选项值。假如所需要的library支持pkg-config,则它们应该被添加到Requires或者Requires.private中
-
Libs: 链接本库时所需要的一些链接选项,和其他一些并不支持pkg-config的library的链接选项值。与Cflags类似
-
Libs.private: 本库所需要的一些私有库的链接选项
1.pkg-config 与.pc文件
2.pkgconfig和环境变量PKG_CONFIG_PATH和PKG_CONFIG_LIBDIR
3.Linux中pkg-config的使用
4.Linux修改环境变量的4种方法
3.4 怎么通过OpenHarmony源码编译ohos-sdk
很简单,如下:
./build/prebuilts_download.sh
./build.sh --product-name ohos-sdk –ccache --target-cpu arm64
1.Musl-libc库编译
该博客,简单介绍了怎么编译生成musl libc库!
2.OpenHarmony富设备移植指南—开源GPU驱动编译
这篇博客我们并不是学习怎么编译GPU,而是学习musl + clang配置编译
3.aarch64-linux-ohos交叉编译rust
4.ohos_cross_tools交叉编译工具链
5.一文带你读懂如何移植三方库到OpenHarmony
++参考价值不大,已经完全过时了!主要描述了,如何移植第三方库到OH平台,这个可以为后续vivante GPU移植到OH参考使用++
6.基于OpenHarmony的第三方库编译移植环境(arm-linux-ohos平台)
7.交叉编译 Arm64 OHOS(鸿蒙系统)版本
8.OpenHarmony musl交叉编译
这篇博客可以参考,可以怎么使用musl + gcc编译
9.clang交叉编译Android clang交叉编译使用musl库
10.Building Linux with Clang/LLVM