手把手教你用C语言实现硬件抽象层、并发调度、状态机、高可靠系统------所有模式附完整代码
目录
- 基础:用C模拟面向对象与状态机
- Harmony/ERT开发流程与模式选用方法
- 硬件访问设计模式(7种)
- 并发与资源管理设计模式(8种)
- 状态机实现设计模式(5种)
- 安全性与可靠性设计模式(6种)
第1章 基础:用C模拟面向对象与状态机
嵌入式C开发者常抱怨C不是面向对象语言。但书中给出了用C模拟类、继承、多态、状态机的成熟技术。这是理解后面所有模式的前提。
1.1 类(Class)的实现
核心思想 :用struct存放数据成员,用一组普通函数作为方法,每个方法的第一个参数是me指针(指向当前对象的指针)。使用头文件公开接口,C文件隐藏实现。
示例:Sensor类
c
// Sensor.h
#ifndef SENSOR_H
#define SENSOR_H
typedef struct Sensor Sensor; // 不透明指针
struct Sensor {
int value; // 属性
int updateFreq;
int filterFreq;
};
// 构造函数与析构函数
void Sensor_Init(Sensor* const me);
void Sensor_Cleanup(Sensor* const me);
Sensor* Sensor_Create(void);
void Sensor_Destroy(Sensor* const me);
// 成员函数(getter/setter)
int Sensor_getValue(const Sensor* const me);
void Sensor_setValue(Sensor* const me, int v);
int Sensor_getUpdateFreq(const Sensor* const me);
void Sensor_setUpdateFreq(Sensor* const me, int freq);
// ...
#endif
c
// Sensor.c
#include "Sensor.h"
#include <stdlib.h>
void Sensor_Init(Sensor* const me) {
me->value = 0;
me->updateFreq = 100;
me->filterFreq = 50;
}
void Sensor_Cleanup(Sensor* const me) {
// 若有动态内存释放,在此处理
}
int Sensor_getValue(const Sensor* const me) {
return me->value;
}
void Sensor_setValue(Sensor* const me, int v) {
me->value = v;
}
int Sensor_getUpdateFreq(const Sensor* const me) {
return me->updateFreq;
}
void Sensor_setUpdateFreq(Sensor* const me, int freq) {
me->updateFreq = freq;
}
// 其它getter/setter...
Sensor* Sensor_Create(void) {
Sensor* me = (Sensor*)malloc(sizeof(Sensor));
if (me != NULL) {
Sensor_Init(me);
}
return me;
}
void Sensor_Destroy(Sensor* const me) {
if (me != NULL) {
Sensor_Cleanup(me);
free(me);
}
}
关键点:
- 使用不透明指针(前向声明
struct Sensor)隐藏实现。 _Init用于初始化已分配的内存(栈或静态对象),_Create动态分配并初始化。- 所有函数第一个参数都是
me,指向当前对象。这模拟了面向对象中的this。
1.2 多态与虚函数(Virtual Function)
核心思想 :在struct中存放函数指针,构造函数中将指针赋值为具体实现。客户通过函数指针调用,实现动态绑定。
示例:队列(Queue)
c
// Queue.h
#ifndef QUEUE_H
#define QUEUE_H
#define QUEUE_SIZE 10
typedef struct Queue Queue;
struct Queue {
int buffer[QUEUE_SIZE];
int head, tail, size;
// 虚函数指针
int (*isFull)(Queue* const me);
int (*isEmpty)(Queue* const me);
void (*insert)(Queue* const me, int k);
int (*remove)(Queue* const me);
};
void Queue_Init(Queue* const me);
void Queue_Cleanup(Queue* const me);
// 默认实现
int Queue_isFull(Queue* const me);
int Queue_isEmpty(Queue* const me);
void Queue_insert(Queue* const me, int k);
int Queue_remove(Queue* const me);
Queue* Queue_Create(void);
void Queue_Destroy(Queue* const me);
#endif
c
// Queue.c
#include "Queue.h"
#include <stdlib.h>
void Queue_Init(Queue* const me) {
me->head = 0;
me->tail = 0;
me->size = 0;
// 设置虚函数指针
me->isFull = Queue_isFull;
me->isEmpty = Queue_isEmpty;
me->insert = Queue_insert;
me->remove = Queue_remove;
}
int Queue_isFull(Queue* const me) {
return (me->size == QUEUE_SIZE);
}
int Queue_isEmpty(Queue* const me) {
return (me->size == 0);
}
void Queue_insert(Queue* const me, int k) {
if (!me->isFull(me)) {
me->buffer[me->head] = k;
me->head = (me->head + 1) % QUEUE_SIZE;
me->size++;
}
}
int Queue_remove(Queue* const me) {
int val = -1;
if (!me->isEmpty(me)) {
val = me->buffer[me->tail];
me->tail = (me->tail + 1) % QUEUE_SIZE;
me->size--;
}
return val;
}
Queue* Queue_Create(void) {
Queue* me = (Queue*)malloc(sizeof(Queue));
if (me) Queue_Init(me);
return me;
}
void Queue_Destroy(Queue* const me) {
free(me);
}
多态的体现:子类可以覆盖这些函数指针,实现自己的行为(见下面的继承示例)。
1.3 继承(Inheritance)的C模拟
核心思想 :将基类对象作为子类结构体的第一个成员。这样基类指针可以指向子类,子类可以调用基类的方法。子类可以重写虚函数指针。
示例:带磁盘缓存的队列(CachedQueue)继承自Queue
c
// CachedQueue.h
#ifndef CACHEDQUEUE_H
#define CACHEDQUEUE_H
#include "Queue.h"
typedef struct CachedQueue CachedQueue;
struct CachedQueue {
Queue base; // 基类必须放在第一个成员
char filename[80];
int numberOnDisk;
Queue* outputQueue; // 额外的聚合
// 新的虚函数
void (*flush)(CachedQueue* const me);
void (*load)(CachedQueue* const me);
};
void CachedQueue_Init(CachedQueue* const me, char* fname);
void CachedQueue_Cleanup(CachedQueue* const me);
// 重写的虚函数实现
int CachedQueue_isFull(CachedQueue* const me);
int CachedQueue_isEmpty(CachedQueue* const me);
void CachedQueue_insert(CachedQueue* const me, int k);
int CachedQueue_remove(CachedQueue* const me);
void CachedQueue_flush(CachedQueue* const me);
void CachedQueue_load(CachedQueue* const me);
CachedQueue* CachedQueue_Create(char* fname);
void CachedQueue_Destroy(CachedQueue* const me);
#endif
c
// CachedQueue.c 关键部分
#include "CachedQueue.h"
#include <string.h>
void CachedQueue_Init(CachedQueue* const me, char* fname) {
// 初始化基类
Queue_Init(&me->base);
// 覆盖虚函数指针
me->base.isFull = (int (*)(Queue*))CachedQueue_isFull;
me->base.isEmpty = (int (*)(Queue*))CachedQueue_isEmpty;
me->base.insert = (void (*)(Queue*, int))CachedQueue_insert;
me->base.remove = (int (*)(Queue*))CachedQueue_remove;
// 初始化自己的属性
strcpy(me->filename, fname);
me->numberOnDisk = 0;
me->outputQueue = Queue_Create();
// 设置新虚函数
me->flush = CachedQueue_flush;
me->load = CachedQueue_load;
}
int CachedQueue_isFull(CachedQueue* const me) {
return Queue_isFull(&me->base) && Queue_isFull(me->outputQueue);
}
void CachedQueue_insert(CachedQueue* const me, int k) {
if (Queue_isFull(&me->base)) {
me->flush(me); // 将基类队列刷新到磁盘
}
Queue_insert(&me->base, k);
}
解释:
- 子类
CachedQueue的第一个成员是Queue base,这保证了内存布局上基类在前,因此(Queue*)cachedQueue是合法的。 - 子类在
_Init中把基类的虚函数指针替换成自己的函数,实现了重写。 - 客户代码如果持有
Queue*指针,调用insert时会调用到子类版本,实现了多态。
1.4 有限状态机(FSM)基础
状态机包含:状态(圆角矩形)、转换(箭头)、事件(触发转换)、动作(入口/出口/转换动作)。
最简单的C实现 :状态变量 + 事件分发函数中的switch-case。
c
// 状态枚举
typedef enum {
STATE_IDLE,
STATE_ACCEPTING,
STATE_CHECKING_LEN,
STATE_VALIDATING_PIN
} State;
// 事件类型枚举
typedef enum {
EV_KEYPRESS,
EV_CANCEL,
EV_ENTER
} EventType;
// 事件结构(可携带数据)
typedef struct {
EventType type;
int key; // 按键值
} Event;
State currentState;
void dispatchEvent(Event e) {
switch (currentState) {
case STATE_IDLE:
if (e.type == EV_KEYPRESS && e.key == CANCEL_KEY) {
// 出口动作
// 转换动作
currentState = STATE_IDLE;
// 入口动作
}
break;
case STATE_ACCEPTING:
if (e.type == EV_KEYPRESS) {
if (isDigit(e.key)) {
// 停留在ACCEPTING状态
addKey(e.key);
} else if (e.key == ENTER_KEY) {
// 转换到CHECKING_LEN
currentState = STATE_CHECKING_LEN;
}
}
break;
// 其它状态...
}
}
这个实现的问题:当状态和事件很多时,单个dispatchEvent函数会非常庞大且难以维护。后面的第5章会介绍更好的实现模式。
第2章 Harmony/ERT过程与模式选用方法
这一章不是设计模式,而是使用模式的方法论。核心是三个步骤:
- 先让功能正确:不考虑优化,实现一个可以工作的模型和代码,并通过单元测试。
- 识别并加权设计标准:列出你要优化的目标(如最坏情况执行时间、内存占用、可维护性、功耗等),给每个目标一个权重(0-10)。
- 选择并应用模式:对候选模式方案进行权衡分析(用电子表格),然后实例化模式。
书中案例:ECG心电数据分发系统。设计标准及权重:
- 执行效率(权重7)
- 可维护性(权重5)
- 运行时灵活性(权重4)
- 内存效率(权重7)
三个候选方案:客户-服务器(轮询)、推送、观察者模式。打分后观察者总分最高,因此选用。
模式实例化 :将模式中的抽象角色(如AbstractSubject、AbstractObserver)替换为具体类(TMDQueue、WaveformDisplay等)。书中给出了完整的观察者模式C实现(已在3.4节详述)。
第3章 硬件访问设计模式
这一章包含7个模式,用于封装和优化硬件交互。
3.1 硬件代理模式(Hardware Proxy)
问题:直接操作硬件寄存器(内存映射、端口、位域)会导致硬件变化时大量代码需要修改。
解决方案 :创建一个代理类,封装所有硬件访问细节(地址、位操作、时序),对外提供高层API(如readValue()、writeCmd())。
UML结构 :ProxyClient → HardwareProxy → HardwareDevice。
HardwareProxy包含deviceAddr(void*或int),公有方法包括access()、mutate()、initialize()、configure()、disable(),私有方法marshal()/unmarshal()处理位编码。
C实现示例:马达控制器
c
// MotorProxy.h
#ifndef MOTOR_PROXY_H
#define MOTOR_PROXY_H
#include <stdint.h>
typedef struct MotorProxy MotorProxy;
struct MotorProxy {
volatile uint16_t* regAddr; // 内存映射寄存器地址
uint16_t rotaryArmLen; // 旋臂长度,用于速度调整
};
void MotorProxy_Init(MotorProxy* me);
void MotorProxy_configure(MotorProxy* me, uint16_t* addr, uint16_t len);
void MotorProxy_enable(MotorProxy* me);
void MotorProxy_disable(MotorProxy* me);
void MotorProxy_setSpeed(MotorProxy* me, uint16_t speed);
uint16_t MotorProxy_getSpeed(MotorProxy* me);
uint8_t MotorProxy_getErrorStatus(MotorProxy* me);
#endif
c
// MotorProxy.c
#include "MotorProxy.h"
// 私有函数:将用户数据打包成硬件寄存器格式
static uint16_t marshal(uint8_t enable, uint8_t direction, uint8_t speed) {
uint16_t reg = 0;
if (enable) reg |= (1 << 0);
if (direction == 1) reg |= (1 << 1);
reg |= (speed & 0x1F) << 3;
return reg;
}
// 私有函数:解包硬件寄存器为用户数据
static void unmarshal(uint16_t reg, uint8_t* enable, uint8_t* direction, uint8_t* speed) {
*enable = (reg >> 0) & 1;
*direction = (reg >> 1) & 1;
*speed = (reg >> 3) & 0x1F;
}
void MotorProxy_configure(MotorProxy* me, uint16_t* addr, uint16_t len) {
me->regAddr = addr;
me->rotaryArmLen = len;
}
void MotorProxy_setSpeed(MotorProxy* me, uint16_t speed) {
// 根据旋臂长度调整实际电机速度(保持末端线速度恒定)
uint16_t adjustedSpeed = speed;
if (me->rotaryArmLen > 0) {
adjustedSpeed = (uint16_t)((speed * 10) / (2 * 3.14159 * me->rotaryArmLen));
}
// 读取当前寄存器,只修改速度位
uint16_t current = *me->regAddr;
current &= ~(0x1F << 3); // 清除速度位
current |= (adjustedSpeed & 0x1F) << 3;
*me->regAddr = current;
}
uint16_t MotorProxy_getSpeed(MotorProxy* me) {
uint16_t reg = *me->regAddr;
uint8_t enable, direction, speedRaw;
unmarshal(reg, &enable, &direction, &speedRaw);
// 反向调整,返回用户期望的末端速度
if (me->rotaryArmLen > 0) {
return (uint16_t)((speedRaw * 2 * 3.14159 * me->rotaryArmLen) / 10);
}
return speedRaw;
}
// 其他函数略...
效果 :硬件寄存器布局变化只需修改marshal/unmarshal和寄存器地址。客户代码完全不变。
3.2 硬件适配器模式(Hardware Adapter)
问题:已经写好的硬件代理提供的接口与系统期望的接口不匹配(例如参数类型、单位、函数名不同)。
解决方案:增加适配器层,将期望的接口调用转换为实际代理的调用。经典的对象适配器形式。
UML结构 :Client → HardwareAdapter(实现ClientInterface) → HardwareProxy → Device。
C实现示例:两个不同的氧气传感器适配器
c
// 期望的接口:返回0-100的浓度,返回cc/min的流量
int expected_getO2Conc(void);
int expected_getO2Flow(void);
// 实际传感器A:getO2Conc直接返回0-100,getO2Flow返回cc/sec
// 适配器A
int AcmeAdapter_getO2Conc(void* proxy) {
return AcmeProxy_readConc(proxy);
}
int AcmeAdapter_getO2Flow(void* proxy) {
return AcmeProxy_readFlow(proxy) * 60; // cc/sec -> cc/min
}
// 实际传感器B:accessO2Conc返回0.000-1.000 double,accessGasFlow返回升/小时
// 适配器B
int UltimateAdapter_getO2Conc(void* proxy) {
double conc = UltimateProxy_getConc(proxy);
return (int)(conc * 100);
}
int UltimateAdapter_getO2Flow(void* proxy) {
double flow_l_per_hr = UltimateProxy_getGasFlow(proxy);
double total_cc_per_min = flow_l_per_hr * 1000.0 / 60.0;
double o2_cc_per_min = total_cc_per_min * UltimateProxy_getConc(proxy);
return (int)o2_cc_per_min;
}
效果:不改动客户代码和硬件代理,只需选择相应的适配器即可更换传感器。
3.3 中介者模式(Mediator)
问题:多个硬件设备(如机器人多关节伺服电机)需要复杂协调,如果它们直接相互通信,会导致网状依赖,难以维护。
解决方案:引入一个中介者对象,集中管理所有设备的协同逻辑。每个设备只和中介者通信。
UML结构 :Mediator持有所有Colaborator的引用,Colaborator在感兴趣的事件发生时通知Mediator。
C实现示例:机器人手臂管理器
c
// 关节类型
typedef struct RotatingJoint { int angle; } RotatingJoint;
typedef struct SlidingJoint { int position; } SlidingJoint;
// 中介者
typedef struct RobotArmManager {
RotatingJoint* joints[4];
SlidingJoint* slides[2];
GraspingManipulator* gripper;
// 预计算的动作序列
struct Action {
int joint1, joint2, joint3, joint4;
int slide1, slide2;
int gripperForce;
int gripperOpen;
} actions[100];
int numSteps;
int currentStep;
} RobotArmManager;
void RobotArmManager_computeTrajectory(RobotArmManager* me, int x, int y, int z, int t) {
// 复杂运动规划算法,生成actions数组
me->numSteps = ...;
}
int RobotArmManager_executeStep(RobotArmManager* me) {
if (me->currentStep >= me->numSteps) return 0;
struct Action* a = &me->actions[me->currentStep];
// 向各个关节发送命令
int err = 0;
err |= RotatingJoint_moveTo(me->joints[0], a->joint1);
err |= RotatingJoint_moveTo(me->joints[1], a->joint2);
// ...
if (a->gripperOpen)
err |= GraspingManipulator_open(me->gripper);
else
err |= GraspingManipulator_close(me->gripper);
me->currentStep++;
return err; // 0表示成功
}
效果:各关节之间无直接耦合,运动策略集中在中介者中,易于修改和调试。
3.4 观察者模式(Observer / Publish-Subscribe)
问题:传感器数据需要分发给多个未知的消费者。如果消费者轮询,效率低;如果服务器主动推,需要服务器知道所有消费者,破坏解耦。
解决方案 :服务器维护一个订阅者列表。消费者通过subscribe将自己的回调函数添加到列表。新数据到达时,服务器遍历列表调用回调。
UML结构 :AbstractSubject(有subscribe、unsubscribe、notify)和AbstractObserver(有update)。具体类实现它们。
C实现(书中GasSensor例子):
c
// 回调函数类型
typedef void (*UpdateFunc)(void* instance, struct GasData* data);
// 通知句柄(链表节点)
typedef struct NotificationHandle {
void* instance;
UpdateFunc updateFunc;
struct NotificationHandle* next;
} NotificationHandle;
// 主题(气体传感器)
typedef struct GasSensor {
GasData* data;
NotificationHandle* subscribers;
} GasSensor;
void GasSensor_subscribe(GasSensor* me, void* instance, UpdateFunc func) {
NotificationHandle* nh = (NotificationHandle*)malloc(sizeof(NotificationHandle));
nh->instance = instance;
nh->updateFunc = func;
nh->next = me->subscribers;
me->subscribers = nh;
}
void GasSensor_notify(GasSensor* me) {
NotificationHandle* nh = me->subscribers;
while (nh) {
nh->updateFunc(nh->instance, me->data);
nh = nh->next;
}
}
void GasSensor_newData(GasSensor* me, int flow, int o2, int n2) {
// 更新data
me->data->flow = flow;
me->data->o2 = o2;
me->data->n2 = n2;
GasSensor_notify(me);
}
观察者示例:
c
typedef struct DisplayClient {
// ...
} DisplayClient;
void DisplayClient_update(void* instance, GasData* data) {
DisplayClient* me = (DisplayClient*)instance;
printf("Flow: %d, O2: %d%%\n", data->flow, data->o2);
}
void main() {
GasSensor sensor;
DisplayClient display;
GasSensor_subscribe(&sensor, &display, DisplayClient_update);
GasSensor_newData(&sensor, 10, 21, 78); // 触发回调
}
效果:完全解耦,支持动态增删订阅者。
3.5 去抖动模式(Debouncing)
问题:机械按钮、继电器等开关在闭合时会产生多次弹跳(几个毫秒内多次通断),导致多个中断或事件。
解决方案:收到第一个中断后,等待一段时间(通常20-50ms),再读取引脚状态。如果状态稳定且与之前不同,才认为是真实事件。
UML结构 :BouncingDevice(硬件)→ Debouncer(使用DebouncingTimer)→ ApplicationClient。
C实现:
c
#define DEBOUNCE_MS 40
typedef struct ButtonDriver {
int oldState; // 上一次确认的状态
int toggleOn; // 应用状态
Button* button;
Timer* timer;
MicrowaveEmitter* emitter;
} ButtonDriver;
void ButtonDriver_eventReceive(ButtonDriver* me) {
// 等待40ms(忙等待或使用定时器)
Timer_delay(me->timer, DEBOUNCE_MS);
int newState = Button_getState(me->button);
if (newState != me->oldState) {
// 真实状态变化
me->oldState = newState;
if (newState == 0) { // 按钮释放
me->toggleOn = !me->toggleOn;
if (me->toggleOn) {
MicrowaveEmitter_start(me->emitter);
Button_setBacklight(me->button, 1);
} else {
MicrowaveEmitter_stop(me->emitter);
Button_setBacklight(me->button, 0);
}
}
}
}
注意:实际使用中应用定时器中断或RTOS的延时函数,不要用忙等待浪费CPU。
3.6 中断模式(Interrupt)
问题:高紧急事件需要立即响应。轮询不能满足实时性。
解决方案:编写中断服务程序(ISR),将它的地址安装到中断向量表。ISR做最少的工作(保存数据到队列、清除中断标志),然后返回。复杂处理在后台任务中执行。
关键点:
- ISR必须保存和恢复CPU寄存器(编译器
interrupt关键字可自动处理)。 - ISR不能调用可能阻塞的函数。
- ISR与任务共享数据时必须用原子操作或临界区。
C实现(以8051为例,使用Keil扩展):
c
// 安装中断向量
void install_button_isr(void) {
// 保存原向量
old_isr = (void (*)())ISR_TABLE[INT0_VECTOR];
// 设置新向量
ISR_TABLE[INT0_VECTOR] = button_isr;
}
// ISR
void button_isr(void) interrupt 0 {
// 读取按键值
uint8_t key = P1 & 0x0F;
// 放入队列(无锁队列,需确保原子性)
key_queue_push(key);
// 清除中断标志
IE0 = 0;
}
后台任务:
c
void key_processing_task(void) {
while (1) {
if (key_queue_not_empty()) {
uint8_t key = key_queue_pop();
// 处理按键
}
// 让出CPU
taskYIELD();
}
}
竞态条件处理:队列操作必须是原子的。可以临时禁用中断或使用无锁环形缓冲区。
3.7 轮询模式(Polling)
问题:硬件不支持中断,或者数据更新频率低、实时性要求不高。
解决方案:定期或不定期检查硬件状态。有两种变体:
- 机会轮询 :在主循环的空闲时机调用
poll()。 - 定期轮询 :用定时器中断触发
poll()。
C实现(定期轮询):
c
// 设备列表
Device* devices[MAX_DEVICES];
int numDevices;
void poll_all(void) {
for (int i=0; i<numDevices; i++) {
if (Device_hasNewData(devices[i])) {
int data = Device_read(devices[i]);
DataClient_update(devices[i]->client, data);
}
}
}
// 定时器ISR
void timer_isr(void) {
// 清除标志
// 调用轮询
poll_all();
}
优缺点:简单可靠,但事件响应延迟取决于轮询周期。周期应小于最短事件间隔。
第4章 并发与资源管理设计模式
这一章解决多任务环境下的调度、同步、资源共享和死锁问题。
4.1 循环执行模式(Cyclic Executive)
适用场景:非常小的系统(无RTOS),或高安全系统中需要确定性行为。
做法:一个无限循环,依次调用每个任务函数。任务函数必须运行到结束(不能有无限循环)。
C实现:
c
void task1(void) { /* 短时操作 */ }
void task2(void) { /* 短时操作 */ }
void task3(void) { /* 短时操作 */ }
int main(void) {
// 初始化
while (1) {
task1();
task2();
task3();
}
}
可调度性条件:设任务i的最坏执行时间为C_i,则所有任务的C_i之和加上循环开销必须 ≤ 最小周期任务的周期。
变体:时间触发循环执行(使用定时器开始每个循环,如果提前完成则等待,防止循环漂移)。
4.2 静态优先级模式(Static Priority)
适用场景:大多数RTOS应用。每个任务有固定优先级,调度器总是运行最高优先级的就绪任务。
优先级分配:最常用的是速率单调调度(RMS):周期越短,优先级越高。RMS在理论上是最优的固定优先级调度算法。
C实现(伪代码,使用RTOS API):
c
void high_priority_task(void* p) {
while (1) {
OS_WaitSemaphore(timer_sem); // 等待50ms周期
// 工作
}
}
void low_priority_task(void* p) {
while (1) {
OS_WaitSemaphore(timer_sem2); // 等待200ms周期
// 工作
}
}
int main() {
OS_Init();
OS_CreateTask(high_priority_task, 1, 1024);
OS_CreateTask(low_priority_task, 2, 1024);
OS_Start();
}
优先级倒置问题:低优先级任务持有互斥锁,高优先级任务等待锁,中优先级任务抢占低优先级,导致高优先级无限等待。解决:使用优先级继承协议(RTOS互斥锁通常支持)。
4.3 临界区模式(Critical Region)
适用场景:极短时间(几个指令)内需要独占资源,且不希望有任务切换开销。
做法:在临界区入口禁用任务调度(或禁用中断),出口处恢复。
C实现:
c
void critical_function(void) {
OS_DISABLE_SCHEDULER(); // 或 asm("DI")
// 操作共享变量
shared_var++;
OS_ENABLE_SCHEDULER(); // 或 asm("EI")
}
缺点:长时间禁用调度会破坏实时性。只能用于微秒级的临界区。
4.4 守卫调用模式(Guarded Call)
适用场景:保护共享资源(如全局变量、硬件),允许多个任务安全访问。
做法 :每个资源配一个互斥信号量。访问资源前lock,访问后unlock。若锁已被占用,调用者阻塞。
C实现:
c
typedef struct {
int data;
Mutex* mutex;
} SharedResource;
void SharedResource_set(SharedResource* res, int val) {
Mutex_lock(res->mutex);
res->data = val;
Mutex_unlock(res->mutex);
}
int SharedResource_get(SharedResource* res) {
Mutex_lock(res->mutex);
int val = res->data;
Mutex_unlock(res->mutex);
return val;
}
优先级继承 :使用支持优先级继承的互斥锁(如VxWorks的semMCreate(SEM_INVERSION_SAFE))。
4.5 队列模式(Queuing)
适用场景:任务间异步传递数据。生产者不关心消费者何时处理,消费者不关心数据何时到达。
做法 :共享一个环形缓冲区(队列),用互斥锁保护。生产者调用insert,消费者调用remove。若队列满,生产者可阻塞或丢弃;若队列空,消费者阻塞。
C实现(简化版):
c
#define QUEUE_SIZE 10
typedef struct {
int buffer[QUEUE_SIZE];
int head, tail, count;
Mutex* mutex;
Semaphore* notFull;
Semaphore* notEmpty;
} Queue;
void Queue_init(Queue* q) {
q->head = q->tail = q->count = 0;
q->mutex = Mutex_create();
q->notFull = Semaphore_create(QUEUE_SIZE); // 计数信号量,初始为容量
q->notEmpty = Semaphore_create(0);
}
void Queue_insert(Queue* q, int item) {
Semaphore_wait(q->notFull); // 等待有空位
Mutex_lock(q->mutex);
q->buffer[q->head] = item;
q->head = (q->head+1) % QUEUE_SIZE;
q->count++;
Mutex_unlock(q->mutex);
Semaphore_signal(q->notEmpty); // 通知有数据
}
int Queue_remove(Queue* q) {
Semaphore_wait(q->notEmpty); // 等待有数据
Mutex_lock(q->mutex);
int item = q->buffer[q->tail];
q->tail = (q->tail+1) % QUEUE_SIZE;
q->count--;
Mutex_unlock(q->mutex);
Semaphore_signal(q->notFull); // 通知有空位
return item;
}
变体:优先级队列(根据消息优先级排序)、溢出到磁盘的缓冲队列。
4.6 汇合模式(Rendezvous)
适用场景:多个任务必须在某点上同步,例如所有任务都完成第一阶段后才能开始第二阶段。
经典实现:线程屏障(Barrier)。
C实现:
c
typedef struct {
int expected; // 需要等待的任务数
int current; // 当前到达的任务数
Mutex* mutex;
Semaphore* barrier; // 初始为0
} Barrier;
void Barrier_init(Barrier* b, int n) {
b->expected = n;
b->current = 0;
b->mutex = Mutex_create();
b->barrier = Semaphore_create(0);
}
void Barrier_wait(Barrier* b) {
Mutex_lock(b->mutex);
b->current++;
if (b->current == b->expected) {
// 最后一个到达,释放所有等待的任务
for (int i=0; i<b->expected; i++) {
Semaphore_signal(b->barrier);
}
b->current = 0; // 重置
}
Mutex_unlock(b->mutex);
Semaphore_wait(b->barrier);
}
4.7 同时锁定模式(Simultaneous Locking)
问题:死锁产生的四个必要条件之一是"持有并等待"。要避免死锁,可以要求任务一次锁住所有需要的资源,要么全锁,要么一个都不锁。
做法 :使用trylock尝试获取所有锁,如果任何一个失败,则释放已经获取的锁,稍后重试。
C实现:
c
int lock_all(Mutex* locks[], int n) {
for (int i=0; i<n; i++) {
if (!Mutex_trylock(locks[i])) {
// 失败,释放已经锁住的
for (int j=0; j<i; j++) {
Mutex_unlock(locks[j]);
}
return 0; // 失败
}
}
return 1; // 成功
}
void do_work(Mutex* r1, Mutex* r2) {
Mutex* locks[] = {r1, r2};
while (!lock_all(locks, 2)) {
// 可选:短暂延迟后重试
delay(10);
}
// 操作资源
// ...
Mutex_unlock(r1);
Mutex_unlock(r2);
}
4.8 排序锁定模式(Ordered Locking)
原理:破坏"循环等待"条件。为每个资源分配唯一的ID,强制所有任务按ID升序锁定资源。
C实现:
c
typedef struct {
int id;
Mutex* mutex;
} OrderedResource;
// 全局资源列表管理器
static int current_max_locked = -1;
static Mutex* global_lock = NULL;
int OrderedResource_lock(OrderedResource* res) {
Mutex_lock(global_lock);
if (res->id <= current_max_locked) {
// 违反顺序
Mutex_unlock(global_lock);
return -1;
}
Mutex_lock(res->mutex);
current_max_locked = res->id;
Mutex_unlock(global_lock);
return 0;
}
void OrderedResource_unlock(OrderedResource* res) {
Mutex_unlock(res->mutex);
// 注意:释放后需要重新计算当前最大锁定ID,比较复杂
// 实际实现中通常用一个栈记录锁定顺序
}
更常见的实现是:设计时规定所有资源固定的锁定顺序,开发人员遵守该顺序。
第5章 状态机实现设计模式
这一章给出5种实现有限状态机的模式,从简单到复杂。
5.1 单事件接收器模式(Single Event Receptor)
特点 :一个事件分发函数,接收包含事件类型和数据的结构体,内部根据当前状态做switch-case。
适用:小型状态机,事件种类少,状态少。
C实现(解析数字的状态机,如识别"12.34"):
c
// 事件类型枚举
typedef enum { EV_DIGIT, EV_DOT, EV_WS, EV_EOS } EventType;
typedef struct {
EventType type;
char digit; // 仅当type == EV_DIGIT有效
} Event;
// 状态枚举
typedef enum { ST_NONE, ST_NO_NUMBER, ST_GOT_NUMBER, ST_PROC_WHOLE, ST_PROC_FRAC } State;
static State state = ST_NO_NUMBER;
static double result = 0.0;
static double tenths = 10.0;
void dispatch(Event e) {
switch (state) {
case ST_NO_NUMBER:
if (e.type == EV_DIGIT) {
// 转换到 GOT_NUMBER 并进入 PROC_WHOLE
state = ST_PROC_WHOLE;
result = e.digit - '0';
tenths = 10.0;
} else if (e.type == EV_DOT) {
// 转换到 GOT_NUMBER 并进入 PROC_FRAC
state = ST_PROC_FRAC;
result = 0.0;
tenths = 10.0;
}
break;
case ST_PROC_WHOLE:
if (e.type == EV_DIGIT) {
result = result * 10 + (e.digit - '0');
} else if (e.type == EV_DOT) {
state = ST_PROC_FRAC;
} else if (e.type == EV_WS || e.type == EV_EOS) {
printf("Result: %g\n", result);
state = ST_NO_NUMBER;
}
break;
case ST_PROC_FRAC:
if (e.type == EV_DIGIT) {
result += (e.digit - '0') / tenths;
tenths *= 10.0;
} else if (e.type == EV_WS || e.type == EV_EOS) {
printf("Result: %g\n", result);
state = ST_NO_NUMBER;
}
break;
}
}
缺点 :dispatch函数随着状态和事件增多变得庞大,且所有状态逻辑混在一起。
5.2 多事件接收器模式(Multiple Event Receptor)
特点 :为每个事件写一个独立的处理函数(如onDigit、onDot等)。函数内部根据当前状态执行动作。
适用:同步状态机,事件种类固定,每个事件的处理逻辑相对独立。
C实现:
c
static State state = ST_NO_NUMBER;
static double result, tenths;
void onDigit(char c) {
switch (state) {
case ST_NO_NUMBER:
// 转换
state = ST_PROC_WHOLE;
result = c - '0';
tenths = 10.0;
break;
case ST_PROC_WHOLE:
result = result * 10 + (c - '0');
break;
case ST_PROC_FRAC:
result += (c - '0') / tenths;
tenths *= 10.0;
break;
}
}
void onDot(void) {
switch (state) {
case ST_PROC_WHOLE:
state = ST_PROC_FRAC;
break;
case ST_NO_NUMBER:
state = ST_PROC_FRAC;
result = 0.0;
tenths = 10.0;
break;
default:
break;
}
}
void onTerminator(void) {
if (state == ST_PROC_WHOLE || state == ST_PROC_FRAC) {
printf("Result: %g\n", result);
state = ST_NO_NUMBER;
}
}
优点 :每个事件函数短小,易于理解。
缺点 :仍然有switch(state),状态增多时每个函数都会变大。
5.3 状态表模式(State Table)
特点:用二维数组(状态×事件)存储转换信息:监护条件、动作函数指针、新状态。调度时直接查表执行。
适用:大的、扁平的状态机(没有嵌套),追求运行时性能和可扩展性。
C实现:
c
// 动作函数类型
typedef void (*Action)(void* ctx);
// 监护条件类型,返回1表示允许转换
typedef int (*Guard)(void* ctx);
typedef struct {
Guard guard;
Action exitAct;
Action transAct;
Action entryAct;
int nextState;
} Transition;
// 假设有3个状态,4个事件
#define STATE_COUNT 3
#define EVENT_COUNT 4
Transition table[STATE_COUNT][EVENT_COUNT];
// 初始化表(在程序启动时填充)
void init_table(void) {
// 例如:状态0收到事件0时,监护条件为NULL(总是真),无exit动作,transAct为func1,entryAct为func2,下一状态为1
table[0][0].guard = NULL;
table[0][0].exitAct = NULL;
table[0][0].transAct = func1;
table[0][0].entryAct = func2;
table[0][0].nextState = 1;
// ... 填充所有单元格
}
// 调度函数
int currentState = 0;
void dispatch(int event, void* ctx) {
Transition* t = &table[currentState][event];
if (t->guard && !t->guard(ctx)) return; // 监护条件不满足
if (t->exitAct) t->exitAct(ctx);
if (t->transAct) t->transAct(ctx);
currentState = t->nextState;
if (t->entryAct) t->entryAct(ctx);
}
优点 :调度时间为O(1),扩展状态或事件只需扩展表格,不改动调度逻辑。
缺点:初始化表格代码冗长;表可能稀疏浪费内存;不支持嵌套状态。
5.4 状态模式(State Pattern,GoF)
特点:将每个状态封装成一个对象(结构体),含有该状态下各事件的处理函数指针。Context持有当前状态对象,所有事件委托给当前状态处理。
适用 :状态逻辑复杂,需要动态改变行为,且希望避免大的switch-case。
C实现:
c
// 前向声明
struct Context;
// 状态虚表
typedef struct State {
void (*onDigit)(struct Context* ctx, char c);
void (*onDot)(struct Context* ctx);
void (*onTerm)(struct Context* ctx);
} State;
// 具体状态对象(单例)
extern State stateNoNumber;
extern State stateProcWhole;
extern State stateProcFrac;
// Context
typedef struct Context {
const State* current;
double result;
double tenths;
} Context;
void Context_init(Context* ctx) {
ctx->current = &stateNoNumber;
ctx->result = 0.0;
ctx->tenths = 10.0;
}
void Context_onDigit(Context* ctx, char c) {
ctx->current->onDigit(ctx, c);
}
// 实现具体状态的行为
static void noNumber_onDigit(Context* ctx, char c) {
ctx->current = &stateProcWhole;
ctx->result = c - '0';
ctx->tenths = 10.0;
}
static void noNumber_onDot(Context* ctx, char c) {
ctx->current = &stateProcFrac;
ctx->result = 0.0;
ctx->tenths = 10.0;
}
// 其它状态...
State stateNoNumber = {
.onDigit = noNumber_onDigit,
.onDot = noNumber_onDot,
.onTerm = noNumber_onTerm
};
// 定义其他状态...
优点 :状态行为完全隔离,新增状态无需修改已有状态;状态可共享(如多个context共用同一状态对象)。
缺点:C实现较繁琐,需要很多函数指针赋值。
5.5 分解与状态模式(Decomposed And-State)
特点:处理UML中的"与状态"(正交区域)。一个复合状态包含多个并行的子状态机,每个子状态机独立运行。事件广播到所有正交区域。
适用:系统有多个独立变化维度(如交通灯的颜色和闪烁频率)。
C实现:
c
// 正交区域1:颜色状态机
typedef enum { COLOR_RED, COLOR_YELLOW, COLOR_GREEN } ColorState;
ColorState colorState = COLOR_RED;
void color_handleEvent(Event e) {
switch (colorState) {
case COLOR_RED: if (e.type == EV_TIMER) colorState = COLOR_GREEN; break;
case COLOR_GREEN: if (e.type == EV_TIMER) colorState = COLOR_YELLOW; break;
case COLOR_YELLOW: if (e.type == EV_TIMER) colorState = COLOR_RED; break;
}
}
// 正交区域2:闪烁速率状态机
typedef enum { RATE_STEADY, RATE_SLOW, RATE_FAST } RateState;
RateState rateState = RATE_STEADY;
void rate_handleEvent(Event e) {
if (e.type == EV_BUTTON_PRESS) {
if (rateState == RATE_STEADY) rateState = RATE_SLOW;
else if (rateState == RATE_SLOW) rateState = RATE_FAST;
else rateState = RATE_STEADY;
}
}
// 顶层Context
void dispatchToAll(Event e) {
color_handleEvent(e);
rate_handleEvent(e);
}
注意:事件可能同时在两个区域触发转换,执行顺序不确定,可能导致竞争条件。书中建议用"事件传播"来强制顺序:一个区域处理事件后生成新事件给另一个区域。
第6章 安全性与可靠性设计模式
6.1 二进制反码模式(Ones' Complement)
原理:存储数据时,同时存储其按位取反的副本。读取时比较,可检测单位翻转或多位翻转。
C实现:
c
typedef struct {
int16_t value;
int16_t inverted; // ~value
} ProtectedInt16;
void ProtectedInt16_set(ProtectedInt16* p, int16_t v) {
p->value = v;
p->inverted = ~v;
}
int16_t ProtectedInt16_get(ProtectedInt16* p) {
if (p->value == (int16_t)~p->inverted) {
return p->value;
} else {
errorHandler(MEMORY_CORRUPTION);
return 0; // 默认安全值
}
}
6.2 CRC模式
原理:为数据块计算CRC校验码(16位或32位),存储或发送时附加。接收或读取时重新计算,比较是否一致。
C实现(查表法CRC-16):
c
static const uint16_t crc16_table[256] = { ... }; // 预计算表
uint16_t crc16(uint8_t* data, size_t len, uint16_t seed) {
uint16_t crc = seed;
for (size_t i=0; i<len; i++) {
crc = (crc << 8) ^ crc16_table[((crc >> 8) ^ data[i]) & 0xFF];
}
return crc;
}
typedef struct {
uint8_t payload[256];
uint16_t crc;
} ProtectedBlock;
int ProtectedBlock_verify(ProtectedBlock* blk) {
uint16_t calc = crc16(blk->payload, sizeof(blk->payload), 0xFFFF);
return (calc == blk->crc);
}
6.3 智能数据模式(Smart Data)
原理:将数据和其合法范围、约束封装成一个类,设置值时自动检查,无效则触发错误。
C实现:
c
typedef struct {
int value;
int min;
int max;
ErrorHandler* err;
} SmartInt;
int SmartInt_set(SmartInt* si, int v) {
if (v < si->min || v > si->max) {
ErrorHandler_raise(si->err, OUT_OF_RANGE);
return -1;
}
si->value = v;
return 0;
}
6.4 通道模式(Channel)
原理:将传感器数据处理流程建模为一系列变换步骤:传感器驱动 → 变换1 → 变换2 → ... → 执行器驱动。每个步骤是一个独立模块,可单独测试和替换。
C实现:
c
// 变换函数类型
typedef int (*Transform)(int input, void* ctx);
typedef struct Channel {
SensorDriver* sensor;
Transform steps[5];
void* stepCtx[5];
int numSteps;
ActuatorDriver* actuator;
} Channel;
void Channel_run(Channel* ch) {
int data = SensorDriver_read(ch->sensor);
for (int i=0; i<ch->numSteps; i++) {
data = ch->steps[i](data, ch->stepCtx[i]);
if (data == ERROR) break;
}
if (data != ERROR) {
ActuatorDriver_write(ch->actuator, data);
}
}
6.5 保护单通道模式(Protected Single Channel)
原理:在通道的某些变换点增加检查(范围、合理性、反向计算等)。发现故障时系统进入故障安全状态。
C实现(在变换中插入检查):
c
int transform_with_check(int input, void* ctx) {
int output = do_transform(input);
if (output < MIN_ACCEPTABLE || output > MAX_ACCEPTABLE) {
enter_failsafe();
return ERROR;
}
return output;
}
6.6 双通道模式(Dual Channel)
原理:使用两个独立通道(可以是同构或异构),通过比较或表决提高可靠性。
几种变体:
- 同构冗余:两个相同通道,输出比较,不一致则关闭。
- 异构冗余:不同设计(避免共因故障),输出比较。
- 三模冗余(TMR):三个通道,投票决定。
- 完整性检查:一个主通道,一个轻量级检查通道,不一致则切换。
- 监视器-执行器:执行通道工作,监视通道用独立传感器检测物理结果。
C实现(双通道比较):
c
typedef struct DualChannel {
Channel chA;
Channel chB;
int tolerance;
} DualChannel;
int DualChannel_run(DualChannel* dc) {
int outA = Channel_run_get(&dc->chA);
int outB = Channel_run_get(&dc->chB);
if (abs(outA - outB) <= dc->tolerance) {
return (outA + outB) / 2; // 平均
} else {
enter_failsafe();
return 0;
}
}
结束语
以上是《C嵌入式编程设计模式》全书的详尽内容。从C模拟面向对象的基础,到7种硬件访问模式、8种并发与资源管理模式、5种状态机实现模式、6种安全可靠性模式,每个模式都给出了可直接使用的C代码。掌握这些模式后,你将能够编写出高内聚、低耦合、实时可靠、易于维护的嵌入式系统。
最后强调:设计模式是优化工具,不是第一原则。请先写出功能正确的代码,再根据设计标准(性能、内存、可维护性等)权衡选择适当的模式。
本文完全基于Bruce Powel Douglass《Design Patterns for Embedded Systems in C》编写,所有代码示例遵循原书风格。