news 2026/3/11 22:15:28

4.3POSIXskin的不兼容性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
4.3POSIXskin的不兼容性

4.3 POSIX skin的不兼容性

4.3.1 mlockall 与栈大小

在 Xenomai 等实时系统中,确保程序运行的确定性和低延迟是至关重要的。为了实现这一点,Xenomai 在其初始化过程中使用了一个关键的 Linux 系统调用mlockall(),以提升内存访问效率并避免潜在的页面错误(page fault)。

本小节将深入解析mlockall的原理、作用及其对线程栈大小的影响,并探讨如何在系统中合理配置。

1. 什么是mlockall()

mlockall()是一个 Linux 系统调用,用于将进程的所有虚拟内存页锁定在物理内存中,防止它们被交换到磁盘或因未分配而引发页面错误。该调用的原型如下:

intmlockall(intflags);

其中,常用的标志包括:

  • MCL_CURRENT:锁住当前已分配的所有内存页。
  • MCL_FUTURE:锁住将来可能分配的内存页。

当一个程序调用mlockall(MCL_CURRENT | MCL_FUTURE)后,Linux 内核会尝试将所有当前和未来使用的内存页都映射到物理内存中,并禁止这些页被换出(swap out)。

调用mlockall(MCL_CURRENT | MCL_FUTURE)不会直接导致立即分配物理页面。mlockall的作用是锁定当前和将来映射的内存页,防止它们被交换到磁盘上的交换空间(swap)。然而,这并不意味着所有虚拟内存都会立即对应到物理内存页。

当你使用mlockall(MCL_CURRENT | MCL_FUTURE)时:

  • MCL_CURRENT标志表示锁定当前已映射的内存页面。
  • MCL_FUTURE标志表示锁定将来映射的内存页面。

这意味着对于已经分配并映射的内存区域(由MCL_CURRENT指示),操作系统将确保这些页面不会被交换出去。但是,如果这些页面之前没有被访问过(即没有发生缺页异常),那么它们可能还没有对应的物理内存页。在这种情况下,物理内存页会在首次访问这些页面时通过缺页处理机制分配。

对于MCL_FUTURE,当新的内存被映射到进程地址空间时(例如通过mallocmmap),这些新映射的页面也将自动被锁定,以防止它们被换出。但是,同样地,这并不会触发对这些页面的实际物理内存分配,直到它们被程序实际访问为止。

因此,虽然mlockall可以保证锁定的内存不会被交换到磁盘上,但它并不能保证所有的虚拟内存页在调用mlockall时都已经被分配了物理内存页。物理内存页的实际分配仍然遵循按需分页的原则,即在第一次访问某个页面时才会真正分配物理内存。 若要确保某些内存区域的物理页面已经被分配,你可能需要显式地访问这些页面的内容(例如,通过读写操作)来触发缺页处理和物理内存的分配。

2. 为什么需要mlockall()

在标准 Linux 中,默认采用按需分页(on-demand paging)机制。也就是说,程序请求内存后并不会立即分配物理页,而是等到第一次访问该内存区域时才触发一次页面错误(page fault),由内核动态分配物理页。

这种机制虽然节省了内存资源,但在实时系统中却存在严重问题:

  1. 页面错误会导致中断处理:发生 page fault 时,内核必须介入进行物理页分配,这会打断当前正在执行的线程。
  2. 切换执行模式:如果当前线程处于实时优先级(primary mode),则页面错误会强制将其降级为普通优先级(secondary mode),从而破坏实时性。
  3. 不可预测的延迟:在极端情况下(如内存不足且需要写回磁盘数据释放页),延迟可能达到毫秒级别。

因此,在 Xenomai 实时框架中,为了避免任何非预期的页面错误,通常会在初始化阶段调用mlockall(),一次性提交并锁定所有内存,从而消除 page fault 带来的不确定性。

在Xenomai应用程序启动时,cobalt_init()初始化过程会自动执行mlockall(MCL_CURRENT | MCL_FUTURE)

//lib/cobalt/init.c cobalt_init() | |--> cobalt_init_1() | | | |--> cobalt_init_2() | | | |--> low_init() | | | |--> mlockall(MCL_CURRENT | MCL_FUTURE)
3. mlockall 的副作用:线程栈大小问题

尽管mlockall()可以显著提高系统的确定性,但它也会带来一些副作用,尤其是在多线程环境中,最明显的就是线程栈大小的分配问题

(1)默认栈大小的问题

在大多数 Linux 平台上,线程默认的栈大小为2MiB甚至更高。例如Ubuntu22.04/RHEL8.9的默认栈大小为8MiB

ulimit-s8192

这意味着,每当创建一个新线程时,系统都会立即为其分配 8MiB 的物理内存空间。在内存受限的嵌入式系统中,如果有大量线程同时运行,这可能会迅速耗尽可用内存。

更糟糕的是,由于mlockall()已经启用,这些栈空间会被立即提交并锁定在内存中,无法延迟分配,进一步加剧内存压力。

(2) Xenomai 的应对策略

为了解决这个问题,Xenomai 对线程栈进行了优化:

  • 默认栈大小被减小:缩小到一个更合理的默认值,以减少内存占用。

PTHREAD_STACK_MIN 是定义在 POSIX 线程库(pthreads)中的一个宏,用于表示创建线程时允许的最小栈大小。这个值并不是指实际分配给每个新线程的栈大小,而是系统支持的最小安全栈大小,以确保程序能正常运行而不出现栈溢出等问题。

在 Linux 系统中,PTHREAD_STACK_MIN 的具体数值可能会根据不同的处理器架构有所不同,但通常它被设置为一个足够小的值,用来作为开发者设定自定义栈大小时的下限标准。例如,在适配ARM64的glibc 2.41上,它的默认值2个64KB的页面,即128KB。

Xenomai定义的宏PTHREAD_STACK_DEFAULT代表了默认栈大小,它取PTHREAD_STACK_MIN和64KB二者中的最大值。

#ifndefPTHREAD_STACK_DEFAULT#definePTHREAD_STACK_DEFAULT\({\int__ret=PTHREAD_STACK_MIN;\if(__ret<65536)\__ret=65536;\__ret;\})#endif/* !PTHREAD_STACK_DEFAULT */
  • 建议显式设置栈大小:对于确实需要更大栈空间的线程,应通过pthread_attr_setstacksize()显式指定更大的栈大小。

示例代码:

pthread_attr_tattr;size_tstack_size=64*1024;// 64 KiBpthread_attr_init(&attr);pthread_attr_setstacksize(&attr,stack_size);pthread_create(&thread_id,&attr,thread_func,NULL);

⚠️ 注意:某些标准库函数(如printf)会在内部使用较多栈空间。如果将栈大小设得太小(如 4KiB),可能导致栈溢出并引发段错误(Segmentation Fault)。

4. 主线程的特殊处理

与普通线程不同,主线程(main thread)并不是通过pthread_create()创建的,因此不能使用pthread_attr_setstacksize()来修改其栈大小。此时,可以通过 shell 命令ulimit来调整主线程的栈限制。

例如,在启动程序前运行:

ulimit-s256# 将栈大小限制为 256 KiB./my_program

此外,即使启用了mlockall(),主线程的栈仍可能在运行时增长(因为它是自动扩展的)。为了防止在关键实时路径上出现 page fault,建议在进入实时模式之前主动“预触碰”主线程栈,即通过访问栈上的变量或数组来强制分配物理页。

示例代码:

chardummy[64*1024];memset(dummy,0,sizeof(dummy));// 强制分配栈空间

因此,在开发 Xenomai 应用程序时,开发者应在保证功能正确性的前提下,合理配置线程栈大小,结合mlockall()和预分配策略,最大化系统的实时性能与稳定性。

4.3.2 实时线程的调度策略

1. 实时线程的基本要求

原生Linux中,使用pthread_create创建的线程,支持以下几种调度策略:

  • SCHED_FIFO:先进先出调度策略。线程一旦开始运行,除非被更高优先级的线程抢占,或者主动放弃 CPU(如调用sched_yield()),否则将持续运行。优先级范围1~99。
  • SCHED_RR:轮转调度策略,类似于 FIFO,但每个线程有一个时间片,时间片用完后会排到队列末尾等待下一轮执行。优先级范围等同于SCHED_FIFO,优先级范围1~99。
  • SCHED_NORMAL:Linux 默认的分时调度策略,不适合实时任务。所有调度策略SCHED_NORMAL的线程,在pthread_create后优先级为0。为了给这些线程在Linux内核中进行优先级排序,Linux通过nice值来重新调节优先级。在Linux系统中,SCHED_OTHER 和 SCHED_NORMAL 实际上指的是同一个调度策略。SCHED_OTHER 是POSIX标准中定义的名称,SCHED_NORMAL 是Linux内核内部使用的别名。

下表列出了Xenomai支持的调度策略及其调度类。相比于Linux中常用的SCHED_NORMALSCHED_FIFOSCHED_RR,Xenomai自行定义了其特有的调度策略:

Linux 调度策略Xenomai 调度策略Xenomai 调度类Xenomai 适应范围
SCHED_NORMALSCHED_NORMALxnsched_class_weak弱实时调度类,优先级为 0
SCHED_NORMALSCHED_NORMALxnsched_class_rt当没有打开 weak 调度类时,使用实时调度类,但是优先级强制为 0
SCHED_FIFOSCHED_FIFOxnsched_class_rt实时调度类,优先级支持范围1~256,Linux 实际只传入 1~99
SCHED_RRSCHED_RRxnsched_class_rt实时调度类,优先级支持范围1~256,Linux 实际只传入 1~99
N/ASCHED_IDLExnsched_class_idle用于空闲调度,优先级必须为 -1
N/ASCHED_COBALTxnsched_class_rt实时调度类,优先级范围0~259
N/ASCHED_WEAKxnsched_class_weak弱实时调度类,优先级范围 0~99
N/ASCHED_SPORADICxnsched_class_sporadic用于处理偶发任务,优先级范围 1~255
N/ASCHED_TPxnsched_class_tp用于时间分区调度,优先级范围 1~255
N/ASCHED_QUOTAxnsched_class_quota用于配额调度,优先级范围 1~255

注意,Xenomai 默认情况下只支持两种调度类:实时调度类(xnsched_class_rt)和 空闲调度类(xnsched_class_idle)。其它调度类需要通过编译选项开启,而且一般来说并不常用。

[*] Xenomai/cobalt ---> Core features ---> [*] Extra scheduling classes [ ] Weak scheduling class (NEW) [ ] Temporal partitioning (NEW) [ ] Sporadic scheduling (NEW) [ ] Thread groups with runtime quota (NEW)

要使一个线程被 Xenomai 调度器识别为实时线程,必须使用SCHED_FIFOSCHED_RR调度策略。

如果一个线程的调度策略被设置为SCHED_NORMAL,会被等同于SCHED_WEAK对待,对应的调度类为xnsched_class_weak,提供相对较弱的实时性保障。虽然在 Linux 中 SCHED_NORMAL 线程的优先级必须为 0,但在 Xenomai 中,SCHED_NORMAL 线程的优先级可以设置为 0~99。如果没有打开weak调度类,则 SCHED_NORMAL 线程的调度类为xnsched_class_rt,优先级强制为 0,且实时线程的状态被标记为XNWEAK

Linux的调度策略到Xenomai的调度策略的映射关系,可以参考POSIX skin中的pthread_createAPI执行过程。

在用户层应用程序中,可以调用POSIX skin中的pthread_createAPI来创建Xenomai 实时线程。pthread_create的执行过程比较复杂,其中有一个环节,会执行sc_cobalt_thread_create系统调用,陷入内核层并执行Xenomai系统调用函数CoBaLt_thread_create

CoBaLt_thread_create经过层层调用,会执行Cobalt内核实现的pthread_create函数。

// kernel/cobalt/posix/thread.cstaticintpthread_create(structcobalt_thread**thread_p,intpolicy,conststructsched_param_ex*param_ex,structtask_struct*task){...snip...sched_class=cobalt_sched_policy_param(&param,policy,param_ex,&tslice);if(sched_class==NULL){xnfree(thread);return-EINVAL;}...snip...}

在上述pthread_create的代码片段中,主要关注 cobalt_sched_policy_param 函数。

cobalt_sched_policy_param 函数的核心功能是将用户空间指定的调度策略(如 SCHED_FIFO、SCHED_RR 等)和调度参数,转换为内核空间使用的调度类和调度策略参数。它会根据不同的调度策略进行相应处理,验证优先级是否合法,最终返回对应的调度类指针。

2. 设置线程的调度属性

有两种方式可以设置线程的调度策略和参数:

  • 方法一:创建线程前设置属性

使用pthread_attr_t属性对象,在调用pthread_create()之前设置调度策略和参数。

#include<pthread.h>#include<sched.h>void*thread_func(void*arg){// 实时线程逻辑returnNULL;}intmain(){pthread_tthread;pthread_attr_tattr;structsched_paramparam;// 初始化属性pthread_attr_init(&attr);// 设置继承调度策略为显式设置pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);// 设置调度策略为 SCHED_FIFOpthread_attr_setschedpolicy(&attr,SCHED_FIFO);// 设置优先级(0 ~ 99)param.sched_priority=50;pthread_attr_setschedparam(&attr,&param);// 创建实时线程pthread_create(&thread,&attr,thread_func,NULL);// 销毁属性对象pthread_attr_destroy(&attr);// 等待线程结束pthread_join(thread,NULL);return0;}
  • 方法二:修改已有线程的调度参数

如果你希望修改主线程或其他已存在的线程的调度属性,可以使用pthread_setschedparam()

structsched_paramparam;param.sched_priority=90;if(pthread_setschedparam(pthread_self(),SCHED_FIFO,&param)!=0){perror("Failed to set real-time priority");}
3. 使用SCHED_FIFO注意事项

⚠️注意:使用SCHED_FIFO时,若线程进入死循环而没有让出 CPU,整个系统可能“冻结”。务必保证线程能定期释放 CPU,或设计合理的退出机制。

以下代码片段可能导致线程永远阻塞,尤其是在未正确初始化互斥量或条件变量时:

pthread_mutex_lock(&mutex);while(!cond)pthread_cond_wait(&cond,&mutex);// 如果 cond/mutex 未初始化,可能造成死锁pthread_mutex_unlock(&mutex);

为了避免此类问题,请确保:

  • 所有同步对象(mutex、cond)都正确初始化;
  • 在销毁前确保没有线程正在等待;
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/3 13:55:20

42、Perl引用的使用与嵌套数据结构构建

Perl引用的使用与嵌套数据结构构建 1. 引用基础与子程序参数传递 在Perl中,引用是一个强大的工具。当修改 @array2 时,它不会影响 @array1 ,因为它们是内容独立的不同数组。而 $arrayref 中对 @array1 的引用,会和 @array1 的当前内容相同,因为引用指向的是和 …

作者头像 李华
网站建设 2026/3/2 21:49:14

Wan2.2-T2V-5B预训练权重开放,支持本地部署

Wan2.2-T2V-5B预训练权重开放&#xff0c;支持本地部署 你有没有过这样的经历&#xff1f; 灵感突然闪现&#xff0c;想做个短视频验证想法&#xff0c;结果刚写完脚本就卡在了渲染上——等了半小时&#xff0c;视频还没跑完&#xff0c;热情早已冷却。 但现在不一样了。 最…

作者头像 李华
网站建设 2026/3/10 19:00:48

47、Perl编程的进阶知识与实用技巧

Perl编程的进阶知识与实用技巧 1. XML数据处理 在Perl中处理XML数据时,可将XML文档映射到变量 $computers ,它是一个哈希引用。这个哈希有一个元素,键为 computer ,其值是另一个哈希的引用,该哈希的键由XML文件中 computer 元素的属性名表示。每个这样的哈希成员的…

作者头像 李华
网站建设 2026/3/10 2:28:02

全网热议!2025年最佳单北斗GNSS变形监测系统推荐榜单

在2025年&#xff0c;市场上的单北斗GNSS变形监测系统种类繁多&#xff0c;各具特色。许多系统不仅能够实时监测地震、滑坡等地质灾害&#xff0c;还能为桥梁等基础设施提供稳定的变形监测服务。这些设备通常依托先进的GNSS技术&#xff0c;结合高精度传感器&#xff0c;确保数…

作者头像 李华
网站建设 2026/3/11 7:14:15

黄金高位AI动能骤减,“非农”与“恐怖数据”AI冲击波蓄势待发

摘要&#xff1a;本文通过构建基于机器学习与深度学习的多维度数据分析模型&#xff0c;结合自然语言处理&#xff08;NLP&#xff09;对非农数据进行语义解析&#xff0c;运用强化学习算法对市场情绪进行动态捕捉&#xff0c;分析现货黄金价格关键就业数据发布背景下的波动逻辑…

作者头像 李华
网站建设 2026/3/5 9:09:50

TensorRT镜像中集成Cuda安装脚本的一键化方案

TensorRT镜像中集成CUDA安装脚本的一键化方案 在现代AI系统的生产部署中&#xff0c;一个看似简单的“推理服务启动”背后&#xff0c;往往隐藏着复杂的环境依赖和版本兼容性问题。你是否曾遇到过这样的场景&#xff1a;开发环境一切正常&#xff0c;但模型一上线就报错 libcud…

作者头像 李华