点击查看《Xenomai/IPIPE源代码情景解析》
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!
6.2.1 Xenomai3: hello world演示线程创建
主函数中使用POSIX线程(pthread)库的pthread_create来创建一个线程,并在线程函数 helloworld 中,使用 clock_nanosleep 实现精确的周期性任务,每隔1s打印一次"Hello World!"。
#include <stdio.h> // 标准输入输出库,用于打印输出
#include <unistd.h> // 提供对POSIX操作系统API的访问,如`clock_nanosleep`
#include <pthread.h> // POSIX线程库,用于创建和管理线程
static pthread_t hellothread; // 用于存储线程的标识符
#define NSEC_PER_SEC 1000000000 // 定义每秒的纳秒数(1秒 = 1,000,000,000纳秒)
static unsigned int cycle = 1000000; // 定义周期时间,单位为us
// 线程函数,每隔一定时间打印 "Hello World!"
static void *helloworld(void *arg)
{
struct sched_param param = { .sched_priority = 80 }; // 定义调度参数,设置线程优先级为80
struct timespec next_period; // 用于存储下一次执行的时间点
// 设置当前线程的名称为 "helloworld"
pthread_setname_np(pthread_self(), "helloworld");
// 设置当前线程的调度策略为 SCHED_FIFO(先进先出调度策略),并设置优先级
pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);
// 获取当前的单调时间(系统启动后的时间,不受系统时间调整影响)
clock_gettime(CLOCK_MONOTONIC, &next_period);
// 无限循环,线程将一直运行
while(1) {
// 将下一次执行时间的纳秒部分增加 cycle * 1000
next_period.tv_nsec += cycle * 1000;
// 如果纳秒部分超过了1秒,则将其减去1秒,并将秒部分加1
while (next_period.tv_nsec >= NSEC_PER_SEC) {
next_period.tv_nsec -= NSEC_PER_SEC;
next_period.tv_sec++;
}
// 使用绝对时间进行睡眠,直到 next_period 指定的时间
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_period, NULL);
// 打印 "Hello World!"
printf("Hello World!\n");
}
}
// 主函数
int main()
{
int ret;
pthread_attr_t thattr; // 定义线程属性对象
// 初始化线程属性对象
pthread_attr_init(&thattr);
// 设置线程属性为可连接状态(即主线程可以等待该线程结束)
pthread_attr_setdetachstate(&thattr, PTHREAD_CREATE_JOINABLE);
// 创建一个新线程,执行 helloworld 函数
pthread_create(&hellothread, &thattr, &helloworld, NULL);
// 主线程等待 hellothread 线程结束
// 注意:在这个例子中,hellothread 线程永远不会结束,因为它在无限循环中
pthread_join(hellothread, NULL);
return 0; // 主函数返回0,表示程序正常结束
}
使用posix skin来进行编译与链接,makefile内容如下。
# 指定编译器为针对aarch64架构的GNU编译器集合中的C++编译器
CC = aarch64-linux-gnu-g++
# 定义Xenomai安装目录的位置
XENO_DESTDIR=/root/ipipe-arm64/xenomai_install
# 定义一个变量来保存Xenomai配置工具的路径,该工具可以生成编译和链接标志
XENO_CONFIG =$(XENO_DESTDIR)/usr/xenomai/bin/xeno-config
# 使用Xenomai配置工具获取POSIX皮肤的编译标志,并存储在变量中
XENO_POSIX_CFLAGS =$(shell DESTDIR=$(XENO_DESTDIR) $(XENO_CONFIG) --skin=posix --cflags)
# 使用Xenomai配置工具获取POSIX皮肤的链接标志,并存储在变量中
XENO_POSIX_LDFLAGS =$(shell DESTDIR=$(XENO_DESTDIR) $(XENO_CONFIG) --skin=posix --ldflags)
# 设置项目路径为当前目录(.)
PROJPATH = .
# 定义最终生成的可执行文件名为hello
EXECUTABLE := hello
# 通过通配符找到所有以.c结尾的源代码文件,并将它们的名称赋值给src变量
src = $(wildcard ./*.c)
# 将找到的源文件名转换为目标文件(.o),并将其列表赋值给obj变量
obj = $(patsubst %.c, %.o, $(src))
# 默认目标:构建所有目标中最先出现的一个,这里是$(EXECUTABLE),即构建'hello'可执行文件
all: $(EXECUTABLE)
# 规则用于创建最终的可执行文件。它依赖于所有的目标文件(.o),使用链接标志进行链接
$(EXECUTABLE): $(obj)
$(CC) -g -o $@ $^ $(XENO_POSIX_LDFLAGS)
# 规则用于从每个源文件(.c)创建对应的目标文件(.o)。这里使用编译标志进行编译
%.o:%.c
$(CC) -g -o $@ -c $< $(XENO_POSIX_CFLAGS)
# 清理规则,删除生成的可执行文件和目标文件,用于清理工作目录
.PHONY: clean
clean:
rm -f $(EXECUTABLE) $(obj)
执行make V=1完成交叉编译与链接,生成可执行文件hello。
aarch64-linux-gnu-g++ -g -o hello.o -c hello.c -I/usr/xenomai/include/cobalt -I/usr/xenomai/include -march=armv8-a -D_GNU_SOURCE -D_REENTRANT -fasynchronous-unwind-tables -D__COBALT__ -D__COBALT_WRAP__
aarch64-linux-gnu-g++ -g -o hello hello.o -Wl,--no-as-needed -Wl,@/usr/xenomai/lib/cobalt.wrappers -Wl,@/usr/xenomai/lib/modechk.wrappers /usr/xenomai/lib/xenomai/bootstrap.o -Wl,--wrap=main -Wl,--dynamic-list=/usr/xenomai/lib/dynlist.ld -L/usr/xenomai/lib -lcobalt -lmodechk -lpthread -lrt -march=armv8-a
把可执行文件hello拷贝到QEMU ARM64中,使用gdb中断hello的执行:
(1)通过(gdb) break hello.c:60 在pthread_join语句定义一个断点。
(2)执行(gdb) run,在pthread_join中断。
(3)通过(gdb) info threads打印出当前线程信息,存在3个线程:主进程/线程“hello”、线程cobalt_print、线程“helloworld”。其中前两个线程的创建过程,在上一章已经分析过了,不再重复!
(4)通过(gdb) shell cat /proc/xenomai/sched/stat,执行shell命令,查看xenomai的线程调度情况。发现“helloworld”线程是一个Xenomai线程。
在代码中,实际使用的是pthread_create来创建线程,是如何转成Xenomai线程的呢?
下一章节解密!
点击查看《Xenomai/IPIPE源代码情景解析》
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!