• UNIX环境高级编程——System V 共享内存区


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


    共享内存的实现,分为两个步骤:
    a. 创建共享内存,使用 shmget 函数。
    b. 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用 shmat 函数。

         对于每个共享内存区,内核维护如下的信息结果,它定义在<sys/shm.h>头文件中:

               struct shmid_ds {
                   struct ipc_perm shm_perm;    /* Ownership and permissions */
                   size_t          shm_segsz;   /* Size of segment (bytes) */
                   time_t          shm_atime;   /* Last attach time */
                   time_t          shm_dtime;   /* Last detach time */
                   time_t          shm_ctime;   /* Last change time */
                   pid_t           shm_cpid;    /* PID of creator */
                   pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
                   shmatt_t        shm_nattch;  /* current #attached */
                   shmat_t         shm_cnattch; /* in-core #attached */
               };

         下面是ipc_per 结构,它含有本共享内存区的访问权限。

    struct ipc_perm {
        key_t          __key;    /* Key supplied to shmget(2) */
        uid_t          uid;      /* Effective UID of owner */
        gid_t          gid;      /* Effective GID of owner */
        uid_t          cuid;     /* Effective UID of creator */
        gid_t          cgid;     /* Effective GID of creator */
        unsigned short mode;     /* Permissions + SHM_DEST and SHM_LOCKED flags */
        unsigned short __seq;    /* Sequence number */
    };


    1.shmget函数(创建共享内存)

         shmget函数创建一个新的共享内存区,或者访问一个已存在的共享内存区。

    #include <sys/shm.h>
    int shmget(key_t key,size_t size,int oflag);

        返回值是一个称为共享内存区标识符的整数,其他三个shmXXX函数就用它来指代这个内存区。

        key即可以是ftok的返回值,也可以是IPC_PRIVATE。

       size以字节为单位指定内存区的大小。当时机操作为创建一个新的共享内存区时,必须指定一个不为0的size值。如果实际操作为访问一个已存在的共享内存区,那么size应为0.

       oflag是读写权限的组合。它还可以与IPC_CREAT或IPC_CREAT | IPC_EXCL按位或。

       当实际操作为创建一个新的共享内存区时,该内存区被初始化为size字节的0.

       注意,shmget创建或打开一个共享内存区,但并没有给调用进程提供访问该内存区的手段。这是shmat函数的目的。


    2.shmat函数(共享内存映射)

       由shmget创建或打开一个共享内存区后,通过调用shmat把它附接到调用进程的地址空间。

    #include <sys/shm.h>
    void* shmat(int shmid,const void *shmaddr,int flag);

    其中shmid是由shmget返回的标识符。shmat的返回值是所指定的共享内存区在调用进程内的起始地址。确定这个地址的规则如下:

    (1)如果shmaddr是一个空指针,那么系统替调用者选择地址。这是推荐的方法。

    (2)如果shmaddr是一个非空指针,那么返回地址取决于调用者是否给flag参数指定了SHM_RND值:

        a.如果没有指定SHM_RND,那么相应的共享内存区附接到由shmaddr参数指定的地址;

        b.如果指定了SHM_RND,那么相应的共享内存附接到由shmaddr参数指定的地址向下舍入一个SHMLBA常值。LBA代表“低端边界地址”。
       默认情况下,只要调用进程具有某个共享内存区得读写权限,它附接该内存区后就能够同时读写该内存区。flag参数中也可以指定SHM_RDONLY值,它限定只读访问。flag参数默认是0,表示共享内存可读写。


    3.shmdt函数(共享内存解除映射)

        当一个进程完成某个共享内存区的使用时,它可以调用shmdt断接这个内存区。

    #include <sys/shm.h>
    int shmdt(const void *shmaddr);

        当一个进程终止时,它当前附接着的所有共享内存区都自动断接掉。

        注意本函数调用并不删除所指定的共享内存区。这个删除工作通过以IPC_RMID命令调用shmctl完成。


    4.shmctl函数

       shmctl提供了对一个共享内存区的多种操作。

    #include <sys/shm.h>
    int shmctl(int shmid,int cmd,struct shmid_ds *buff);

        该函数提供了三个命令:

       IPC_RMID   从系统中删除由shmid标识的共享内存区并拆除它。

       IPC_SET     给所指定的共享内存区设置其shmid_ds结构的以下三个成员:shm_perm.uid,shm_perm.gid和shm_perm.mode,他们的值来自buff参数指向的结构中的相应成员。shm_ctime的值也用当前时间替换。

       IPC_STAT    (通过buff参数)向调用者返回所指定共享内存区当前的shmid_ds结构。


    5.shmget程序(shmget.c)
      下面是给出的shmget程序使用指定的路径名和长度创建一个共享内存区。
    #include "unpipc.h"
    int main(int argc,char ** argv)
    {
      int c,id,oflag;
      char *ptr;
      size_t length;
      oflag = SVSHM_MODE | IPC_CREAT;
      while( ( c= getopt(argc,argv,"e") ) != -1 )
       {
         switch(c){
         case 'e':
             oflag |= IPC_EXCL;
             break;
          }
        }
       if(optind != argc -2)
         err_quit("usage: shmget [-e] <pathname> <length>");
    
       length = atoi(argv[optind + 1]);
       id = shmget(ftok(argv[optind],0),length,oflag);
       ptr = shmat(id,NULL,0);
       exit(0);
    }

    20    shmget创建由用户指定其名字和大小的共享内存区。作为命令行参数传递进来的路径名由ftok映射成一个system V IPC键。如果指定了- e选项,那么一旦该内存已存在就会出错。如果我们知道该内存区已存在,那么在命令行上的长度参数必须指定为0.

    21    shmat把该内存区附接到当前进程的地址空间。本程序然后终止,不过既然system V共享内存区至少具有随内核的持续性,那么这不会删除该共享内存区。


    6.shmrnid程序(shmrmid.c)
      如下给出的只是以一个IPC_RMID命令调用shmctl,以便从系统中删除一个共享内存区。

    #include "unpipc.h"
    int main(int argc,char ** argv)
    {
      int id;
      if(argc != 2)
       err_quit("usage: shmrmid<pathname>");
    
      id = shmget(ftok(argv[1],0),0,SVSHM_MODE);
      shmctl(id,IPC_RMID,NULL);
      exit(0);
    }


    7.shmwrite程序(shmwrite.c)

       下面给出了shmwrite.c程序,它往一个共享内存区中写入一个模式:0.1.2........254.255.0.1等等。

    #include "unpipc.h"
    int main(int argc,char **argv)
    {
      int i,id;
      struct shmid_ds buff;
      unsigned char *ptr;
    
      if(argc != 2)
        err_quit("usage: shmwrite <pathname>");
    
      id = shmget(ftok(argv[1],0),0,SVSHM_MODE);
      ptr = shmat(id,NULL,0);
      shmctl(id,IPC_STAT,&buff);
    
      /*set: ptr[0] = 0,ptr[1] = 1,etc.*/
      for(i = 0;i < buff.shm_segsz;i++)
          *ptr++ = i%256;
      exit(0);
    }

    11-13   使用shmget打开所指定的共享内存区后由shmat把它附接到当前进程的地址空间。其大小通过以一个IPC_STAT命令调用shmctl取得。
    16-17   往该共享内存区中写入给定的模式。
     

    8.shmread程序(shmread.c)

       下面给出的代码验证shmwrite写入的模式。

    #include "unpipc.h"
    int main(int argc,char** argv)
    {
      int i,id;
      struct shmid_ds buff;
      unsigned char c,*ptr;
    
      if(argc != 2)
        err_quit("usage: shmread <pathname>");
    
      id = shmget(ftok(argv[1],0),0,SVSHM_MODE);
      ptr = shmat(id,NULL,0);
      shmctl(id,IPC_STAT,&buff);
    
      /*check that ptr[0] = 0,ptr[1] = 1,etc.*/
      for(i = 0;i < buff.shm_segsz;i++)
        if((c = *ptr++) != (i%256))
           err_ret("ptr[%d] = %d",i,c);
      exit(0);
    }
    11-13   打开并附接所指定的共享内存区。其大小通过以一个IPC_STAT命令调用shmctl获取。
    16-18   验证由shmwrite写入的模式。



    示例代码:

    #include <stdlib.h>  
    #include <stdio.h>  
    #include <string.h>  
    #include <errno.h>  
    #include <unistd.h>  
    #include <sys/stat.h>  
    #include <sys/types.h>  
    #include <sys/ipc.h>  
    #include <sys/shm.h>  
       
    #define PERM S_IRUSR | S_IWUSR  
     
    int main(int argc, char **argv)  
    {  
             int shmid;  
             char *p_addr, *c_addr;  
       
             if(argc != 2){  
                     fprintf(stderr,"Usage: %s
    ", argv[0]);  
                     exit(0);  
             }  
       
             //创建1k的共享内存,可读可写  
             if((shmid = shmget(IPC_PRIVATE, 1024, PERM)) == -1){  
                     fprintf(stderr, "Create share memory error: %s
    ",strerror(errno));  
             }  
       
             //创建子进程  
             if(fork()){ //父进程,写操作  
                     p_addr = shmat(shmid, NULL , 0); //映射到父进程中  
                                                  //地址为NULL,说明让系统自动指定地址。  
                     memset(p_addr, '', 1024);  
                     strncpy(p_addr, argv[1], 1024);  //拷贝命令行输入字符到共享内存  
                     wait(NULL);
    				 shmctl(shmid,IPC_RMID,NULL);
                     exit(0);  
             } else{  
                     sleep(1);  //子进程,读操作  
                     c_addr = shmat(shmid, 0 ,0); //共享内存映射到子进程  
                     printf("Client get %s
    ", c_addr); 				 
                     exit(0);  
             }  
    }  

    运行结果:

    huangcheng@ubuntu:~$ ./a.out huangcheng
    Client get huangcheng


  • 相关阅读:
    java中怎么跳出两层for循环
    卡斯特信号有限公司面经
    唯一索引、普通索引、主键索引的区别
    ES中的查询操作
    sql:union 与union的使用和区别
    Java中多个集合的交集,并集和差集
    Angular动态组件
    Angular惰性加载的特性模块
    spring定时器
    索引
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6332664.html
Copyright © 2020-2023  润新知