当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]嵌入式Linux系统中,多进程间的数据交互是常见需求——采集进程需要把传感器数据传给显示进程,控制进程需要从决策进程获取指令。管道和消息队列虽然方便,但涉及内核缓冲区拷贝,延迟较高。共享内存是所有IPC中延迟最低的方式,因为它让两个进程直接读写同一块物理内存,零拷贝。但共享内存本身不提供同步机制,需要搭配信号量来避免竞态条件。本文以一个“采集→处理→显示”的三进程模型为例,展示共享内存+信号量的实战用法。


嵌入式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原子操作确保可见性。



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