共享内存

本文深入探讨了共享内存的概念,包括内存映射文件和System V IPC。介绍了如何通过mmap创建匿名内存映射,利用/dev/zero设备文件,以及对映射区大小与文件大小关系的实验验证。同时,详细阐述了System V共享内存区对象的创建、管理及限制,如shmget、shmat、shmdt等函数的使用和权限设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

共享内存区是可用IPC形式中最快的,一旦这样的内存区映射到共享它的进程的地址空间,这些

进程间数据的传递就不再涉及内核。在另一篇文章点击打开链接中已经提到了,

mmap的内部机制。少了用户空间到内核空间的数据复制,内核的缺页故障,触发中断处理程序,

通过映射关系在磁盘读取数据。

mmap,munmap,msync函数之前的文章已经说过了,这里增加一些新的内容:

1>  4.4SBD匿名内存映射。它彻底避免了文件的创建和打开。其办法是吧mmap的flags参数置为

MAP_SHARED | MAP_ANON。把fd参数指定为-1.offset参数则被忽略。

这样的内存区初始化为0,如下:

mmap(NULL,PASGSIZE,PROT_READ | PROT_WRITE | PROT_EXEC,MAP_SHARED,-1,0);

2> SVR4提供/dev/zero设备文件,我们open它之后可在mmap调用中使用得到的描述符。从该设备

读时返回的字节全为0,写往该设备的所有字节则被丢弃。

fd=open("/dev/zero",O_RDWR);

ptr=mmap(NULL,PAGESIZE,PROT_WRITE | PROT_READ,MAP_SHARED,fd,0);

//这个不是很好理解,用的也比较少,日后再细细研究吧。

---------------------------------------------------------------------------------------------------------

上一个mmap的文章里也提到了关于文件大小和映射区大小的关系,当时只是复制了

他人的文章,现在手动重新验证下:

#include <iostream>
#include <unistd.h>
#include <cstdio>
#include<sys/stat.h>
#include<fcntl.h>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/mman.h>
#include <errno.h>
using namespace std;
#define FILE_MODE 511
#define PAGESIZE (sysconf(_SC_PAGESIZE))

int main(int argc,char ** argv)
{
	//命令行参数文件大小+映射区大小+文件名
	char c;
	int fsize,msize;
	int fd;
	int flag=0;
	char filename[256]={0};
	char  sBuf[256]={0};
	while( ( c = getopt(argc,argv,"f:m:d:a") )!= -1 )
	{
		switch(c)
		{
			case 'f':
				{
					printf(":%s\n",optarg);
					fsize=atoi(optarg);					
					break;
				}
			case 'm':
				{
					printf(":%s\n",optarg);
					msize=atoi(optarg);
					break;
				}
			case 'd':
					unlink(optarg);
					cout << "unlink mmap" << endl;
					exit(0);
					break;
			case 'a':
					flag=1;
					cout << "文件自增" << endl;
					break;
					
		}
	}
	if(optind != (argc -1 ) )
		perror("optind");
	memcpy(filename,argv[argc-1],sizeof(filename));
	if( ( fd= open(filename,O_CREAT | O_RDWR | O_TRUNC ,FILE_MODE)) < 0)
		perror("open file");
	//ftruncate(fd,fsize);
	cout << fsize << "--" <<  msize << endl;
	char *ptr=(char*)mmap(NULL,msize,PROT_READ | PROT_WRITE ,MAP_SHARED,fd,0);
	int max=(fsize > msize ? fsize : msize);
	for(int i=0;i < max;i+=PAGESIZE)
	{
		if(flag==1)
		{
			cout << "size >> " <<  i+PAGESIZE-1  <<  endl;
			ftruncate(fd,i+PAGESIZE-1);
			sleep(1);
		}
		ptr[i]='1';
		cout << "ptr[" << i << "]=" << ptr[i] << "\t\n";
		ptr[i+PAGESIZE-1]='1';
		cout << "ptr[" << i+PAGESIZE-1 << "]=" << ptr[i+PAGESIZE-1] << "\t\n";
	}
	return 0;
}

分析:

fsize:5000    msize:5000

0-8191这2页是实际映射区,范围内是可以读写的,但是5000-8191范围内读写并不体现在文件内。文件大小始终是5000

书上说是返回段错误(segmentation),我这里没出错,但不影响结论

fsize:5000   msize:15000

看图吧:

SIGSEGV(分段错误),SIGBUS(总线错误)

可以看出,内核知道被映射的底层支撑对象的大小。即使该对象的描述符别关闭也一样。内核允许我们给mmap

指定一个大于该对象的大小参数,但我们访问不了该对象以远的部分。

fsize:0     msize:15000+

文件的大小一直递增,只要在映射区大小以内,就可以访问,共享的效果也在。所以以后可以先确定映射区的大小,

设定一个较大的值,文件大小后期根据需要变化就行了

-----------------------------------------------------------------------------------------------------------------------------------------------------

以上是posix内存映射的第一种:内存映射文件

下面讨论第二种:共享内存区对象

#include <sys/mman.h>

int shm_open(const char * name,int oflag,mode_t mode);
                                               //成功则为非负描述符,失败返回-1
int shm_unlink(const char * name);
                                               //成功返回0,出错则为-1
shm_open  --.> open

shm_unlink --> unlink

需要注意的是:

1> oflag必须含有O_RDONLY只读标志。(读写也行)

2> mode必须显示的指定一个值,如没有O_CREAT,mode则为0

--------------------ftruncate---------fstat------------------------------

#include <unistd.h>

int ftruncate(int fd,off_t length);

//成功返回0,出错返回-1
posix就该函数对普通文件和共享内存区对象处理的定义不同:

1> 对于一个普通文件,如果该文件的大小大于length参数,额外的数据就会被丢弃掉。如果改文

件的大小小于lenhth,那么改文件是否修改以及是否增长是未加说明的。

实际上对于一个普通文件,扩展大小的可移植方法是,先lseek到偏移到length-1处,然后write一个

字节的数据。所幸的是,几乎所有的unix实现都支持ftruncate扩展一个文件

2>对于一个内存区对象,ftruncate吧该对象得大小设置成length字节

注意:posix.1并没有制定一个新创建的共享内存区对象的初始内容。这有可能成为一个安全漏洞。

#include <sys/types.h>
#include <sys/stat.h>

int fstat(int fd,struct stat * buf);
//成功返回0,出错返回-1

struct stat{
...
mode_t st_mode; //FILE_MODE
uid_t st_uid;  //属主id
gid_t st_gid;  //属主组id
off_t st_size; //大小
...
}

第一次使用,上手测一下:

------------------------------------------system V 共享内存------------------------------------------

#include <sys/shm.h>
int shmget(key_t key,size_t size,int oflag);
//成功返回共享内存区对象,出错返回-1

void * shmat(int shmid,const void * shmaddr,int flag);
//成功返回映射区的起始地址,出错返回-1

int shmdt(const void * shmaddr);
//成功返回0,出错返回-1

int shmctl(int shmid,int cmd,struct shmid_ds *buff);
//成功返回0,出错返回-1

key_t键

三种类型的system V IPC使用key_t值作为它们的名字。头文件<sys/types.h>吧key_t

这个数据类型定义为一个整数,它通常是一个至少32位的整数。这些整数值通常是由

ftok函数赋予的。

#include <sys/ipc.h>

key_t ftok(const *pathname, int id);
//成功返回ipc键,失败为-1
ftok函数的典型实现是调用stat函数,然后组合以下三个值;

1> pathname所在的文件系统的信息(stat结构的st_dev成员)

2> 该文件在本文件系统内的索引节点号(stat结构的st_ino成员)

3> id的低序8位(不能为0)

注意:指定一个key为IPC_PRIVATE能保证创建一个唯一的对象。没有一对ID和PATHNAME组合

会导致ftok产生IPC_PRIVATE这个键值。

shmget得oflag参数包含了posix下的oflag | mode,并且system V下是这样的:IPC_CREAT而不是

ftok函数的第一个参数是一个已经存在的路径名

O_CREAT。

shmget功能上类似于open/shm_open

shmat功能上类似于mmap

shmdt功能上类似于close  //当一个进程终止时,它当前附接着的所有共享内存区都自动断接掉,并不删除

shmctr  包含unlink功能

shmctl的cmd参数有如下的三个值:

1> IPC_RMID 从系统上删除有shmid标识的共享内存区并拆除它。  //类似于unlink

//删除一个共享内存区指的是使其标识符失效,这样以后针对该标识符的shmat,shmdt和shmctl函数调用

必定失败。拆除一个共享内存区指的是释放或回收与它对应的数据结构,包括删除存放在其上的数据。拆除操作

要到该共享内存区的引用计数变为0时才进行。另外当某个shmat调用发现所指定的共享内存区的引用计数变为0

时也顺便删除它。联想string的浅拷贝,写时复制的析构函数的计数器减一,当到了0时才真的删除数据,回收

空间

2> IPC_SET 给所指定的共享内存区设置其shmid_ds结构的以下三个成员,shm_perm.uid,shm_perm.gid,

shh_perm.mode.它们的值来自于buff参数指向的结构体中的相应成员。shm_ctime的值也用当前时间替换

3> IPC_STAT (通过buff参数)向调用者返回所指定共享内存区当前的shmid_ds结构

对于每一个共享内存区,内核维护如下的信息结构,它定义在 <sys/shm.h>头文件中

struct shmid_ds{
   struct  ipc_perm  shm_perm; //成员储存了共享内存对象的存取权限及其它一些信息。
   size_t  shm_segsz;//成员定义了共享的内存大小(以字节为单位)。
   pid_t   shm_lpid; //成员保存了最近一次连接共享内存的进程的 pid
   pid_t   shm_cpid; //成员保存了创建共享内存的进程的 pid
   shmatt_t  shm_nattch;  //成员保存了与共享内存连接的进程数目
   shmat_t   shm_cnattch;
   time_t    shm_atime; //成员保存了最近一次进程连接共享内存的时间
   time_t    shm_dtime; //成员保存了最近一次进程断开与共享内存的连接的时间。
   time_t    shm_ctime; //成员保存了最近一次 shmid_ds 结构内容改变的时
};

内核给每个IPC对象维护一个信息结构,其内容和内核给文件维护的信息类似。
struct ipc_perm{
  uid_t     uid; //属主用户id
  gid_t     gid; //属主组id
  uid_t     cuid; //创建者用户id
  gid_t     cgid; //创建者组id
  mode_t     mode; //读写权限
  ulong_t    seq; //计数器
  key_t      key; //键值
};

共享内存区限制:

shmmax     一个共享内存区的最大字节数

shmmnb   一个共享内存区的最小字节数

shmmni    系统范围内最大共享内存区标识符数

shmseg   每个进程附接的最大共享内存区数


代码实践:

#include <iostream>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
using namespace std;

int main(int argc,char ** argv)
{
	key_t key;
	system("ipcs -m");
	shmctl(589824,IPC_RMID,NULL);
	shmctl(622593,IPC_RMID,NULL);
	cout << IPC_PRIVATE << endl;
	key = ftok("/usr/wang/26/shmmap.c",0x01);
	cout << "key=" << key << endl;
	int id=shmget(key,2000,IPC_CREAT |  0600);
	if(id < 0)
		cout << "id=" << id << endl;
	char * ptr=(char * )shmat(id,NULL,0);
	pid_t pid;
	if( ( pid = fork() ) == 0 )
	{
		memcpy(ptr,argv[1],strlen(argv[1]));
		cout << "child writed!" << endl;
		sleep(3);
		cout << "child exit!" << endl;
		exit(0);
	}
	sleep(3);
	cout << "father reading ..." << endl;
	cout << ptr << endl;
	waitpid(pid,NULL,0);
	system("ipcs -m");
	shmdt(ptr);
	cout << "shmdt..." << endl;
	system("ipcs -m");
	sleep(100);
	return 0;
}	


-------------------------------------ending-------------------------


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值