共享内存区是可用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-------------------------