4.2 深入理解符号包装机制
4.2.1 POSIX 应用与实时需求的冲突
本章将深入探讨 Xenomai 中独特的符号包装机制,这一机制在保证 POSIX 应用兼容性的同时,巧妙地引入实时功能,为开发人员在构建实时应用时提供了极大的灵活性和便利。
传统的 POSIX 应用程序基于标准的 POSIX API 进行开发,如使用 pthread 系列函数进行多线程操作。然而,在实时性要求较高的应用场景中(如工业自动化控制、航空航天等领域),标准 POSIX 应用可能无法满足严格的实时性约束。
Xenomai 的符号包装机制为 POSIX 应用程序的实时性增强提供了一种强大而灵活的解决方案。开发人员在掌握其原理和应用场景的基础上,结合实际项目的需要,合理运用这一机制,能够在保证 POSIX 兼容性的前提下,有效地提升应用程序的实时性能,满足嵌入式实时系统开发中的各种复杂需求。
4.2.2 符号包装的关键:链接参数与 cobalt.wrappers 文件
- 链接参数的作用 :在构建 POSIX 应用程序二进制可执行文件时,通过添加特定的链接参数
-Wl,@/root/xenomai/xenomai -v3.2.4 -install/usr/xenomai/lib/cobalt.wrappers,能够引导链接器按照指定的规则进行符号链接。这一参数使得链接器在处理 pthread 相关函数(如 pthread_create 等)时,优先考虑 cobalt.wrappers 文件中定义的包装符号,从而实现对 POSIX 函数的重定向。 - cobalt.wrappers 文件内容解析 :该文件中列举了众多 POSIX 函数的
--wrap指令,例如--wrap pthread_attr_init、--wrap pthread_create等。--wrap是链接器技术,当使用--wrap symbol选项时,链接器会将对原始 symbol 的调用转换为对__wrap_symbol函数的调用。以 pthread_create 为例,这使得在链接过程中,应用程序对 pthread_create 的调用会隐式地链接到 libcobalt 库中定义的__wrap_pthread_create函数,从而实现了对 POSIX 函数的包装和重定向。 - 二进制兼容性 :符号包装机制确保了与原始 POSIX 接口的二进制兼容性。这意味着,尽管进行了符号重定向和包装,但应用程序在调用这些函数时,其二进制接口与标准 POSIX 接口保持一致,无需对应用程序代码进行大量修改即可在 Xenomai 环境下运行。
4.2.3 符号实现原理与调用方式
在 Xenomai 的 include/cobalt/wrappers.h 文件中,通过一系列的宏定义,实现了对 POSIX 函数调用方式的灵活控制。例如,#define __WRAP(call) __wrap_ ## call、#define __STD(call) __real_ ## call、#define __COBALT(call) __cobalt_ ## call、#define __RT(call) __COBALT(call)等宏定义。这些宏通过符号拼接等操作,分别对应了对包装函数、原始标准库函数以及 Cobalt 实现函数的调用。
// include/cobalt/wrappers.h
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
#define __WRAP(call) __wrap_ ## call
#define __STD(call) __real_ ## call
#define __COBALT(call) __cobalt_ ## call
#define __RT(call) __COBALT(call)
#define COBALT_DECL(T, P) \
__typeof__(T) __RT(P); \
__typeof__(T) __STD(P); \
__typeof__(T) __WRAP(P)
#define COBALT_IMPL(T, I, A) \
__typeof__(T) __wrap_ ## I A __attribute__((alias("__cobalt_" __stringify(I)), weak)); \
__typeof__(T) __cobalt_ ## I A
- 函数的声明
COBALT_DECL
宏定义COBALT_DECL用于声明函数,它涉及到如下宏定义:
| 宏名称 | 作用 | 符号类型 | 函数前缀 | 可覆盖性 |
|---|---|---|---|---|
| __WRAP | 生成弱符号包装函数入口 | 弱符号 | __wrap_ | ✅ 可被覆盖 |
| __RT | 等同于__COBALT | 强符号 | __cobalt_ | ❌ 不可覆盖 |
| __COBALT | 直接调用 Cobalt 实现 | 强符号 | __cobalt_ | ❌ 不可覆盖 |
| __STD | 调用原始 libc 实现 | 强符号 | __real_ | ❌ 不可覆盖 |
- __wrap_与__cobalt_前缀函数的定义
COBALT_IMPL
// 原始宏调用形式
COBALT_IMPL(int, sem_init, (sem_t *sem, int pshared, unsigned value));
// 宏展开结果
__typeof__(int) __wrap_sem_init(sem_t *sem, int pshared, unsigned value)
__attribute__((alias("__cobalt_sem_init"), weak));
__typeof__(int) __cobalt_sem_init(sem_t *sem, int pshared, unsigned value);
通过__attribute__((alias))编译器扩展属性,实现了__wrap_sem_init等包装符号与 Cobalt 实现符号(如__cobalt_sem_init)的绑定。同时,弱符号属性允许第三方库重新定义__wrap_sem_init,覆盖默认行为。这种机制既保证了默认的实时实现,又为系统扩展和自定义功能提供了灵活性。
以下是一个简单的示例代码,展示了如何在实际编程中运用 Xenomai 的符号包装机制:
// 示例代码:符号包装机制的应用
#include <cobalt/wrappers.h>
// 定义一个 POSIX 线程函数
void* my_thread_function(void* arg) {
// 线程具体实现代码
return NULL;
}
int main() {
pthread_t thread;
int ret;
// 使用 __RT 宏调用包装的 pthread_create 函数
ret = __RT(pthread_create(&thread, NULL, my_thread_function, NULL));
if (ret != 0) {
// 错误处理
}
// 线程后续操作,如等待线程结束等
return 0;
}
在这个示例中,我们通过__RT(pthread_create(...))显式地强制调用了经过包装的 pthread_create 函数。这样,在链接阶段,链接器会将这个调用重定向到 libcobalt 库中的__cobalt_pthread_create函数。从而为应用程序提供实时线程功能。
- __real_前缀函数的定义
__STD 宏 :__STD()宏用于调用标准库实现,相当于调用__real_后缀的函数。
标准库中的函数通常不会直接定义为带有__real_前缀。标准库提供的函数是按照 POSIX 或其他相关标准定义的,例如pthread_create、sem_init等。这些函数在标准库中的定义不包含__real_前缀。
然而,当你使用符号包装机制(如 Xenomai 的包装机制)时,链接器会为这些标准库函数生成__real_前缀的别名。例如,当你使用–wrap pthread_create链接选项时,链接器会确保__real_pthread_create是一个指向原始pthread_create实现的别名。
如果需要在代码中显式调用原始的标准库实现,可以通过__STD()宏来实现。
一个示例展示了如何使用__STD()宏调用标准库实现:
#include <cobalt/wrappers.h>
#include <semaphore.h>
int main() {
sem_t sem;
// 使用 __STD 宏调用标准的 sem_init 函数
if (__STD(sem_init(&sem, 0, 0)) != 0) {
// 错误处理
}
// 信号量后续操作
return 0;
}
在这个场景中,__STD(sem_init(&sem, 0, 0))直接调用了原始的 POSIX 标准信号量初始化函数,完全绕过了 Xenomai 的符号包装机制。这在需要验证标准 POSIX 信号量行为或者在标准 POSIX 环境下运行时非常有用。
4.2.4 符号机制下编译静态程序
由于使用了–wrap标志,应用程序与 Xenomai POSIX skin 库的静态版本进行链接时,需要分为两个阶段。为了帮助处理这个问题,Xenomai提供一个名为“wrap-link.sh”的脚本。
Xenomai 3.2.4版本的wrap-link.sh脚本并不支持-help参数,与官网描述不符。已经提交 patch: scripts/wrap-link.sh: Add support for -h|–help option 并合入主线。
为了避免 glibc 参与符号拦截(wrap),latency是静态编译的,因为glibc的部分符号在运行时被动态解析,这些符号不会经过Cobalt wrappers,两阶段链接本质上就是为了 latency 这种静态程序准备的,避免延迟测量结果被污染。
root@xeno-demo:~# wrap-link.sh
/usr/xenomai/bin/wrap-link.sh [options] command-line
Split command-line in two parts for linking static applications with
the Xenomai POSIX/Cobalt interface in two stages, so that symbols
from the system libraries are not wrapped.
Options:
-q be quiet
-v be verbose (print each command before running it)
-n dry run (print all commands but don't run any)
Example:
/usr/xenomai/bin/wrap-link.sh -v gcc -o foo foo.o -Wl,@/usr/xenomai/lib/cobalt.wrappers -L/usr/xenomai/lib -lcobalt -lmodechk -lpthread -lrt
will print and run:
+ gcc -o foo.tmp -Wl,-Ur -nostdlib foo.o -Wl,@/usr/xenomai/lib/cobalt.wrappers -Wl,@/usr/xenomai/lib/modechk.wrappers -L/usr/xenomai/lib
+ gcc -o foo foo.tmp -L/usr/xenomai/lib -lcobalt -lmodechk -lpthread -lrt
+ rm foo.tmp
以ARM64交叉编译为例,设置编译器CC = aarch64-linux-gnu-gcc。在构建二进制文件的过程中,在编译阶段,使用$(CC);但是在链接阶段,需要在makefile或链接命令中,把$(CC)替换成$(xenomai_srcdir)/scripts/wrap-link.sh $(CC),才能正确的处理-static参数,完成静态程序的链接。
另外,从原理上来说,wrap-link.sh不仅可以完成静态程序的链接,也能兼容动态程序的链接。所以,建议默认都使用$(CCLD)来连接程序。
当前使用的Xenomai v3.2.4在支持静态程序存在一个bug,静态链接的程序执行后会之间报告Aborted并退出。此bug已经报告给社区,并得到fix。只需要合并 Patch: lib/cobalt: Unbreak sigshadow installation for statically linked applications 即可支持静态程序。
如下是一个支持编译静态程序的Makefile,会构建出一个静态链接的hello程序。
XENO_DESTDIR=/root/xenomai/xenomai-v3.2.4-install
CC = aarch64-linux-gnu-g++
CCLD = $(XENO_DESTDIR)/usr/xenomai/bin/wrap-link.sh $(CC)
XENO_CONFIG =$(XENO_DESTDIR)/usr/xenomai/bin/xeno-config
XENO_POSIX_CFLAGS =$(shell DESTDIR=$(XENO_DESTDIR) $(XENO_CONFIG) --skin=posix --cflags)
XENO_POSIX_LDFLAGS =$(shell DESTDIR=$(XENO_DESTDIR) $(XENO_CONFIG) --skin=posix --ldflags)
PROJPATH = .
EXECUTABLE := hello
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))
all: $(EXECUTABLE)
$(EXECUTABLE): $(obj)
$(CCLD) -g -static -o $@ $^ $(XENO_POSIX_LDFLAGS)
%.o:%.c
$(CC) -g -o $@ -c $< $(XENO_POSIX_CFLAGS)
.PHONY: clean
clean:
rm -f $(EXECUTABLE) $(obj)
构建出hello静态程序后,使用file命令可以看到这是一个statically linked程序。
file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=42da2920acbf98af514bb3fcd0d8967d662b38ba, for GNU/Linux 3.7.0, with debug_info, not stripped
4.2.5 符号调用方式的应用场景与优势
通过深入理解 Xenomai 的符号包装机制,开发人员可以充分利用其优势,在 POSIX 应用开发中灵活地引入实时功能,同时保持代码的兼容性、可移植性和可维护性。在实际编程中,合理运用__RT()、__COBALT()和__STD()等宏,结合对链接参数和 cobalt.wrappers 文件的正确配置,能够高效地构建出满足实时性要求的高质量应用程序。
在实际应用中,开发人员可以根据项目的实时性需求、代码结构以及维护成本等多方面因素,灵活选择合适的符号调用方式。例如,在一个需要高度实时性的控制系统中,对于关键的线程创建、信号量操作等函数,可以优先使用__COBALT()宏直接调用 Cobalt 实现,以确保实时性能。而对于一些辅助功能或者不涉及实时约束的部分,可以采用__RT()宏或者__STD()宏,以平衡实时性和标准兼容性。
- 大型 POSIX 代码库的实时功能集成 :在大型的 POSIX 代码库中,直接修改代码以引入实时功能往往成本高昂且风险较大。Xenomai 的符号包装机制允许开发人员在不破坏原有代码结构和可移植性的前提下,通过选择性地调用
__RT()或__COBALT()宏,逐步引入实时功能,实现 POSIX 应用程序向实时应用的平滑过渡。 - 实时服务库的开发 :当开发不依赖链接器包装机制的实时服务库时,使用
__COBALT()宏可以确保库中的 POSIX 函数调用直接使用 Cobalt 实现,从而提供稳定可靠的实时服务。这有助于构建高效的实时系统组件,满足实时性要求苛刻的应用需求。 - 保持 POSIX 标准兼容性的应用程序开发 :对于那些需要同时运行在标准 POSIX 环境和 Xenomai 实时环境下的应用程序,符号包装机制通过
__STD()宏提供了对标准库实现的访问。开发人员可以在代码中根据实际运行环境和功能需求,灵活地在实时实现和标准实现之间进行切换,大大提高了应用程序的通用性和适应性。
939

被折叠的 条评论
为什么被折叠?



