实时系统编程:实时调度、锁的实时性、内存分配器的实时优化

实时系统编程,说白了就是跟时间赛跑。

我刚开始接触实时系统时,总觉得只要代码逻辑对就行。直到有一次,一个工业控制项目在产线上突然卡顿,机械臂差点撞到操作员。嗯,从那以后我才真正理解——在实时系统里,时间正确性功能正确性同等重要。

实时调度的核心逻辑

实时调度,就是决定哪个任务先跑、跑多久。我个人习惯把调度策略分成两类:

  • 硬实时:错过截止时间 = 系统崩溃。比如安全气囊控制器。
  • 软实时:偶尔迟到可以接受,但别太过分。比如视频播放器。

最常见的调度算法是 RM(Rate Monotonic)EDF(Earliest Deadline First)

RM 调度:周期越短的任务,优先级越高。简单粗暴,但静态优先级容易出问题。

EDF 调度:谁先到截止时间,谁先跑。动态优先级,理论上最优,但实现复杂。

我在项目中遇到过一个问题:用 RM 调度时,三个任务周期分别是 5ms、10ms、20ms。看起来没问题,但实际跑起来,高优先级的任务把 CPU 占满了,低优先级的任务一直饿死。后来我算了一下 CPU 利用率,发现已经超过 RM 的调度上限了。

避坑指南:RM 调度的可调度性条件是 CPU 利用率 ≤ n(2^(1/n) - 1)。n 是任务数量。n=3 时,上限约 0.78。我曾经因为没算这个,吃了大亏。

锁的实时性:别让锁成为瓶颈

实时系统里用锁,最怕两件事:优先级反转死锁

优先级反转是什么?说白了就是:一个低优先级的任务拿着锁,高优先级的任务等着用,结果中优先级的任务插进来把低优先级的任务抢走了 CPU。高优先级任务反而被晾在一边。

解决办法有三个:

  1. 优先级继承:低优先级任务拿到锁后,临时提升到等待该锁的最高优先级任务的优先级。
  2. 优先级天花板:给每个锁设定一个最高优先级,谁拿锁谁就提到这个优先级。
  3. 无锁编程:能不用锁就别用。用原子操作、CAS、读写锁替代。

注意:优先级继承不是万能的。如果锁的嵌套层级太深,继承链会变得复杂,反而增加调度开销。我建议锁的嵌套不要超过两层。

代码示例:一个简单的优先级继承实现(伪代码)

// 锁结构体
typedef struct {
    int owner_priority;      // 当前持有者的原始优先级
    int inherited_priority;  // 继承后的优先级
    task_t *owner;           // 当前持有者
    task_t *waiters[MAX];    // 等待队列
} mutex_t;

void lock(mutex_t *m, task_t *t) {
    if (m->owner == NULL) {
        m->owner = t;
        m->owner_priority = t->priority;
        return;
    }
    // 如果有更高优先级的任务在等,提升持有者的优先级
    if (t->priority > m->owner_priority) {
        m->inherited_priority = t->priority;
        set_task_priority(m->owner, t->priority);
    }
    // 加入等待队列
    enqueue(m->waiters, t);
}

内存分配器的实时优化

标准库的 malloc/free 在实时系统里就是定时炸弹。为什么?

  • 分配时间不确定。可能几微秒,也可能几毫秒(触发系统调用或内存整理)。
  • 容易产生碎片。碎片多了,分配失败的概率就高了。
  • 多线程环境下,锁竞争严重。

我常用的实时内存分配策略:

策略 适用场景 优点 缺点
固定大小块分配 任务内存大小已知 O(1) 分配,无碎片 内存利用率低
伙伴系统 大小变化但可预测 分配快,合并简单 内部碎片
线程本地缓存 多线程高频分配 无锁,极快 线程间无法共享

我个人最推荐:固定大小块分配 + 线程本地缓存。前者保证确定性,后者减少锁竞争。我在一个无人机飞控项目里就是这么做的,内存分配时间稳定在 200ns 以内。

代码示例:一个简单的固定大小块分配器

#define BLOCK_SIZE 64
#define BLOCK_COUNT 1024

typedef struct {
    char data[BLOCK_SIZE];
    int next_free;  // 指向下一个空闲块索引
} block_t;

static block_t pool[BLOCK_COUNT];
static int free_head = 0;

void init_allocator() {
    for (int i = 0; i < BLOCK_COUNT - 1; i++) {
        pool[i].next_free = i + 1;
    }
    pool[BLOCK_COUNT - 1].next_free = -1;
}

void* rt_malloc() {
    if (free_head == -1) return NULL;
    int idx = free_head;
    free_head = pool[idx].next_free;
    return pool[idx].data;
}

void rt_free(void *ptr) {
    if (ptr == NULL) return;
    block_t *b = (block_t*)((char*)ptr - offsetof(block_t, data));
    b->next_free = free_head;
    free_head = b - pool;
}

避坑指南:我曾经在实时分配器里用了链表,结果每次分配都要遍历,时间完全不可控。后来改成数组索引 + 空闲链表,分配时间才稳定下来。记住:实时系统里,确定性比效率更重要

实时系统编程知识体系

实时系统编程 实时调度 锁的实时性 内存分配器优化 RM 调度 EDF 调度 优先级继承 优先级天花板 固定大小块 伙伴系统 核心目标:确定性 > 效率 实时系统编程三大核心:调度、锁、内存 三者相互影响,需要整体优化

实时系统编程,说白了就是三个字:确定性。调度要确定,锁要确定,内存分配也要确定。我见过太多项目,功能跑得飞快,但一到实时场景就崩。原因往往就是某个环节的时间不确定性被放大了。

嗯,最后说一句:别迷信花哨的算法。简单、稳定、可预测,才是实时系统的王道。

公众号:蓝海资料掘金营,微信deep3321