Linux嵌入式环境C语言多进程通信:共享内存+信号量的低延迟数据交互
嵌入式Linux系统中,多进程间的数据交互是常见需求——采集进程需要把传感器数据传给显示进程,控制进程需要从决策进程获取指令。管道和消息队列虽然方便,但涉及内核缓冲区拷贝,延迟较高。共享内存是所有IPC中延迟最低的方式,因为它让两个进程直接读写同一块物理内存,零拷贝。但共享内存本身不提供同步机制,需要搭配信号量来避免竞态条件。本文以一个“采集→处理→显示”的三进程模型为例,展示共享内存+信号量的实战用法。
一、共享内存的创建与映射
使用POSIX共享内存(shm_open+mmap)或System V共享内存(shmget+shmat)。POSIX接口更现代,推荐使用。
#include <sys/mman.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#define SHM_NAME "/sensor_shm"
#define DATA_SIZE 4096
typedef struct {
float temperature;
float pressure;
uint32_t timestamp;
uint8_t valid;
} sensor_data_t;
// 创建/打开共享内存
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, DATA_SIZE);
sensor_data_t *shared_data = (sensor_data_t *)mmap(NULL, DATA_SIZE,
PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
close(shm_fd);
每个进程调用相同的shm_open+mmap,即可获得指向同一块物理内存的指针。
二、信号量的同步
使用POSIX有名信号量(sem_open)实现互斥和同步。需要两个信号量:一个用于互斥访问共享内存(mutex),一个用于通知数据就绪(ready)。
// 创建信号量
sem_t *mutex = sem_open("/sem_mutex", O_CREAT, 0666, 1); // 初始值1
sem_t *ready = sem_open("/sem_ready", O_CREAT, 0666, 0); // 初始值0
// 写入进程(采集)
void collector_task(void) {
while (1) {
sensor_data_t data = read_sensor();
sem_wait(mutex); // 请求互斥锁
memcpy(shared_data, &data, sizeof(data));
shared_data->valid = 1;
sem_post(mutex); // 释放互斥锁
sem_post(ready); // 通知读取进程
sleep(1);
}
}
// 读取进程(处理/显示)
void processor_task(void) {
sensor_data_t local;
while (1) {
sem_wait(ready); // 等待数据就绪
sem_wait(mutex);
memcpy(&local, shared_data, sizeof(local));
sem_post(mutex);
process_and_display(&local);
}
}
sem_wait阻塞直到信号量值大于0,然后减1;sem_post将信号量加1。mutex保证同一时刻只有一个进程操作共享内存,ready实现生产-消费同步。
三、低延迟的关键
零拷贝:数据直接从共享内存读取,无需经过内核缓冲区。
用户态同步:信号量操作在用户态完成(基于futex),不陷入内核(除非竞争)。
缓存友好:将共享数据结构设计为cache line对齐,避免伪共享。
// 对齐到64字节(常见cache line大小)
typedef struct __attribute__((aligned(64))) {
float temperature;
float pressure;
uint32_t timestamp;
uint8_t valid;
uint8_t pad[3]; // 填充到64字节
} sensor_data_t;
四、清理与异常处理
进程退出时必须释放资源,否则共享内存和信号量会残留。
void cleanup(void) {
munmap(shared_data, DATA_SIZE);
shm_unlink(SHM_NAME); // 删除共享内存对象
sem_close(mutex);
sem_close(ready);
sem_unlink("/sem_mutex");
sem_unlink("/sem_ready");
}
通常由最后一个退出的进程负责shm_unlink和sem_unlink。
五、三个翻车高发点
忘记初始化共享内存:mmap后共享内存内容是随机的,写入进程必须先初始化有效字段(如valid=0),读取进程应检查valid标志。
信号量死锁:如果某个进程在持有mutex时崩溃,其他进程会永久阻塞。解决方案是使用sem_timedwait设置超时,超时后重建信号量。
多核缓存一致性问题:ARM多核处理器中,一个核写入共享内存,另一个核可能读到旧值。使用__sync_synchronize()或C11原子操作确保可见性。





