共享内存(进程间的通信方式)

目录

1.共享内存的特点

2.函数接口

3.有关共享内存的系统命令


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;


如果本文中存在概念错误或代码错误的情况,请批评指正。