嵌入式学习 day35:进程间通信-消息队列、共享空间及有名信号量

一、消息队列

1.1 什么是消息队列

消息队列是有格式的消息传递机制,数据按消息块发送和接收,每个消息可以指定类型。

1.2 IPC对象的查看与删除

bash

复制代码
ipcs           # 查看所有IPC对象
ipcs -q        # 查看消息队列
ipcs -m        # 查看共享内存
ipcs -s        # 查看信号量

ipcrm -q msqid # 删除消息队列
ipcrm -m shmid # 删除共享内存

二、消息队列函数接口

步骤 函数 作用
1 ftok() 生成IPC对象键值
2 msgget() 创建/获取消息队列
3 msgsnd() 发送消息
4 msgrcv() 接收消息
5 msgctl() 控制/删除消息队列

2.1 ftok : 生成键值

c

复制代码
key_t ftok(const char *pathname, int proj_id);

c

复制代码
key_t key = ftok(".", 'a');
if (key == -1) {
    perror("ftok");
    return -1;
}

2.2 msgget : 创建消息队列

c

复制代码
int msgget(key_t key, int msgflg);

c

复制代码
int msqid = msgget(key, IPC_CREAT | 0664);
if (msqid == -1) {
    perror("msgget");
    return -1;
}

2.3 msgsnd : 发送消息

c

复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

消息结构体必须按照固定格式:

c

复制代码
struct msgbuf {
    long mtype;        // 消息类型(必须 > 0)
    char mtext[128];   // 消息内容
};

c

复制代码
struct msgbuf msg;
msg.mtype = 1;
strcpy(msg.mtext, "hello");
msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0);

2.4 msgrcv : 接收消息

c

复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

c

复制代码
struct msgbuf msg;
ssize_t ret = msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0);
if (ret > 0) {
    printf("收到:%s\n", msg.mtext);
}

msgtyp 取值

msgtyp 效果
> 0 接收指定类型的消息
== 0 接收队列中第一条消息(FIFO)
< 0 接收类型小于等于 abs(msgtyp) 的最小类型消息

2.5 msgctl : 控制消息队列

c

复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

c

复制代码
msgctl(msqid, IPC_RMID, NULL);   // 删除消息队列

三、共享内存

3.1 什么是共享内存

共享内存是进程间通信最快的方式,多个进程直接映射同一块物理内存,数据不需要在内核和用户空间之间复制。

3.2 共享内存操作步骤

步骤 函数 作用
1 ftok() 生成键值
2 shmget() 创建共享内存
3 shmat() 映射到进程地址空间
4 读写 使用共享内存
5 shmdt() 解除映射
6 shmctl() 删除共享内存

3.3 shmget : 创建共享内存

c

复制代码
int shmget(key_t key, size_t size, int shmflg);

c

复制代码
int shmid = shmget(key, 1024, IPC_CREAT | 0664);

3.4 shmat : 映射

c

复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);

c

复制代码
char *shmaddr = (char *)shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1) {
    perror("shmat");
    return -1;
}

3.5 shmdt : 解除映射

c

复制代码
int shmdt(const void *shmaddr);

c

复制代码
shmdt(shmaddr);

3.6 shmctl : 控制共享内存

c

复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

c

复制代码
shmctl(shmid, IPC_RMID, NULL);   // 删除共享内存

四、信号量

4.1 什么是信号量

信号量本质上是一个计数器,用于控制同时访问共享资源的进程数量。在进程间通信中,信号量通常配合共享内存使用,用来解决多个进程同时读写共享数据的同步问题。

4.2 信号量的基本概念

概念 含义
信号量数组 可以一次性创建多个信号量,用下标区分
P操作(申请) sem_op = -1,资源数减1,不够则阻塞
V操作(释放) sem_op = +1,资源数加1,唤醒等待进程
SEM_UNDO 进程异常退出时自动归还信号量,防止死锁

4.3 信号量函数接口

步骤 函数 作用
1 ftok() 生成IPC对象键值
2 semget() 创建/获取信号量数组
3 semctl() 初始化/控制信号量
4 semop() 申请(P)或释放(V)信号量
5 semctl(IPC_RMID) 删除信号量

4.4 semget : 创建信号量

c

复制代码
int semget(key_t key, int nsems, int semflg);
  • key:IPC对象键值(ftok生成)

  • nsems:信号量的个数(创建多个信号量)

  • semflgIPC_CREAT | 0664

c

复制代码
int semid = semget(key, 2, IPC_CREAT | 0664);
if (-1 == semid) {
    perror("fail to semget");
    return -1;
}

注意:nsems=2 表示创建2个信号量,用下标 01 来区分不同的信号量。

4.5 semctl : 初始化/控制信号量

c

复制代码
int semctl(int semid, int semnum, int cmd, ...);
  • semid:信号量ID

  • semnum:要操作信号量的下标

  • cmdSETVAL 设置信号量的值,IPC_RMID 删除

第四个参数需要用到 union semun 联合体:

c

复制代码
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
};

c

复制代码
union semun myun;
myun.val = 0;   // 初始化读信号量为0
semctl(semid, 0, SETVAL, myun);   // 下标0:读信号量

myun.val = 1;   // 初始化写信号量为1
semctl(semid, 1, SETVAL, myun);   // 下标1:写信号量

4.6 semop : 申请和释放信号量

c

复制代码
int semop(int semid, struct sembuf *sops, size_t nsops);

c

复制代码
struct sembuf {
    unsigned short sem_num;   // 信号量下标
    short          sem_op;    // -1=申请(P),+1=释放(V)
    short          sem_flg;   // SEM_UNDO 等
};

申请信号量(P操作)

c

复制代码
struct sembuf mybuf;
mybuf.sem_num = 1;       // 下标1:写信号量
mybuf.sem_op = -1;       // 申请(资源数减1)
mybuf.sem_flg = SEM_UNDO; // 异常退出自动归还
semop(semid, &mybuf, 1);

释放信号量(V操作)

c

复制代码
mybuf.sem_num = 0;       // 下标0:读信号量
mybuf.sem_op = +1;       // 释放(资源数加1)
mybuf.sem_flg = SEM_UNDO;
semop(semid, &mybuf, 1);

sem_op 为负数就是"申请"(资源-1),为正数就是"释放"(资源+1)。配合 SEM_UNDO 是标准写法,能防止进程崩溃导致信号量卡死。信号量初始值设为1时,它就是一个互斥锁;设为N时,就是控制并发数量的计数器。

4.7 删除信号量

c

复制代码
semctl(semid, 0, IPC_RMID);

五、完整示例

下面用代码演示信号量的典型用法:

5.1 初始化信号量

c

复制代码
key_t key = ftok("/tmp", 'a');
int semid = semget(key, 2, IPC_CREAT | 0664);   // 创建2个信号量

union semun myun;
myun.val = 0;   // 下标0:读信号量,初始为0
semctl(semid, 0, SETVAL, myun);

myun.val = 1;   // 下标1:写信号量,初始为1
semctl(semid, 1, SETVAL, myun);

这里用2个信号量实现读写同步

信号量 初始值 含义
下标0(读) 0 没有数据可读,读进程先阻塞
下标1(写) 1 可以写入数据

5.2 写进程逻辑

c

复制代码
struct sembuf mybuf;

// 申请写信号量
mybuf.sem_num = 1;
mybuf.sem_op = -1;
mybuf.sem_flg = SEM_UNDO;
semop(semid, &mybuf, 1);
printf("申请到写信号量,可以写数据\n");

// 写入共享内存...

// 释放读信号量(通知读进程)
mybuf.sem_num = 0;
mybuf.sem_op = +1;
mybuf.sem_flg = SEM_UNDO;
semop(semid, &mybuf, 1);
printf("释放读信号量,通知读进程\n");

5.3 读进程逻辑

c

复制代码
struct sembuf mybuf;

// 申请读信号量
mybuf.sem_num = 0;
mybuf.sem_op = -1;
mybuf.sem_flg = SEM_UNDO;
semop(semid, &mybuf, 1);
printf("申请到读信号量,可以读数据\n");

// 读取共享内存...

// 释放写信号量(通知写进程)
mybuf.sem_num = 1;
mybuf.sem_op = +1;
mybuf.sem_flg = SEM_UNDO;
semop(semid, &mybuf, 1);
printf("释放写信号量,通知写进程\n");

六、今日总结

今天学了消息队列、共享内存、信号量,核心要点如下:

消息队列

  1. ftok() 生成键值,msgget() 创建队列

  2. msgsnd() 发送消息,msgrcv() 按类型接收消息

  3. 消息结构体第一个成员必须是 long mtype

  4. 支持按类型接收,比管道更灵活

共享内存

  1. 最快的进程间通信方式,数据不需要内核中转(零拷贝)

  2. shmget() 创建,shmat() 映射到进程空间

  3. 使用完 shmdt() 解除映射,shmctl(IPC_RMID) 删除

  4. 多进程同时访问需要配合信号量同步

信号量

  1. 计数器,用于控制共享资源的并发访问

  2. semget() 创建信号量数组,semctl() 初始化(SETVAL)

  3. semop() 执行P操作(sem_op=-1 申请)和V操作(sem_op=+1 释放)

  4. 配合 SEM_UNDO 防止进程崩溃导致信号量卡死

今日感悟

进程间通信的方式各有适用场景。共享内存最快但需要信号量配合同步,消息队列虽然慢一点但有格式、按类型接收更方便。实际开发中根据需求选择,追求速度用共享内存+信号量,追求灵活性用消息队列。

相关推荐
从零开始的嵌入式之旅29 分钟前
day33
linux·c语言·经验分享·笔记·嵌入式硬件
qetfw1 小时前
Debian lftp FTP 客户端使用:登录、目录切换与文件传输
linux·运维·debian
神威难绷泪1 小时前
Linux应用软件编程:线程分离属性 互斥机制 同步机制 死锁
linux·开发语言·算法·线程
2401_862880821 小时前
Linux应用层开发 --- 管道
linux·运维·c语言
NJCloud1 小时前
OpenStack(四)——进阶篇之私有网络配置与块存储服务(Cinder)部署
linux·网络·云计算·openstack
raindayinrain1 小时前
深入理解Linux内核--ext文件系统,open/write/read/close执行流程,性能优化
linux·性能优化·文件系统·文件系统调用
一条破秋裤1 小时前
STM32 学习笔记:STM32 简介与学习路线
笔记·stm32·学习
jing.wang_20251 小时前
制作一个带用户操作界面的精简Linux操作系统
linux·服务器
念何架构之路2 小时前
Gin辅助模块与学习路径
学习·gin