前言
在开发实时应用时,我们希望软件具备良好的跨平台和可移植,既能在实时linux也能在RTOS上工作,为实现这个目的,我们会选择使用POSIX API来设计实时应用,实现在不同的操作系统和平台上的可移植性。这样只要有POSIX支持,应用程序从一个操作系统移植到另一个操作系统,无需对源代码进行较大的修改,可以减少很多重复工作(想象很美好)。
但是,实时应用除关注接口基本功能一致外,还关注接口的实时性(执行时间确定性),那不同的实时操作系统的POSIX底层实现/行为实不实时呢?
以精确定时为例,精确定时是我们常用的操作系统服务之一,比如工业以太网(ECAT、PN...)中的通信周期,通信周期的准确控制离不开精确定时;在无线基站和终端的slot调度中,精确定时确保了数据传输的有序与高效;在实时仿真中,仿真步长的精确控制更是模拟真实场景的关键等等,这些都离不开POSIX定时接口。
本文首先简要概述POSIX标准,随后深入剖析POSIX定时接口在常用开放内核源码的RTOS(实时操作系统)上的上的实现原理做简单介绍。通过本文的介绍,希望能为读者在实际应用中提供有价值的参考,避免在RTOS中使用POSIX遇到不必要的困扰。
POSIX简介
POSIX是 IEEE(Institue of Electrical and Electronics Engineers,电气和电子工程师学会)为了规范各种 UNIX 操作系统提供的 API 接口而定义的一系列互相关联标准的总称,其正式称呼为 IEEE1003,国际标准名称为 ISO / IEC9945。此标准源于一个大约开始于 1985 年的项目。
POSIX 这个名称是由理查德 · 斯托曼(Richard Stallman)应 IEEE 的要求而提议的一个易于记忆的名称。它是 Portable Operating System Interface(可移植操作系统接口)的缩写,而 X 则表明其对 Unix API 的传承。POSIX更能正确表示这一系列相关标准,所以术语POSIX最初被用作IEEE Std 1003.1-1988的同义词,即POSIX.1(n=1)。这保持了符号“POSIX”可读性的优点,而不会与POSIX系列标准产生歧义。
关于有关POSIX. 1的背景、受众和目的的更多信息,参看该链接 https://www.opengroup.org/austin/papers/backgrounder.html
POSIX标准涉及操作系统方方面面,我们主要关注以下两个标准:
- IEEE Std 1003.1 "便携式操作系统接口 (POSIX™)"--最初主要针对 UNIX 系统,1988 年第一版,2018 年最新版
- IEEE Std 1003.13 "标准化应用环境配置文件(AEP)--POSIX™ 实时和嵌入式应用支持"
IEEE Std 1003.1标准,最初只用于UNIX系统。POSIX定义了一系列应用程序编程接口(API),用于在源代码级别实现可移植性。POSIX最早发布于1988年,最新版本于2018年发布。它提供了一种标准化的应用程序环境配置文件(AEP),用于实现实时和嵌入式应用程序的支持。通过遵循POSIX标准,开发人员可以编写与POSIX兼容的源代码,从而实现在不同的操作系统和平台上的可移植性。这使得开发人员能够更轻松地将应用程序从一个操作系统移植到另一个操作系统,而无需对源代码进行较大的修改。
POSIX.13(IEEE Std 1003.13)对实时和嵌入式应用程序的支持:IEEE Std 1003.13-2003是一个针对实时系统的标准,提供了对嵌入式应用程序的支持,实时配置文件可总结如下。该标准定义了几个实时系统Profiles,所有Profiles都包括部分或全部POSIX.1、.1b和.1c,以及开发平台的POSIX.2和.2a的部分内容,这些Profiles分别为PSE51、PSE52、PSE53和PSE54。
- PSE51:最小实时配置文件(Minimal real-time)适用于资源受限的嵌入式系统。它提供了最基本的实时功能,包括任务调度、中断处理和简单的同步机制,这个子集不需要多进程(线程)和文件系统。
- PSE52:实时控制器配置文件(Realtime controller)适用于需要严格的实时控制和调度的系统。它在PSE51的基础上增加了简单文件系统和消息队列等功能,提供了精确的任务调度、硬实时响应和实时数据处理能力。
- PSE53:专用实时配置文件(Dedicated real-time)适用于需要高度可预测性和可靠性的实时系统。它提供了多进程、网络支持,更强大的实时功能,包括任务优先级管理、严格的时间限制和实时资源管理。
- PSE54:多用途实时配置文件(Multi-purpose real-time)适用于需要同时支持多种实时应用的系统。它提供了完整的文件系统、shell,灵活的任务调度、多任务并发和多种实时通信机制。
综上,PSOXI标准不仅包含应用开发时常说的POSIX API,还包括文件系统、网络、shell等整个应用程序的运行环境。
目前符合 POSIX 标准协议的操作系统有:UNIX、BSD、Linux、iOS、Android、SylixOS、VxWorks、RTEMS、LynxOS 等,由于这些OS对 POSIX 的支持,其他兼容 POSIX 系统上的应用程序可以非常方便地移植到这些系统上。
RTOS对POSIX的实现情况
这里简单介绍几个常用RTOS的POSIX支持情况:
RTOS | POSIX支持情况 |
---|---|
PREEMPT-RT | 完全兼容 |
Xenomai | 完全兼容,实时部分xenomai内核和libcobalt提供,非实时部分linux内核和glibc提供 |
VxWorks | User space(RTP):POSIX.13 PSE52 (少数接口存在限制)Kernel space:POSIX.1部分接口和POSIX.1可选功能中的一些实时接口 |
RTEMS | POSIX 1003.1b-1993. POSIX 1003.1b-1993。POSIX 1003.1h/D3.Open Group Single UNIX Specification.单进程; |
SylixOS | 兼容IEEE 1003(ISO/IEC 9945)操作系统接口规范兼容POSIX 1003.1b(ISO/IEC 9945-1)实时编程的标准 |
Zephry | PSE54 |
EcOS 3.0 | ISO/IEC 9945—1(少部分省略) |
FreeRTOS | 实验性,仅实现了约 20% 的 POSIX API |
RT-Thread | 未找到明确说明,从源码上看兼容大部分POSIX |
Zephyr
详见Zephyr开发文档https://docs.zephyrproject.org/latest/samples/posix/philosophers/README.html
Zephyr支持路线图 https://static.sched.com/hosted_files/eoss24/40/ZDS-2024-POSIX-Roadmap-for-Zephyr-LTSv3.pdf
FreeRTOS
FreeRTOS-Plus-POSIX 可实现 POSIX 线程 API 的小子集。借助此子集,熟悉 POSIX API 的应用程序开发者可使用类似线程原语的 POSIX 开发 FreeRTOS 应用程序。FreeRTOS-Plus-POSIX 仅实现了约 20% 的 POSIX API。因此,无法仅使用此包装器将现有的 POSIX 兼容应用程序或 POSIX 兼容库移植到 FreeRTOS 内核上运行。
FreeRTOS-Plus-POSIX 实现了部分 IEEE Std 1003.1-2017 版《开放组技术标准基础规范》,第 7 期。FreeRTOS-Plus-POSIX 包括以下 POSIX 线程标头文件的实现,详细信息请参阅 FreeRTOS-Plus-POSIX API 文档了解。
需要注意的是:FreeRTOS posix接口不支持动态创建任务,即开始调度后不能再创建新的任务。
RTOS提供的POSIX接口实时吗?
在实时应用场景中,精确定时是我们常用的接口,比如工业以太网ECAT、PN中的通信周期,无线基站/终端中的slot调度,实时仿真中的仿真步长等等。
POSIX中常用的定时接口有nanosleep()、clock_nanosleep()、timer_create()/timer_settime()等,但这些RTOS实现的POSIX实时吗?或者说定时精度如何?
nanosleep
在POSIX标准中,对nanosleep()
睡眠时间分辨率的定义为1/HZ,即操作系统的Tick周期时间,意味着实际睡眠分辨率与系统时钟滴答周期近似,下表总结了常见RTOS的nanosleep()
底层实现及精度,从标准上看,这些RTOS都符合POSIX标准,但HZ不足以支撑很多us级定时应用场景。
RTOS | 实现原理 | 精度 |
---|---|---|
PREEMPT-RT | 高精度hrtimer,每次定时最终由硬件timer中断handler中唤醒 | ns |
Xenomai | 高精度hrtimer,每次定时最终由硬件timer中断handler中唤醒 | ns |
VxWorks | 睡眠时间转换对齐到Tick | Tick |
RTEMS | ||
SylixOS | 未启用LW_CFG_TIME_HIGH_RESOLUTION_EN时,睡眠时间转换为Tick,每个Tick中统一处理;启用LW_CFG_TIME_HIGH_RESOLUTION_EN后,CPU不足一个Tcik的nanosleep转换为while(1)死等时间到达。 | Tick或ns |
Zephry | 睡眠时间转换为Tick | Tick |
EcOS 3.0 | 睡眠时间转换为Tick | Tick |
FreeRTOS | 睡眠时间转换为Tick | Tick |
RT-Thread | 5.x版本虽然实现了hrtimer逻辑,但是你用时会发现是个半成品,可以选择一个硬件Timer来实现hrtimer底层定时,但是这样每次只能有一个任务处于定时状态(用全局变量来维护timer的参数,且ctime层 hrtime、cputime接口职责隔离不清晰,无可扩展性,可以看出"能跑就行",质量堪忧);默认情况下还是基于Tick。 | Tick或ns |
这里不得不提一下,一些操作系统所宣传的ns级延时,基本指的是逻辑如下,对于用惯了xenomai/Peempt-RT的hrtimer来说,不明白CPU死等的方式有什么值得宣传的(这让我想起了郭天祥老师...)。
udeley(){
do {
从硬件读取当前时间;
} while (当前时间未到达指定时间);
}
Timer-不同linux版本和xenomai的实现差异
计时器管理器提供的服务包括:
- timer_create - 创建每进程定时器
- timer_delete - 删除每进程定时器
- timer_settime - 设置下一个定时器的到期时间
- timer_gettime - 获取定时器的剩余时间
- timer_getoverrun - 获取定时器超时计数
在xenomai中,我们通常会通过如下方式来定时触发周期任务:
sev.sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
sev.sigev_value.sival_ptr = &timerid;
sev.sigev_notify_thread_id = get_thread_pid();
/*timer create*/
timer_create(CLOCK_MONOTONIC, &sev, &timerid);
/* Start the timer */
timer_overrun = timer_getoverrun(timerid);
/*get current time*/
err = clock_gettime(CLOCK_MONOTONIC, &now);
tspec.it_value = now;
tspec.it_value.tv_sec += 0;
tspec.it_value.tv_nsec += 2000000;
tsnorm(&tspec.it_value);
tspec.it_interval.tv_sec = 0;
tspec.it_interval.tv_nsec = 1000000; /**/
timer_settime(timerid, TIMER_ABSTIME, &tspec, NULL);
while (1)
s = sigwait(&GlobalSignalMaskSet, &sig);
/*.............*/
}
但是,PREEMPT-RT和xenomai中关于timer的接口底层实现有区别:
PREEMPT-RT Timer实现原理
大家都知道,linux中断分为上半部和下半部,未启用PREEMPT-RT时,上半部表示硬件中断上下文,即响应中断就直接执行中断上半部;
启用PREEMPT-RT后,有所不同,通常上半部由中断线程来处理,即中断产生后,唤醒中断线程来处理中断上半部,此时中断上半部在线程上下文执行。并不是所有中断都是中断线程来执行,比如系统Timer中断就是不能中断线程化的,还是在硬件中断上下执行。对于中断下半部,基本没有变化,还是由softirq、workqueue线程等执行。
linux内核中,进程创建的每个timer都会对应内核中高精度timer的一个对象,这些hrtimer用红黑树组织,所有timer最后由硬件timer中断驱动运行,运行原理如下:
- 未启用PREEMPT-RT时,hrtimer由Hrtimer softirq驱动,即硬件定时超时,唤醒软中断处理,软中断线程检查hrtimer超时情况,并调用对应的超时处理函数,超时处理函数中会唤醒对应的线程;
启用PREEMPT-RT后,情况有所有不同,在PREEMPT-RT中,hrtimer分为两类,一类是在硬中断上下执行超时回调(HRTIMER_MODE_HARD),一类在软中断上下文执行超时回调(HRTIMER_MODE_SOFT);
- 对于HRTIMER_MODE_HARD因为在中断上下文执行的超时回调,时延低,任务定时精度高;
- 对于HRTIMER_MODE_SOFT timer,软中断到期模式的高精度定时器,到期的时候在类型为HRTIMER_SOFTIRQ的软中断里面执行定时器回调函数。在实时内核中,软中断由软中断线程执行,或者在进程开启软中断的时候执行,任务时延高定时不精确。
- 如果没有指定到期模式,那么在实时内核中默认使用软中断到期模式。
那PREEMPT-RT中,POSIX API底层用的是HRTIMER_MODE_HARD还是HRTIMER_MODE_SOFT?
API | timer类型 |
---|---|
nanosleep()clock_nanosleep() | HRTIMER_MODE_HARD |
timer_create()+timer_settime() | 4.16版本以前高精度定时器总是在硬中断里面执行定时器回调函数,所以timer相关接口定时精确,详见https://www.spinics.net/lists/kernel/msg3208465.html4.16版本及以后版本增加HRTIMER_MODE_SOFT,timer系列接口定时不再精确(不实时,抖动大)。 |
Xenomai Timer实现原理
原理,详见本博客博文xenomai时间子系统。
xenomai内核提供的所有POSIX接口都是实时的。
总结
我们仅比较了两个实时应用常见API在不同RTOS中的实现,应该明白,POSIX只是一个API标准,不同的系统底层实现不同,我们在将我们的实时任务移植适配到不同的RTOS的时候,需要事先评估用到的POSIX接口在这些RTOS上的实时行为与我们的期待是否相符。
参考
https://www.baeldung.com/linux/posix
https://docs.rtems.org/branches/master/posix-users/index.html
https://ecos.sourceware.org/docs-latest/ref/posix-standard-support.html