共享内存(进程间的通信方式)
目录
1.共享内存的特点
(1)共享内存是一种最高效的进程间的通信方式,进程可以直接读写内存,而进程之间不需要通过任何数据的拷贝。
(2)内核中有一块供多个进程交换信息的内存区,可由需要访问的进程将内存区映射到自己的私有地址空间。
(3)进程之间可以读写内核的这一内存区,而不需要进行数据拷贝。
(4)由于出现多个进程共享一段内存的情况,需要依靠互斥锁,信号量等同步机制来实现内存共享的功能。
2.函数接口
(1)key_t ftok(const char *pathname,int proj_id);
功能:产生一个独一无二的key值;
参数:pathname->提前创建的可访问文件的文件名;proj_id:任意一个字符;
返回值:成功则返回生成的key值,失败则返回-1;
//代码演示
//创建key值
key_t key;
//文件的inode号和字符的ASCII码拼接成key值;
key = ftok("./file",'a'); //“file”是已存在的可访问文件的路径,‘a’是任意一个字符;
//key值创建失败会返回-1;
if(key < 0)
{
perror("ftok err");
return -1;
}
(2)int shmget(key_t key,size_t size,int shmflg);
功能:创建或打开共享内存;
参数:key->键值;size->共享内存的大小;
shmflg->常被设置为IPC_CREAT|IPC_EXCL|0666,前两项的作用是创建的共享内存如果不存在,则会创建该共享内存,并返回共享内存id,如果该共享内存已经存在,则会返回-1并生成EEXIST错误码;
返回值:成功则返回共享内存id,失败则返回-1;
//代码演示
//创建共享内存
int shmid;
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("shmid:%d,key:%#xn",shmid,key);
(3)void *shmat(int shmid,const void *shmaddr,int shmflg);
功能:映射共享内存,把指定的共享内存映射到进程的地址空间用于访问;
参数:shmid->共享内存的id号;shmaddr->一般为NULL,表示由系统自动完成映射;
shmflg->SHM_RDONLY代表对该共享内存进行只读操作;0代表可读可写;
返回值:成功则返回映射后的地址,失败则返回-1;
//代码演示
//创建映射,映射类型要和指针类型保持一致
char *p = NULL;
p = (char *)shmat(shmid,NULL,0);
//错误判断,返回值-1也要和指针保持相同类型
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
(4)int shmdt(const void *shmaddr);
功能:取消映射;
参数:shmaddr->要取消的映射地址;
返回值:成功则返回0,失败则返回-1;
//接上方创建映射代码
//取消映射
shmdt(p);
(5)int shmctl(int shmid,int cmd,struct shmid_ds *buf);
功能:删除共享内存,或对共享内存进行操作;
参数:shmid->共享内存id;
cmd-> IPC_STAT获得共享内存的属性信息,存放在第三个参数buf中;
IPC_SET设置共享内存的属性信息,要设置的属性存放在第三个参数buf中;
IPC_RMID删除共享内存,第三个参数设置为NULL;
返回值:成功则返回0,失败则返回-1;
3.有关共享内存的系统命令
1.ipcs -m:查看系统中的共享内存;
2.ipcrm -m shmid:删除指定共享内存;
代码演示:创建共享内存,创建映射,向共享内存中写入字符串hello,并输出到终端;
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//1.创建key值
key_t key;
//“.file”是已存在的可访问文件的路径名,'a'是任意的一个字符
//文件的inode号和字符ascii码拼接成key值;
key = ftok("./file",'a');
//key值创建失败会返回-1;
if(key < 0)
{
perror("ftok err");
return -1;
}
//2.创建共享内存(share memory)
//key值,内存大小,权限
int shmid;
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("shmid:%d,key:%#xn",shmid,key);
//3.映射共享内存
//共享内存id,NULL,0代表可读可写,SHM_RDONLY代表可读;
char *p;
//强转为指针p的类型
p = (char *)shmat(shmid,NULL,0);
//创建失败的返回值为-1,-1也要强转为指针p的类型;
if(p == (char *)-1)
{
perror("shmat err");
return -1;
}
//p目前指向的是共享内存的首地址,所以应该使用复制函数给p指向的空间赋值;
strcpy(p,"hello");
printf("%sn",p);
//取消映射
shmdt(p);
//删除共享内存
/*共享内存id号,IPC_RMID删除共享内存,第三个参数为NULL;IPC_STAT获取共享内存
属性信息,放在第三个参数中,IPC_SET设置共享内存属性信息,要设置的属性放在第三
个参数中*/
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
代码练习:使用共享内存,实现在一个进程循环从终端输入字符串,另一个进程循环输出,当输入“quit”时结束;
//read.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
//使用标志位保证先输入后输出
struct msg{
int flag; //标志位
char buf[32]; //保存输入的字符串
};
int main(int argc, const char *argv[])
{
key_t key;
int shmid;
struct msg *p = NULL;
//1.创建key值
if((key = ftok("./app", 'a')) < 0)
{
perror("ftok err");
return -1;
}
//2.创建或打开共享内存
if((shmid = shmget(key, 128, IPC_CREAT|IPC_EXCL|0666)) <= 0)
{
if(errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
//3.映射共享内存
if((p = shmat(shmid, NULL, 0)) == (struct msg *)-1)
{
perror("shmat err");
shmctl(shmid, IPC_RMID, NULL);
return -1;
}
//标志位初始化为0;
p->flag = 0;
//循环输入
while(1)
{
//标志位为0时进入输入循环;
if(p->flag == 0)
{
//使用fgets获取在终端中输入的任何字符;
fgets(p->buf, 32, stdin);
//信号量变为1;
p->flag = 1;
if(strcmp(p->buf, "quitn") == 0)
break;
}
}
//4.取消映射
shmdt(p);
return 0;
}
//write.c
//同上方read.c程序
//标志位初始化为0,保证先输入再输出
p->flag = 0;
while(1)
{
//如果先循环输出程序,运行到此处,程序阻塞,开始运行输入程序;
//输入程序结束后,标志位变为1,再从此处继续运行
if(p->flag == 1)
{
if(strcmp(p->buf, "quitn") == 0)
break;
printf("data:%s", p->buf);
//标志位变为0,保证输出操作结束后,重新开始输入
p->flag = 0;
}
}
//4.取消映射
shmdt(p);
//5.删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
如果本文中存在概念错误或代码错误的情况,请批评指正。