xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务

简介: 本文介绍了如何将POSIX应用程序编译为在Xenomai实时内核上运行的程序。

版权声明:本文为本文为博主原创文章,转载请注明出处。如有错误,欢迎指正。

1. 引出问题

上一篇文章xenomai内核解析--双核系统调用(一)以X86处理器为例,分析了xenomai内核调用的流程,读了以后可能会觉得缺了点什么,你可能会有以下疑问:

  1. 系统中的两个内核都是POSIX接口实现系统调用,那么我写一个POSIX接口的应用程序,怎样知道它调用的内核,或者说怎样成为运行在cobalt内核的RT应用,而不是普通linux应用?
  2. 对于同一个POSIX接口,可能我的程序中,既需要xenomai内核提供服务(xenomai 系统调用),又需要调用linux内核提供服务(linux内核系统调用),或者说既有libcobalt,又有glibc库,他们是如何实现或区分的?

2. 编译链接

对于问题1,答案是:由编译的链接过程决定,链接的库不同当然执行的也就不同,如果普通的编译,则该应用编译后是一个普通linux运用。如果要编译为xenomai应用,则需要链接到xenomai库,但链接时是通过符号表(symbol)来链接的,当然也就要从代码符号(symbol)入手,我们会有疑惑xenomai是如何狸猫换太子的?首先来看一个常用的编译xenomai 应用的makefile:

XENO_CONFIG := /usr/xenomai/bin/xeno-config

PROJPATH = .

CFLAGS := $(shell $(XENO_CONFIG)   --posix --alchemy --cflags)
LDFLAGS := $(shell $(XENO_CONFIG)  --posix --alchemy --ldflags)
INCFLAGS= -I$(PROJPATH)/include/


EXECUTABLE := rt-app

src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))

all: $(EXECUTABLE)

$(EXECUTABLE): $(obj)
        $(CC) -g -o $@ $^  $(INCFLAGS) $(CFLAGS) $(LDFLAGS)

%.o:%.c
        $(CC) -g -o $@ -c $<  $(INCFLAGS) $(CFLAGS) $(LDFLAGS)

.PHONY: clean
clean:
        rm -f $(EXECUTABLE) $(obj)

其中最重要的就是编译时需要xeno-config来生成gcc参数。xeno-config在我们编译安装xenomai库后,默认放在/usr/bin/xeno-config

$ /usr/bin/xeno-config --help
xeno-config --verbose
        --core=cobalt
        --version="3.1"
        --cc="gcc"
        --ccld="/usr/bin/wrap-link.sh gcc"
        --arch="x86"
        --prefix="/usr"
        --library-dir="/usr/lib"
Usage xeno-config OPTIONS
Options :
        --help
        --v,--verbose
        --version
        --cc
        --ccld
        --arch
        --prefix
        --[skin=]posix|vxworks|psos|alchemy|rtdm|smokey|cobalt
        --auto-init|auto-init-solib|no-auto-init
        --mode-check|no-mode-check
        --cflags
        --ldflags
        --lib*-dir|libdir|user-libdir
        --core
        --info
        --compat

例如编译一个POSIX接口的实时应用,参数--cflags表示编译,指定接口(skin)--posix,就能得到编译该程序的gcc参数了,看着没什么特别的:

$ /usr/bin/xeno-config --posix --cflags
-I/usr/include/xenomai/cobalt -I/usr/include/xenomai -D_GNU_SOURCE -D_REENTRANT -fasynchronous-unwind-tables -D__COBALT__ -D__COBALT_WRAP__

再看链接--ldflags表示链接,如下得到链接参数:

$ /usr/bin/xeno-config --ldflags --posix
-Wl,--no-as-needed -Wl,@/usr/lib/cobalt.wrappers -Wl,@/usr/lib/modechk.wrappers  /usr/lib/xenomai/bootstrap.o -Wl,--wrap=main -Wl,--dynamic-list=/usr/lib/dynlist.ld -L/usr/lib -lcobalt -lmodechk -lpthread -lrt

这一看就多出不少东西,重点就在这cobalt.wrappersmodechk.wrappers,两个文件的内容如下:

...
--wrap open
--wrap open64
--wrap socket
--wrap close
--wrap ioctl
--wrap read
....
--wrap recv
--wrap send
--wrap getsockopt
--wrap setsockop
...

里面是一些posix系统调用,前面的--wrap是什么作用?这是执行链接过程的程序ld的一个参数,通过man ld可以找到该参数的说明:

--wrap symbol
           Use a wrapper function for symbol.  Any undefined reference to symbol will be resolved to "__wrap_symbol".  Any undefined reference to "__real_symbol" will be resolved to symbol.

           This can be used to provide a wrapper for a system function.  The wrapper function should be called "__wrap_symbol".  If it wishes to call the system function, it should call "__real_symbol".

           Here is a trivial example:

                   void *
                   __wrap_malloc (size_t c)
                   {
                     printf ("malloc called with %zu\n", c);
                     return __real_malloc (c);
                   }

           If you link other code with this file using --wrap malloc, then all calls to "malloc" will call the function "__wrap_malloc" instead.  The call to "__real_malloc" in "__wrap_malloc" will call the real "malloc"
           function.

           You may wish to provide a "__real_malloc" function as well, so that links without the --wrap option will succeed.  If you do this, you should not put the definition of "__real_malloc" in the same file as
           "__wrap_malloc"; if you do, the assembler may resolve the call before the linker has a chance to wrap it to "malloc".

简单来说就是:任何 对symbol未定义 的 引用 (undefined reference) 将 解析为 __wrap_symbol. 任何 对__real_symbol未定义 的 引用 将 解析为 symbol。意思就是我们代码里使用到且是参数--wrap指定的符号symbol(即文件里的内容),链接的时候就认为它是__wrap_symbol,同时编译参数还指定了头文件目录,这样代码中头文件stdio.h编译时使用的是libcobalt中的stdio.h,stdio.h中声明了__wrap_symbol,比如我们代码用到了open()在链接的时候是与库中的__wrap_open()链接的,__wrap_open就是在xenomai 实时库libcobalt中实现,我们还有另一个问题,既然链接到了libcobalt,但我们是要Linux来服务,又是怎么让Linux来提供服务的呢?看libcobalt具体实现可以知道答案。

对于xeno-config的其他更多参数可通过xenomai Manual Page了解。

这样就将POSIX接口源码编译成一个xenomai可执行程序了。

3. libcobalt中的实现

下面来看问题2,既然我们已将一个接口链接到实时内核库libcobalt,当然由实时内核库libcobalt来区分该发起linux内核调用还是xenomai内核系统。与上一篇文章一样,以一个POSIX接口pthread_cretate()来解析libcobalt中的实现。

xenomai线程的创建流程比较复杂,需要先让linux创建普通线程,然后再由xenomai创建该线程的shadow 线程,即xenomai调度的实时线程,很符合我们上面的提出的问题2。 说到这先简答介绍一下xenomai实时线程的创建,详细的创建流程后面会写专门写一篇文章解析,敬请期待。

pthread_cretate()不是一个系统调用,由NPTL(Native POSIX Threads Library)实现(NPTL是Linux 线程实现的现代版,由UlrichDrepper 和Ingo Molnar 开发,以取代LinuxThreads),NPTL负责一个用户线程的用户空间栈创建、内存分配、初始化等工作,与linux内核配合完成线程的创建。每一线程映射一个单独的内核调度实体(KSE,Kernel Scheduling Entity)。内核分别对每个线程做调度处理。线程同步操作通过内核系统调用实现。

xenomai coblat作为实时任务的调度器,每个实时线程需要对应到 coblat调度实体,如果要创建实时线程就需要像linux那样NPTL与linux 内核深度结合,那么coblat与libcoblat实现将会变得很复杂。在这里,xenomai使用了一种方式,由NPTL方式去完成实时线程实体的创建(linux部分),在普通线程的基础上附加一些属性,对应到xenomai cobalt内核实体时能被实时内核cobalt调度。

所以libcoblat库中的实时线程创建函数pthread_cretate最后还是需要使用 glibc的pthread_cretate函数,xenomai只是去扩展glibc pthread_cretate创建的线程,使这个线程可以在实时内核cobalt调度。

pthread_cretate()在libcobalt中pthread.h文件中定义如下:

COBALT_DECL(int, pthread_create(pthread_t *ptid_r,
                const pthread_attr_t *attr,
                void *(*start) (void *),
                void *arg));

COBALT_DECL宏在wrappers.h中如下,展开上面宏,会为pthread_create()生成三个类型函数:

#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)

int __cobalt_pthread_create(pthread_t *ptid_r,
                const pthread_attr_t *attr,
                void *(*start) (void *),
                void *arg);
int __wrap_pthread_create(pthread_t *ptid_r,
                const pthread_attr_t *attr,
                void *(*start) (void *),
                void *arg);
int __real_pthread_create(pthread_t *ptid_r,
                const pthread_attr_t *attr,
                void *(*start) (void *),
                void *arg);

声明pthread_create()函数的这三个宏意思为:

  • __RT(P):__cobalt_pthread_create 明确表示Cobalt实现的POSIX函数
  • __COBALT(P):与__RT()等效。
  • __STD(P):__real_pthread_create表示这是原始的POSIX函数(Linux glibc实现),cobalt库内部通过它来表示调用原始的POSIX函数(glibc NPTL).
  • __WRAP(P)__wrap_pthread_create__cobalt_pthread_create 的弱别名,如果编译器编译时知道有该函数其它的实现,该函数就会被覆盖。

    主要关注前面两个,对于最后一个宏,如果外部库想覆盖已有的函数,应提供其自己的__wrap_pthread_create()实现,来覆盖Cobalt实现的pthread_create()版本。 原始的Cobalt实现仍可以引用为__COBALT(pthread_create)。由宏COBALT_IMPL来定义:

    #define COBALT_IMPL(T, I, A)                                \
    __typeof__(T) __wrap_ ## I A __attribute__((alias("__cobalt_" __stringify(I)), weak));    \
    __typeof__(T) __cobalt_ ## I A
    

最后cobalt库函数pthread_create实现主体为(xenomai3.x.x\lib\cobalt\thread.c):

COBALT_IMPL(int, pthread_create, (pthread_t *ptid_r,
                  const pthread_attr_t *attr,
                  void *(*start) (void *), void *arg))
{
    pthread_attr_ex_t attr_ex;
    ......
    return pthread_create_ex(ptid_r, &attr_ex, start, arg);
}

COBALT_IMPL定义了__cobalt_pthread_create函数及该函数的一个弱别名__wrap_pthread_create,调用这两个函数执行的是同一个函数体。

对于 NPTL函数pthread_create,在Cobalt库里使用__STD()修饰,展开后即__real_pthread_create(),其实只是NPTL pthread_create()的封装,__real_pthread_create()会直接调用 NPTL pthread_create,在lib\cobalt\wrappers.c实现如下:

/* pthread */
__weak
int __real_pthread_create(pthread_t *ptid_r,
              const pthread_attr_t * attr,
              void *(*start) (void *), void *arg)
{
    return pthread_create(ptid_r, attr, start, arg);
}

它调用的就是glibc中的pthread_create函数.同样我们接着__cobalt_pthread_create()看哪里调用的.

int pthread_create_ex(pthread_t *ptid_r,
              const pthread_attr_ex_t *attr_ex,
              void *(*start) (void *), void *arg)
{
    ......
    __STD(sem_init(&iargs.sync, 0, 0));

    ret = __STD(pthread_create(&lptid, &attr, cobalt_thread_trampoline, &iargs));/*__STD 调用标准库的函数*/
    if (ret) {
        __STD(sem_destroy(&iargs.sync));
        return ret;
    }

    __STD(clock_gettime(CLOCK_REALTIME, &timeout));
    .....
}

下面再看另一个例子,实时任务在代码中使用了linux的网络套接字(xenomai任务也是一个linux任务,也可以使用linux来提供服务,只不过会影响实时性),有以下代码,:

....
    int sockfd,ret;                                            
    struct sockaddr_in addr;                           
    sockfd = socket(PF_INET, SOCK_STREAM, 0);            
.....
    bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr_in));        
....

该代码编译时链接到了libcobalt,socket()函数即libcobalt中的__cobalt_socket(),其定义在xenomai-3.x.x\lib\cobalt\rtdm.c,如下:

COBALT_IMPL(int, socket, (int protocol_family, int socket_type, int protocol))
{
    int s;
    s = XENOMAI_SYSCALL3(sc_cobalt_socket, protocol_family,
                 socket_type, protocol);
    if (s < 0) {
        s = __STD(socket(protocol_family, socket_type, protocol));
    }
    return s;
}

可以看到,libcobalt中的函数会先尝试调用实时内核cobalt的系统调用, 当cobalt系统调用不成功的时候才继续尝试通过__STD()宏来调用linux系统调用(cobalt内核根据socket协议类型参数PF_INET,SOCK_STREAM判断),这样就有效的分清了是linux系统调用还是xenomai系统调用,这也是所有libcobalt实现的posix都链接到libobalt库的原因。

一般情况下,可以直接在代码中使用__STD()宏指明我们调用的linux内核的服务,修改如下:

....
    int sockfd,ret;                                            
    struct sockaddr_in addr;                           
    sockfd = __STD(socket(PF_INET, SOCK_STREAM, 0));            
.....
     __STD(bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr_in)));
....

现在一切都明了了,一个函数编译时通过参数链接到xenomai库后,通过__STD()宏来表示使用linux接口。

4. 总结

  • 在实时程序或实时库libcobalt中,通过__STD()宏,或添加前缀__real__来表示使用linux接口;可以使用xenomai内定义宏区分__XENO__来兼容Linux和xenomai平台。
    /* Open a plain Linux UDP socket. */
    #ifndef __XENO__
         fd = socket(PF_INET, SOCK_DGRAM, 0);
    #else /* __XENO__ */
         fd = __real_socket(PF_INET, SOCK_DGRAM, 0);
    #endif /* __XENO__ */
    
  • 对于一个未指明的接口,libcobalt会先尝试发起xenomai系统调用,不成功会接着尝试linux内核系统调用。
  • 如果我们向libcobalt库中新添加一个libcobalt库中没有的自定义POSIX函数/系统调用时,一定要在内部先尝试发起xenomai系统调用,不成功时接着尝试linux内核系统调用,此外还必须将该接口添加到文件xenomai\lib\cobalt\cobalt.wrappers中,这样才能正确链接,否则编译后的应用还是原来的。
目录
相关文章
|
21小时前
|
Linux 数据处理
Linux中的localectl命令:深入了解与应用
`localectl`是Linux中用于管理区域设置的`systemd`工具,涉及日期、时间、货币格式等。它通过修改配置文件调整系统本地化,并确保数据处理工具的正确运行。主要功能包括列出所有区域设置(`list-locales`)、设置主区域(如`set-locale LANG=en_US.UTF-8`)和查看当前状态。使用时注意理解区域设置差异,备份配置,测试设置效果,并保持系统一致性。查阅文档以获取更多操作详情。
|
22小时前
|
Linux 数据处理
Linux中的setarch命令:深入探索与实际应用
`setarch`是Linux中用于调整程序执行环境的命令,允许在不同CPU架构或功能标志之间切换,支持跨架构运行二进制文件和测试程序兼容性。它通过设置`PERSONALITY`标志改变指令集解释方式。关键参数包括指定架构(-a)和设置CPU功能标志(-R)。在实际应用中,如需在32位模式下运行64位程序,可以使用`setarch i386 &lt;command&gt;`。使用时注意程序需求、测试验证,避免滥用,并在必要时备份。查阅文档以获取详细信息。
|
22小时前
|
存储 Linux 数据处理
Linux中的link命令:深入解析与实际应用
**Linux的`ln`命令详解:创建硬链接和软链接。硬链接共享相同inode,不占额外空间;软链接(符号链接)如快捷方式,可跨文件系统。使用`-s`创建软链接,`-f`强制覆盖。注意选择合适链接类型,避免循环链接,确保目标存在。**
|
22小时前
|
Linux 数据处理 开发者
Linux命令ldd:深入解析动态链接器依赖关系
`ldd`是Linux下分析可执行文件动态依赖的工具,它揭示了程序运行所需的共享库。通过模拟动态链接过程,`ldd`列出库文件路径,帮助理解程序环境和解决运行时问题。主要参数包括`-d`、`-r`、`-u`和`-v`。例如,`ldd my_program`展示`my_program`的依赖关系。注意,`ldd`不显示间接依赖,完整依赖树可能需借助其他工具。确保系统库完整且版本兼容是使用`ldd`时的关键。
|
22小时前
|
Unix Linux 异构计算
用于实时 Linux® 的 TwinCAT Runtime,基于 Linux® 的实时控制器开辟了新的应用可能性
**TwinCAT Runtime for Linux®** 打开了倍福实时控制的新领域,支持在同一PC上运行多个独立实例,促进硬件整合和效率提升。基于Debian的定制Linux发行版,内置实时内核,允许GPU驱动的高速应用如机器视觉。利用Docker等容器技术,实现模块化控制和资源优化,降低工程成本。此发行版将逐步扩展到更多倍福设备,为Linux用户提供熟悉的TwinCAT编程环境和增强的灵活性。
|
1天前
|
安全 Linux 数据处理
探索Linux的kmod命令:管理内核模块的利器
`kmod`是Linux下管理内核模块的工具,用于加载、卸载和管理模块及其依赖。使用`kmod load`来加载模块,`kmod remove`卸载模块,`kmod list`查看已加载模块,`kmod alias`显示模块别名。注意需有root权限,且要考虑依赖关系和版本兼容性。最佳实践包括备份、查阅文档和使用额外的管理工具。
|
1天前
|
消息中间件 监控 安全
深入解析Linux命令ipcmk:IPC对象管理新视角
`ipcmk`非标准Linux命令,假设的IPC对象创建工具,用于演示如何管理消息队列、信号量和共享内存。虽无此命令,但理解其概念有助于掌握IPC管理。例如,创建命名消息队列`my_mq`,最大1000消息,可模拟使用`ipcmk -t mq -n my_mq -q 1000`。实际操作中,应根据需求选择合适IPC机制,设置安全权限,监控使用并及时清理。
|
2天前
|
Ubuntu Linux C语言
【opencv】opencv在windows和linux的应用
【opencv】opencv在windows和linux的应用
|
2天前
|
Linux 数据处理
Linux命令中的tic:深入了解与高效应用
`tic`是Linux中用于处理 Tektronix 4015 终端文本解释的命令,源于troff文本格式化系统。尽管现代系统不再常见该终端,`tic`仍用于特定数据处理,尤其是与troff配合,将troff输出转换为特定格式。使用时注意兼容性、输入格式和参数调整,如`-a`设定宽高比。通过`man tic`可获取更多详情。示例:`troff document.tr | tic`。了解这些能更好地处理特定文本格式任务。
|
3天前
|
Linux 数据处理 数据库
深入解析Linux命令id:理解用户身份与权限
`id`命令在Linux中用于显示用户身份(UID, GID和附加组)。它查看系统用户数据库获取信息。参数如`-u`显示UID,`-g`显示GID,`-G`显示附加组,结合`-n`显示名称而非ID。用于确认命令执行者身份,确保权限正确。在脚本中使用时注意权限管理,遵循最小权限原则。