4.7 POSIX进程与线程实例
4.7.1 构建latency
latency是Xenomai测试套件中的一个应用程序,用于测量系统的实时性能,特别是上下文切换和中断响应的延迟。编译并生成Xenomai应用层代码的过程中,latency会被自动编译,默认安装到usr/xenomai/bin/latency。
latency源码位于testsuite/latency/latency.c,可以作为一个实例来说明如何使用POSIX skin进行实时进程和实时线程编程。
在Xenomai源码中,latency的Makefile是通过Automake工具生成的。为了更好地模拟并演示如何构建Xenomai应用程序,手动来重新构建latency。
将makefile与latency.c放在同一个目录下,执行make命令,即可生成latency应用。
- makefile文件
- 使用
POSIX skin进行编译链接 - 使用
wrap-link.sh进行链接
- 使用
XENO_DESTDIR=/root/xenomai/xenomai-v3.2.4-install
CC = aarch64-linux-gnu-gcc
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) \
-lm
PROJPATH = .
EXECUTABLE := latency
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))
all: $(EXECUTABLE)
$(EXECUTABLE): $(obj)
$(CCLD) -g -o $@ $^ $(XENO_POSIX_LDFLAGS)
%.o:%.c
$(CC) -g -o $@ -c $< $(XENO_POSIX_CFLAGS)
.PHONY: clean
clean:
rm -f $(EXECUTABLE) $(obj)
latency.c源码
直接编译latency.c源码,会报告找不到cobalt/uapi/syscall.h头文件。
这个头文件实际上并不需要,直接移除,即可编译通过。
diff --git a/testsuite/latency/latency.c b/testsuite/latency/latency.c
index fe688b9b8..0b6964bb9 100644
--- a/testsuite/latency/latency.c
+++ b/testsuite/latency/latency.c
@@ -556,8 +556,6 @@ static void faulthand(int sig)
#ifdef CONFIG_XENO_COBALT
-#include <cobalt/uapi/syscall.h>
-
static const char *reason_str[] = {
[SIGDEBUG_UNDEFINED] = "received SIGDEBUG for unknown reason",
[SIGDEBUG_MIGRATE_SIGNAL] = "received signal",
-
为什么用户空间代码几乎不再用这个头文件了?
因为Xenomai 3.x,尤其是使用Dovetail之后,已经提供了更好的用户空间API封装,开发者可以直接使用Xenomai的 POSIX skin 或者 Alchemy skin 来进行实时编程,而不需要直接调用底层的Cobalt内核接口。 -
那为什么这个头文件还必须存在?
- Cobalt 层保持ABI(Application Binary Interface)稳定性,老版本的libcobalt.so, 实用应用程序和Xenomai 2 的extensions必须能在新版本内核上继续工作,就是不需要重新编译。
- Xenomai 的构建过程会使用
cobalt/uapi来生成Cobalt的syscall表,Wrapper,Trampoline 代码(系统调用从Linux用户态到Cobalt co-kernel的入口跳板) 和Stub 码(发起系统调用的参数封装层) - 这是一个历史遗留但必须保留的 interface,旧的 binary 依然依赖它,内核内部的 syscall dispatching 表(系统调用分发表)仍然使用它
4.7.2 执行latency
latency能在用户态任务、内核态周期性任务、内核态定时器中断处理函数等不同模式下进行延迟测量,还能记录最小、最大、平均延迟等数据,并通过直方图、统计信息等方式展示结果。
| 参数 | 说明 |
|---|---|
| -h | 打印最小、平均、最大延迟的直方图,方便直观查看延迟分布情况。 |
| -g | 将直方图以 Gnuplot 格式导出到指定的 中,便于后续用 Gnuplot 工具进行可视化处理。 |
| -s | 打印最小、平均、最大延迟的统计信息,如方差、标准差等,帮助分析延迟数据特征。 |
| -H | 设置直方图的大小,默认值为 200。若最后一个桶数据已满,可增大该值以容纳更多数据。 |
| -B | 设置直方图中每个桶的大小,默认值为 1000ns。减小该值能提高延迟数据统计的分辨率。 |
| -p <period_us> | 设置采样周期,单位为微秒。程序会按照该周期进行延迟采样。 |
| -l | 设置每显示多少行数据后输出一次表头,默认值为 21。设置为 0 可抑制表头输出。 |
| -T <test_duration_seconds> | 设置测试持续时间,单位为秒。默认值为 0,表示需手动通过 ^C 结束测试。 |
| -q | 当使用 -T 参数时,抑制 RTD 和 RTH 行的输出,保持输出简洁。 |
| -t <test_mode> | 指定测试模式,0 表示用户态任务(默认),1 表示内核态周期性任务,2 表示内核态定时器中断处理函数。 |
| -f | 每当出现新的最大延迟时,冻结跟踪信息,方便调试问题。 |
| -c | 将测量任务固定到指定编号的 CPU 上运行,避免任务在不同 CPU 间迁移带来的影响。 |
| -P | 设置任务优先级,仅适用于测试模式 0 和 1。 |
| -b | 遇到模式切换时终止测试。 |
为了演示用户层线程的创建,使用默认的用户态任务测试模式。在执行latency时,不增加任何参数,使用默认即可。latency会持续打印延迟信息,如下是执行后的输出。因为是在QEMU下执行的,所以延迟很大,可以忽略延迟信息。
$ ./latency
== 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| 66.708| 495.528| 36315.492| 38| 0| 66.708| 36315.492
RTD| 90.538| 445.924| 2583.055| 41| 0| 66.708| 36315.492
RTD| 62.389| 454.657| 1977.077| 47| 0| 62.389| 36315.492
...snip...
在latency执行后,会在当前控制台持续输出。可以通过ssh -p 56789 localhost登录到QEMU ARM64虚拟机,在新的控制台观察latency信息。
- 执行
cat /proc/xenomai/sched/threads查看Xenomai实时线程信息。- 主线程latency:对应于Linux中的latency进程,PID号为523。
- 子线程sampling:用户态任务,用于延迟采样,PID号为525。
- 子线程display:用于打印信息,PID号为526。
$ 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 523 rt cobalt 0 - X latency
0 525 rt cobalt 0 - W display-523
0 526 rt cobalt 99 - Wt sampling-523
- 执行
top -H -b -n 1 -p 523查看Linux中PID号为523的latency进程。- -H:显示线程(H 表示 “Threads-mode”,默认显示进程)。
- -b:以批处理模式(非交互式)运行,适合输出到文件或管道。
- -n 1:仅运行一次迭代后退出。
- -p 523: Linux中latency进程的PID为523。
$ top -H -b -n 1 -p 523
top - 10:00:28 up 7:41, 2 users, load average: 0.00, 0.00, 0.00
Threads: 4 total, 0 running, 4 sleeping, 0 stopped, 0 zombie
%Cpu(s): 6.9 us, 0.0 sy, 0.0 ni, 93.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 3671.2 total, 3477.8 free, 83.1 used, 110.3 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 3553.4 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
523 root 20 0 77976 11684 3076 S 0.0 0.3 0:00.09 latency
524 root 20 0 77976 11684 3076 S 0.0 0.3 0:00.71 cobalt_printf
525 root 20 0 77976 11684 3076 S 0.0 0.3 0:00.42 display-523
526 root rt 0 77976 11684 3076 S 0.0 0.3 0:00.00 sampling-523
相比Xenomai实时线程,在Linux中多出了一个cobalt_printf子线程。cobalt_printf子线程是latency进程在启动过程中创建的,它定期将缓冲区内容刷新到目标流(如 stdout、stderr 或其他文件流)。
4.7.3 实时线程的创建
以创建实时线程sampling为实例,分析创建实时线程的要点。
if (test_mode == USER_TASK) {
setup_sched_parameters(&tattr, priority);
CPU_ZERO(&cpus);
CPU_SET(cpu, &cpus);
ret = pthread_attr_setaffinity_np(&tattr, sizeof(cpus), &cpus);
if (ret)
error(1, ret, "pthread_attr_setaffinity_np()");
ret = pthread_create(&latency_task, &tattr, latency, NULL);
if (ret)
error(1, ret, "pthread_create(latency)");
pthread_attr_destroy(&tattr);
}
1. setup_sched_parameters()函数初始化线程调度属性
setup_sched_parameters 函数的主要功能是初始化线程属性对象,设置线程的调度继承策略、调度策略以及调度参数。
static void setup_sched_parameters(pthread_attr_t *attr, int prio)
{
struct sched_param p;
int ret;
ret = pthread_attr_init(attr);
if (ret)
error(1, ret, "pthread_attr_init()");
ret = pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED);
if (ret)
error(1, ret, "pthread_attr_setinheritsched()");
ret = pthread_attr_setschedpolicy(attr, prio ? SCHED_FIFO : SCHED_OTHER);
if (ret)
error(1, ret, "pthread_attr_setschedpolicy()");
p.sched_priority = prio;
ret = pthread_attr_setschedparam(attr, &p);
if (ret)
error(1, ret, "pthread_attr_setschedparam()");
}
-
初始化线程属性对象
- 调用 pthread_attr_init 函数对传入的 attr 进行初始化,若初始化失败则调用 error 函数输出错误信息并终止程序。
-
设置调度继承策略
- 调用 pthread_attr_setinheritsched 函数将调度继承策略设置为 PTHREAD_EXPLICIT_SCHED,表示线程将使用 attr 中显式设置的调度策略和参数,若设置失败则报错终止。
-
设置调度策略
- 依据 prio 的值选择调度策略,若 prio 不为 0 则使用 SCHED_FIFO(实时先进先出调度策略),否则使用 SCHED_OTHER(默认的分时调度策略)。对于sampling线程,传入的prio值为HIPRIO(99),对应的调度策略为SCHED_FIFO。
- 调用 pthread_attr_setschedpolicy 函数进行设置,失败则报错终止。
-
设置调度优先级
- 将 prio 赋值给 p.sched_priority,对于sampling线程,传入的prio值为HIPRIO(99)。
- 调用 pthread_attr_setschedparam 函数将该调度参数设置到 attr 中,失败则报错终止。
2. pthread_attr_setaffinity_np()函数设置CPU亲和性
pthread_attr_setaffinity_np 是一个非标准(_np 表示非可移植)的函数,用于设置线程属性对象的 CPU 亲和性。
CPU_ZERO(&cpus);
CPU_SET(cpu, &cpus);
ret = pthread_attr_setaffinity_np(&tattr, sizeof(cpus), &cpus);
if (ret)
error(1, ret, "pthread_attr_setaffinity_np()");
-
CPU_ZERO(&cpus);
- 功能:CPU_ZERO 是一个宏,定义在 <sched.h> 头文件中,用于将 cpu_set_t 类型的集合 cpus 清零。cpu_set_t 类型用于表示一组 CPU 核心,将集合清零意味着初始化该集合,使其不包含任何 CPU 核心。
-
CPU_SET(cpu, &cpus);
- 功能:CPU_SET 也是一个宏,同样定义在 <sched.h> 头文件中,用于将指定编号的 CPU 核心添加到 cpu_set_t 类型的集合中。
- 参数:cpu 的有效取值范围要介于 0 和 CPU_SETSIZE - 1 之间,CPU_SETSIZE 同样是定义在 <sched.h> 里的宏,它规定了 cpu_set_t 集合所能表示的最大 CPU 数量。
- 示例代码:CPU_SET(2, &cpus),将编号为
2的 cpu 添加到 cpus 集合中,意味着后续要让线程在这个CPU 2核心上运行。
-
pthread_attr_setaffinity_np(&tattr, sizeof(cpus), &cpus);
- 功能:pthread_attr_setaffinity_np 是一个非标准(_np 表示非可移植)的函数,用于设置线程属性对象的 CPU 亲和性。该函数会将指定线程属性对象关联的线程限制在 cpus 集合所包含的 CPU 核心上运行。
3. pthread_create()函数创建实时线程
pthread_create 是 POSIX 中的一个标准函数,用于创建并立即启动一个新的线程。
ret = pthread_create(&latency_task, &tattr, latency, NULL);
if (ret)
error(1, ret, "pthread_create(latency)");
参数说明:
-
参数1:
&latency_task:- 指向 pthread_t 类型变量 latency_task 的指针,新创建线程的线程 ID 会存储在该变量中。
-
参数2:
&tattr- 指向 pthread_attr_t 类型变量 tattr 的指针,该变量已经通过 setup_sched_parameters 函数配置了线程的调度策略和优先级等属性。
-
参数3:
latency- 新线程启动时要执行的函数,函数原型为 void *latency(void *cookie)。
- void *latency(void *cookie) 函数执行后,会设置子线程的名称为
sampling-%d,其中%d传入的是子线程的PID。
-
参数4:
NULL- 传递给 latency 函数的参数,这里表示不传递额外参数。
4. 实时线程display的特殊性
除了实时线程sampling之外,latency也用相同的方式创建了实时线程display,但是有一处不同:实时线程display的优先级是0。
当优先级为0时,调用 pthread_attr_setschedpolicy 函数设置的调度策略为SCHED_OTHER(默认的分时调度策略)。这也意味着,实时线程display在Xenomai中的调度策略为xnsched_class_weak,属于弱实时调度类。
考虑到实时线程display的作用,主要是频繁的向终端打印信息,选择优先级为0是恰当的,可以避免影响实时线程sampling的实时性!
4.7.3 主线程的处理
主线程一般不会执行实时任务,而是
int main(int argc, char *const *argv)
{
...snip...
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGALRM);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
...snip...
__STD(sigwait(&mask, &sig));
finished = 1;
cleanup();
return 0;
}
1. 阻塞指定信号
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGALRM);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
-
sigemptyset函数- 功能:sigemptyset 是一个函数,定义在 <signal.h> 头文件中,用于将 sigset_t 类型的信号集 mask 初始化为空集。这意味着在调用该函数之后,mask 信号集中不包含任何信号。
- 参数:&mask 是 sigset_t 类型变量 mask 的地址,函数会对该地址指向的信号集进行操作。
-
sigaddset函数- 功能:sigaddset 函数同样定义在 <signal.h> 头文件中,用于将指定的信号添加到 sigset_t 类型的信号集中。
- 参数:&mask 是信号集 mask 的地址,后面的信号常量表示要添加到信号集中的信号。
- 这几行代码分别将 SIGINT、SIGTERM、SIGHUP 和 SIGALRM 信号添加到 mask 信号集中。
- SIGINT:通常是用户按下 Ctrl+C 时发送给进程的中断信号。
- SIGTERM:用于请求进程正常终止的信号,是系统关机或 kill 命令默认发送的信号。
- SIGHUP:终端断开连接时发送给进程的信号,也可用于通知进程重新加载配置。
- SIGALRM:定时器到期时发送的信号,在代码里由 alarm 函数触发。
-
pthread_sigmask函数- 功能:pthread_sigmask 函数定义在 <pthread.h> 头文件中,用于设置线程的信号掩码。信号掩码决定了哪些信号会被阻塞,即线程暂时不会响应这些信号。这里 SIG_BLOCK 表示将 mask 信号集中的信号添加到当前线程的信号掩码中,从而阻塞这些信号。
- 参数:
- SIG_BLOCK:操作标志,表示将指定信号集添加到当前信号掩码中。
- &mask:指向要添加的信号集的指针。
- NULL:若不需要获取之前的信号掩码,可传入 NULL。
2. 等待被阻塞的信号
使用 sigwait 函数来等待被阻塞的信号: SIGINT、SIGTERM、SIGHUP 和 SIGALRM 。sigwait 函数会阻塞当前线程,直到 set 信号集中的某个信号被递送。
__STD(sigwait(&mask, &sig));
- 参数说明
- set:指向 sigset_t 类型信号集的指针,该信号集指定了 sigwait 函数要等待的信号集合。
- sig:指向 int 类型变量的指针,用于存储实际接收到的信号编号。当 sigwait 函数返回时,这个变量会被赋值为接收到的信号值。
为了让 sigwait 正常工作,调用该函数的线程需要先阻塞 set 信号集中的所有信号,否则信号可能会被默认的信号处理函数处理,而不会被 sigwait 捕获。一旦 set 信号集中的某个信号被递送,sigwait 函数会解除阻塞,将接收到的信号编号存储到 sig 指向的变量中,然后返回 0 表示成功。
在latency的main函数中,sigwait(&mask, &sig) 会阻塞主线程,并使得主线程切换到此模式运行。次模式是指线程由标准 Linux 内核调度的状态。
$ 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 523 rt cobalt 0 - X latency
0 525 rt cobalt 0 - W display-523
0 526 rt cobalt 99 - Wt sampling-523
查看主线程latency的STAT字段,它处于X状态,代表其运行在次模式。
| 状态 | 定义 | bit位 | 含义 |
|---|---|---|---|
| X | XNRELAX | 0x00000080 | 处于secondary模式(非实时上下文) |
如果用户按下 Ctrl+C 发送 SIGINT 信号、使用 kill 命令发送 SIGTERM 信号、终端断开发送 SIGHUP 信号或者 alarm 定时器到期发送 SIGALRM 信号,当接收到这些信号中的任意一个时,sigwait 解除阻塞,将接收到的信号编号存于 sig 变量。
随后程序把 finished 标记设为 1,并调用 cleanup 函数进行资源清理和测试结果输出,最后正常退出。这样做能让程序统一处理终止信号,保证资源正确释放。
2362

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



