进程间通信下(IPC)
总共五种,分别是:管道、信号量、消息队列、共享内存、套接字
使用ipcs可以看到Linux下存在的IPC
ipcrm -m+shmid 删除指定共享内存
ipcrm -s+semid 删除指定信号量
ipcrm -q+msqid 删除指定消息队列
3.共享内存
共享内存允许多个进程共享同一块物理内存区域。这意味着:
一个进程写入共享内存的数据,其他进程可以立即看到
不需要内核在进程间复制数据(这是它快的主要原因)
进程可以直接读写内存,就像操作普通内存一样
零拷贝、速度快、容量大
共享内存函数:
#include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);参数key_t key,共享内存段的键值(跟信号量的semget()参数非常像不是吗)
参数size_t size,共享内存的内存大小(信号量这里是信号量个数)
参数int shmflg,权限和创作标志(依旧和信号量一模一样)
权限位与文件权限类似(0600),创建选项:
IPC_CREAT:如果不存在则创建,如果已存在则忽略
IPC_EXCL:与IPC_CREAT一起使用,如果已存在则失败返回值:创建成功返回共享内存的标识符(非负整数),失败返回-1(共享内存的shmget()函数和信号量的semget()相似程度极高,结合起来进行记忆)
void *shmat(int shmid, const void *shmaddr, int shmflg);参数int shmid,
shmget()返回的共享内存标识符参数const void* shmaddr,指定附加地址,通常填NULL就行
参数int shmflg,附加标志(填0就行,感觉有点故弄玄乎)
返回值
成功返回共享内存段在进程地址空间中的起始地址,失败返回(void*)-1(这个函数理解成将共享内存的地址映射过来的作用就行,填写参数也非常简单)
int shmdt(const void *s);
非常简单的函数,作用是当前进程失去对这块共享内存的控制(即解除映射)
参数const void* s,共享内存段在进程地址空间中的起始地址(函数shmat()的返回值)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);参数int shmid,同上,共享内存标识符
参数int cmd,控制命令
IPC_RMID:标记删除共享内存段(当所有进程都分离后真正删除)
IPC_STAT:获取共享内存状态信息
IPC_SET:设置共享内存参数参数struct shmid_ds* buf,指向状态结构的指针(我才发现老师疏忽了这个函数...这个是啥意思我也不懂捏)
下面通过一段简单的代码演示共享内存的用法:
此时通过a进程往共享内存中写入了一句"hello",a进程结束后,并没有调用shmctl()函数,所以这块共享内存依旧存在。
注:第一个参数的键值必须相同才能指向同一片空间,而因为当前空间已经被创建,所以后面两个参数填多少都已经无所谓了。此时b进程也映射了这块共享内存,可以对里面的内容进行访问了。
此时对a进程和b进程进行编写,可以得到共享内存的数据。
但是此时存在一个问题:我们若是先运行b,此时共享内存中还什么内容都没有写入,则b进程是读取不到内容的(或是读取到随机值),那么就必须控制b进程在a进程之后才能读取共享内存,有什么办法可以做到呢?
很容易便可以想到通过信号量的pv操作来控制进程的先后,让a进程写一次,b进程就可以读取一次。那么此时就会引发第二个问题,我们需要几个信号量?信号量的初值?p,v操作该放到哪里?
毫无疑问,我们需要两个信号量(s1和s2),其中s1控制a进程是否往共享内存中写入数据,所以初值为1(即一开始就可以写入数据),而s2控制b进程能否读取共享内存中的数据,所以初值为0(即一开始共享内存无数据,所以不能进行读取)。
如图所示,s1先进行p操作减为0,此时写入数据,然后对s2进行v操作+1,则此时b进程不会再被阻塞住,读出数据后再对s1进行v操作,使得s1恢复到初值,循环成立。
sem.c的代码为:
#include "sem.h"
int semid = -1;
void sem_init()//创建两个信号量,或 获取id
{
semid = semget((key_t)1234,2,IPC_CREAT|IPC_EXCL|0600);//全新创建,已存在,则失败
if( semid == -1 )
{
semid = semget((key_t)1234,2,0600);//获取
if( semid == -1 )
{
printf("semget err\n");
}
}
else
{
union semun a;
int arr[2] = {1,0};
for(int i = 0; i < 2; i++)
{
a.val = arr[i];
if( semctl(semid,i,SETVAL,a) == -1 )
{
printf("semctl setval err\n");
}
}
}
}void sem_p(enum SEMID index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;//p
buf.sem_flg = SEM_UNDO;
if( semop(semid,&buf,1) == -1 )
{
printf("p err\n");
}
}
void sem_v(enum SEMID index)
{
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;//v
buf.sem_flg = SEM_UNDO;
if( semop(semid,&buf,1) == -1 )
{
printf("v err\n");
}}
void sem_destroy()
{
if( semctl(semid,0,IPC_RMID) == -1 )
{
printf("destroy err\n");
}
}
(最好自己实现一遍,会更好理解,代码本身也不难,主要还是记忆)
a.c与b.c的代码(仅展示与之前不同的地方):
a.c b.c
至此,通过信号量来控制a进程写入一句话,b进程才能读取一段话的程序便设计完成了,通过编译运行来看看结果:
当输入end时,a进程和b进程同时结束。
4.消息队列
一种异步通信机制,用于在分布式系统中传递消息。它允许不同的应用程序或服务通过发送和接收消息来进行通信,而无需彼此直接连接或同时在线。
消息队列函数:
消息队列要求消息结构必须包含一个 long 类型的消息类型字段:
// 基本消息结构 struct msgbuf { long mtype; // 消息类型,必须 > 0 char buff[32]; // 消息数据(可以是任意类型) };#include <sys/msg.h> int msgget(key_t key, int msgflg);参数key_t key,消息队列的键值(依旧键值,和前面的相似)
参数int msgflg,权限标志
权限位与文件权限类似(0600),创建选项:
IPC_CREAT:如果不存在则创建,如果已存在则忽略
IPC_EXCL:与IPC_CREAT一起使用,如果已存在则失败返回值:消息队列标识符(和信号量以及共享内存很像,就是少了一个参数)
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);(发送消息)参数int msqid,消息队列标识符(依旧msgget()返回值)
参数const void* msgp,指向消息缓冲区的指针(即基本消息结构体的指针)
参数size_t msgsz,消息数据的长度(上面结构体中sizeof(buff)的大小)
参数int msgflg,发送标志,一般填0即可
返回值:成功为0,失败-1。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);(接收消息)参数int msqid,消息队列标识符(依旧msgget()返回值)
参数const void* msgp,指向消息缓冲区的指针(即基本消息结构体的指针)
参数size_t msgsz,消息数据的长度(上面结构体中sizeof(buff)的大小)
参数long msgtyp,接收指定的消息类型(上面结构体中的mtype对应),若是=0则接收所有消息,<0则接收mtype最小的消息
参数int msgflg,发送标志,一般填0即可
int msgctl(int msqid, int cmd, struct msqid_ds *buf);(控制消息队列)参数int msqid,消息队列标识符(依旧msgget()返回值)
参数int cmd,控制命令
常用命令:
IPC_RMID:立即删除消息队列
IPC_STAT:获取消息队列状态信息
IPC_SET:设置消息队列参数
IPC_INFO:获取系统消息队列限制信息(Linux 特有)
MSG_INFO:获取消息队列资源使用情况(Linux 特有)参数 struct msqid_ds* buf,指向状态结构的指针
返回值:成功为0,失败-1。
演示代码:
发送消息 接收消息
个人小结:IPC这块感觉很难记啊,不过好在自己编了一次代码之后理解倒是深了一部分,不过感觉还是差得远啊,后面就到线程了,这块还是要不断复习,牢固记忆。