IPC - Unix - 共享内存

1、共享内存

共享内存允许两个或多个进程共享一给定的存储区,是最快的一种进程间通信机制。对于像管道和消息队列等通信方式,需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。共享内存系统仅在建立共享内存区域时需要系统调用;一旦建立共享内存,所有访问都可作为常规内存访问,无需借助内核

共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V(shm)共享内存机制实现。应用接口和原理很简单,内部机制复杂。共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。为了实现更安全通信,往往还与信号灯等同步机制共同使用。

C programming in the UNIX environment的编程手册,一般都会为进程间用共享内存的方法通信提供两组方法:由于POSIX标准比较通用,一般建议使用该标准定义的方法集。

1.      POSIX定义的 :shm_open、shm_unlink、ftruncate、fstat、mmap、munmap、msync、mprotect
2.      SYSTEM V定义的 :shmget、shmat、shmdt、shmctl、ftok

关于这两者的差异稍微提一下:

  1. 二者本质上是类似的,mmap可以看到文件的实体,而 shmget 对应的文件在交换分区上的 shm 文件系统内,无法直接 cat 查看

  2. 安全性:mmap 方式对应的真实文件,如果用户有权限即可查看,甚至删除
                  shmget 方式其实也一样,好了一层皮罢了(ipcrm -m …)

  3. 一致性:mmap 方式下各进程映射文件的相同部分可以共享内存
                  shmget 时各个进程共享同一片内存区
        不建议使用交叠的方式使用 mmap

  4. 持续性:进程挂了重启不丢失内容,二者都可以做到
                  机器挂了重启,mmap 可以不丢失内容(文件内保存了OS同步过的映像),而 shmget 会丢失

  5. 易用性:mmap 的接口会简单一些

  6. 通用性:posix 的 mmap 会相对广泛一些

  7. 其他:mmap在某些内核版本下会频繁读写磁盘,需要注意一下

  8. (1)mmap保存到实际硬盘,实际存储并没有反映到主存上。优点:储存量可以很大(多于主存)(这里一个问题,需要高手解答,会不会太多拷贝到主存里面???);缺点:进程间读取和写入速度要比主存的要慢。

    (2)shm保存到物理存储器(主存),实际的储存量直接反映到主存上。优点,进程间访问速度(读写)比磁盘要快;缺点,储存量不能非常大(多于主存)

    使用上看:如果分配的存储量不大,那么使用shm;如果存储量大,那么使用mmap。

  9. mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

  10. 1、mmap是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射。而对于shm而言,shm每个进程最终会映射到同一块物理内存。shm保存在物理内存,这样读写的速度要比磁盘要快,但是存储量不是特别大。
    2、相对于shm来说,mmap更加简单,调用更加方便,所以这也是大家都喜欢用的原因。
    3、另外mmap有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。

如果你担心会因误删文件导致 mmap 出错,那就用 shmget 吧,否则的话直接mmap就可以了,用起来简单一些

 

2、共享内存 - POSIX - shm_open、shm_unlink、ftruncate、fstat、mmap、munmap、msync、mprotect

实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

2.1 mmap

#include <unistd.h>
#include <sys/mman.h>

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);

函数说明:mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。
参数说明:

参数说明
start指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。
length代表将文件中多大的部分对应到内存。
prot 代表映射区域的保护方式,有下列组合:
  • PROT_EXEC  映射区域可被执行;
  • PROT_READ  映射区域可被读取;
  • PROT_WRITE  映射区域可被写入;
  • PROT_NONE  映射区域不能存取。
  • 由于只能映射已经打开的文件,所以这个权限位不能超出open函数指定的权限,比如说在open的时候指定为只读,那就不能在此时指定PORT_WRITE
flags会影响映射区域的各种特性:
  • MAP_FIXED  如果参数 start 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
  • MAP_SHARED  对应射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
  • MAP_PRIVATE  对应射区域的写入操作会产生一个映射文件的复制,即私人的"写入时复制" (copy on write)对此区域作的任何修改都不会写回原来的文件内容。
  • MAP_ANONYMOUS  建立匿名映射,此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
  • MAP_DENYWRITE  只允许对应射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
  • MAP_LOCKED  将映射区域锁定住,这表示该区域不会被置换(swap)。
在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
fdopen()返回的文件描述词,代表欲映射到内存的文件。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
offset文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页(PAGE_SIZE)大小的整数倍。

返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。
错误代码:

  • EBADF  参数fd 不是有效的文件描述词。
  • EACCES  存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED 则要有PROT_WRITE 以及该文件要能写入。
  • EINVAL  参数start、length 或offset 有一个不合法。
  • EAGAIN  文件被锁住,或是有太多内存被锁住。
  • ENOMEM  内存不足。

注意:匿名内存映射 : mmap调用时,flags设为MAP_SHARED | MAP_ANON,fd设为-1,offset设为0即可

2.2 msync

默认情况下,内核采用虚拟内存算法保持内存映射文件与内存映射区的同步,前提是指定了MAP_SHARED标志,但这种同步可能不是立即生效的,而是在随后某个时间进行。但有时候我们修改完数据并进行下一步操作之前,需要确认数据已经同步完成,这时可调用msync函数。

#include<unistd.h>
#include<sys/mman.h>

int msync ( void *addr , size_t len, int flags)

参数说明:

参数说明
addrmmap返回的数值
len指定映射区的长度,它需要与mmap中指定相同。
flags
  • MS_ASYNC  不要求内核做什么,让内核自主去执行同步,执行异步写,msync立即返回;
  • MS_SYNC  要求内核在返回之前把写操作完成,执行同步写,msync等同步完成才返回;
  • MS_INVALIDATE  是一个可选的标志,它告诉内核丢弃没有同步的部分,使高速缓存的数据失效;

MS_ASYNC 、MS_SYNC 必须指定其一

返回值:成功则返回0;失败则返回-1;多进程的内存

可能的错误:EBUSY/ EINVAL/ ENOMEM

2.3 mprotect 

mprotect()函数可以修改调用进程内存页的保护属性,如果调用进程尝试以违反保护属性的方式访问该内存,则内核会发出一个SIGSEGV信号给该进程。这个可以用来做多线程的内存保护功能。

#include<unistd.h>
#include<sys/mman.h>

int mprotect(void *addr, size_t len, int prot);

参数说明:

参数说明
addrmmap返回的数值
len指定映射区的长度,它需要与mmap中指定相同。
prot

可以取以下几个值,并可以用“|”将几个属性结合起来使用:

  • PROT_READ:内存段可读;
  • PROT_WRITE:内存段可写;
  • PROT_EXEC:内存段可执行;
  • PROT_NONE:内存段不可访问

返回值:成功则返回0;失败则返回-1;

可能的错误:

1)EACCES:无法设置内存段的保护属性。当通过 mmap(2) 映射一个文件为只读权限时,接着使用 mprotect() 标志为 PROT_WRITE这种情况就会发生。
2)EINVAL:addr不是有效指针,指向的不是某个内存页的开头,或者不是系统页大小的倍数。
3)ENOMEM:内核内部的结构体无法分配

如果调用进程内存访问行为侵犯了这些设置的保护属性,内核会为该进程产生 SIGSEGV (Segmentation fault,段错误)信号,并且终止该进程。

2.4 munmap

当进程结束或利用exec 相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述词时不会解除映射。注意:当映射关系解除后,对原来映射地址的访问将导致段错误发生。 

#include <unistd.h>       
#include <sys/mman.h>

int munmap(void *start, size_t length);

参数说明:

参数说明
start映射内存起始地址,mmap返回的数值
len指定映射区的长度,它需要与mmap中指定相同。

返回值:成功则返回0;失败则返回-1;

可能的错误:errno被设为以下的某个值   

EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区

2.5 shm_open和shm_unlink函数

shm_open用于创建一个新的Posix共享内存对象或打开一个已存在的Posix共享内存对象。
shm_unlink用于从系统中删除一个Posix共享内存对象。

//成功返回非负描述符,失败返回-1
int shm_open(const char *name, int oflag, mode_t mode);

//成功返回0,失败返回-1
int shm_unlink(const char *name);

shm_open 参数说明:注意:该函数和open函数类似

  • name:共享内存对象的名字,必须以/打头,并且后续不能有其它/ ,形如/somename长度不能超过NAME_MAX(255)
  • oflag:与open函数类似,可以是O_RDONLY、O_RDWR,还可以按位或上O_CREAT、O_EXCL、O_TRUNC等。oflag参数不能设置O_WRONLY标志
  • mode:和mq_open、sem_open不同,shm_open的mode参数总是必须指定,当指定了O_CREAT标志时,mode为用户权限位,否则将mode设为0

shm_open 返回值:shm_open的返回值是一个描述符,它随后用作mmap的第五个参数fd。

这边一并给出open的参数说明:int open(const char * pathname,  int flags,  mode_t mode)

参数 pathname 指向欲打开的文件路径字符串. 下列是参数flags 所能使用的旗标:
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件. 上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合.
O_CREAT 若欲打开的文件不存在则自动建立该文件.
O_EXCL 如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.
O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机.
O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失.
O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.
O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
O_NDELAY 同O_NONBLOCK.
O_SYNC 以同步的方式打开文件.
O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.
O_DIRECTORY 如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。注:此为Linux2. 2 以后特有的旗标, 以避免一些系统安全问题.

参数mode 则有下列数种组合, 只有在建立新文件时才会生效, 此外真正建文件时的权限会受到umask 值所影响, 因此该文件权限应该为 (mode-umaks).
S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限.
S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限.
S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限.
S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限.
S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限.
S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限.
S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限.
S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限.
S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限.
S_IROTH 00004 权限, 代表其他用户具有可读的权限
S_IWOTH 00002 权限, 代表其他用户具有可写入的权限.
S_IXOTH 00001 权限, 代表其他用户具有可执行的权限.
 

2.6 ftruncate和fstat函数

处理mmap的时候,普通文件或Posix共享内存对象的大小都可以通过调用ftruncate设置。

#include <unistd.h>

//成功返回0,失败返回-1
int ftruncate(int fd, off_t length):
  • 对于普通文件,若文件长度大于length,额外的数据会被丢弃;若文件长度小于length,则扩展文件大小到length
  • 对于Posix共享内存对象,ftruncate把该对象的大小设置成length字节

我们调用ftruncate来指定新创建的Posix共享内存对象大小,或者修改已存在的Posix共享内存对象大小。

  • 创建新的Posix共享内存对象时指定大小是必须的,否则访问mmap返回的地址会报bus error错误
  • 当打开一个已存在的Posix共享内存对象时,可以调用fstat来获取该对象的信息
#include <sys/stat.h>
#include <sys/types.h>

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

stat结构有12个或以上的成员,然而当fd指代一个Posix共享内存对象时,只有四个成员含有信息:

struct stat
{
    mode_t st_mode;  //用户访问权限
    uid_t  st_uid;   //user id of owner
    gid_t  st_gid;   //group id of owner
    off_t  st_size;  //文件大小
};

2.7 POSIX共享内存的方法

Posix.1提供了两种在任意进程间共享内存区的方法。

  • 内存映射IO:该方法其实也可以用于无亲缘关系进程间共享内存
  • Posix共享内存:这是Posix IPC的第三种机制

这两种技术都需要调用mmap,区别在于mmap参数fd的获取手段:

  • 内存映射IO通过open获得
  • Posix共享内存通过shm_open获得

3、共享内存 - SYSTEM V - shmat、shmctl、shmdt、shmget

 

 

 

 

 

共享内存应用中的问题及解决方法

https://www.ibm.com/developerworks/cn/aix/library/au-cn-sharemem/index.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值