进程间通信IPC
1.共享内存
1.1 概念
共享内存指的是操作系统在物理内存中申请一块空间,应用程序可以映射到这块空间,进行直接读写操作。
1.2 特点
- 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据拷贝
- 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
- 进程就可以直接读写这一块内存区域,不需要进行数据的拷贝,从而大大提高的效率
- 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等


1.3 步骤
- 创建唯一key值 ftok
- 创建或打开共享内存 shmget
- 映射共享内存到用户空间 shmat
- 撤销映射 shmdt
- 删除共享内存 shmctl
1.4 函数接口
1.4.1 创建key值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:创建key值
参数: pathname: 文件名
proj_id:取整数的低8位数据
返回值:成功:key值
失败:-1,并设置errno错误码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main(int argc, char const *argv[])
{
key_t key = ftok("./app", 'a');
if(key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
return 0;
}
补充:
key值是根据pathname的inode号和proj_id的低8位组合而成的。如0x61013096
pathname只要是路径中存在的文件即可

ls -i 查看文件inode号
1.4.2 创建共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
shared memory get
int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:
key 键值
size 共享内存的大小
创建 检测错误
shmflg IPC_CREAT|IPC_EXCL|0777 创建共享内存的时候的权限
返回值:成功 shmid 共享内存的id
出错 -1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
int shmid;
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 64, 0666);
}
else
{
perror("shmget err");
return -1;
}
}
printf("%d\n", shmid);
return 0;
}
查看创建的共享内存的命令:ipcs -m
再次执行时会出现报错的情况:
我第一次执行的时候已经创建了这个共享内存,当再一次去执行的时候,肯定会提示文件已存在,共享内存已经存在了

4.4.3 映射共享内存
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数:shmid:共享内存的id号
shmadd:一般为NULL,表示由系统自动完成映射
如果不为NULL,那么由用户指定
shmflg:SHM_RDONLYH就是对该共享内存进行只读操作
0 可读可写
返回值:成功:完成映射后的地址
出错:(void *)-1的地址
printf("%d\n", shmid);
// 映射共享内存
char *p = (char *)shmat(shmid, NULL, 0);
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
// 阻塞,查看是否有没有映射成功
getchar();
return 0;
}
1.4.4 取消映射
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:取消映射
参数:shmaddr:要取消映射的共享内存地址
返回值:成功:0
失败:-1
// 映射共享内存
char *p = (char *)shmat(shmid, NULL, 0);
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
// 读写操作
// 取消映射
shmdt(p);
return 0;
}
1.4.5 删除共享内存
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:(删除共享内存), 对共享内存进行各种操作
参数:shmid 共享内存id
cmd IPC_STAT 获取shmid属性信息,存放在第三个参数
IPC_SET设置shmid属性信息,要设置的属性存放在第三个参数
IPC_RMID删除共享内存,此时第三个参数为NULL
buf 是一个结构体指针,但是我们是删除共享内存,所以没有意义,我们直接设置为NULL就可以
返回值:成功 0
失败 -1
// 取消映射
shmdt(p);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
1.5 操作命令
ipcs -m: 查看系统中的共享内存
ipcrm -m shmid:删除共享内存
ps: 不能直接删除掉还存在进程使用的共享内存。
这时候可以用ps -ef对进程进行查看,kill掉多余的进程后,再使用ipcs查看。
练习
两个进程实现通信,一个进程输入字符串,另一个进程打印数据,当输入quit时结束
gcc input.c -o in 输入字符串
gcc output.c -o out 打印数据
./in ./out
加入标志位实现,输入一次输出一次
flag ---> 0 不可以打印(没有输入新内容)
flag---> 1 可以打印(进行了新内容输入)
struct shm{
char buf[32];
int flag;
}
struct shm *p = shmat();
input.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
struct shm
{
char buf[32];
int flag;
};
int main(int argc, char const *argv[])
{
int shmid;
char buf[32] = {};
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 64, 0666);
}
else
{
perror("shmget err");
return -1;
}
}
printf("%d\n", shmid);
// 映射共享内存
struct shm *p = (struct shm *)shmat(shmid, NULL, 0);
if (p == (struct shm *)-1)
{
perror("shmat err");
return -1;
}
p->flag = 0;
// 读写操作
while (1)
{
fgets(p->buf, 32, stdin);
p->flag = 1;
// 处理\n
if (p->buf[strlen(p->buf) - 1] == '\n')
p->buf[strlen(p->buf) - 1] = '\0';
if (!strcmp(p->buf, "quit"))
break;
}
// 取消映射
shmdt(p);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
output.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
struct shm
{
char buf[32];
int flag;
};
int main(int argc, char const *argv[])
{
int shmid;
char buf[32] = {};
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 64, 0666);
}
else
{
perror("shmget err");
return -1;
}
}
printf("%d\n", shmid);
// 映射共享内存
struct shm *p = (struct shm *)shmat(shmid, NULL, 0);
if (p == (struct shm *)-1)
{
perror("shmat err");
return -1;
}
p->flag = 0;
// 读写操作
while (1)
{
if(p->flag == 1)
{
if(!strcmp(p->buf, "quit"))
break;
printf("buf: %s\n", p->buf);
p->flag = 0;
}
}
// 取消映射
shmdt(p);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
2.信号灯集
2.1 特点
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;
System V信号灯集是一个或者多个信号灯集的一个集合。其中每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作
2.2 步骤
- 创建key值:ftok
- 创建或打开初始化信号灯集:semget
- 初始化信号灯:semctl
- PV操作:semop
- 删除信号灯集:semctl
2.3 操作命令
ipcs -s:查看信号灯集
ipcrm -s semid:删除信号灯集
2.4 函数接口
2.4.1 创建信号灯集
man 2 semget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值
nsems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限,
通常为IPC_CREAT | IPC_EXCL | 0666
返回值:成功:信号灯集ID
失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
int semid;
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if(semid < 0)
{
if (errno == EEXIST)
{
semid = semget(key, 2, 0666);
}
else
{
perror("semget err");
return -1;
}
}
printf("%d\n", semid);
return 0;
}
2.4.2 初始化或删除信号灯集
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
功能:信号灯集的控制(初始化、删除)
参数:semid:信号灯集id
semnum:要操作集合中的信号灯编号
cmd:
GETVAL:获取信号灯的值
SETVAL:设置信号灯的值
IPC_RMID:从系统中删除信号灯集合
...:当cmd为SETVAL,需要传递共用体
返回值:成功 0
失败 -1
共用体格式:
union semun {
int val; /* 信号量的初值 */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
补充:
1.当cmd为SETVAL时需要传递第四个参数,类型为共用体。
用法:
union semun {
int val;
};
union semun sem;
sem.val = 10;
semctl(semid, 0, SETVAL, sem); //对编号为0的信号灯设置初值为10
2.当cmd为IPC_RMID时,表示删除信号灯集
用法:semctl(semid,0,IPC_RMID)//0:表示信号灯的编号,指定任意一个即可删除
3.当cmd为GETVAL时,表示获取信号灯的值
用法:printf("%d\n",semctl(semid,0,GETVAL));
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
union semun
{
int val;
};
int main(int argc, char const *argv[])
{
int semid;
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if(semid < 0)
{
if (errno == EEXIST)
{
semid = semget(key, 2, 0666);
}
else
{
perror("semget err");
return -1;
}
}
else
{
union semun sem;
sem.val = 10;
semctl(semid, 0, SETVAL, sem); // 对编号为0的信号灯设置初值为10
sem.val = 0;
semctl(semid, 1, SETVAL, sem); // 对编号为1的信号灯设置初值为0
}
printf("%d\n", semid);
printf("%d\n", semctl(semid, 0, GETVAL));
printf("%d\n", semctl(semid, 1, GETVAL));
return 0;
2.4.3 PV操作
int semop ( int semid, struct sembuf *opsptr, size_t nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集ID
opsptr:操作方式
nops: 要操作的信号灯的个数 1个
返回值:成功 :0
失败:-1
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
// -1 : 分配资源,P操作
short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
使用:
申请资源 P操作:
mysembuf.sem_num = 0;
mysembuf.sem_op = -1;
mysembuf.sem_flg = 0;
semop(semid, &mysembuf, 1);
释放资源 V操作:
mysembuf.sem_num = 0;
mysembuf.sem_op = 1;
mysembuf.sem_flg = 0;
semop(semid, &mysembuf, 1);
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
union semun
{
int val;
};
int main(int argc, char const *argv[])
{
int semid;
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid < 0)
{
if (errno == EEXIST)
{
semid = semget(key, 2, 0666);
}
else
{
perror("semget err");
return -1;
}
}
else
{
union semun sem;
sem.val = 10;
semctl(semid, 0, SETVAL, sem); // 对编号为0的信号灯设置初值为10
sem.val = 0;
semctl(semid, 1, SETVAL, sem); // 对编号为1的信号灯设置初值为0
}
printf("%d\n", semid);
printf("%d\n", semctl(semid, 0, GETVAL));
printf("%d\n", semctl(semid, 1, GETVAL));
// p操作(申请资源) -1
struct sembuf buf = {0, -1, 0};
semop(semid, &buf, 1);
printf("0p操作:%d\n", semctl(semid, 0, GETVAL)); // 9
// v操作(释放资源) +1
buf.sem_num = 1;
buf.sem_op = 1;
buf.sem_flg = 0;
semop(semid, &buf, 1);
printf("1v操作:%d\n", semctl(semid, 1, GETVAL)); // 1
// 删除信号灯集
semctl(semid, 0, IPC_RMID);
return 0;
}
将初始化和pv操作封装成函数:
union semun
{
int val;
};
// 初始化
void seminit(int semid, int num, int val)
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
// pv操作
void sem_op(int semid, int num, int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
input.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
union semun
{
int val;
};
// 初始化
void seminit(int semid, int num, int val)
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
// pv操作
void sem_op(int semid, int num, int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
int shmid, semid;
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
}
printf("%#x\n", key);
shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
// 在man手册中返回值为0表示失败
// 但是有的时候返回值为0的时候可以创建成功但是不可以使用
if (shmid <= 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 64, 0666);
}
else
{
perror("shmget err");
return -1;
}
}
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == EEXIST)
{
semid = semget(key, 2, 0666);
}
else
{
perror("semget err");
return -1;
}
}
else
{
seminit(semid, 0, 0);
}
printf("%d\n", shmid);
// 映射共享内存
char *p = (char *)shmat(shmid, NULL, 0);
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
// 读写操作
while(1)
{
scanf("%s", p);
sem_op(semid, 0, 1);
if(!strcmp(p, "quit"))
break;
}
// 取消映射
shmdt(p);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
output.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
union semun
{
int val;
};
// 初始化
void seminit(int semid, int num, int val)
{
union semun sem;
sem.val = val;
semctl(semid, num, SETVAL, sem);
}
// pv操作
void sem_op(int semid, int num, int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid, &buf, 1);
}
int main(int argc, char const *argv[])
{
int shmid, semid;
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
}
printf("%#x\n", key);
shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
// 在man手册中返回值为0表示失败
// 但是有的时候返回值为0的时候可以创建成功但是不可以使用
if (shmid <= 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 64, 0666);
}
else
{
perror("shmget err");
return -1;
}
}
semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
if (semid <= 0)
{
if (errno == EEXIST)
{
semid = semget(key, 2, 0666);
}
else
{
perror("semget err");
return -1;
}
}
else
{
seminit(semid, 0, 0);
}
printf("%d\n", shmid);
// 映射共享内存
char *p = (char *)shmat(shmid, NULL, 0);
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
// 读写操作
while(1)
{
sem_op(semid, 0, -1);
if(!strcmp(p ,"quit"))
break;
printf("%s\n", p);
}
// 取消映射
shmdt(p);
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
3.消息队列
3.1 特点
- 消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
- 消息队列可以按照类型来发送/接收消息
- 在Linux下消息队列的大小有限制。
- 消息队列个数最多为16个;
- 消息队列最容量最多为16384字节;
- 每个消息内容最多为8192字节。

3.2 步骤
- 创建key值
- 创建或打开消息队列:msgget
- 添加消息:msgsnd
- 读取消息:msgrcv
- 删除消息队列:msgctl
3.3 函数接口
3.3.1 创建消息队列
3.3.1 创建消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数: key值
flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666
返回值:成功:msgid
失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
int main(int argc, char const *argv[])
{
int msgid;
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key);
msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid < 0)
{
if (errno == EEXIST)
{
msgid = msgget(key, 0666);
}
else
{
perror("msgget err");
return -1;
}
}
printf("%d\n", msgid);
return 0;
}
3.3.2 添加消息
int msgsnd(int msgid, const void *msgp, size_t size, int flag);
功能:添加消息
参数:msgid:消息队列的ID
msgp:指向消息的指针。常用消息结构msgbuf如下:
struct msgbuf{
long mtype; //消息类型
char mtext[N]}; //消息正文
}
size:发送的消息正文的字节数
flag:IPC_NOWAIT消息没有发送完成函数也会立即返回
0:直到发送完成函数才返回
返回值:成功:0
失败:-1
msg_t msg;
msg.type = 1;
strcpy(msg.name, "zhangsan");
msg.age = 18;
msg.socre = 100;
msgsnd(msgid, &msg, sizeof(msg)-sizeof(long), 0);
3.3.3 读取消息
int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
功能:读取消息
参数:msgid:消息队列的ID
msgp:存放读取消息的空间
size:接受的消息正文的字节数
msgtype:0:接收消息队列中第一个消息。
大于0:接收消息队列中第一个类型为msgtyp的消息.
小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
flag:0:若无消息函数会一直阻塞
IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度
失败:-1
msg_t m;
msgrcv(msgid, &m, sizeof(m)-sizeof(long), 1, 0);
printf("%s %d %f\n", m.name, m.age, m.socre);
3.3.4 删除消息队列
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列ID
cmd:
IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。
IPC_SET:设置消息队列的属性。这个值取自buf参数。
IPC_RMID:从系统中删除消息队列。
buf:消息队列缓冲区
返回值:成功:0
失败:-1
msgctl(msgid, IPC_RMID, NULL);
3.4 操作命令
ipcs -q:查看创建的消息队列
ipcrm -q [msqid]:删除消息队列
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)