Posix共享内存区:
共享内存是最快的可用IPC形式。它允许多个不相关(无亲缘关系)的进程去访问同一部分逻辑内存。
如果需要在两个进程之间传输数据,共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核。这样就可以减少系统调用时间,提高程序效率。
共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。
要注意的是共享内存本身没有提供任何同步功能。也就是说,在第一个进程结束对共享内存的写操作之前,并没有什么自动功能能够预防第二个进程开始对它进行读操作。共享内存的访问同步问题必须由程序员负责。可选的同步方式有互斥锁、条件变量、读写锁、纪录锁、信号灯。
实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止。
相应的API如下:
shm_open 函数
功能:
用来创建或打开一个共享内存对象
原型:
int shm_open(const char* name,int oflag,mode_t mode);
参数:
name:共享内存对象的名字
oflag:与open 函数类似,可以是O_RDONLY,O_RDWR,还可以按位或上O_CREAT,O_EXCL,O_TRUNC等.
mode:此参数总是需要设置,如果oflag没有指定 O_CREAT, 可以指定为0;
返回值: 成功返回非负整数文件描述符;失败返回-1
ftruncate
功能:
修改共享内存对象大小
原型:
int ftruncate(int fd,off_t length);
参数:
fd:文件描述符
length:长度
返回值:
成功返回0,失败返回-1
fstat函数
功能:获取共享内存对象信息
原型:
int fstat(int fd,struct stat *buf);
参数:
fd: 文件描述符
buf: 返回共享内存状态。stat结构有12个或以上的成员,然而当fd指代一个共享内存区对象时。只有4个成员含有信息。
Struct stat
{
mode_t st_mode;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
}
返回值:
成功返回0,失败返回-1
shm_unlink函数
功能:删除一个共享内存对象
原型:
int shm_unlink(const char* name);
参数:
name:共享内存对象的名字
返回值:成功返回0,失败返回-1;
下面来看一个简单的测试程序:
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
typedef struct Content
{
char name[32];
unsigned char age;
}Con;
void Test1()
{
int shmid;
shmid=shm_open("myshm",O_CREAT | O_RDWR,0775);
if(shmid == -1)
printf("shmid error\n");
if(ftruncate(shmid,sizeof(Con)) == -1)
printf("ftruncate error\n");
struct stat buf;
if(fstat(shmid,&buf) == -1)
printf("stat error\n");
printf("size=%d,mode=%o\n",buf.st_size,buf.st_mode & 0777);
close(shmid);
}
测试结果:可以看到共享内存区的大小 已经和Content结构体一样大,都是33字节。
共享内存区创建的文件默认保存在/dev/shm路径中
root@zhf-maple:/dev/shm# ls -al
总用量 4
drwxrwxrwt 2 root root 80 5月 11 14:36 .
drwxr-xr-x 21 root root 4240 5月 11 14:13 ..
-rw-r----- 1 www-data www-data 4096 5月 11 14:13 mono.1538
-rwxrwxr-x 1 zhf zhf 36 5月 11 14:35 myshm
使用od -c查看myshm中的内容,可以看到全被初始化为0
root@zhf-maple:/dev/shm# od -c myshm
0000000 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0000040 \0 \0 \0 \0
0000044
下面来测试往共享内存区里面写数据,在这里要用到mmap函数
mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE
和
MAP_SHARED标志建立起来的文件映射,其st_ctime
和
st_mtime在对映射区写入之后,但在msync()通过MS_SYNC
和
MS_ASYNC两个标志调用之前会被更新。
void
*mmap(void *start, size_t length, int prot, int flags,
int
fd, off_t offset);
int
munmap(void *start, size_t length);
返回说明:
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void
*)-1],munmap返回-1。errno被设为以下的某个值
EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区
参数:
start:映射区的开始地址。
length:映射区的长度。
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC
//页内容可以被执行
PROT_READ
//页内容可以被读取
PROT_WRITE
//页可以被写入
PROT_NONE
//页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED
//使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED
//与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE
//建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE
//这个标志被忽略。
MAP_EXECUTABLE
//同上
MAP_NORESERVE
//不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED
//锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN
//用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS
//匿名映射,映射区不与任何文件关联。
MAP_ANON
//MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE
//兼容标志,被忽略。
MAP_32BIT
//将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE
//为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK
//仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
offset:被映射对象内容的起点。
2.
系统调用munmap()
int
munmap( void * addr, size_t len )
该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。
写入测试函数:
void
Shem_write()
{
int
shmid;
unsigned
char *ptr;
struct
stat stat;
shmid=shm_open("myshm",O_CREAT
| O_RDWR,0775);
if(shmid
== -1)
printf("shmid
error\n");
if(fstat(shmid,&stat)
== -1)
printf("stat
error\n");
ptr=mmap(NULL,stat.st_size,PROT_READ
| PROT_WRITE,MAP_SHARED,shmid,0);
close(shmid);
for(int
i=0;i<stat.st_size;i++)
{
*ptr++=i;
}
printf("%c\n",*ptr);
}
查看写入的结果:
root@zhf-maple:/dev/shm#
od -c myshm
0000000
\0 001 002 003 004 005 006 \a \b \t \n \v \f \r 016 017
0000021
020 021 022 023 024 025 026 027 030 031 032 033
共享内存区读取程序:
void
Shem_read()
{
int
i,shmid;
struct
stat stat;
unsigned
char *ptr;
shmid=shm_open("myshm",O_RDONLY,0775);
fstat(shmid,&stat);
ptr=mmap(NULL,stat.st_size,PROT_READ,MAP_SHARED,shmid,0);
close(shmid);
for(i=0;i<stat.st_size;i++)
{
printf("value=%d\n",*(ptr+i));
}
}
执行结果:
如果我们想结果转换为结构体的形式,函数可以更新如下,将ptr赋值给Con结构体
void Shem_read()
{
int i,shmid;
struct stat stat;
void *ptr;
unsigned char *tmp;
Con *c;
shmid=shm_open("myshm",O_RDONLY,0775);
fstat(shmid,&stat);
ptr=mmap(NULL,stat.st_size,PROT_READ,MAP_SHARED,shmid,0);
close(shmid);
tmp=(unsigned char *)ptr;
for(i=0;i<stat.st_size;i++)
{
printf("value=%d\n",*(tmp+i));
}
c=(Con *)ptr;
printf("age=%d",c->age);
}
可以看到age的数值等于最后一个字节的值,也就是32.