实时系统编程:实时调度、锁的实时性、内存分配器的实时优化
实时系统编程,说白了就是跟时间赛跑。
我刚开始接触实时系统时,总觉得只要代码逻辑对就行。直到有一次,一个工业控制项目在产线上突然卡顿,机械臂差点撞到操作员。嗯,从那以后我才真正理解——在实时系统里,时间正确性和功能正确性同等重要。
实时调度的核心逻辑
实时调度,就是决定哪个任务先跑、跑多久。我个人习惯把调度策略分成两类:
- 硬实时:错过截止时间 = 系统崩溃。比如安全气囊控制器。
- 软实时:偶尔迟到可以接受,但别太过分。比如视频播放器。
最常见的调度算法是 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。高优先级任务反而被晾在一边。
解决办法有三个:
- 优先级继承:低优先级任务拿到锁后,临时提升到等待该锁的最高优先级任务的优先级。
- 优先级天花板:给每个锁设定一个最高优先级,谁拿锁谁就提到这个优先级。
- 无锁编程:能不用锁就别用。用原子操作、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;
}
避坑指南:我曾经在实时分配器里用了链表,结果每次分配都要遍历,时间完全不可控。后来改成数组索引 + 空闲链表,分配时间才稳定下来。记住:实时系统里,确定性比效率更重要。
实时系统编程知识体系
实时系统编程,说白了就是三个字:确定性。调度要确定,锁要确定,内存分配也要确定。我见过太多项目,功能跑得飞快,但一到实时场景就崩。原因往往就是某个环节的时间不确定性被放大了。
嗯,最后说一句:别迷信花哨的算法。简单、稳定、可预测,才是实时系统的王道。