当前位置:首页 > 工业控制 > 工业控制
[导读]标准Linux下控制线程“平均1ms”很容易,但偶尔跳几毫秒甚至几十毫秒——工业伺服、运动控制受不了的是这个长尾。 PREEMPT_RT的价值不是让程序变快,是把调度延迟的尾部收窄:自旋锁改成可抢占的rt_mutex、中断处理线程化、优先级继承防反转,高优先级控制线程能更快拿到CPU。


标准Linux下控制线程“平均1ms”很容易,但偶尔跳几毫秒甚至几十毫秒——工业伺服、运动控制受不了的是这个长尾。 PREEMPT_RT的价值不是让程序变快,是把调度延迟的尾部收窄:自旋锁改成可抢占的rt_mutex、中断处理线程化、优先级继承防反转,高优先级控制线程能更快拿到CPU。

一、内核配置:先选对抢占模型

6.12+主线直接开CONFIG_PREEMPT_RT;老LTS(5.15/6.1/6.6)要先打对应版本RT补丁,版本号必须严格匹配。

menuconfig路径:General setup → Preemption Model → Fully Preemptible Kernel (Real-Time)。配套选项:

CONFIG_HIGH_RES_TIMERS=y:高分辨率定时器

CONFIG_HZ_1000=y:1ms节拍粒度

调试开关全关:lockdep、ftrace、kgdb会插进调度路径毁掉确定性

电源管理关C-states、关DVFS节能

启动参数把实时核隔离出来:

isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3

控制线程绑到2/3核,ksoftirqd、RCU、日志进程全挪走。

二、实时线程:SCHED_FIFO + 绑核

只开RT内核不够,用户线程默认还是SCHED_OTHER。 控制线程必须显式切SCHED_FIFO,且优先级别踩99——watchdog/migration就在99,踩了系统会卡死,应用层一般取20~80。

pthread_attr_t attr;

struct sched_param sp = {.sched_priority = 70};


pthread_attr_init(&attr);

pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

pthread_attr_setschedparam(&attr, &sp);

/* 关键:否则继承父线程的非实时策略 */

pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

pthread_create(&tid, &attr, control_loop, NULL);


/* 绑到隔离核 */

cpu_set_t c;

CPU_ZERO(&c); CPU_SET(2, &c);

pthread_setaffinity_np(tid, sizeof(c), &c);

主循环里别用相对sleep,累积误差会漂。

三、1ms周期:clock_nanosleep绝对时间

相对nanosleep每次重新算剩余时间,信号打断会偏;绝对模式按下一周期时间点睡,零漂移。

#define PERIOD_NS 1000000L

struct timespec next;


void *control_loop(void *arg){

   clock_gettime(CLOCK_MONOTONIC, &next);

   while(running){

       do_control();                 // 读编码器/算PID/发PDO


       next.tv_nsec += PERIOD_NS;

       if(next.tv_nsec >= 1000000000L){

           next.tv_sec++; next.tv_nsec -= 1000000000L;

       }

       clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);

   }

   return NULL;

}

CLOCK_MONOTONIC不受系统校时影响,比CLOCK_REALTIME稳。

四、实时路径零分配

周期函数里最致命的两件事:malloc/new触发缺页、printf碰文件系统。 启动阶段做完:

mlockall(MCL_CURRENT|MCL_FUTURE);   // 锁内存防换页

缓冲区、PID状态、通信帧全预分配;日志走无锁环形队列,另起低优先级线程异步写。控制循环内不调用任何可能阻塞的系统调用,只读取“上一周期已就绪”的数据。

五、cyclictest验证抖动

别只看平均值,看Max和直方图尾部:

cyclictest -p 80 -a 2 -i 1000 -D 10m -m -h 200

空载合格线:Max < 50μs;带载(stress-ng压CPU+IO)Max < 100μs算可用。

测之前固定CPU频率、关irqbalance、停rsyslog。普通内核对比一组,能直观看到PREEMPT_RT把偶发毫秒尖峰压下去。

六、落坑清单

只装RT内核不改调度策略→还是SCHED_OTHER

pthread忘了EXPLICIT_SCHED→优先级被继承覆盖

控制线程优先级设99→饿死内核线程系统卡死

实时线程绑核但IRQ没隔离→中断照样抢

周期循环用nanosleep相对睡→长运行漂移

循环里printf/malloc→缺页引入毫秒级尖峰

没mlockall→后台内存压力换页卡顿

DVFS开着→同段代码耗时随频率变

高速电流环(>5kHz)放Linux→还是交给MCU

只测空载Max→现场一带载就超

七、落地边界

PREEMPT_RT能压的是“CPU调度延迟”:中断线程化、锁可抢占、高优先级任务快速唤醒。 它管不到NPU/GPU内部队列、DDR带宽竞争、驱动器固件响应、外部总线延迟。

工程上合理分工:

Linux+RT:100~1000Hz上位控制、轨迹规划、CANopen主站、EtherCAT调度

MCU/驱动器:电流环、FOC、PWM、急停、硬件限位

通信层带时间戳+序号+超时降级,任一层错过周期都能进安全态。

真正“抖动<1ms”不是内核补丁单挑出来的,是四件套叠加:RT抢占内核 + SCHED_FIFO绑隔离核 + 周期绝对睡眠 + 实时路径零分配零缺页。少一个,cyclictest直方图右尾就会冒出来;四个都做对,工业1ms控制环通常能稳在几十微秒级抖动。再往上追微秒级电流环,就该让STM32或伺服驱动器接手了——Linux实时不等于全栈硬实时。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭