精华内容
下载资源
问答
  • 自己整理的Linux5个共享内存头文件, 通过编写共享内存实验,进一步了解使用共享内存的具体步骤,同时加深对共享内存的理解。在本实验中,采用信号量作为同步机制完善两个进程(“生产者”和“消费者”)之间的通信。
  • 【需求描述】 1、共享内存保存信息 2、提供接口写入共享内存 3、提供接口获取共享内存 【编写语言:C】 【环境:linux】 1、写入内存一千万条数据 耗时:5.356秒 2、读取内存一千万条数据 耗时:1.449秒
  • 共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。 早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU可以在各个进程访问到...

    共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。

    早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU可以在各个进程访问到这片内存。

    现阶段广泛应用于多媒体、Graphics领域的共享内存方式,某种意义上不再强调映射到进程虚拟地址空间的概念(那无非是为了让CPU访问),而更强调以某种“句柄”的形式,让大家知道某一片视频、图形图像数据的存在并可以借助此“句柄”来跨进程引用这片内存,让视频encoder、decoder、GPU等可以跨进程访问内存。所以不同进程用的加速硬件其实是不同的,他们更在乎的是可以通过一个handle拿到这片内存,而不再特别在乎CPU访问它的虚拟地址(当然仍然可以映射到进程的虚拟地址空间供CPU访问)。

    只要内存的拷贝(memcpy)仍然是一个占据内存带宽、CPU利用率的消耗大户存在,共享内存作为Linux进程间通信、计算机系统里各个不同硬件组件通信的最高效方法,都将持续繁荣。关于内存拷贝会大多程度地占据CPU利用率,这个可以最简单地尝试拷贝1080P,帧率每秒60的电影画面,我保证你的系统的CPU,蛋会疼地不行。

    我早就想系统地写一篇综述Linux里面各种共享内存方式的文章了,但是一直被带娃这个事业牵绊,今日我决定顶着娃娃们的山呼海啸,也要写一篇文章不吐不快。

    共享内存的方式有很多种,目前主流的方式仍然有:

    共享内存的方式

    1.基于传统SYS V的共享内存;

    2.基于POSIX mmap文件映射实现共享内存;

    3.通过memfd_create()和fd跨进程共享实现共享内存;

    4.多媒体、图形领域广泛使用的基于dma-buf的共享内存。

    SYS V共享内存

    历史悠久、年代久远、API怪异,对应内核代码linux/ipc/shm.c,当你编译内核的时候不选择CONFIG_SYSVIPC,则不再具备此能力。

    你在Linux敲ipcs命令看到的share memory就是这种共享内存:

    下面写一个最简单的程序来看共享内存的写端sw.c:

    以及共享内存的读端sr.c:

    编译和准备运行:

    在此之前我们看一下系统的free:

    下面运行sw和sr:

    我们发现sr打印出来的和sw写进去的是一致的。这个时候我们再看下free:

    可以看到used显著增大了(711632 -> 715908), shared显著地增大了(2264 -> 6360),而cached这一列也显著地增大326604->330716。

    我们都知道cached这一列统计的是file-backed的文件的page cache的大小。理论上,共享内存属于匿名页,但是由于这里面有个非常特殊的tmpfs(/dev/shm指向/run/shm,/run/shm则mount为tmpfs):

    所以可以看出tmpfs的东西其实真的是有点含混:我们可以理解它为file-backed的匿名页(anonymous page),有点类似女声中的周深。前面我们反复强调,匿名页是没有文件背景的,这样当进行内存交换的时候,是与swap分区交换。磁盘文件系统里面的东西在内存的副本是file-backed的页面,所以不存在与swap分区交换的问题。但是tmpfs里面的东西,真的是在统计意义上统计到page cache了,但是它并没有真实的磁盘背景,这又和你访问磁盘文件系统里面的文件产生的page cache有本质的区别。所以,它是真地有那么一点misc的感觉,凡事都没有绝对,唯有变化本身是不变的。

    也可以通过ipcs找到新创建的SYS V共享内存:

    POSIX共享内存

    我对POSIX shm_open()、mmap () API系列的共享内存的喜爱,远远超过SYS V 100倍。原谅我就是一个懒惰的人,我就是讨厌ftok、shmget、shmat、shmdt这样的API。

    上面的程序如果用POSIX的写法,可以简化成写端psw.c:

    读端:

    编译和执行:

    这样我们会在/dev/shm/、/run/shm下面看到一个文件:

    坦白讲,mmap、munmap这样的API让我找到了回家的感觉,刚入行做Linux的时候,写好framebuffer驱动后,就是把/dev/fb0 mmap到用户空间来操作,所以mmap这样的 API,真的是特别亲切,像亲人一样。

    当然,如果你不喜欢shm_open()这个API,你也可以用常规的open来打开文件,然后进行mmap。关键的是mmap,wikipedia如是说:

    mmap

    In computing, mmap(2) is a POSIX-compliant Unix system call that maps files or devices into memory. It is a method of memory-mapped file I/O. It implements demand paging, because file contents are not read from disk directly and initially do not use physical RAM at all. The actual reads from disk are performed in a "lazy" manner, after a specific location is accessed. After the memory is no longer needed, it is important to munmap(2) the pointers to it. Protection information can be managed using mprotect(2), and special treatment can be enforced using madvise(2).

    POSIX的共享内存,仍然符合我们前面说的tmpfs的特点,在运行了sw,sr后,再运行psw和psr,我们发现free命令再次戏剧性变化:

    请将这个free命令的结果与前2次的free结果的各个字段进行对照:

    第3次比第2次的cached大了这么多?是因为我编写这篇文章边在访问磁盘里面的文件,当然POSIX的这个共享内存本身也导致cached增大了。

    memfd_create

    如果说POSIX的mmap让我找到回家的感觉,那么memfd_create()则是万般惊艳。见过这种API,才知道什么叫天生尤物——而且是尤物中的尤物,它完全属于那种让码农第一眼看到就会两眼充血,恨不得眼珠子夺眶而出贴到它身上去的那种API;一般人见到它第一次,都会忽略了它的长相,因为它的身材实在太火辣太抢眼了。

    先不要浮想联翩,在所有的所有开始之前,我们要先提一下跨进程分享fd(文件描述符,对应我们很多时候说的“句柄”)这个重要的概念。

    众所周知,Linux的fd属于一个进程级别的东西。进入每个进程的/proc/pid/fd可以看到它的fd的列表:

    这个进程的0,1,2和那个进程的0,1,2不是一回事。

    某年某月的某一天,人们发现,一个进程其实想访问另外一个进程的fd。当然,这只是目的不是手段。比如进程A有2个fd指向2片内存,如果进程B可以拿到这2个fd,其实就可以透过这2个fd访问到这2片内存。这个fd某种意义上充当了一个中间媒介的作用。有人说,那还不简单吗,如果进程A:

    fd = open();

    open()如果返回100,把这个100告诉进程B不就可以了吗,进程B访问这个100就可以了。这说明你还是没搞明白fd是一个进程内部的东西,是不能跨进程的概念。你的100和我的100,不是一个东西。这些基本的东西你搞不明白,你搞别的都是白搭。

    Linux提供一个特殊的方法,可以把一个进程的fd甩锅、踢皮球给另外一个进程(其实“甩锅”这个词用在这里不合适,因为“甩锅”是一种推卸,而fd的传递是一种分享)。我特码一直想把我的bug甩(分)锅(享)出去,却发现总是被人把bug甩锅过来。

    那么如何甩(分)锅(享)fd呢?

    Linux里面的甩锅需要借助cmsg,用于在socket上传递控制消息(也称Ancillary data),使用SCM_RIGHTS,进程可以透过UNIX Socket把一个或者多个fd(file descriptor)传递给另外一个进程。

    比如下面的这个函数,可以透过socket把fds指向的n个fd发送给另外一个进程:

    而另外一个进程,则可以透过如下函数接受这个fd:

    那么问题来了,如果在进程A中有一个文件的fd是100,发送给进程B后,它还是100吗?不能这么简单地理解,fd本身是一个进程级别的概念,每个进程有自己的fd的列表,比如进程B收到进程A的fd的时候,进程B自身fd空间里面自己的前面200个fd都已经被占用了,那么进程B接受到的fd就可能是201。数字本身在Linux的fd里面真地是一点都不重要,除了几个特殊的0,1,2这样的数字外。同样的,如果你把 cat /proc/interrupts 显示出的中断号就看成是硬件里面的中断偏移号码(比如ARM GIC里某号硬件中断),你会发现,这个关系整个是一个瞎扯。

    知道了甩锅API,那么重要的是,当它与memfd_create()结合的时候,我们准备甩出去的fd是怎么来?它是memfd_create()的返回值。

    memfd_create()这个函数的玄妙之处在于它会返回一个“匿名”内存“文件”的fd,而它本身并没有实体的文件系统路径,其典型用法如下:

    我们透过memfd_create()创建了一个“文件”,但是它实际映射到一片内存,而且在/xxx/yyy/zzz这样的文件系统下没有路径!没有路径!没有路径!

    所以,当你在Linux里面编程的时候,碰到这样的场景:需要一个fd,当成文件一样操作,但是又不需要真实地位于文件系统,那么,就请立即使用memfd_create()吧,它的manual page是这样描述的:

    memfd_create

    memfd_create() creates an anonymous file and returns a file descriptor that refers to it. The file behaves like a regular file, and so can be modified, truncated, memory-mapped, and so on. However, unlike a regular file, it lives in RAM and has a volatile backing storage.

     

    重点理解其中的regular这个单词。它的行动像一个regular的文件,但是它的背景却不regular。

    那么,它和前面我们说的透过UNIX Socket甩锅fd又有什么关系呢?memfd_create()得到了fd,它在行为上类似规则的fd,所以也可以透过socket来进行甩锅,这样A进程相当于把一片与fd对应的内存,分享给了进程B。

    下面的代码进程A通过memfd_create()创建了2片4MB的内存,并且透过socket(路径/tmp/fd-pass.socket)发送给进程B这2片内存对应的fd:

    下面的代码进程B透过相同的socket接受这2片内存对应的fd,之后通过read()读取每个文件的前256个字节并打印:

    上述代码参考了:

    https://openforums.wordpress.com/2016/08/07/open-file-descriptor-passing-over-unix-domain-sockets/

    上述的代码中,进程B是在进行read(fds[i], buffer, sizeof(buffer)),这体现了基于fd进行操作的regular特点。当然,如果是共享内存,现实的代码肯定还是多半会是mmap:

    mmap(NULL, SIZE, PROT_READ, MAP_SHARED, fd, 0);

    那么,透过socket发送memfd_create() fd来进行进程间共享内存这种方法,它究竟惊艳在哪里?

    我认为首要的惊艳之处在于编程模型的惊艳。API简单、灵活、通用。进程之间想共享几片内存共享几片内存,想怎么共享怎么共享,想共享给谁共享给谁,无非是多了几个fd和socket的传递过程。比如,我从互联网上面收到了jpeg的视频码流,一帧帧的画面,进程A可以创建多片buffer来缓存画面,然后就可以透过把每片buffer对应的fd,递交给另外的进程去解码等。Avenue to Jane(大道至简),简单的才是最好的!

    memfd_create()的另外一个惊艳之处在于支持“封印”(sealing,就是你玩游戏的时候的封印),sealing这个单词本身的意思是封条,在这个场景下,我更愿意把它翻译为“封印”。中国传说中的封印,多是采用如五行、太极、八卦等手段,并可有例如符咒、法器等物品的辅助。现指对某个单位施加一种力量,使其无法正常使用某些能力的本领(常出现于玄幻及神魔类作品,游戏中)。我这一生,最喜欢玩的游戏就是《仙剑奇侠传》和《轩辕剑——天之痕》,不知道是否暴露年龄了。

    采用memfd_create()的场景下,我们同样可以用某种法器,来控制共享内存的shrink、grow和write。最初的设想可以详见File Sealing & memfd_create()这篇文章:

    https://lwn.net/Articles/591108/

    我们如果在共享内存上施加了这样的封印,则可以限制对此片区域的ftruncate、write等动作,并建立某种意义上进程之间的相互信任,这是不是很拉风?

    还记得镇压孙悟空的五行山顶的封印吗?还记得孙悟空的紧箍咒吗?还记得悟空每次离开师傅的时候在师傅周围画的一个圈吗?

    封印

    * SEAL_SHRINK: If set, the inode size cannot be reduced * SEAL_GROW: If set, the inode size cannot be increased * SEAL_WRITE: If set, the file content cannot be modified

    File Sealing & memfd_create()文中举到的一个典型使用场景是,如果graphics client把它与graphics compoistor共享的内存交给compoistor去render,compoistor必须保证可以拿到这片内存。这里面的风险是client可能透过ftruncate()把这个memory shrink小,这样compositor就拿不到完整的buffer,会造成crash。所以compositor只愿意接受含有SEAL_SHRINK封印的fd,如果没有,对不起,我们不能一起去西天取经。

    在支持memfd_create()后,我们应尽可能地使用这种方式来替代传统的POSIX和SYS V,基本它也是一个趋势,比如我们在wayland相关项目中能看到这样的patch:

    dma_buf

    dma_buf定义

    The DMABUF framework provides a generic method for sharing buffers between multiple devices. Device drivers that support DMABUF can export a DMA buffer to userspace as a file descriptor (known as the exporter role), import a DMA buffer from userspace using a file descriptor previously exported for a different or the same device (known as the importer role), or both.

     

    简单地来说,dma_buf可以实现buffer在多个设备的共享,应用可以把一片底层驱动A的buffer导出到用户空间成为一个fd,也可以把fd导入到底层驱动 B。当然,如果进行mmap()得到虚拟地址,CPU也是可以在用户空间访问到已经获得用户空间虚拟地址的底层buffer的。

    上图中,进程A访问设备A并获得其使用的buffer的fd,之后通过socket把fd发送给进程B,而后进程B导入fd到设备B,B获得对设备A中的buffer的共享访问。如果CPU也需要在用户态访问这片buffer,则进行了mmap()动作。

    为什么我们要共享DMA buffer?想象一个场景:你要把你的屏幕framebuffer的内容透过gstreamer多媒体组件的服务,变成h264的视频码流,广播到网络上面,变成流媒体播放。在这个场景中,我们就想尽一切可能的避免内存拷贝

    技术上,管理framebuffer的驱动可以把这片buffer在底层实现为dma_buf,然后graphics compositor给这片buffer映射出来一个fd,之后透过socket发送fd 把这篇内存交给gstreamer相关的进程,如果gstreamer相关的“color space硬件转换”组件、“H264编码硬件组件”可以透过收到的fd还原出这些dma_buf的地址,则可以进行直接的加速操作了。比如color space透过接收到的fd1还原出framebuffer的地址,然后把转化的结果放到另外一片dma_buf,之后fd2对应这片YUV buffer被共享给h264编码器,h264编码器又透过fd2还原出YUV buffer的地址。

    这里面的核心点就是fd只是充当了一个“句柄”,用户进程和设备驱动透过fd最终寻找到底层的dma_buf,实现buffer在进程和硬件加速组件之间的zero-copy,这里面唯一进行了exchange的就是fd。

    再比如,如果把方向反过来,gstreamer从网络上收到了视频流,把它透过一系列动作转换为一片RGB的buffer,那么这片RGB的buffer最终还要在graphics compositor里面渲染到屏幕上,我们也需要透过dma_buf实现内存在video的decoder相关组件与GPU组件的共享。

    Linux内核的V4L2驱动(encoder、decoder多采用此种驱动)、DRM(Direct Rendering Manager,framebuffer/GPU相关)等都支持dma_buf。比如在DRM之上,进程可以透过

    int drmPrimeHandleToFD(int fd,
    uint32_t handle,
    uint32_t flags,
    int * prime_fd 
    )

    获得底层framebuffer对应的fd。如果这个fd被分享给gstreamer相关进程的video的color space转换,而color space转换硬件组件又被实现为一个V4L2驱动,则我们可以透过V4L2提供的如下接口,将这片buffer提供给V4L2驱动供其导入:

    如果是multi plane的话,则需要导入多个fd:

    相关细节可以参考这个文档:

    https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dmabuf.html

    一切都是文件不是文件创造条件也要把它变成文件!这就是Linux的世界观。是不是文件不重要,关键是你得觉得它是个文件。在dma_buf的场景下,fd这个东西,纯粹就是个"句柄",方便大家通过这么一个fd能够对应到最终硬件需要访问的buffer。所以,透过fd的分享和传递,实际实现跨进程、跨设备(包括CPU)的内存共享。

    如果说前面的SYS V、POSIX、memfd_create()更加强调内存在进程间的共享,那么dma_buf则更加强调内存在设备间的共享,它未必需要跨进程。比如:

    有的童鞋说,为嘛在一个进程里面设备A和B共享内存还需要fd来倒腾一遍呢?我直接设备A驱动弄个全局变量存buffer的物理地址,设备B的驱动访问这个全局变量不就好了吗?我只能说,你对Linux内核的只提供机制不提供策略,以及软件工程每个模块各司其责,高内聚和低耦合的理解,还停留在裸奔的阶段。在没有dma_buf等类似机制的情况下,如果用户空间仍然负责构建策略并连接设备A和B,人们为了追求代码的干净,往往要进行这样的内存拷贝:

    dma_buf的支持依赖于驱动层是否实现了相关的callbacks。比如在v4l2驱动中,v4l2驱动支持把dma_buf导出(前面讲了v4l2也支持dma_buf的导入,关键看数据方向),它的代码体现在:

    drivers/media/common/videobuf2/videobuf2-dma-contig.c中的:

    其中的vb2_dc_dmabuf_ops是一个struct dma_buf_ops,它含有多个成员函数:

    当用户call VIDIOC_EXPBUF这个IOCTL的时候,可以把dma_buf转化为fd:

    int ioctl(int fd, VIDIOC_EXPBUF, struct v4l2_exportbuffer *argp);

    对应着驱动层的代码则会调用dma_buf_fd():

    应用程序可以通过如下方式拿到底层的dma_buf的fd:

    dma_buf的导入侧设备驱动,则会用到如下这些API:

    dma_buf_attach()
    dma_buf_map_attachment()
    dma_buf_unmap_attachment()
    dma_buf_detach()

    下面这张表,是笔者对这几种共享内存方式总的归纳:

    落花满天蔽月光,借一杯附荐凤台上。

    全剧终

     

    本文分享自微信公众号 - Linux阅码场(LinuxDev),作者:宋宝华

    原始发表时间:2019-12-09

     

    https://cloud.tencent.com/developer/article/1551288

     

    https://segmentfault.com/a/1190000021126368?utm_source=tag-newest

    展开全文
  • linux 共享内存简单使用
  • 在使用共享内存的程序异常退出时,由于没有释放掉共享内存,在调试时会出现错误。您可以使用shell命令来查看与释放已经分配的共享内存,下面将详细说明如何进行查看和释放分配的共享内存的方法。
  • linux存储--共享内存机制shm(十三)

    千次阅读 2020-07-02 11:42:40
    实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号...

    实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号量之类的手段来配合。

    共享内存原理与shm系统

    共享内存,顾名思义,就是两个或多个进程都可以访问的同一块内存空间,一个进程对这块空间内容的修改可为其他参与通信的进程所看到的。

    显然,为了达到这个目的,就需要做两件事:一件是在内存划出一块区域来作为共享区;另一件是把这个区域映射到参与通信的各个进程空间。

    通常在内存划出一个区域的方法是,在内存中打开一个文件,若通过系统调用mmap()把这个文件所占用的内存空间映射到参与通信的各个进程地址空间,则这些进程就都可以看到这个共享区域,进而实现进程间的通信。

    为了方便,再把mmap()的原理简述如下:

    mmap()原型如下:

    void * mmap(void *start, size_t len, int prot, int flags, int fd, off_t offset);
    

    其中,参数fd用来指定被映射的文件;offset指定映射的起始位置偏移量(通常为0);len指定文件被映射部分的长度;start用来指定映射到虚地址空间的起始位置(通常为NULL,即由系统确定)。

    mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

    mmap()映射过程示意图如下所示:
    在这里插入图片描述
    那么mmap是怎么形成这个文件映射过程呢?

    mmap本身其实是一个很简单的操作,在进程页表中添加一个页表项,该页表项是物理内存的地址。调用mmap的时候,内核会在该进程的地址空间的映射区域查找一块满足需求的空间用于映射该文件,然后生成该虚拟地址的页表项,改页表项此时的有效位(标志是否已经在物理内存中)为0,页表项的内容是文件的磁盘地址,此时mmap的任务已经完成。

    简而言之,就是在进程对应的虚存段添加一个段,也就是创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。在创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,引发缺页异常,内核进行请页。

    IPC的共享内存通信方式与上面的mmap()方式极为相似,但因为建立一个文件的目的仅是为了通信,于是这种文件没有永久保存的意义,因此IPC并没有使用正规的文件系统,而是在系统初始化时在磁盘交换区建立了一个专门用来实现共享内存的特殊临时文件系统shm,当系统断电后,其中的文件会全部自行销毁。

    Linux共享内存结构

    Linux的一个共享内存区由多个共享段组成。用来描述共享内存段的内核数据结构shmid_kernel如下:

    struct shmid_kernel /* private to the kernel */
    {	
    	struct kern_ipc_perm	shm_perm;        //描述进程间通信许可的结构
    	struct file *		shm_file;            //指向共享内存文件的指针
    	unsigned long		shm_nattch;            //挂接到本段共享内存的进程数
    	unsigned long		shm_segsz;            //段大小
    	time_t			shm_atim;            //最后挂接时间
    	time_t			shm_dtim;            //最后解除挂接时间
    	time_t			shm_ctim;            //最后变化时间
    	pid_t			shm_cprid;            //创建进程的PID
    	pid_t			shm_lprid;            //最后使用进程的PID
    	struct user_struct	*mlock_user;
    };
    

    shmid_kernel中最重要的域是指针shm_file,它指向临时文件file对象。当进程需要使用这个文件进行通信时,由内核负责将其映射到用户地址空间。

    为了便于管理,内核把共享内存区的所有描述结构shmid_kernel都存放在结构ipc_id_ary中的一个数组中。结构ipc_id_ary的定义如下:

    struct ipc_id_ary
    {
            int size;
            struct kern_ipc_perm *p[0];            //存放段描述结构的数组
    };
    

    同样,为了描述一个共享内存区的概貌,内核使用了数据结构ipc_ids。该结构的定义如下:

    struct ipc_ids {
    	int in_use;
    	unsigned short seq;
    	unsigned short seq_max;
    	struct rw_semaphore rw_mutex;
    	struct idr ipcs_idr;
            struct ipc_id_ary *entries;        //指向struct ipc_id_ary的指针
    };
    

    由多个共享段组成的共享区的结构如下所示:
    在这里插入图片描述

    共享内存的使用

    头文件:

    #include <sys/shm.h>
    

    共享内存的打开或创建

    进程可以通过调用函数shmget()来打开或创建一个共享内存区。函数shmget()内部由系统调用sys_shmget来实现。函数shmget()的原型如下:

    int shmget(key_t key, size_t size, int flag);
    

    其中,参数key为用户给定的键值。

    所谓的键值,是在IPC的通信模式下每个IPC对象的名字。进程通过键值识别所有的对象。如果不使用键,进程将无法获取IPC对象,因此IPC对象并不存在于进程本身所使用的的内存中。

    因此任何进程都无法为一块共享内存定义一个键值。因此,在调用函数shmget()时,需要key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,指定一个键值并返回这块共享内存的IPC标识符ID,然后再设法将这个新的共享内存的标识符ID告诉其他需要使用这个共享内存区的进程。

    函数中的参数size为所申请的共享存储段的长度(以页为单位)。

    函数中的参数flag为标志,常用的有效标志有IPC_CREAT和IPC_EXCL,它们的功能与文件打开函数open()的O_CREAT和O_EXCL相当。如果用户希望所创建的共享内存区可读,则需要使用标志S_IRUSR;若可读,则需要使用标志S_IWUSR。

    函数shmget()调用成功后,返回共享内存区的ID,否则返回-1。

    Linux用shmid_ds数据结构表示每个新建的共享内存。当shmget()创建一块新的共享内存后,返回一个可以引用该共享内存的shmid_ds数据结构的标识符。定义在include/linux/shm.h文件中的shmid_ds如下:

    struct shmid_ds {
    	struct ipc_perm		shm_perm;	/* operation perms */
    	int			shm_segsz;	/* size of segment (bytes) */
    	__kernel_time_t		shm_atime;	/* last attach time */
    	__kernel_time_t		shm_dtime;	/* last detach time */
    	__kernel_time_t		shm_ctime;	/* last change time */
    	__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */
    	__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */
    	unsigned short		shm_nattch;	/* no. of current attaches */
    	unsigned short 		shm_unused;	/* compatibility */
    	void 			*shm_unused2;	/* ditto - used by DIPC */
    	void			*shm_unused3;	/* unused */
    };
    

    例如:调用函数shmget()为当前进程创建一个共享内存区。

    代码如下:

    int main(void)
    {
            int shmid;
            if((shmid = shmget(IPC_PRIVATE, 10, IPC_CREAT)) < 0)
            {
                    perror("shmget error!");
                    exit(1);
            }else
                    printf("shmget success!");
     
            return 0;
    }
    

    共享内存与进程的连接

    如果一个进程已创建或打开一个共享内存,则在需要使用它时,要调用函数shmat()把该共享内存连接到进程上,即要把待使用的共享内存映射到进程空间。函数shmat()通过系统调用sys_shmat()实现。函数shmat()的原型如下:

    void * shmat(int shmid, char __user * shmaddr, int shmflg);
    

    其中,参数shmid为共享内存的标识;参数shmaddr为映射地址,如果该值为0,则由内核决定;参数shmflg为共享内存的标志,如果shmflg的值为SHM_RDONLY,则进程以只读的方式访问共享内存,否则以读写方式访问共享内存。

    若函数调用成功,则返回共享存储段地址;若出错,则返回-1。

    断开共享内存与进程的连接

    调用函数shmdt()可以断开共享内存与进程的连接,其原型如下:

    int shmdt(coid * addr);
    

    其中,参数addr为共享存储段的地址,即调用shmat时的返回值。shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1。

    共享内存的控制

    调用函数shmctl()可以对共享内存进行一些控制,其原型如下:

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

    其中,参数shmid为共享存储段的ID;参数cmd为控制命令,常用的值有IPC_STAT(赋值)、IPC_SET(赋值)、IPC_RMID(删除)、SHM_LOCK(上锁)、SHM_UNLOCK(解锁)等等;参数buf为struct shmid_ds类型指针,由buf返回的数值与命令参数cmd表示的操作相关。

    共享内存不会随着程序的结束而自动消除,要么调用shmctl()删除,要么手动使用命令ipcrm -m shmid去删除,否则一直保留在系统中,直至系统掉电。

    例子:调用函数shmget()为当前进程创建一个共享内存区并使用它。

    代码如下:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <sys/stat.h>
     
    int main(void)
    {
    	int shm_id;							//定义共享内存键
    	char* shared_memory;				//定义共享内存指针
    	struct shmid_ds shmbuffer;			//定义共享内存缓冲
    	int shm_size;						//定义共享内存大小
     
    	shm_id = shmget(IPC_PRIVATE, 0x6400, IPC_CREAT | IPC_EXCL | S_IRUSE | S_IWUSE);		//创建一个共享内存区
    	shared_memory = (char*)shmat(shm_id, 0, 0);					//绑定到共享内存
    	printf("shared memory attached at address %p\n", shared_memory);
     
    	shmctl(shm_id, IPC_STAT, &shmbuffer);				//读共享内存结构struct shmid_ds
    	shm_size = shmbuffer.shm_segsz;						//自结构struct shmid_ds获取内存大小
    	printf("segment size:%d\n", shm_size);
     
    	sprintf(shared_memory, "Hello,world.");				//向共享内存中写入一个字符串
    	shmdt(shared_memory);							//脱离该共享内存
     
    	shared_memory = (char*)shmat(shm_id, (void *)0x500000, 0);			//重新绑定共享内存
    	printf("shared memory reattched at address %p\n", shared_memory);
    	printf("%s\n", shared_memory);
    	shmdt(shared_memory);						//脱离该共享内存
    	shmctl(shm_id, IPC_RMID, 0);				//释放共享内存
     
    	return 0;
    }
    

    共享内存的互斥

    从上面的叙述中可以看到,共享内存是一种低级的通信机制,它没有提供进程间同步和互斥的功能。所以,共享内存通常是要与信号量结合使用。

    原文链接:https://blog.csdn.net/qq_38410730/article/details/81488145?utm_medium=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

    展开全文
  • LinuxLinux共享内存

    万次阅读 多人点赞 2018-08-10 19:17:45
    实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号...

    实现进程间通信最简单也是最直接的方法就是共享内存——为参与通信的多个进程在内存中开辟一个共享区。由于进程可以直接对共享内存进行读写操作,因此这种通信方式效率特别高,但其弱点是,它没有互斥机制,需要信号量之类的手段来配合。

     

    共享内存原理与shm系统

    共享内存,顾名思义,就是两个或多个进程都可以访问的同一块内存空间,一个进程对这块空间内容的修改可为其他参与通信的进程所看到的。

    显然,为了达到这个目的,就需要做两件事:一件是在内存划出一块区域来作为共享区;另一件是把这个区域映射到参与通信的各个进程空间。

    通常在内存划出一个区域的方法是,在内存中打开一个文件,若通过系统调用mmap()把这个文件所占用的内存空间映射到参与通信的各个进程地址空间,则这些进程就都可以看到这个共享区域,进而实现进程间的通信。

    为了方便,再把mmap()的原理简述如下:

    mmap()原型如下:

    void * mmap(void *start, size_t len, int prot, int flags, int fd, off_t offset);

    其中,参数fd用来指定被映射的文件;offset指定映射的起始位置偏移量(通常为0);len指定文件被映射部分的长度;start用来指定映射到虚地址空间的起始位置(通常为NULL,即由系统确定)。

    mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

    mmap()映射过程示意图如下所示:

    那么mmap是怎么形成这个文件映射过程呢?

    mmap本身其实是一个很简单的操作,在进程页表中添加一个页表项,该页表项是物理内存的地址。调用mmap的时候,内核会在该进程的地址空间的映射区域查找一块满足需求的空间用于映射该文件,然后生成该虚拟地址的页表项,改页表项此时的有效位(标志是否已经在物理内存中)为0,页表项的内容是文件的磁盘地址,此时mmap的任务已经完成。 

    简而言之,就是在进程对应的虚存段添加一个段,也就是创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。在创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,引发缺页异常,内核进行请页。

    IPC的共享内存通信方式与上面的mmap()方式极为相似,但因为建立一个文件的目的仅是为了通信,于是这种文件没有永久保存的意义,因此IPC并没有使用正规的文件系统,而是在系统初始化时在磁盘交换区建立了一个专门用来实现共享内存的特殊临时文件系统shm,当系统断电后,其中的文件会全部自行销毁。

    文章参考:mmap内存映射认真分析mmap:是什么 为什么 怎么用

     

    Linux共享内存结构

    Linux的一个共享内存区由多个共享段组成。用来描述共享内存段的内核数据结构shmid_kernel如下:

    struct shmid_kernel /* private to the kernel */
    {	
    	struct kern_ipc_perm	shm_perm;        //描述进程间通信许可的结构
    	struct file *		shm_file;            //指向共享内存文件的指针
    	unsigned long		shm_nattch;            //挂接到本段共享内存的进程数
    	unsigned long		shm_segsz;            //段大小
    	time_t			shm_atim;            //最后挂接时间
    	time_t			shm_dtim;            //最后解除挂接时间
    	time_t			shm_ctim;            //最后变化时间
    	pid_t			shm_cprid;            //创建进程的PID
    	pid_t			shm_lprid;            //最后使用进程的PID
    	struct user_struct	*mlock_user;
    };

    shmid_kernel中最重要的域是指针shm_file,它指向临时文件file对象。当进程需要使用这个文件进行通信时,由内核负责将其映射到用户地址空间。

    为了便于管理,内核把共享内存区的所有描述结构shmid_kernel都存放在结构ipc_id_ary中的一个数组中。结构ipc_id_ary的定义如下:

    struct ipc_id_ary
    {
            int size;
            struct kern_ipc_perm *p[0];            //存放段描述结构的数组
    };

    同样,为了描述一个共享内存区的概貌,内核使用了数据结构ipc_ids。该结构的定义如下:

    struct ipc_ids {
    	int in_use;
    	unsigned short seq;
    	unsigned short seq_max;
    	struct rw_semaphore rw_mutex;
    	struct idr ipcs_idr;
            struct ipc_id_ary *entries;        //指向struct ipc_id_ary的指针
    };

    由多个共享段组成的共享区的结构如下所示:

     

    共享内存的使用

    头文件:

    #include <sys/shm.h>

    共享内存的打开或创建

    进程可以通过调用函数shmget()来打开或创建一个共享内存区。函数shmget()内部由系统调用sys_shmget来实现。函数shmget()的原型如下:

    int shmget(key_t key, size_t size, int flag);

    其中,参数key为用户给定的键值。

    所谓的键值,是在IPC的通信模式下每个IPC对象的名字。进程通过键值识别所有的对象。如果不使用键,进程将无法获取IPC对象,因此IPC对象并不存在于进程本身所使用的的内存中。

    因此任何进程都无法为一块共享内存定义一个键值。因此,在调用函数shmget()时,需要key设为IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,指定一个键值并返回这块共享内存的IPC标识符ID,然后再设法将这个新的共享内存的标识符ID告诉其他需要使用这个共享内存区的进程。

    函数中的参数size为所申请的共享存储段的长度(以页为单位)。

    函数中的参数flag为标志,常用的有效标志有IPC_CREAT和IPC_EXCL,它们的功能与文件打开函数open()的O_CREAT和O_EXCL相当。如果用户希望所创建的共享内存区可读,则需要使用标志S_IRUSR;若可读,则需要使用标志S_IWUSR。

    函数shmget()调用成功后,返回共享内存区的ID,否则返回-1。

    Linux用shmid_ds数据结构表示每个新建的共享内存。当shmget()创建一块新的共享内存后,返回一个可以引用该共享内存的shmid_ds数据结构的标识符。定义在include/linux/shm.h文件中的shmid_ds如下:

    struct shmid_ds {
    	struct ipc_perm		shm_perm;	/* operation perms */
    	int			shm_segsz;	/* size of segment (bytes) */
    	__kernel_time_t		shm_atime;	/* last attach time */
    	__kernel_time_t		shm_dtime;	/* last detach time */
    	__kernel_time_t		shm_ctime;	/* last change time */
    	__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */
    	__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */
    	unsigned short		shm_nattch;	/* no. of current attaches */
    	unsigned short 		shm_unused;	/* compatibility */
    	void 			*shm_unused2;	/* ditto - used by DIPC */
    	void			*shm_unused3;	/* unused */
    };

    例如:调用函数shmget()为当前进程创建一个共享内存区。

    代码如下:

    int main(void)
    {
            int shmid;
            if((shmid = shmget(IPC_PRIVATE, 10, IPC_CREAT)) < 0)
            {
                    perror("shmget error!");
                    exit(1);
            }else
                    printf("shmget success!");
    
            return 0;
    }

    共享内存与进程的连接

    如果一个进程已创建或打开一个共享内存,则在需要使用它时,要调用函数shmat()把该共享内存连接到进程上,即要把待使用的共享内存映射到进程空间。函数shmat()通过系统调用sys_shmat()实现。函数shmat()的原型如下:

    void * shmat(int shmid, char __user * shmaddr, int shmflg);

    其中,参数shmid为共享内存的标识;参数shmaddr为映射地址,如果该值为0,则由内核决定;参数shmflg为共享内存的标志,如果shmflg的值为SHM_RDONLY,则进程以只读的方式访问共享内存,否则以读写方式访问共享内存。

    若函数调用成功,则返回共享存储段地址;若出错,则返回-1。

    断开共享内存与进程的连接

    调用函数shmdt()可以断开共享内存与进程的连接,其原型如下:

    int shmdt(coid * addr);

    其中,参数addr为共享存储段的地址,即调用shmat时的返回值。shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1。

    共享内存的控制

    调用函数shmctl()可以对共享内存进行一些控制,其原型如下:

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

    其中,参数shmid为共享存储段的ID;参数cmd为控制命令,常用的值有IPC_STAT(赋值)、IPC_SET(赋值)、IPC_RMID(删除)、SHM_LOCK(上锁)、SHM_UNLOCK(解锁)等等;参数buf为struct shmid_ds类型指针,由buf返回的数值与命令参数cmd表示的操作相关。

    共享内存不会随着程序的结束而自动消除,要么调用shmctl()删除,要么手动使用命令ipcrm -m shmid去删除,否则一直保留在系统中,直至系统掉电。

    例子:调用函数shmget()为当前进程创建一个共享内存区并使用它。

    代码如下:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <sys/stat.h>
    
    int main(void)
    {
    	int shm_id;							//定义共享内存键
    	char* shared_memory;				//定义共享内存指针
    	struct shmid_ds shmbuffer;			//定义共享内存缓冲
    	int shm_size;						//定义共享内存大小
    
    	shm_id = shmget(IPC_PRIVATE, 0x6400, IPC_CREAT | IPC_EXCL | S_IRUSE | S_IWUSE);		//创建一个共享内存区
    	shared_memory = (char*)shmat(shm_id, 0, 0);					//绑定到共享内存
    	printf("shared memory attached at address %p\n", shared_memory);
    
    	shmctl(shm_id, IPC_STAT, &shmbuffer);				//读共享内存结构struct shmid_ds
    	shm_size = shmbuffer.shm_segsz;						//自结构struct shmid_ds获取内存大小
    	printf("segment size:%d\n", shm_size);
    
    	sprintf(shared_memory, "Hello,world.");				//向共享内存中写入一个字符串
    	shmdt(shared_memory);							//脱离该共享内存
    
    	shared_memory = (char*)shmat(shm_id, (void *)0x500000, 0);			//重新绑定共享内存
    	printf("shared memory reattched at address %p\n", shared_memory);
    	printf("%s\n", shared_memory);
    	shmdt(shared_memory);						//脱离该共享内存
    	shmctl(shm_id, IPC_RMID, 0);				//释放共享内存
    
    	return 0;
    }

    共享内存的互斥

    从上面的叙述中可以看到,共享内存是一种低级的通信机制,它没有提供进程间同步和互斥的功能。所以,共享内存通常是要与信号量结合使用。

     

    展开全文
  • linux共享内存实现

    千次阅读 2019-04-19 20:57:58
    答:共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之...

    答:共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

     

    采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

     

    Linux的2.2.x内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存,但还没实现Posix共享内存,本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。

     

    一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面

     

    1、page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。

     

    2、文件与address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。

     

    3、进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。

     

    4、对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区(swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。

    注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。

     

    5、所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。

    注:一个共享内存区域可以看作是特殊文件系统shm中的一个文件,shm的安装点在交换区上。

     

    上面涉及到了一些数据结构,围绕数据结构理解问题会容易一些。

     

    二、mmap()及其相关系统调用

     

    mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

     

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

     

    1、mmap()系统调用形式如下:

     

    void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )

    参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数,读者可参考mmap()手册页获得进一步的信息。

     

    2、系统调用mmap()用于共享内存的两种方式:

     

    (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下: 

     

            fd=open(name, flag, mode);

    if(fd<0)

            ...

            

    ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。 

     

    (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。

    对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。

     

    3、系统调用munmap()

     

    int munmap( void * addr, size_t len )

    该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

     

    4、系统调用msync()

     

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

    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

     

    三、mmap()范例

     

    下面将给出使用mmap()的两个范例:范例1给出两个进程通过映射普通文件实现共享内存通信;范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方,下面是通过mmap()映射普通文件实现进程间的通信的范例,我们通过该范例来说明mmap()实现共享内存的特点及注意事项。

     

    范例1:两个进程通过映射普通文件实现共享内存通信

     

    范例1包含两个子程序:map_normalfile1.c及map_normalfile2.c。编译两个程序,可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件,把该文件映射到进程的地址空间,并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间,然后对映射后的地址空间执行读操作。这样,两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。

     

    下面是两个程序代码:

     

    /*-------------map_normalfile1.c-----------*/

    #include <sys/mman.h>;

    #include <sys/types.h>;

    #include <fcntl.h>;

    #include <unistd.h>;

    typedef struct{

            char name[4];

            int  age;

    }people;

     

    main(int argc, char** argv) // map a normal file as shared mem:

    {

            int fd,i;

            people *p_map;

            char temp;

            

            fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);

            lseek(fd,sizeof(people)*5-1,SEEK_SET);

            write(fd,"",1);

            

            p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );

            close( fd );

            temp = 'a';

            for(i=0; i<10; i++)

            {

                    temp += 1;

                    memcpy( ( *(p_map+i) ).name, &temp,2 );

                    ( *(p_map+i) ).age = 20+i;

            }

            printf(" initialize over /n ");

            sleep(10);

     

            munmap( p_map, sizeof(people)*10 );

            printf( "umap ok /n" );

    }

     

    /*-------------map_normalfile2.c-----------*/

    #include <sys/mman.h>;

    #include <sys/types.h>;

    #include <fcntl.h>;

    #include <unistd.h>;

    typedef struct{

            char name[4];

            int  age;

    }people;

     

    main(int argc, char** argv)        // map a normal file as shared mem:

    {

            int fd,i;

            people *p_map;

            fd=open( argv[1],O_CREAT|O_RDWR,00777 );

            p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

            for(i = 0;i<10;i++)

            {

            printf( "name: %s age %d;/n",(*(p_map+i)).name, (*(p_map+i)).age );

     

            }

            munmap( p_map,sizeof(people)*10 );

    }

     

    map_normalfile1.c首先定义了一个people数据结构,(在这里采用数据结构的方式是因为,共享内存区的数据往往是有固定格式的,这由通信的各个进程决定,采用结构的方式有普遍代表性)。map_normfile1首先打开或创建一个文件,并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始,设置了10个people结构。然后,进程睡眠10秒钟,等待其他进程映射同一个文件,最后解除映射。

     

    map_normfile2.c只是简单的映射一个文件,并以people数据结构的格式从mmap()返回的地址处读取10个people结构,并输出读取的值,然后解除映射。

     

    分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后,在一个终端上先运行./map_normalfile2 /tmp/test_shm,程序输出结果如下:

     

    initialize over

    umap ok

     

    在map_normalfile1输出initialize over 之后,输出umap ok之前,在另一个终端上运行map_normalfile2 /tmp/test_shm,将会产生如下输出(为了节省空间,输出结果为稍作整理后的结果):

     

    name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;

    name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

     

    在map_normalfile1 输出umap ok后,运行map_normalfile2则输出如下结果:

     

    name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;

    name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;

     

    从程序的运行结果中可以得出的结论

     

    1、 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;

     

    2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。

    注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。

     

    3、 文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。

     

    范例2:父子进程通过匿名映射实现共享内存

     

    #include <sys/mman.h>;

    #include <sys/types.h>;

    #include <fcntl.h>;

    #include <unistd.h>;

    typedef struct{

            char name[4];

            int  age;

    }people;

    main(int argc, char** argv)

    {

            int i;

            people *p_map;

            char temp;

            p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);

            if(fork() == 0)

            {

                    sleep(2);

                    for(i = 0;i<5;i++)

                            printf("child read: the %d people's age is %d/n",i+1,(*(p_map+i)).age);

                    (*p_map).age = 100;

                    munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。

                    exit();

            }

            temp = 'a';

            for(i = 0;i<5;i++)

            {

                    temp += 1;

                    memcpy((*(p_map+i)).name, &temp,2);

                    (*(p_map+i)).age=20+i;

            }

     

            sleep(5);

            printf( "parent read: the first people,s age is %d/n",(*p_map).age );

            printf("umap/n");

            munmap( p_map,sizeof(people)*10 );

            printf( "umap ok/n" );

    }

     

    考察程序的输出结果,体会父子进程匿名共享内存:

     

    child read: the 1 people's age is 20

    child read: the 2 people's age is 21

    child read: the 3 people's age is 22

    child read: the 4 people's age is 23

    child read: the 5 people's age is 24

     

    parent read: the first people,s age is 100

    umap

    umap ok

     

    四、对mmap()返回地址的访问

     

    前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:

     

     

     

     

     

    注意:文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例:

     

    #include <sys/mman.h>;

    #include <sys/types.h>;

    #include <fcntl.h>;

    #include <unistd.h>;

    typedef struct{

            char name[4];

            int  age;

    }people;

     

    main(int argc, char** argv)

    {

            int fd,i;

            int pagesize,offset;

            people *p_map;

            

            pagesize = sysconf(_SC_PAGESIZE);

            printf("pagesize is %d/n",pagesize);

            fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);

            lseek(fd,pagesize*2-100,SEEK_SET);

            write(fd,"",1);

            offset = 0;        //此处offset = 0编译成版本1;offset = pagesize编译成版本2

            p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);

            close(fd);

            

            for(i = 1; i<10; i++)

            {

                    (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;

                    printf("access page %d over/n",i);

                    (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;

                    printf("access page %d edge over, now begin to access page %d/n",i, i+1);

                    (*(p_map+pagesize/sizeof(people)*i)).age = 100;

                    printf("access page %d over/n",i+1);

            }

            munmap(p_map,sizeof(people)*10);

    }

     

    如程序中所注释的那样,把程序编译成两个版本,两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间(大小为:pagesize*2-99),版本1的被映射部分是整个文件,版本2的文件被映射部分是文件大小减去一个页面后的剩余部分,不到一个页面大小(大小为:pagesize-99)。程序中试图访问每一个页面边界,两个版本都试图在进程空间中映射pagesize*3的字节数。

     

    版本1的输出结果如下:

     

    pagesize is 4096

    access page 1 over

    access page 1 edge over, now begin to access page 2

    access page 2 over

    access page 2 over

    access page 2 edge over, now begin to access page 3

    Bus error                //被映射文件在进程空间中覆盖了两个页面,此时,进程试图访问第三个页面

     

    版本2的输出结果如下:

     

    pagesize is 4096

    access page 1 over

    access page 1 edge over, now begin to access page 2

    Bus error                //被映射文件在进程空间中覆盖了一个页面,此时,进程试图访问第二个页面

     

    结论:采用系统调用mmap()实现进程间通信是很方便的,在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容,可以参考一下相关重要数据结构来加深理解。在本专题的后面部分,将介绍系统v共享内存的实现。

    在共享内存(上)中,主要围绕着系统调用mmap()进行讨论的,本部分将讨论系统V共享内存,并通过实验结果对比来阐述两者的异同。系统V共享内存指的是把所有共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。

     

    系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm中的一个文件(这是通过shmid_kernel结构联系起来的),后面还将阐述。

     

    1、系统V共享内存原理

     

    进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel结构注同时,还将在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry及inode结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。

     

    注:每一个共享内存区都有一个控制结构struct shmid_kernel,shmid_kernel是共享内存区域中非常重要的一个数据结构,它是存储管理和文件系统结合起来的桥梁,定义如下:

     

    struct shmid_kernel /* private to the kernel */

    {        

            struct kern_ipc_perm        shm_perm;

            struct file *                shm_file;

            int                        id;

            unsigned long                shm_nattch;

            unsigned long                shm_segsz;

            time_t                        shm_atim;

            time_t                        shm_dtim;

            time_t                        shm_ctim;

            pid_t                        shm_cprid;

            pid_t                        shm_lprid;

    };

     

    该结构中最重要的一个域应该是shm_file,它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件,一般情况下,特殊文件系统shm中的文件是不能用read()、write()等方法访问的,当采取共享内存的方式把其中的文件映射到进程地址空间后,可直接采用访问内存的方式对其访问。

     

    这里我们采用[1]中的图表给出与系统V共享内存相关数据结构:

     

     

     

     

     

    正如消息队列和信号灯一样,内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。上图中的shm_ids.entries变量指向一个ipc_id结构数组,而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针。到这里读者应该很熟悉了,对于系统V共享内存区来说,kern_ipc_perm的宿主是shmid_kernel结构,shmid_kernel是用来描述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。同时,在shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件,这样,共享内存区域就与shm文件系统中的文件对应起来。

     

    在创建了一个共享内存区域后,还要将它映射到进程地址空间,系统调用shmat()完成此项功能。由于在调用shmget()时,已经创建了文件系统shm中的一个同名文件与共享内存区域相对应,因此,调用shmat()的过程相当于映射文件系统shm中的同名文件过程,原理与mmap()大同小异。

     

    2、系统V共享内存API

     

    对于系统V共享内存,主要有以下几个API:shmget()、shmat()、shmdt()及shmctl()。

     

    #include <sys/ipc.h>;

    #include <sys/shm.h>;

     

    shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。shmat()把共享内存区域映射到调用进程的地址空间中去,这样,进程就可以方便地对共享区域进行访问操作。shmdt()调用用来解除进程对共享内存区域的映射。shmctl实现对共享内存区域的控制操作。这里我们不对这些系统调用作具体的介绍,读者可参考相应的手册页面,后面的范例中将给出它们的调用方法。

     

    注:shmget的内部实现包含了许多重要的系统V共享内存机制;shmat在把共享内存区域映射到进程空间时,并不真正改变进程的页表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。

     

    3、系统V共享内存限制

     

    在/proc/sys/kernel/目录下,记录着系统V共享内存的一下限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。

     

    在[2]中,给出了这些限制的测试方法,不再赘述。

     

    4、系统V共享内存范例

     

    本部分将给出系统V共享内存API的使用方法,并对比分析系统V共享内存机制与mmap()映射普通文件实现共享内存之间的差异,首先给出两个进程通过系统V共享内存通信的范例:

     

    /***** testwrite.c *******/

    #include <sys/ipc.h>;

    #include <sys/shm.h>;

    #include <sys/types.h>;

    #include <unistd.h>;

    typedef struct{

            char name[4];

            int age;

    } people;

    main(int argc, char** argv)

    {

            int shm_id,i;

            key_t key;

            char temp;

            people *p_map;

            char* name = "/dev/shm/myshm2";

            key = ftok(name,0);

            if(key==-1)

                    perror("ftok error");

            shm_id=shmget(key,4096,IPC_CREAT);        

            if(shm_id==-1)

            {

                    perror("shmget error");

                    return;

            }

            p_map=(people*)shmat(shm_id,NULL,0);

            temp='a';

            for(i = 0;i<10;i++)

            {

                    temp+=1;

                    memcpy((*(p_map+i)).name,&temp,1);

                    (*(p_map+i)).age=20+i;

            }

            if(shmdt(p_map)==-1)

                    perror(" detach error ");

    }

    /********** testread.c ************/

    #include <sys/ipc.h>;

    #include <sys/shm.h>;

    #include <sys/types.h>;

    #include <unistd.h>;

    typedef struct{

            char name[4];

            int age;

    } people;

    main(int argc, char** argv)

    {

            int shm_id,i;

            key_t key;

            people *p_map;

            char* name = "/dev/shm/myshm2";

            key = ftok(name,0);

            if(key == -1)

                    perror("ftok error");

            shm_id = shmget(key,4096,IPC_CREAT);        

            if(shm_id == -1)

            {

                    perror("shmget error");

                    return;

            }

            p_map = (people*)shmat(shm_id,NULL,0);

            for(i = 0;i<10;i++)

            {

            printf( "name:%s/n",(*(p_map+i)).name );

            printf( "age %d/n",(*(p_map+i)).age );

            }

            if(shmdt(p_map) == -1)

                    perror(" detach error ");

    }

     

    testwrite.c创建一个系统V共享内存区,并在其中写入格式化数据;testread.c访问同一个系统V共享内存区,读出其中的格式化数据。分别把两个程序编译为testwrite及testread,先后执行./testwrite及./testread 则./testread输出结果如下:

     

    name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;

    name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

     

    通过对试验结果分析,对比系统V与mmap()映射普通文件实现共享内存通信,可以得出如下结论:

     

    1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。

     

    2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。

     

    3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。

     

    结论:

     

    共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。

     

    共享内存涉及到了存储管理以及文件系统等方面的知识,深入理解其内部机制有一定的难度,关键还要紧紧抓住内核使用的重要数据结构。系统V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,要通过shmat将这个内存区映射到本进程的虚拟地址空间。

    展开全文
  • 共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU可以在各个进程访问到这...
  • Linux共享内存编程(共享存储空间)

    万次阅读 多人点赞 2018-07-29 23:44:08
    最简单的共享内存的使用流程 ①ftok函数生成键值 ②shmget函数创建共享内存空间 ③shmat函数获取第一个可用共享内存空间的地址 ④shmdt函数进行分离(对共享存储段操作结束时的步骤,并不是从系统中删除共享内存...
  • Linux C++ 共享内存

    千次阅读 2020-08-24 17:34:07
    要知道在使用共享内存时,首先需要创建一个文件来充当共享内存,但是在系统中可能存在许许多多这样的文件,所以需要唯一一个编号来标志一个个这样的文件。ftok正是根据文件inode和proj_id来产生一个唯一的编号来表示...
  • 共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU...
  • linux 删除 共享内存 how to list and delete shared memory in linux? 如何在Linux中列出和删除共享内存? List all shared memories in your Linux Systems > $ ipcs -m 列出您的Linux 系统中的所有共享...
  • Linux共享内存与互斥锁

    千次阅读 2018-08-07 09:02:34
    Linux共享内存 共享内存是从系统的空闲内存池中分配,并希望访问它的每个进程都能连接它。连接的过程称为映射。映射后,每个进程都可通过访问自己的内存而访问共享内存区域,进而与其它进程进行通信。 共享内存...
  • Linux共享内存及共享内存API

    千次阅读 2018-01-23 16:31:45
    共享内存从服务器拷贝文件数据到客户端: 共享内存基本API: #include #include 1. int shmget(key_t key,size_t size,int shmflg); 功能:用来创建共享内存 key:是这个共享内存段的名字 size:...
  • Linux共享内存和Qt共享内存SharedMemory简介Linux共享内存使用Qt共享内存SharedMemory使用 简介 Linux系统下进程通信的方式有很多: 管道(pipe) 命名管道(FIFO) 内存映射(mapped memeory) 消息队列(message queue) ...
  • linux共享内存实验

    千次阅读 2018-07-31 10:30:48
     通过编写共享内存实验,进一步了解使用共享内存的具体步骤,同时加深对共享内存的理解。在本实验中,采用信号量作为同步机制完善两个进程(“生产者”和“消费者”)之间的通信,其功能类似于4.6节中的实例。在实例...
  • LINUX共享内存使用常见陷阱与分析

    千次阅读 2018-04-19 15:52:59
    所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享...
  • Linux进程间通讯的方法有信号,消息队列,管道,共享内存。本篇介绍共享内存。 每一个进程都有自己的虚拟地址空间,每个进程的空间之间严格区分,互不干扰。但通过系统调用开辟一片内存,让不同进程都连接到此空间...
  • 很好的东西,是基于linux2.6内核的,很好用的!!!!!!!!!!!!1
  • Linux 修改共享内存限制

    千次阅读 2019-03-23 20:13:42
    缺省设置只适合小安装(缺省最大共享内存是 32 MB)。不过,其它的缺省值都相当大, 通常不需要改变。最大的共享内存段设置可以用sysctl接口设置。 比如,要允许 128 MB,并且最大的总共享内存数为 2097152 页(缺省...
  • Linux共享内存编程实例

    万次阅读 2014-08-24 00:32:09
    转子: [cpp] view plaincopy ... 在GNU/Linux中所有的进程都有唯一的虚拟地址空间,而共享内存应用编程接口API允许一个进程使   用公共内存区段。但是对内存的共享访问其复杂度也相应增加
  • linux共享内存文件的权限问题

    千次阅读 2010-11-05 10:35:00
    1.root用户下使用shm_open创建共享内存时,用其他的用户登录后不能够连接到该共享内存,查看出错信息发现时因为权限不够。 后修改创建的时的权限就解决了。具体如下: <br />//系统错误号 ...
  • linux 共享内存 shm_open ,mmap的正确使用

    万次阅读 多人点赞 2019-11-06 11:30:53
     linux共享内存是通过tmpfs这个文件系统来实现的,tmpfs文件系的目录为/dev/shm,/dev/shm是驻留在内存 RAM 当中的,因此读写速度与读写内存速度一样,/dev/shm的容量默认尺寸为系统内存大小的一半大小,使用df -h...
  • mmap系统调用并不是完全为了... mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
  • linux c 共享内存实现读写操作

    千次阅读 2018-05-17 16:55:26
    linux c 共享内存实现读写操作【需求描述】1、共享内存保存信息2、提供接口写入共享内存3、提供接口获取共享内存【编写语言:C】【环境:linux】1、写入内存一千万条数据 耗时:5.356秒2、读取内存一千万条数据 耗时...
  • 说明:编译时加上参数 -lrt -lpthread 要不然找不到库文件
  • Linux共享内存使用常见陷阱与分析

    千次阅读 2016-07-11 15:05:20
    所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享...
  • Linux共享内存的查看和释放

    万次阅读 2018-05-22 19:08:11
    说明:1、查看共享内存,使用命令:ipcs -m2、删除共享内存,使用命令:ipcrm -m [shmid]使用如下:[plain] view plain copy[negivup@negivup mycode]$ ipcs -m 查看共享内存区 ------ Shared Memory ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 263,975
精华内容 105,590
关键字:

linux共享内存文件

linux 订阅