`
cuiyadll
  • 浏览: 194996 次
文章分类
社区版块
存档分类
最新评论

Linux 进程间通信 - 共享内存shmget方式(转)

阅读更多

        共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接 访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所 有进程就可以立刻看到其中的内容。这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中。但是它不需要在所有进程的虚拟内存中都有相同的虚拟地 址。
 
 
图 共享内存映射图  
                         

         象所有的 System V IPC对象一样,对于共享内存对象的获取是由key控制。内存共享之后,对进程如何使用这块内存就不再做检查。它们必须依赖于其它机制,比如System V的信号灯来同步对于共享内存区域的访问(信号灯如何控制对临界代码的访问另起一篇说话)。
 
        每一个新创建的共享内存对象都用一个shmid_kernel数据结构来表达。系统中所有的shmid_kernel数据结构都保存在shm_segs向量表中,该向量表的每一个元素都是一个指向shmid_kernel数据结构的指针。
shm_segs向量表的定义如下:
struct shmid_kernel *shm_segs[SHMMNI];

 
    SHMMNI为128,表示系统中最多可以有128个共享内存对象。
 
    数据结构shmid_kernel的定义如下:
    struct shmid_kernel
    {    
        struct shmid_ds u;         /* the following are private */
        unsigned long shm_npages;  /* size of segment (pages) */
        unsigned long *shm_pages;  /* array of ptrs to frames -> SHMMAX */ 
        struct vm_area_struct *attaches;  /* descriptors for attaches */
    };

 
    其中:
    shm_pages代表该共享内存对象的所占据的内存页面数组,数组里面的每个元素当然是每个内存页面的起始地址.
    shm_npages则是该共享内存对象占用内存页面的个数,以页为单位。这个数量当然涵盖了申请空间的最小整数倍.
    (A new shared memory segment,  with size  equal to the value of size rounded up to a multiple of PAGE_SIZE)
    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 */
    };

 
        attaches描述被共享的物理内存对象所映射的各进程的虚拟内存区域。每一个希望共享这块内存的进程都必须通过系统调用将其关联(attach)到它 的虚拟内存中。这一过程将为该进程创建了一个新的描述这块共享内存的vm_area_struct数据结构。创建时可以指定共享内存在它的虚拟地址空间的 位置,也可以让Linux自己为它选择一块足够的空闲区域。
 
        这个新的vm_area_struct结构是维系共享内存和使用它的进程之间的关系的,所以除了要关联进程信息外,还要指明这个共享内存 数据结构shmid_kernel所在位置; 另外,便于管理这些经常变化的vm_area_struct,所以采取了链表形式组织这些数据结构,链表由attaches指向,同时 vm_area_struct数据结构中专门提供了两个指针:vm_next_shared和 vm_prev_shared,用于连接该共享区域在使用它的各进程中所对应的vm_area_struct数据结构。 

图 System V IPC 机制 - 共享内存 

               

        Linux为共享内存提供了四种操作。
        1. 共享内存对象的创建或获得。与其它两种IPC机制一样,进程在使用共享内存区域以前,必须通过系统调用sys_ipc (call值为SHMGET)创建一个键值为key的共享内存对象,或获得已经存在的键值为key的某共享内存对象的引用标识符。以后对共享内存对象的访 问都通过该引用标识符进行。对共享内存对象的创建或获得由函数sys_shmget完成,其定义如下:
int sys_shmget (key_t key, int size, int shmflg)
 
    这里key是表示该共享内存对象的键值,size是该共享内存区域的大小(以字节为单位),shmflg是标志(对该共享内存对象的特殊要求)。
 
    它所做的工作如下:
    1) 如果key == IPC_PRIVATE,则总是会创建一个新的共享内存对象。
 但是  (The name choice IPC_PRIVATE was perhaps unfortunate, IPC_NEW would more clearly show its function)
    * 算出size要占用的页数,检查其合法性。
    * 申请一块内存用于建立shmid_kernel数据结构,注意这里申请的内存区域大小不包括真正的共享内存区,实际上,要等到第一个进程试图访问它的时候才真正创建共享内存区。
    * 根据该共享内存区所占用的页数,为其申请一块空间用于建立页表(每页4个字节),将页表清0。
    * 搜索向量表shm_segs,为新创建的共享内存对象找一个空位置。
    * 填写shmid_kernel数据结构,将其加入到向量表shm_segs中为其找到的空位置。
    * 返回该共享内存对象的引用标识符。
 
    2) 在向量表shm_segs中查找键值为key的共享内存对象,结果有三:
    * 如果没有找到,而且在操作标志shmflg中没有指明要创建新共享内存,则错误返回,否则创建一个新的共享内存对象。
    * 如果找到了,但该次操作要求必须创建一个键值为key的新对象,那么错误返回。
    * 否则,合法性、认证检查,如有错,则错误返回;否则,返回该内存对象的引用标识符。
 
    共享内存对象的创建者可以控制对于这块内存的访问权限和它的key是公开还是私有。如果有足够的权限,它也可以把共享内存锁定在物理内存中。
    参见include/linux/shm.h
 
    2. 关联。在创建或获得某个共享内存区域的引用标识符后,还必须将共享内存区域映射(粘附)到进程的虚拟地址空间,然后才能使用该共享内存区域。系统调用 sys_ipc(call值为SHMAT)用于共享内存区到进程虚拟地址空间的映射,而真正完成粘附动作的是函数sys_shmat,
 
其定义如下:   

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

       void *shmat(int shmid, const void *shmaddr, int shmflg);


 
    其中:
     shmid是shmget返回的共享内存对象的引用标识符;
    shmaddr用来指定该共享内存区域在进程的虚拟地址空间对应的虚拟地址;
    shmflg是映射标志;
    返回的是 在进程中的虚拟地址
 
    该函数所做的工作如下:
    1) 根据shmid找到共享内存对象。
    2) 如果shmaddr为0,即用户没有指定该共享内存区域在它的虚拟空间中的位置,则由系统在进程的虚拟地址空间中为其找一块区域(从1G开始);否则,就用shmaddr作为映射的虚拟地址。
   (If  shmaddr  is NULL, the system chooses a suitable (unused) address a他 which to attach the segment)
    3) 检查虚拟地址的合法性(不能超过进程的最大虚拟空间大小—3G,不能太接近堆栈栈顶)。
    4) 认证检查。
    5) 申请一块内存用于建立数据结构vm_area_struct,填写该结构。
    6) 检查该内存区域,将其加入到进程的mm结构和该共享内存对象的vm_area_struct队列中。
 
    共享内存的粘附只是创建一个vm_area_struct数据结构,并将其加入到相应的队列中,此时并没有创建真正的共享内存页。
 
    当进程第一次访问共享虚拟内存的某页时,因为所有的共享内存页还都没有分配,所以会发生一个page fault异常。当Linux处理这个page fault的时候,它找到发生异常的虚拟地址所在的vm_area_struct数据结构。在该数据结构中包含有这类共享虚拟内存的一组处理程序,其中的 nopage操作用来处理虚拟页对应的物理页不存在的情况。对共享内存,该操作是shm_nopage(定义在ipc/shm.c中)。该操作在描述这个 共享内存的shmid_kernel数据结构的页表shm_pages中查找发生page fault异常的虚拟地址所对应的页表条目,看共享页是否存在(页表条目为0,表示共享页是第一次使用)。如果不存在,它就分配一个物理页,并为它创建一 个页表条目。这个条目不但进入当前进程的页表,同时也存到shmid_kernel数据结构的页表shm_pages中。
 
    当下一个进程试图访问这块内存并得到一个page fault的时候,经过同样的路径,也会走到函数shm_nopage。此时,该函数查看shmid_kernel数据结构的页表shm_pages时, 发现共享页已经存在,它只需把这里的页表项填到进程页表的相应位置即可,而不需要重新创建物理页。所以,是第一个访问共享内存页的进程使得这一页被创建, 而随后访问它的其它进程仅把此页加到它们的虚拟地址空间。
 
    3. 分离。当进程不再需要共享虚拟内存的时候,它们与之分离(detach)。只要仍旧有其它进程在使用这块内存,这种分离就只会影响当前的进程,而不会影响 其它进程。当前进程的vm_area_struct数据结构被从shmid_ds中删除,并被释放。当前进程的页表也被更新,共享内存对应的虚拟内存页被 标记为无效。当共享这块内存的最后一个进程与之分离时,共享内存页被释放,同时,这块共享内存的shmid_kernel数据结构也被释放。
 
  系统调用sys_ipc (call值为SHMDT) 用于共享内存区与进程虚拟地址空间的分离,而真正完成分离动作的是函数    

    sys_shmdt,其定义如下:
    int sys_shmdt (char *shmaddr)
 
    其中shmaddr是进程要分离的共享页的开始虚拟地址。
 
    该函数搜索进程的内存结构中的所有vm_area_struct数据结构,找到地址shmaddr对应的一个,调用函数do_munmap将其释放。
 
    在函数do_munmap中,将要释放的vm_area_struct数据结构从进程的虚拟内存中摘下,清除它在进程页表中对应的页表项(可能占多个页表项). 
  
    如果共享的虚拟内存没有被锁定在物理内存中,分离会更加复杂。因为在这种情况下,共享内存的页可能在系统大量使用内存的时候被交换到系统的交换磁盘。为了 避免这种情况,可以通过下面的控制操作,将某共享内存页锁定在物理内存不允许向外交换。共享内存的换出和换入,已在第3章中讨论。
 
    4. 控制。Linux在共享内存上实现的第四种操作是共享内存的控制(call值为SHMCTL的sys_ipc调用),它由函数sys_shmctl实现。 控制操作包括获得共享内存对象的状态,设置共享内存对象的参数(如uid、gid、mode、ctime等),将共享内存对象在内存中锁定和释放(在对象 的mode上增加或去除SHM_LOCKED标志),释放共享内存对象资源等。
 
    共享内存提供了一种快速灵活的机制,它允许进程之间直接共享大量的数据,而无须使用拷贝或系统调用。共享内存的主要局限性是它不能提供同步,如果两个进程 企图修改相同的共享内存区域,由于内核不能串行化这些动作,因此写的数据可能任意地互相混合。所以使用共享内存的进程必须设计它们自己的同步协议,如用信 号灯等。

以下是使用共享内存机制进行进程间通信的基本操作:

需要包含的头文件:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

1.创建共享内存:

 int shmget(key_t key,int size,int shmflg);

参数说明:

key:用来表示新建或者已经存在的共享内存去的关键字。

size:创建共享内存的大小。

shmflg:可以指定的特殊标志。IPC_CREATE,IPC_EXCL以及低九位的权限。

eg:

int shmid;

shmid=shmget(IPC_PRIVATE,4096,IPC_CREATE|IPC_EXCL|0660);

if(shmid==-1)

perror("shmget()");

 

2.连接共享内存

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

参数说明

shmid:共享内存的关键字

shmaddr:指定共享内存出现在进程内存地址的什么位置,通常我们让内核自己决定一个合适的地址位置,用的时候设为0。

shmflg:制定特殊的标志位。

eg:

int shmid;

char *shmp;

shmp=shmat(shmid,0,0);

if(shmp==(char *)(-1))

perror("shmat()\n");

3.使用共享内存

在使用共享内存是需要注意的是,为防止内存访问冲突,我们一般与信号量结合使用。

4.分离共享内存:当程序不再需要共享内后,我们需要将共享内存分离以便对其进行释放,分离共享内存的函数原形如下:

int shmdt(char *shmaddr);


 

5. 释放共享内存

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

分享到:
评论

相关推荐

    Linux_进程间通信 共享内存shmget方式详解

    Linux_进程间通信_-_共享内存shmget方式

    Linux进程间通信

    shmget函数 shmat函数 Linux进程间通信——使用共享内存 使用消息队列进行进程间通信

    linux 共享内存浅析

    如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。但是刚才我们的两个进程没有任何关系,所以就用ftok()算出来一个标识符使用了。 int shmsiz 是这块内存的大小. int flag 是这块内存的模式(mode)...

    详解Linux进程间通信——使用共享内存

    共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的...

    Linux /Unix 共享内存

    如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来代替。如果两个进程没有任何关系,所以就用ftok()算出来一个标识符(或者自己定义一个)使用了。 int size 是这块内存的大小. int flag 是这块内存的...

    UNIX网络编程 卷2 进程间通信 带完整书签,完整目录

    10.12 进程间共享信号量 205 10.13 信号量限制 206 10.14 使用FIFO实现信号量 206 10.15 使用内存映射I/O实现信号量 210 10.16 使用System V信号量实现Posix信号量 218 10.17 小结 224 习题 225 第11章 ...

    操作系统实验4-4实验报告.docx

    (3) 了解实现进程间通信的不同方法之间的区别、特点和适用情况。 实验内容 (1)编写发送信息程序shmsnd.c和接受信息程序shmrcv.c,其功能分别为: //shmsnd.c// 创建一个共享存储区间; 将它附接到自己的地址空间...

    Linux程序设计 第4版.haozip01

    第13章 进程间通信:管道 443 13.1 什么是管道 443 13.2 进程管道 444 13.3 将输出送往popen 445 13.3.1 传递更多的数据 446 13.3.2 如何实现popen 447 13.4 pipe调用 449 13.5 父进程和子进程 451 13.5.1 ...

    c-inter-process-communication-example

    还有其他/新的解决方案可以实现进程间通信。 也许这些解决方案更适合您的问题。 因此,在采用某种技术之前,请查看有关,和的linux手册部分。 此示例包含一个main.c文件,其中显示框通过fork函数以及通过shmget , ...

    华中科技大学操作系统实验报告.docx

    实验三 Linux进程间通信 一.实验目的 (1) 分析进程争用临界资源的现象,学习解决进程互斥的方法; (2) 学习如何利用进程的“软中断”、管道机制进行进程间的通信,并加深对上述通信机制的理解; (3) 了解系统...

    Linux程序设计 第4版.haozip02

    第13章 进程间通信:管道 443 13.1 什么是管道 443 13.2 进程管道 444 13.3 将输出送往popen 445 13.3.1 传递更多的数据 446 13.3.2 如何实现popen 447 13.4 pipe调用 449 13.5 父进程和子进程 451 13.5.1 ...

    Linux高性能服务器编程

    13.6.4 共享内存的POSIX方法 13.6.5 共享内存实例 13.7 消息队列 13.7.1 msgget系统调用 13.7.2 msgsnd系统调用 13.7.3 msgrcv系统调用 13.7.4 msgctl系统调用 13.8 IPC命令 13.9 在进程间传递文件描述符 ...

    UnixProgramming

    进程间通信: 匿名管道pipe,命名管道(FIFO)mkfifo System V IPC(还有一组POSIX接口,更易用,可以参考《TLPI》) 信号量semget、semop、semctl 消息队列msgget、msgsnd/msgrcv、msgctl 共享内存shmget、shmat/shmdt...

    linux操作系统实验二

    (1)消息的创建,发送和接收。 ① 使用系统调用msgget (), msgsnd (), msgrev (), 及msgctl ...(2)共享存储区的创建、附接和段接。 使用系统调用shmget(),shmat(),sgmdt(),shmctl(),编制一个与上述功能相同的程序。

    操作系统实验

    5、进程间通信(IPC):共享内存机制 (1) 共享存储区的创建,附接和断接 使用系统调用shmget(),shmat(),msgdt(),shmctl(),编制一长度为1K的消息发送和接收的程序。 1)为了便于操作和观察结果,用一个程序作为...

Global site tag (gtag.js) - Google Analytics