共享内存
共享内存
Linux上进程的地址空间都是相互独立的,每个进程有3G的属于自己的虚拟地址空间,操作系统为每一个进程维护一个页表,进程所映射的物理空间都是不同的。而共享内存就是为了打破这一点,共享内存通过内核对象使得不同的进程在自己的虚拟地址空间上分配一块空间映射到相同的物理内存空间上。这块物理空间对于映射到上面的每个进程而言都是可以访问的 ,这就达到了内存共享的目的。如下图所示:
共享内存为了实现不同进程之间的互斥访问,需要手动用信号量和PV操作来控制
操作接口
0、头文件
#include <sys/shm.h>
1、获取或者创建内核对象,并且指定共享内存的大小(系统分配物理空间时,按照页进行分配)
int shmget(key_t key, int size, int flag); //创建内核对象并申请物理空间 shm: shared memory共享内存
key:标识,两个进程要使用相同的共享内存,key要相同
size:创建共享内存时,指定共享内存的大小
flag:可选,指定操作权限,IPC_CREAT,IPC_EXCEL
返回值:返回共享内存id
执行完第一步只是把空间申请了出来,还没有把虚拟地址空间映射到这块内存上,要接着执行以下第二步
2、将虚拟地址空间映射到共享内存的物理空间
void* shmat(int shmid, const void* shmaddr, int shmflg);
shmid:由shmget返回的共享内存id
shmaddr:一般给NULL,由系统自动选择映射的虚拟地址空间
shmflg:一般给0,可以给SHM_RDONLY为只读模式,其他的为读写
返回值:成功返回共享内存的首地址,出错返回-1,注意出错返回的是-1不是NULL
3、断开当前进程进程虚拟地址空间与共享内存的物理空间的映射
int shmdt(const void *shmaddr);
shmaddr:断开当前进程的shmaddr 指向的共享内存映射
返回值:成功返回 0, 失败返回-1
4、操作共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//第三个参数必须有
shmid:shmget返回的共享内存ID号
cmd:删除:IPC_RMID, 设置:IPC_SET,cmd其他参数如下:
(摘自《UNIX环境高级编程》)
注意:使用IPC_RMID删除共享内存时,因为有连接计数器,除非最后一个进程与该共享段断开连接,则删除该共享存储段,否则,并不会真正删除该共享段,但是该段标识符(内核对象)会被立即删除,不能再使用shmat方法与该段建立映射
以上说明了:一个进程调用该方法删除共享存储,不会影响之前已经和该共享存储段建立映射关系的进程
buf:内核为每一个共享存储段设置了一个shmid_ds结构,如下:
返回值:成功返回0,失败返回-1
共享内存的特点
共享内存通信是最快的IPC。在通信过程中相比于管道少了两次数据的拷贝:写入时不用从缓冲buf中写入,读取时不用从内存中读取到缓冲buf中
查看与删除共享内存的命令
查看
ipcs -m
删除
ipcrm -m shmid
关于ipcs/ipcrm命令的更多介绍请查看上一篇博客末尾:Linux:进程间通信——管道、信号量
例:通过共享内存通信
题目:A进程执行写,然后B进程读并且转换成大写输出,且对该块内存的读写不能同时进行
A进程.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h" //上一篇博客封装好的信号量操作方法
int main()
{
//创建共享内存
int shmid = shmget((key_t)1234, 128, 0664 | IPC_CREAT);
assert(shmid != -1);
//映射虚拟地址与共享内存
char *ptr = (char*)shmat(shmid, NULL, 0);
assert(ptr != (char*)-1 );//shmar出错返回的是-1
//创建信号量
int initVal[] = {1,0};
int semid = SemGet(1234, initVal, 2);
assert(semid != -1);
//A进程写
while(1)
{
SemP(semid, 0);//写信号量在initVal里下标是0
printf("input: ");
fgets(ptr, 127, stdin);
SemV(semid,1); //唤醒读操作,读信号量在initVal里下标是1
if(strncmp(ptr,"end",3) == 0)
break;
}
//断开连接
shmdt(ptr);
exit(0);
}
B进程.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h" //上一篇博客封装好的信号量操作方法
int main()
{
//获取共享内存
int shmid = shmget((key_t)1234, 128, 0664 | IPC_CREAT);
assert(shmid != -1);
//映射虚拟地址与共享内存
char *ptr = (char*)shmat(shmid, NULL, 0);
assert(ptr != (char*)-1 );//shmar出错返回的是-1
//获取信号量
int initVal[] = {1,0};
int semid = SemGet(1234, initVal, 2);
assert(semid != -1);
//B进程读
while(1)
{
SemP(semid, 1);//阻塞读操作,读信号量在initVal里下标是1
if(strncmp(ptr,"end",3) == 0)
{
break;
}
int i = 0;
for(; i<strlen(ptr)-1;++i)
{
printf("%c",toupper(ptr[i]));
fflush(stdout);
sleep(1);
}
printf("\n");
SemV(semid,0);//唤醒写操作,写信号量在initVal里下标是0
}
//断开连接
shmdt(ptr);
exit(0);
}
运行结果:
注意:进程执行完毕记得使用ipcs/ipcrm命令删除信号量内核对象和共享内存内核对象
消息队列
消息队列
管道和共享内存传输的是流数据(字节流形式),传输的数据是不可分割的整体;
消息则是一段一段的,数据报形式(类型+数据)
队列:先进先出,消息队列类似优先级队列,消息队列里的是类型+数据,可以指定类型来读取,在相同类型下,按照先进先出的顺序,消息队列在内核空间
操作接口
0、头文件
#include <sys/msg.h>
1、创建或者获取一个消息队列
int msgget(key_t key, int msqflg);
key:用户标识,使用相同消息队列的进程key值要求一样
msqflg:IPC_CREAT
返回值:成功返回消息队列ID,失败返回-1
2、发送消息
int msgsnd(int msqid, const void*msqp, size_t msqsz, int msqflg)//msq: message queue消息队列
msqid:msgget返回的消息队列ID
msqp:struct msgbuf结构体指针
struct msgbuf结构体如下:
struct msgbuf { long mtype; //消息类型,必须大于0 char mtext[SIZE]; // 消息数据,SIZE为最长的消息数据的长度 };
msqsz:指定mtext中有效数据的长度
msqflg:一般设置为0,可以设置为IPC_NOWAIT非阻塞标志
返回值:成功返回0,失败返回-1
3、接收消息
ssize_t msgrcv(int msqid, void *msqp, size_t msqsz, long msqtyp, int msqflg);
msqid:消息队列ID值
msqp:msgbuf结构体指针
msqsz:接收数据的大小
msqtyp:指定接收到的数据类型,类型可以为0
msqflg:一般设置为0,可以设置为IPC_NOWAIT
返回值:成功返回消息的数据部分(mtext)的长度,失败返回-1
msgcv成功执行时,内核更新与该消息队列相关联的msqid_ds结构,并将队列中的消息数减一
4、控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds*buf);
msqid:
cmd:删除:IPC_RMID,cmd其他参数如下
buf:每一个消息队列都有一个msqid_ds结构体与其相关联,msqid_ds结构体如下:
返回值:成功返回0,失败返回-1
例:通过消息队列通信
题目:a进程发送消息,b进程从消息队列中接收消息并输出
发送的消息的数据报结构体:msgbuf
#pragma once
typedef struct msgbuf
{
long mtype;
char mtext[128];
}MsgBuf;
msqa.c发送消息
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "msgbuf.h"//数据报结构体
int main(int argc, char *argv[])//argc[1]放类型,argv[2]里放数据
{
if(argc < 3)
{
printf("please input type and data\n");
exit(1);
}
//数据报中填充数据
MsgBuf mess;
memset(&mess, 0 , sizeof(mess));
sscanf(argv[1], "%d", &mess.mtype);//设置数据
strcpy(mess.mtext, argv[2]);
//创建消息队列
int msgid = msgget((key_t)1234, IPC_CREAT | 0664);
assert(msgid != -1);
//发送消息
msgsnd(msgid, &mess, strlen(mess.mtext), 0);
exit(0);
}
msqb.c接收消息
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "msgbuf.h"//数据报结构体
int main(int argc, char *argv[])//根据类型来获取信息,argv[1]放类型
{
if(argc < 2)
{
printf("please input type\n");
exit(1);
}
//创建接收消息的缓冲区
MsgBuf mess;
memset(&mess, 0, sizeof(mess));
//根据类型接收消息队列中的消息
long type = 0;
sscanf(argv[1], "%d", &type);
int msgid = msgget((key_t)1234, IPC_CREAT | 0664);
assert(msgid != -1);
msgrcv(msgid, &mess, 127, type, 0);
printf("type: %d, data: %s\n", mess.mtype, mess.mtext);
}
运行结果:按顺序依次发送消息(类型+数据),接收时按类型优先级依次读取(先进先出)
测试:msgrcv成功执行时,内核更新与该消息队列相关联的msqid_ds结构,并将队列中的消息数减一