翻译官方文档realtime:documentation:howto:applications:application_base [Wiki]
POSIX API构成了在PREEMPT_RT下运行的实时应用程序的基础。对于实时线程,使用POSIX线程(pthread)。每个实时应用程序都需要在调度、优先级、内存锁定和堆栈预置等几个基本领域进行适当的处理。
基本先决条件
接下来的小节将介绍三个基本先决条件,然后是一个简短的例子来说明这些方面。
日程安排和优先级
调度策略和优先级必须由应用程序明确设置。这有两种可能性:
1.使用sched_setscheduler()
在计算RT特定内容之前,需要在pthread的start例程中调用此函数。
2.使用pthread属性
函数pthread_attr_setchedpolicy()和pthread_attr_setschedparam()提供了设置策略和优先级的接口。此外,需要使用PTHREAD_attr_setinheriched()将调度器继承正确设置为PTHREAD_EXPLICIT_SCHED。这迫使新线程使用pthread属性指定的策略和优先级,而不使用
3. 创建实时线程的线程的继承调度。pthread条件变量的问题
依赖glibc的libpthread的多线程应用程序容易出现意外的延迟,因为其条件变量实现不支持优先级继承(bugzilla)。不幸的是,glibc的DNS解析器和异步I/O实现反过来又依赖于这些条件变量。librtpi是另一种LGPL许可的pthread实现,它支持优先级继承,其API尽可能接近glibc。另一种MUSL libc具有类似于glibc的pthread条件变量实现。
内存锁定
看这里
RT线程堆栈
看这里
功能:以非root用户身份以RT优先级运行应用程序
一些Pthread API,如mlockall()、Pthread_attr_setchedpolicy(),默认情况下和约定需要root才能成功完成工作。因此,需要设置RT调度策略和优先级的RT应用程序通常通过sudo运行。
有一个更好的方法;sudo赋予进程root权限。这引起了黑客的兴趣:-)。相反,您应该利用强大的POSIX能力模型!这样,进程(和线程)只获得它们所需的功能,仅此而已。这遵循了infosec的最佳实践,即最小特权原则。
默认情况下,应用程序一开始就没有功能;还要注意,功能是每个线程的资源(本质上转换为任务结构的位掩码,当然是每个线程)。在各种能力位中,能力手册页(7)显示CAP_SYS_NICE是在这种情况下使用的适当能力;功能(7)手册页中的一段代码揭示了这一点:
…CAP_SYS_NICE
降低进程nice值(nice(2),setpriority(2)),并更改任意进程的nice值;
为调用进程设置实时调度策略,并为任意进程设置调度策略和优先级(sched_setscheduler(2)、sched_setparam(2)和sched_setattr(2));
为任意进程设置CPU亲和性(sched_setaffinity(2));
为任意进程设置I/O调度类和优先级(ioprio_set(2));
将migrate_pages(2)应用于任意进程,并允许进程迁移到任意节点;
将move_pages(2)应用于任意进程;
将MPOL_MF_MODE_ALL标志与mbind(2)和MOVE_pages(2)一起使用…
好的,太好了,但如何在应用程序上设置这个功能位呢?
一种方法是通过capget()/capset()系统调用以编程方式进行。(请注意,使用libcap库包装器cap_[g|s]et-proc(3)通常更容易:https://man7.org/linux/man-pages/man3/cap_get_proc.3.html.本手册页甚至提供了这样做的一个小例子)。
另一种简单的方法是利用systemd并将应用程序作为服务运行;在服务单元中,指定功能(请参阅systemd.exec(5)上的手册页;https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Capabilities.
也许最简单的方法是:通过setcap(8)实用程序(它的手册页:https://man7.org/linux/man-pages/man8/setcap.8.html). setcap/getcap通常是libcap包的一部分。例如:
sudo setcap CAP_SYS_NICE+eip<您的应用程序二进制可执行文件>
您可以将此行放入应用程序Makefile(或等效文件)中。(getcap(8)实用程序可用于验证“哑功能”二进制文件现在是否已设置CAP_SYS_NICE位)!
现在,您已经准备好以非root身份运行它,这是一种更安全的方法。
这是一个POSIX实时(Real-Time)线程的示例程序,使用单个POSIX线程 (pthread
) 作为实时线程。该程序实现了一个基本的实时线程创建和管理,利用了POSIX线程库和相关的调度策略。下面是对该程序的逐句讲解:
c
复制代码
/* * POSIX Real Time Example * using a single pthread as RT thread */
- 这是程序的注释,表明它是一个POSIX实时线程的示例程序,使用一个pthread作为实时线程。
c
复制代码
#include <limits.h> #include <pthread.h> #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <sys/mman.h>
- 包含了一些头文件:
limits.h
:定义了系统范围的限制和常量。pthread.h
:提供了POSIX线程的API。sched.h
:用于线程调度相关的定义(例如调度策略)。stdio.h
:用于标准输入输出。stdlib.h
:提供了常用的库函数(如exit()
)。sys/mman.h
:用于内存管理(如mlockall()
)。
c
复制代码
void *thread_func(void *data) { /* Do RT specific stuff here */ return NULL; }
- 这是线程函数
thread_func
,在创建线程后执行的代码。它在当前版本中没有实际功能,只是简单地返回NULL
。用户可以在注释标注的地方插入实时(RT)相关的操作。
c
复制代码
int main(int argc, char* argv[]) { struct sched_param param; pthread_attr_t attr; pthread_t thread; int ret;
- 进入
main()
函数:sched_param param
:用于存储线程调度参数(例如优先级)。pthread_attr_t attr
:线程属性对象,用于设置线程的各种属性。pthread_t thread
:线程ID。int ret
:用于存储函数调用的返回值。
c
复制代码
/* Lock memory */ if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { printf("mlockall failed: %m\n"); exit(-2); }
- 使用
mlockall()
锁定进程的地址空间,防止其被交换到磁盘上。MCL_CURRENT|MCL_FUTURE
标志表示当前和将来的内存分配都要锁定。 - 如果锁定失败,打印错误信息并退出程序,返回
-2
。
c
复制代码
/* Initialize pthread attributes (default values) */ ret = pthread_attr_init(&attr); if (ret) { printf("init pthread attributes failed\n"); goto out; }
- 调用
pthread_attr_init()
初始化线程属性对象attr
,为后续设置调度策略和优先级做准备。如果失败,打印错误信息并跳转到out
标签结束程序。
c
复制代码
/* Set a specific stack size */ ret = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); if (ret) { printf("pthread setstacksize failed\n"); goto out; }
- 调用
pthread_attr_setstacksize()
设置线程的栈大小为最小值PTHREAD_STACK_MIN
。如果设置失败,打印错误并跳转到out
标签。
c
复制代码
/* Set scheduler policy and priority of pthread */ ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); if (ret) { printf("pthread setschedpolicy failed\n"); goto out; }
- 使用
pthread_attr_setschedpolicy()
设置线程的调度策略为SCHED_FIFO
,这是实时调度的一种策略,表示先进先出。若失败,打印错误并跳转到out
。
c
复制代码
param.sched_priority = 80; ret = pthread_attr_setschedparam(&attr, ¶m); if (ret) { printf("pthread setschedparam failed\n"); goto out; }
- 设置线程的优先级为
80
,并使用pthread_attr_setschedparam()
应用该优先级。若失败,打印错误并跳转到out
。
c
复制代码
/* Use scheduling parameters of attr */ ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); if (ret) { printf("pthread setinheritsched failed\n"); goto out; }
- 使用
pthread_attr_setinheritsched()
设置线程继承调度策略,PTHREAD_EXPLICIT_SCHED
表示显式使用当前设置的调度策略。若失败,打印错误并跳转到out
。
c
复制代码
/* Create a pthread with specified attributes */ ret = pthread_create(&thread, &attr, thread_func, NULL); if (ret) { printf("create pthread failed\n"); goto out; }
- 使用
pthread_create()
创建线程,指定attr
属性,并执行thread_func
。若线程创建失败,打印错误并跳转到out
。
c
复制代码
/* Join the thread and wait until it is done */ ret = pthread_join(thread, NULL); if (ret) printf("join pthread failed: %m\n");
- 使用
pthread_join()
等待线程thread
结束。若失败,打印错误信息。
c
复制代码
out: return ret; }
- 程序结束,返回值为
ret
。
源代码:
/*
* POSIX Real Time Example
* using a single pthread as RT thread
*/
#include <limits.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
void *thread_func(void *data)
{
/* Do RT specific stuff here */
return NULL;
}
int main(int argc, char* argv[])
{
struct sched_param param;
pthread_attr_t attr;
pthread_t thread;
int ret;
/* Lock memory */
if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
printf("mlockall failed: %m\n");
exit(-2);
}
/* Initialize pthread attributes (default values) */
ret = pthread_attr_init(&attr);
if (ret) {
printf("init pthread attributes failed\n");
goto out;
}
/* Set a specific stack size */
ret = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
if (ret) {
printf("pthread setstacksize failed\n");
goto out;
}
/* Set scheduler policy and priority of pthread */
ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
if (ret) {
printf("pthread setschedpolicy failed\n");
goto out;
}
param.sched_priority = 80;
ret = pthread_attr_setschedparam(&attr, ¶m);
if (ret) {
printf("pthread setschedparam failed\n");
goto out;
}
/* Use scheduling parameters of attr */
ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
if (ret) {
printf("pthread setinheritsched failed\n");
goto out;
}
/* Create a pthread with specified attributes */
ret = pthread_create(&thread, &attr, thread_func, NULL);
if (ret) {
printf("create pthread failed\n");
goto out;
}
/* Join the thread and wait until it is done */
ret = pthread_join(thread, NULL);
if (ret)
printf("join pthread failed: %m\n");
out:
return ret;
}