在Linux系统中,进程间通信(IPC,Inter-Process Communication)是多任务编程中不可或缺的一环。上一篇我们介绍了管道和信号,本篇将聚焦于三种更加高级和灵活的IPC方式:消息队列、共享内存和信号灯(也称信号量集)。它们是System V IPC的典型代表,共享相同的设计理念——通过内核维护的IPC对象进行通信。
1. IPC对象与键值
消息队列、共享内存和信号灯都属于IPC对象(即内核中用于通信的内存文件)。每个IPC对象由一个唯一的键值(key)标识,通过ftok函数生成。键值类似于文件名,进程通过它来获取或创建同一个IPC对象。
ftok 函数
c
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
-
功能:根据一个存在的文件路径
pathname和一个整数proj_id生成一个键值。 -
参数:
-
pathname:一个存在的可访问文件的路径。 -
proj_id:项目ID,通常取一个ASCII字符(如'A')或1~255之间的整数。
-
-
返回值:成功返回键值(key_t类型),失败返回-1。
查看和删除IPC对象
-
ipcs:查看当前系统中的IPC对象(消息队列、共享内存、信号灯)。bash
ipcs -q # 仅查看消息队列 ipcs -m # 仅查看共享内存 ipcs -s # 仅查看信号灯
-
ipcrm:删除IPC对象。bash
ipcrm -Q key # 根据键值删除消息队列 ipcrm -q msqid # 根据ID删除消息队列 ipcrm -M key # 根据键值删除共享内存 ipcrm -m shmid # 根据ID删除共享内存 ipcrm -S key # 根据键值删除信号灯 ipcrm -s semid # 根据ID删除信号灯
2. 消息队列
消息队列是一个存放在内核中的链表,每个消息具有独立的类型,进程可以按照类型读取消息,从而实现有序通信。
2.1 创建/获取消息队列 —— msgget
c
#include <sys/msg.h> int msgget(key_t key, int msgflg);
-
功能:创建或获取一个消息队列的ID。
-
参数:
-
key:IPC键值,可由ftok生成,或使用IPC_PRIVATE创建私有队列。 -
msgflg:标志位,常用组合:-
IPC_CREAT:若队列不存在则创建。 -
IPC_EXCL:与IPC_CREAT同时使用,若队列已存在则报错。 -
权限位,如
0664。
-
-
-
返回值:成功返回消息队列ID(非负整数),失败返回-1。
2.2 发送消息 —— msgsnd
c
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
-
功能:向消息队列发送一条消息。
-
参数:
-
msqid:消息队列ID。 -
msgp:指向消息缓冲区的指针,缓冲区必须包含一个长整型成员(消息类型)和紧随其后的消息数据。 -
msgsz:消息数据的大小(不包括消息类型)。 -
msgflg:通常为0,或IPC_NOWAIT(非阻塞)。
-
-
返回值:成功返回0,失败返回-1。
消息结构示例:
c
struct msgbuf {
long mtype; /* 消息类型,必须 > 0 */
char mtext[1]; /* 消息数据,可定义为任意长度 */
};
2.3 接收消息 —— msgrcv
c
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
-
功能:从消息队列接收消息。
-
参数:
-
msqid:消息队列ID。 -
msgp:存放消息的缓冲区。 -
msgsz:缓冲区大小(用于接收消息数据)。 -
msgtyp:指定接收的消息类型:-
0:接收队列中的第一条消息。 -
>0:接收类型等于msgtyp的第一条消息。 -
<0:接收类型小于或等于msgtyp绝对值的最小类型的第一条消息。
-
-
msgflg:通常为0,或IPC_NOWAIT、MSG_NOERROR等。
-
-
返回值:成功返回实际接收到的数据字节数,失败返回-1。
2.4 控制消息队列 —— msgctl
c
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
功能:对消息队列执行控制操作。
-
参数:
-
msqid:消息队列ID。 -
cmd:命令,常用:-
IPC_RMID:删除消息队列。 -
IPC_STAT:获取队列状态,存入buf。 -
IPC_SET:设置队列属性。
-
-
buf:与命令相关的结构体指针,删除时传NULL。
-
-
返回值:成功返回0,失败返回-1。
2.5 示例:两个程序通过消息队列通信
send.c(发送端)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_SIZE 128
struct msgbuf {
long mtype;
char mtext[MSG_SIZE];
};
int main() {
key_t key = ftok(".", 'a');
int msqid = msgget(key, IPC_CREAT | 0664);
if (msqid == -1) {
perror("msgget");
exit(1);
}
struct msgbuf msg;
msg.mtype = 1; // 使用类型1
while (1) {
printf("Send: ");
fgets(msg.mtext, MSG_SIZE, stdin);
if (strncmp(msg.mtext, "quit", 4) == 0) break;
if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
perror("msgsnd");
break;
}
}
msgctl(msqid, IPC_RMID, NULL); // 最后删除队列(仅由一方删除)
return 0;
}
recv.c(接收端)
c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_SIZE 128
struct msgbuf {
long mtype;
char mtext[MSG_SIZE];
};
int main() {
key_t key = ftok(".", 'a');
int msqid = msgget(key, IPC_CREAT | 0664);
if (msqid == -1) {
perror("msgget");
exit(1);
}
struct msgbuf msg;
while (1) {
if (msgrcv(msqid, &msg, MSG_SIZE, 1, 0) == -1) {
perror("msgrcv");
break;
}
printf("Received: %s", msg.mtext);
if (strncmp(msg.mtext, "quit", 4) == 0) break;
}
return 0;
}
3. 共享内存
共享内存是最高效的IPC方式,它允许多个进程直接共享同一块物理内存,避免了数据拷贝。但由于多个进程同时访问,通常需要配合信号灯等同步机制。
3.1 创建/获取共享内存 —— shmget
c
#include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
-
功能:创建或获取一个共享内存段。
-
参数:
-
key:键值。 -
size:共享内存大小(字节),创建时需指定,获取时可填0。 -
shmflg:标志位,与msgget类似,如IPC_CREAT | 0664。
-
-
返回值:成功返回共享内存ID,失败返回-1。
3.2 映射共享内存 —— shmat
c
void *shmat(int shmid, const void *shmaddr, int shmflg);
-
功能:将共享内存段附加到进程的地址空间。
-
参数:
-
shmid:共享内存ID。 -
shmaddr:指定映射地址,通常传NULL让系统自动选择。 -
shmflg:标志,如SHM_RDONLY(只读),默认为0(读写)。
-
-
返回值:成功返回映射后的虚拟地址,失败返回
(void *)-1。
3.3 解除映射 —— shmdt
c
int shmdt(const void *shmaddr);
-
功能:将共享内存从当前进程分离。
-
参数:
shmaddr为shmat返回的地址。 -
返回值:成功返回0,失败返回-1。
3.4 控制共享内存 —— shmctl
c
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
-
功能:对共享内存执行控制操作。
-
参数:
-
cmd:常用IPC_RMID(删除共享内存)。 -
buf:通常传NULL。
-
-
返回值:成功返回0,失败返回-1。
3.5 示例:两个进程通过共享内存交换数据
write.c(写入数据)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
int main() {
key_t key = ftok(".", 'b');
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0664);
if (shmid == -1) {
perror("shmget");
exit(1);
}
char *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1) {
perror("shmat");
exit(1);
}
while (1) {
printf("Input: ");
fgets(addr, SHM_SIZE, stdin);
if (strncmp(addr, "quit", 4) == 0) break;
}
shmdt(addr);
// 通常由最后使用的进程删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
read.c(读取数据)
c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
int main() {
key_t key = ftok(".", 'b');
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0664);
if (shmid == -1) {
perror("shmget");
exit(1);
}
char *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1) {
perror("shmat");
exit(1);
}
while (1) {
if (addr[0] != '\0') { // 简单轮询,实际应用中需同步
printf("Read: %s", addr);
if (strncmp(addr, "quit", 4) == 0) break;
addr[0] = '\0'; // 清空标记
}
}
shmdt(addr);
return 0;
}
4. 信号灯(信号量集)
信号灯是一个或多个信号量的集合(数组),用于进程/线程间的同步与互斥。与单个信号量不同,System V信号灯允许对多个信号量同时操作。
4.1 创建/获取信号灯 —— semget
c
#include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
-
功能:创建或获取一个信号灯集。
-
参数:
-
key:键值。 -
nsems:信号量的个数。 -
semflg:标志位,如IPC_CREAT | 0664。
-
-
返回值:成功返回信号灯ID,失败返回-1。
4.2 控制信号灯 —— semctl
c
int semctl(int semid, int semnum, int cmd, ...);
-
功能:对信号灯集中的某个信号量执行控制命令。
-
参数:
-
semid:信号灯ID。 -
semnum:信号量下标(从0开始)。 -
cmd:常用命令:-
IPC_RMID:删除信号灯集。 -
SETVAL:设置信号量的值(需要第四个参数)。 -
GETVAL:获取信号量的值。
-
-
第四个参数(可选):类型为
union semun,需用户自定义。
-
-
返回值:成功取决于命令,失败返回-1。
union semun(通常需在程序中定义):
c
union semun {
int val; /* 用于 SETVAL */
struct semid_ds *buf; /* 用于 IPC_STAT/IPC_SET */
unsigned short *array; /* 用于 GETALL/SETALL */
struct seminfo *__buf; /* 用于 IPC_INFO */
};
4.3 操作信号量 —— semop
c
int semop(int semid, struct sembuf *sops, size_t nsops);
-
功能:对信号灯集中的信号量进行P/V操作(申请/释放)。
-
参数:
-
semid:信号灯ID。 -
sops:指向struct sembuf数组的指针,每个元素描述一个操作。 -
nsops:数组元素个数。
-
-
返回值:成功返回0,失败返回-1。
struct sembuf 定义:
c
struct sembuf {
unsigned short sem_num; /* 信号量下标 */
short sem_op; /* 操作数:正数为释放(V),负数为申请(P),0为等待到0 */
short sem_flg; /* 标志:IPC_NOWAIT 或 SEM_UNDO */
};
4.4 示例:共享内存 + 信号灯实现同步
下面的示例利用两个信号量实现“写一次、读一次”的同步。信号量0用于表示数据是否可读(初值0),信号量1用于表示缓冲区是否可写(初值1)。
(
可以加一句:
semctl(semid, 0, IPC_RMID);
)


5. 总结
| IPC方式 | 特点 | 适用场景 |
|---|---|---|
| 消息队列 | 消息有类型,可选择性接收;内核维护队列;数据有边界。 | 需要按类型分类处理的通信,如客户端-服务器。 |
| 共享内存 | 最快,无需数据拷贝;但需要同步机制。 | 大量数据交换,配合信号灯使用。 |
| 信号灯 | 用于同步与互斥,可操作多个信号量。 | 控制对共享资源的访问,实现进程同步。 |
这三种IPC方式灵活强大,但也需要谨慎管理资源(及时删除IPC对象),避免内存泄漏和死锁。熟练掌握它们,将为你的Linux系统编程打下坚实基础。
作业练习:
-
利用消息队列实现两个进程的聊天功能。



&spm=1001.2101.3001.5002&articleId=158470072&d=1&t=3&u=89c007e2df9d4098aa494eaf7aece2ac)
606

被折叠的 条评论
为什么被折叠?



