前言

人形机器人软件系统是一个对实时性要求较高的软件系统。它包含低层级运动闭环、运动规划与行为决策、环境感知、人机交互等任务。在这些任务中,有些任务对实时性要求较高,例如,EtherCAT 主站、力矩闭环。有些任务对实时性要求不高,例如,环境感知、人机交互任务。本文将从PREEMPT_RT Linux、操作系统调度策略、CPU内核隔离、内存锁定、进程亲和性绑定方面进行阐述,详细讲解如何构建一个既能满足各任务实时性需求,又能满足功能多样性需求的四足机械狗/人形机器人软件系统。

一、什么是 PREEMPT_RT

PREEMPT_RT(Real-Time Preemption),俗称 RT 补丁,是一套对 Linux 内核进行大规模改造的补丁集。其目标是把标准 Linux 从软实时改造为具备确定性低延迟的实时 Linux,让内核几乎全部代码路径支持抢占。PREEMPT_RT 四大核心改造如下。

1. 自旋锁改造:spinlock → rtmutex(可睡眠锁)

  • 普通 spinlock_t 在 PREEMPT_RT 下不再忙等、不再关闭抢占;
  • 内部基于rtmutex(实时互斥锁)实现,争锁失败时线程主动让出 CPU;
  • 自带优先级继承(PI),缓解优先级反转;
  • 极短底层同步代码保留 raw_spinlock_t(真正不可抢占)。

2. 中断线程化(Threaded IRQs)

硬件中断只保留极简上半部(ack 中断);绝大多数中断处理逻辑放到独立内核线程运行。这样做的好处有

  • 中断线程拥有优先级,可以被更高优先级实时任务抢占;
  • 不再出现 “中断长时间霸占 CPU” 阻塞实时任务。

3. 最大化内核抢占点

普通 CONFIG_PREEMPT:遇到 spinlock 临界区就不能抢占。
PREEMPT_RT:几乎所有内核代码都允许抢占,只有极少数极短底层代码路径禁止抢占。

4. 增强高精度定时器 hrtimer

为周期性控制任务(伺服、运动控制)提供稳定纳秒级定时能力。

二、Linux 四种抢占模式对比

配置 名称 实时能力 典型最大延迟 适用场景
PREEMPT_NONE 无抢占 最差 数 ms~ 几十 ms 服务器、高性能计算
PREEMPT_VOLUNTARY 自愿抢占 一般 ~2ms 桌面发行版默认
PREEMPT 可抢占内核(低延迟) 中等 几百 μs~1ms 普通低延迟嵌入式
PREEMPT_RT 实时抢占内核 最优 几十 μs(优化后 < 50μs) 工业控制、机器人、伺服、音频

三、SCHED_FIFO 与 SCHED_RR调度策略

SCHED_FIFO 与 SCHED_RR都属于Linux实时调度策略,这两种实时策略能不能用,要看你的 Linux 内核有没有开启对应的抢占选项(PREEMPT_RT 或者 CONFIG_PREEMPT)。

1. SCHED_FIFO(先进先出)

(1) SCHED_FIFO规则

1) 同一优先级下多个任务排队;
2) 一个任务拿到 CPU 后,持续运行直到以下任意一种情况才让出 CPU
  • 任务主动阻塞(sleep、等待信号量、I/O);
  • 任务主动调用 sched_yield() 放弃 CPU;
  • 有更高优先级任务就绪,被抢占;
3) 任务不会因为时间片用完被强制切走,没有时间片概念。

(2) SCHED_FIFO例子

  • 优先级90、80两个FIFO任务。90任务就绪,直接抢占80任务。
  • 同优先级两个 FIFO 任务 A、B:A 运行后,只要不阻塞,B 永远得不到调度。

(3) SCHED_FIFO适用场景

SCHED_FIFO适用于不希望中途被同优先级任务打断的场景。例如,机器人伺服闭环、EtherCAT 主站、周期性硬实时控制等任务。

(4) C代码设置SCHED_FIFO

#include <sched.h>

void set_realtime_fifo(int prio)
{
    struct sched_param sp;
    sp.sched_priority = prio;
    sched_setscheduler(0, SCHED_FIFO, &sp);
}

2. SCHED_RR(轮询实时,Round-Robin)

(1) SCHED_RR规则

  • 跨优先级行为和 FIFO 完全一致:高优先级随时抢占低优先级;
  • 同一优先级内部加入时间片机制;
  • 同优先级多个任务轮流执行,任务用完时间片后,放到同优先级队列尾部;
  • 若任务中途阻塞,再次唤醒后排在队列末尾。
  • 默认时间片由内核参数控制 /proc/sys/kernel/sched_rr_timeslice_ms,默认 100ms。

(2) 例子

同优先级 90的RR 任务 A、B:

  • A 运行一段时间,时间片耗尽 → 切换 B;
  • B 时间片耗尽切回 A;
  • 如果此时来了优先级 91 任务,立刻抢占 A/B。

(3) 适用场景

1) 场景 1:多传感器同步采集系统(工业检测平台)
  • 设备同时采集:高速相机图像、激光测距、IMU 数据,三类采集线程设置相同实时优先级:
  • 采集线程都需要周期性抢占普通任务;
  • 没有任何一路采集拥有绝对最高权限,不能允许某一路持续占用 CPU 导致其他传感器丢帧;
  • SCHED_RR 依靠时间片轮转,让多路采集线程轮流执行,均衡分配 CPU,防止单个传感器线程长期运行阻塞其余采集任务。
2) 场景 2:多通道实时音频处理(专业声卡、音频工作站)
  • 多路音频轨道渲染、音效滤波、混音输出,全部设置为同一实时优先级;
  • 如果用 SCHED_FIFO:最先被调度到的轨道线程会一直运行,其他声道饥饿,产生爆音、丢采样;
  • 使用 SCHED_RR:同优先级各路音频线程按时间片轮流执行,保证所有声道均衡获得 CPU 时间,避免单一路径独占,保障多路音频同步输出。

四、cpu亲和性

CPU 亲和性(CPU Affinity),就是强制把某个线程 / 进程 “绑定到一个或多个 CPU 核心上运行”,限制内核调度器不能随意把它迁移到其他核心。

1. 为什么迁移 CPU 会带来延迟

线程从 CPU0 切换到 CPU1 时:

  • L1/L2 缓存失效,新核心缓存为空,大量 Cache Miss;
  • TLB 刷新;
  • 跨 NUMA 节点内存访问延迟飙升;
  • 引入不可预测的调度抖动。

2. cpu亲和性在Linux中分为两种

  • 软亲和性:调度器倾向尽量不迁移,不强制;
  • 硬亲和性:通过 API 显式掩码绑定,强制限制可运行的核心集合。

3. CPU 掩码

掩码每一位代表一个核心:
0b0001 → 只能跑在 CPU0
0b0101 → 可以跑在 CPU0、CPU2

4. C代码绑定当前线程到 CPU3

cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(3, &mask);
sched_setaffinity(0, sizeof(mask), &mask);

五、内存锁

mlock:把进程虚拟内存锁定在物理内存,禁止内核把这块内存交换(swap)到磁盘。

1. 背景知识

Linux 默认采用虚拟内存 + 按需分页(Demand Paging):

  • 程序启动时,只有虚拟地址,物理内存不一定立刻分配;
  • 内存不足时,内核会把不常用页面换出到 swap 磁盘;
  • 程序访问尚未载入物理内存的地址 → 缺页异常(Page Fault)。内核要从磁盘读取页面,耗时毫秒级甚至十几 ms,对于微秒级实时闭环是致命延迟尖峰。
    实时任务最大敌人之一:运行中途触发缺页故障。mlock 就是用来杜绝运行时 page fault。

2. 内存锁两个系统调用

(1) mlock(),锁定指定一段内存区域(堆、全局数组、共享内存)。

int mlock(const void *addr, size_t len);

mlock 做了两件核心事

  • 强制所有虚拟页立刻映射到物理内存,主动触发缺页(在初始化阶段一次性完成);
  • 标记页面不可被回收、不可 swap out,运行期间不会再产生磁盘 IO 类缺页。

(2) mlockall()

// MCL_CURRENT:锁定当前已经分配的所有内存
// MCL_FUTURE:锁定将来新分配的内存(malloc/mmap)
int mlockall(int flags);

工程实时程序最常用:mlockall(MCL_CURRENT | MCL_FUTURE)

六、四足机械狗/人形机器人实时软件进程隔离规则

以下隔离规则统一用主流工控平台8核CPU举例。

1. 底层运动控制层隔离规则

底层运动控制代表程序有EtherCAT 主站、力矩闭环、全身 WBC 控制器。这类底层运动控制程序应独占CPU3,不与任何其他线程共享核心。调试策略用先进先出SCHED_FIFO调度,不要使用SCHE_RR。进程启动第一时间执行 mlockall(MCL_CURRENT | MCL_FUTURE),防止运行时缺页。线程内部禁止调用阻塞IO、printf大量打印、磁盘写操作。禁止调用ROS、GPU推理等耗时不定的库。EtherCAT网卡中断IRQ手动绑定CPU3。不要把视觉、SLAM任何线程放到CPU3,一旦CPU3出现计算尖峰,容易导致伺服丢周期,机器人摔倒。

2. 运动规划层隔离规则

运动规划层的代表程序有步态规划、平衡控制、状态估计等。这类程序的典型周期为200Hz~500Hz,指定其运行在CPU2。优先级低于伺服闭环,保证当闭环线程需要CPU时,可以立刻抢占规划线程。允许做矩阵运算、QP优化(WBC二次规划)。规划层计算超时只会影响步态流畅性,不会造成电机失控。步态规划可以和状态估计共享CPU2,但是尽量拆分为独立线程,区分优先级。

3. 环境感知层隔离规则

环境感知的代表程序有相机、激光雷达、SLAM等。全部不允许这类程序进入隔离实时核,可指定这类程序运行在CPU1。图像推理、点云处理耗时抖动极大,属于典型“计算时长不可预测任务”。对于这类程序一律使用SCHED_OTHER CFS 调度。如果需要略微降低延迟,可少量使用SCHED_RR策略。感知结果通过共享内存或内存零拷贝传递给运动规划程序。避免让感知线程直接调用运动控制接口。

4. 人机交互层隔离规则

人机交互程序主要有手柄、语音对话、上位机客户端、网页控制端等。对于这类程序可指定在CPU0或CPU4中执行,采用普通分时调度即可。因为,它们只负责解析指令、输出目标姿态/目标速度给运动规划层。交互模块丢失几帧指令只会造成操控卡顿,不影响机器人稳定。此外,网络收发、语音解码存在不确定延迟,不要将这类程序提升至实时优先级。

七、四足机械狗/人形机器人软件进程隔离架构图

在这里插入图片描述

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐