什么是共享内存

百度百科定义:共享内存指 (shared memory)在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存(Cache)。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。

共享内存是 Unix下的多进程之间的通信方法,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。

特点

所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。

但内部没有共享内存互斥访问机制,所以往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

涉及函数

ftok:系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值

头文件

#include

#include

复制代码

函数原型

key_t ftok( const char * fname, int id )

fname:指定的文件名(已经存在的文件名),一般使用当前目录,如:"."

id:id是子序号。虽然是int类型,但是只使用8bits(1-255)。

在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

如:指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,

换算成16进制为0x26,则最后的key_t返回值为0x26010002。

查询文件索引节点号的方法是: ls -i

当删除重建文件后,索引节点号由操作系统根据当时文件系统的使用情况分配,

因此与原来不同,所以得到的索引节点号也不同。

复制代码

shmget:得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符

头文件

#include

#include

复制代码函数原型

int shmget(key_t key, size_t size, int shmflg)

key

0(IPC_PRIVATE):会建立新共享内存对象

大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值

size

大于0的整数:新建的共享内存大小,以字节为单位

0:只获取共享内存时指定为0

shmflg

0:取共享内存标识符,若不存在则函数会报错

IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存则报错

使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

函数返回值

成功:返回共享内存的标识符

出错:-1,错误原因存于error中

错误代码

EINVAL:参数size小于SHMMIN或大于SHMMAX

EEXIST:预建立key所指的共享内存,但已经存在

EIDRM:参数key所指的共享内存已经删除

ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL)

ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位

EACCES:没有权限

ENOMEM:核心内存不足

复制代码

shmat:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问

头文件

#include

#include

复制代码函数原型

void *shmat(int shmid, const void *shmaddr, int shmflg)

hmid:共享内存标识符

shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置

shmflg:SHM_RDONLY:为只读模式,其他为读写模式

fork后子进程继承已连接的共享内存地址。

exec后该子进程与已连接的共享内存地址自动脱离(detach)。

进程结束后,已连接的共享内存地址会自动脱离(detach)

函数返回值

成功:附加好的共享内存地址

出错:-1,错误原因存于errno中

错误代码

EACCES:无权限以指定方式连接共享内存

EINVAL:无效的参数shmid或shmaddr

ENOMEM:核心内存不足

复制代码

shmdt:与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存

头文件

#include

#include

复制代码函数原型

int shmdt(const void *shmaddr)

shmaddr:连接的共享内存的起始地址

本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程

函数返回值

成功:0

出错:-1,错误原因存于error中

错误代码

EINVAL:无效的参数shmaddr

复制代码

shmctl:完成对共享内存的控制

头文件

#include

#include

复制代码函数原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf)

shmid:共享内存标识符

cmd

IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中

IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内

IPC_RMID:删除这片共享内存

buf:共享内存管理结构体。具体说明参见共享内存内核结构定义部分

函数返回值

成功:0

出错:-1,错误原因存于error中

错误代码

EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存

EFAULT:参数buf指向无效的内存地址

EIDRM:标识符为shmid的共享内存已被删除

EINVAL:无效的参数cmd或shmid

EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行

复制代码

实例代码

shm_write.c
#include 
#include 
#include 
#include 
#include 
int main(int argc, const char * argv[]) {
//生成一个key
key_t key = ftok("./", 88);
//创建共享内存,返回一个id
//数字 4 、2 和 1表示读、写、执行权限
//用户、所属组、其他组都有读写权限
int shmid = shmget(key, 8, IPC_CREAT|0666); // IPC_CREAT: Create entry if key does not exist
if (shmid == -1) {
perror("shmget failed");
//exit(0) 表示程序正常退出,exit⑴/exit(-1)表示程序异常退出。
exit(1);
}
//映射共享内存,得到虚拟地址
//shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
//shmflg: SHM_RDONLY:为只读模式,其他为读写模式
void *p = shmat(shmid, NULL, 0);
if (p == (void *)-1) {
perror("shmat failed");
exit(2);
}
//写共享内存
int *pp = p;
*pp = 0x123456;
*(pp + 1) = 0xffffff;
//解除映射
if (shmdt(p) == -1) {
printf("shmdt failed");
exit(3);
}
printf("解除映射成功,点击回车销毁共享内存\n");
getchar();
//IPC_RMID:删除这片共享内存
if (shmctl(shmid, IPC_RMID, NULL)) {
perror("shmctl failed");
exit(4);
}
return 0;
}

复制代码

shm_read.c
#include 
#include 
#include 
#include 
#include 
int main() {
//生成key
key_t key = ftok("./", 88);
//获取共享内存,返回id
//0:只获取共享内存时指定为0
//shmflg0:取共享内存标识符,若不存在则函数会报错
int shmid = shmget(key, 0, 0);
if (shmid == -1) {
perror("shmget failed");
exit(1);
}
//映射共享内存,得到虚拟地址
void *p = shmat(shmid, 0, 0);
if (p == (void *)-1) {
perror("shmat failed");
exit(2);
}
//读取共享内存
int data1 = *(int *)p;
int data2 = *((int *)p + 1);
printf("从共享内存中读取了,%x 和 %x\n", data1, data2);
//解除映射
if(shmdt(p) == -1) {
perror("shmdt failed");
exit(3);
}
return 0;
}

复制代码

编译代码

clang -o shm_write shm_write.c

clang -o shm_read shm_read.c

复制代码

执行代码

./shm_write

./shm_read

复制代码