6.4 非POSIX skin进程间通信
Xenomai 实现的实时API,不仅包括 POSIX skin,还包括Alchemy skin、VxWorks skin、pSOS skin。
这些非POSIX skin,支持进程间通信的前提,是在编译 Xenomai 用户层的时候,必须打开 --enable-pshared 选项!
6.4.1 原理
--enable-pshared 启用后,会定义 CONFIG_XENO_PSHARED 宏。此选项允许不同进程间共享资源,像共享内存、信号量等。启用 --enable-pshared 后,脚本会检查系统是否支持 shm_open 函数,若不支持则报错。
回顾用户层 RTOS API章节,针对非 POSIX skin,使用 xeno-config 获取链接标识时,会增加 -lcopperplate。libcopperplate库实现了一个接口转换层copperplate interface,其初始化接口为 copperplate_init。
copperplate_init
-> get_session_root:
-> heapobj_pkg_init_private: initialize main private heap
-> mem = malloc(size);
-> ret = heapmem_init(&heapmem_main, mem, size);
-> heapobj_pkg_init_shared: initialize main shared heap
-> create_main_heap: Bind to (and optionally create) the main session's heap
-> fd = shm_open(hobj->fsname, O_RDWR|O_CREAT, 0660);
-> m_heap = __STD(mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0));
copperplate interface 会为实时应用在用户态创建私有堆内存 heapmem_main,和共享堆内存:
- 共同点:二者都没有从 Cobalt 内核系统堆
sys heap中分配内存,而是在用户态独立申请一块内存,使用各自的用户态内存分配器管理。 - 不同点:私有堆通过 malloc 分配,共享堆通过 shm_open 打开共享内存,并通过 mmap 映射到应用程序。
首先介绍一下有堆内存 heapmem_main。
针对私有堆内存 heapmem_main,在用户层由三个分配器,默认使用 heapmem 分配器:
heapobj-malloc.c:本质上是 Linux 的 malloc。heapobj-heapmem.c:heapmem 分配器(默认)。heapobj-tlsf.c:tlsf 分配器。
下面详细介绍一下共享堆内存的创建原理:
- 创建共享堆内存的函数
heapobj_pkg_init_shared,由--enable-pshared选项控制。-
如果没有打开
--enable-pshared选项,heapobj_pkg_init_shared仅仅是一个空函数。static inline int heapobj_pkg_init_shared(void) { return 0; } -
如果打开了
--enable-pshared选项,则heapobj_pkg_init_shared定义在lib/copperplate/heapobj-pshared.c。
-
- 共享堆内存本质上基于POSIX共享内存实现的
- 通过 shm_open 打开共享内存,通过 mmap 映射到应用程序。
- 只要确保 shm_open 的共享内存名称相同,那么不同的进程将会使用同一块内存。
O_CREAT确保了要么新建,要么使用已经创建的同名共享内存。
- 每个进程的共享堆内存的名称,被称为
session,提前在get_session_root初始化。- 应用启动时,传入
--session=<label>[/<group>],可以指定session名称。 - 如果没有指定,默认情况下,
session的值为anon@线程ID,例如anon@12656。
- 应用启动时,传入
综上,不同进程在启动时,通过 --session=<label>[/<group>] 传入相同的 session 名称,则各个进程会使用同一块内存作为共享堆内存。在进程运行过程中,申请共享对象时,会从共享堆内存中申请内存空间。
以 Alchemy skin 中的 rt_queue_create 为例:
rt_queue_create
-> heapobj_init
-> 如果没有打开 `--enable-pshared` 选项
-> __heapobj_init_private: 从私有堆内存申请
-> 如果打开了 `--enable-pshared` 选项
-> sheapmem_alloc: 从共享堆内存申请
最后,需要指出,只有链接了 libcopperplate 库的应用程序,才支持如下参数:
-
--mem-pool-size=<size[K|M|G]>
-
--shared-registry
-
--registry-root=
-
--session=[/]
static void copperplate_help(void)
{
fprintf(stderr, "--mem-pool-size=<size[K|M|G]> size of the main heap\n");
fprintf(stderr, "--no-registry suppress object registration\n");
fprintf(stderr, "--shared-registry enable public access to registry\n");
fprintf(stderr, "--registry-root=<path> root path of registry\n");
fprintf(stderr, "--session=<label>[/<group>] enable shared session\n");
}static struct setup_descriptor copperplate_interface = {
.name = "copperplate",
.init = copperplate_init,
.options = copperplate_options,
.parse_option = copperplate_parse_option,
.help = copperplate_help,
};copperplate_setup_call(copperplate_interface);
根据 copperplate_help 函数可知,只有链接了 libcopperplate 库的应用程序才能解析上述参数。换个角度来说,使用 POSIX skin 实现的应用程序并不支持这些参数,例如 Xenomai 自带的 latency 工具。
6.4.2 查看实时对象的信息
在编译 Xenomai 用户层的时候,打开 --enable-registry[=/registry-root-path] 选项,可以通过 FUSE 导出进程的注册表状态,可以通过读取这些文件来获取有关现有实时对象的信息,例如任务、信号量、消息队列等。导出的默认路径为/var/run/xenomai。
打开 --enable-registry[=/registry-root-path] 选项,构建 Xenomai 库时需要工具链中提供 FUSE 开发库,并且目标内核中必须启用 CONFIG_FUSE_FS。
针对应用程序来说,在链接时还必须增加 -lcopperplate 链接 libcopperplate 库。当然,对于非 POSIX skin,使用 xeno-config 获取链接标识时,总会自动增加 -lcopperplate。
导出的原理如下:
copperplate_init
->get_session_root
->registry_pkg_init
->connect_regd
->socket
->spawn_daemon
-> sbin/sysregd
->__registry_pkg_init
->registry_add_dir("/"); /* Create the fs root. */
registry_pkg_init函数- 打开
--enable-registry[=/registry-root-path]选项,会生成 CONFIG_XENO_REGISTRY 宏定义,registry_pkg_init函数定义在lib/copperplate/registry.c。 - 如果没有打开,则
registry_pkg_init函数仅为一个空函数。
- 打开
sysregd守护进程sysregd守护进程负责在用户层挂载并构建/var/run/xenomai/目录。registry_pkg_init如果通过socket无法连接到sysregd守护进程,则会自己发起此守护进程。- 可以自己手动发起
sysregd守护进程sysregd --root=/var/run/xenomai --shared --anon --daemonize --linger
以 Xenomai 源码自带的 altency 为例,它是 latency 的 Alchemy skin 重新实现版本,默认会链接 libcopperplate 库。 Xenomai 源码编译完成后,altency 位于 xenomai-v3.2.4-build/demo/alchemy 目录,默认安装到 /usr/xenomai/demo 目录。
# ./altency
== Sampling period: 1000 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
RTT| 00:00:01 (periodic user-mode task, 1000 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD| 70.585| 480.700| 1342.298| 4| 0| 70.585| 1342.298
RTD| 71.925| 544.017| 1402.927| 8| 0| 70.585| 1402.927
RTD| 80.641| 457.550| 1388.125| 10| 0| 70.585| 1402.927
altency 启动后,可以看到进程 PID 为 18839,同时启动了 sysregd 守护进程。
# cat /proc/xenomai/sched/threads
CPU PID CLASS TYPE PRI TIMEOUT STAT NAME
0 0 idle core -1 - R [ROOT/0]
1 0 idle core -1 - R [ROOT/1]
2 0 idle core -1 - R [ROOT/2]
3 0 idle core -1 - R [ROOT/3]
0 18839 rt cobalt 0 - X altency
0 18855 rt cobalt 0 - X sysregd
0 18857 rt cobalt 0 - X sysregd
0 18858 rt cobalt 0 - X altency
0 18859 rt cobalt 0 - W alt-display-18839
0 18860 rt cobalt 99 - Wt alt-sampling-18839
查看 /var/run/xenomai 目录,可以看到 session 的默认名称为 anon\@18839:
# tree /var/run/xenomai/root/anon\@18839/
/var/run/xenomai/root/anon@18839/
|-- 18839
| `-- alchemy
| |-- semaphores
| | `-- dispsem-18839
| `-- tasks
| |-- alt-display-18839
| `-- alt-sampling-18839
`-- system
|-- heaps
|-- threads
`-- version
5 directories, 6 files
如果使用命令 ./altency --session=test 指定 session 的名字,那么session 的名称会变为指定的 test 。
# tree /var/run/xenomai/root/test/
/var/run/xenomai/root/test/
|-- 18912
| `-- alchemy
| |-- semaphores
| | `-- dispsem-18912
| `-- tasks
| |-- alt-display-18912
| `-- alt-sampling-18912
`-- system
|-- heaps
|-- threads
`-- version