文章目录
- 1.基本认识
- 1.1 概念介绍
- 1.2主要原理
- 2.使用方法
- 2.1创建共享内存shmget
- 2.1.1 shmget
- 2.1.2 ftok
- 2.2映射地址空间shmat
- 2.3 访问共享内存
- 2.4 同步和互斥
- 3.接口封装
- 3.1 创建shm_create
- 3.2 映射shm_connect
- 3.3 多进程共享内存
共享内存是一种机制,也是进程间进行通信的一种方式
1.基本认识
1.1 概念介绍
不同进程的资源通常是独立的,他们所占用的内存互相独立,不可互相访问。
而共享内存
允许多个进程访问同一块内存区域,从而实现数据的共享和交换。是一种高效的进程通信
方法。
在共享内存中,多个进程可以将同一块物理内存,映射到它们各自的虚拟地址中,使它们可以直接读写该内存的内容,而无需通过消息传递等其它通信方式,也就是“完全无需额外的拷贝”。这种直接的内存访问,使得数据交换更高效。
1.2主要原理
要理解共享内存,首先要理解“虚拟内存”这一概念。
虚拟内存是进程直接操作的地址,不同进程可以存在相同的虚拟内存地址,因为实际所代表的物理地址并不相同
每个进程内的虚拟地址是连续的,但实际映射在物理地址上,却不是连续的。
通常来说各进程之间不会共用物理内存地址,如下图所示:
进程A和进程B各自的虚拟内存
,分别映射在实际物理内存
的不同区域上。
那么,共享内存
就是改变这种映射关系,让不同的进程“共用物理内存”。如下图所示:
这个时候,进程A的第3块虚拟内存,与进程B的第1块虚拟内存,就指向同一块物理内存了。
也就是它们共享内存了。之后其中一个改写了这块内存的内容,另一个就可以直接读到,省去了中间(系统内核)的数据拷贝。这就是所谓“共享内存效率最高”的原因。
但从中我们不难看出,共享内存也是有缺点的,那就是大家都是读写同一块内存,很容易冲突。所以共享内存通常还需要搭配其它同步机制使用。
2.使用方法
共享内存的使用并不难,就是改变内存映射,获取到同一个指向共享内存的地址,然后读写数据。
2.1创建共享内存shmget
第一步是创建一个共享内存区域,并分配一块内存来存储数据。
2.1.1 shmget
#include <sys/ipc.h>
#include <sys/shm.h>
/***********************************
* @para key 共享内存的键值,用于标识共享内存段。通常使用 ftok 函数生成键值。
* @para size 共享内存段的大小,以字节为单位
* @para shmflg 共享内存的标志位,用于指定创建共享内存的权限和行为
* @return 成功时,返回共享内存的标识符(即共享内存的ID)
* 失败时,返回-1,并设置相应的错误码
*
*********************************/
int shmget(key_t key, size_t size, int shmflg);
//example
key_t key = ftok(".", 123); //详见下一节
int shmid = shmget(key, 1024, IPC_CREAT | 0666);
2.1.2 ftok
上述出现了一个 ftok
函数,这个函数返回了一个 key_t
。
这个有什么作用呢?其实,这个 key 是不同进程间使用同一片物理内存的关键。如果要进行共享,就大家都得使用同一个 key。否则,系统如何知道哪几个进程之间要共享呢。
而 ftok 则是使用一个“路径(文件或目录)”加一个“立即数”,来产生一个 key。只要大家的这两个参数相同,计算出来的 key 就是一致的。
其原型如下:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
pathname:一个存在的文件路径名,用于生成键值。通常选择一个已经存在的文件。
proj_id:一个整数,用于区分不同的IPC资源。通常选择一个非零的整数。
返回值:
如果成功,返回一个键值(key)。
如果失败,返回-1,并设置相应的错误码。
关于ftok
,还有几个要点:
- ftok 的路径可以随便设置,但必须是实际存在的
- ftok 只是想取文件 inode 这个唯一数值,和文件权限无关
- proj_id 在一些系统(如 UNIX)上,实际取值是1到255。
为什么对 ftok 说那么多,因为这个 key 对很多进程间通信都是一个关键。不只是共享内存,其它诸如消息队列、信号量之类的方法,也会用到。
2.2映射地址空间shmat
第二步就是,将创建出的共享内存空间,映射到本进程的虚拟地址空间。
映射使用的是 shmat,原型如下:
参数:
shmid:共享内存的标识符(即共享内存的ID),由shmget函数返回
shmaddr:指定共享内存段附加到当前进程地址空间的地址。通常设置为 NULL,表示由系统自动选择一个合适的地址
shmflg:共享内存的标志位,用于指定附加共享内存的权限和行为
返回值:
成功时,返回指向共享内存段附加地址的指针。
失败时,返回 -1,并设置相应的错误码。
例子:
#include <sys/types.h>
#include <sys/shm.h>
char *shmaddr = shmat(shmid, NULL, 0);
2.3 访问共享内存
这一步比较简单,就是向共享内存(也可以理解为自己的地址空间)写数据和读数据。
memcpy(shmaddr, source_buff, source_length); //写
memcpy(target_buff, shmaddr, target_length); //读
2.4 同步和互斥
之前提到过共享内存的不足。由于多个进程可以同时访问共享内存,需要使用同步和互斥机制来确保数据的一致性和正确性。常见的同步机制包括信号量、互斥锁和条件变量等。详情请看进程互斥访问-信号量。
3.接口封装
3.1 创建shm_create
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
#define KEY_NUM 126
#define FILE_PATH "./share.txt"
int shm_create_with_key(const char *filepath, int len, int key_num)
{
key_t key = ftok(filepath, key_num);
if(key < 0)
{
printf("create key error!\n");
return key;
}
int shmid = shmget(key, len, IPC_CREATE|0666);//666代表可读可写
if(shmid < 0)
{
return shmid;
printf("create share mem error!\n")
}
return shmid
}
int shm_create(int len)
{
return shm_create_with_key(FILE_PATH, len, KEY_NUM);
}
3.2 映射shm_connect
char* shm_connect(int shm_id)
{
char *mem = shmat(shm_id, NULL, 0);
if(mem < 0)
{
printf("shmat error!\n");
return mem;
}
return mem;
}
3.3 多进程共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define KEY_NUM 126
#define FILE_PATH "./share.txt"
//使用文件路径作为共享内存
int shm_create_with_key(const char *filepath, int len, int key_num)
{
key_t key = ftok(filepath, key_num);
if(key < 0)
{
printf("create key error!\n");
return key;
}
int shmid = shmget(key, len, IPC_CREAT|0666);//666代表可读可写
if(shmid < 0)
{
return shmid;
printf("create share mem error!\n");
}
return shmid;
}
int shm_create(int len)
{
return shm_create_with_key(FILE_PATH, 100, KEY_NUM);
}
char* shm_connect(int shm_id)
{
char *mem = (char*)shmat(shm_id, NULL, 0);
if(mem < 0)
{
printf("shmat error!\n");
return NULL;
}
return mem;
}
int main()
{
int shm_id = shm_create(10);
if(shm_id < 0)
{
printf("shm_create error!\n");
return -1;
}
char *mem = shm_connect(shm_id);
if(!mem)
{
printf("shm_connect error!\n");
return -1;
}
int i = 10;
while (--i)
{
printf("%s\n", mem);
sleep(1);
}
shmdt(mem);
return 0;
}
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "string.h"
#define KEY_NUM 126
#define FILE_PATH "./share.txt"
int shm_create_with_key(const char *filepath, int len, int key_num)
{
key_t key = ftok(filepath, key_num);
if(key < 0)
{
printf("create key error!\n");
return key;
}
int shmid = shmget(key, len, IPC_CREAT|0666);//666代表可读可写
if(shmid < 0)
{
return shmid;
printf("create share mem error!\n");
}
return shmid;
}
int shm_create(int len)
{
return shm_create_with_key(FILE_PATH, 100, KEY_NUM);
}
char* shm_connect(int shm_id)
{
char *mem = (char*)shmat(shm_id, NULL, 0);
if(mem < 0)
{
printf("shmat error!\n");
return NULL;
}
return mem;
}
int main()
{
int shm_id = shm_create(10);
if(shm_id < 0)
{
printf("shm_create error!\n");
return 0;
}
char *mem = shm_connect(shm_id);
if(!mem)
{
printf("shm_connect error!\n");
return 0;
}
//初始化
memcpy(mem, "hello", 6);
int i = 0;
while (i<10)
{
mem[0] = '1'+i;
++i;
sleep(1);
}
shmdt(mem);
return 0;
}