Linux进程间通信(IPC)编程实践(七)共享内存的使用-System V共享内存(API)

上一篇博文提到的系统调用mmap通过映射一个普通文件实现共享内存。那么本文中介绍的System V 共享内存则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm中的一个文件。执行过程是先调用shmget,再调用shmat。对于每个共享的内存区,内核维护如下的信息结构,定义在<sys/shm.h>头文件中。

[cpp]  view plain  copy
  1. //System V 共享内存基本数据结构    
  2. struct shmid_ds    
  3. {    
  4.     struct ipc_perm shm_perm;    /* Ownership and permissions: System V IPC所共有的数据结构 */    
  5.     size_t          shm_segsz;   /* Size of segment (bytes): 共享内存段的大小 */    
  6.     time_t          shm_atime;   /* Last attach time */    
  7.     time_t          shm_dtime;   /* Last detach time */    
  8.     time_t          shm_ctime;   /* Last change time */    
  9.     pid_t           shm_cpid;    /* PID of creator */    
  10.     pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */    
  11.     shmatt_t        shm_nattch;  /* No. of current attaches */    
  12.     ...    
  13. };    

System V共享内存常用API

[cpp]  view plain  copy
  1. #include <sys/ipc.h>  
  2. #include <sys/shm.h>  
  3. /* 
  4. 创建一个新的内存共享区或者访问一个已经存在的共享内存区 
  5. 返回共享内存区标识符 
  6. */  
  7. int shmget(key_t key, size_t size, int shmflg);  
  8. /* 
  9. 创建或打开一个共享内存区后,调用shmat把它连接到调用进程的地址空间 
  10. */  
  11. void *shmat(int shmid, const void *shmaddr,int shmflg);  
  12. /*  
[cpp]  view plain  copy
  1. </pre><pre name="code" class="cpp" style="font-size: 14px;">当一个进程完成某个共享内存区的使用时,调用shmdt断开这个内存区  
  2. */  
  3. int shmdt(const void *shmaddr);  
  4. /* 
  5. 对内存区进行多种操作 
  6. cmd取值: 
  7. IPC_RMID:从系统中删除由shmid标识的共享内存区并拆除它 
  8. IPC_SET:给指定的共享内存区设置其shmid_ds结果成员 
  9. IPC_STAT:通过buff参数向调用者返回所指定共享内存区当前的shmid_ds结构 
  10. */  
  11. int shmctl(int shmid, int cmd, struct shmid_ds *buf);  

调用shmget函数使用指定的路径名和长度创建一个共享内存区,如下:

[cpp]  view plain  copy
  1. int shmget(key_t key, size_t size, int shmflg);  

 创建共享内存,并将该内存的内容初始化为0

   打开一个已经存在共享内存, 如果打开时不知道共享内存的大小, 可以将size指定为0, shmflg可以指定为0(按照默认的权限打开);    

参数:

   key:这个共享内存段名字;

   size:共享内存大小(bytes);

   shmflg:用法类似msgget中的msgflg参数;

返回值:

   成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

[cpp]  view plain  copy
  1. /**示例: 创建并打开一个共享内存 **/    
  2. int main(int argc,char **argv)    
  3. {    
  4.     const int SHM_SIZE = 1024;    
  5.     int shmid = shmget(0x1234, SHM_SIZE, 0666|IPC_CREAT);    
  6.     if (shmid == -1)    
  7.         err_exit("shmget error");    
  8.     cout << "share memory get success" << endl;    
  9. }    
shmat

[cpp]  view plain  copy
  1. void *shmat(int shmid, const void *shmaddr, int shmflg);  

 连接到本进程地址空间, 成功连接之后, 对该内存的操作就与malloc来的一块内存非常类似了, 而且如果这块内存中有数据, 则就可以直接将其中的数据取出来。

参数:

   shmaddr:指定连接的地址(大小不知道的话推荐使用NULL)

   shmflg:一般指定为0, 表示可读,可写; 而它的另外两个可能取值是SHM_RND和SHM_RDONLY(见下)

返回值:

   成功返回一个指针,指向共享内存起始地址;失败返回(void *) -1


shmaddr与shmflg组合说明

shmaddr为NULL

Linux内核自动为进程连接到进程的内存(推荐使用)

shmaddr不为NULL且shmflg无SHM_RND标记

以shmaddr为连接地址

shmaddr不为NULL且shmflg设置了SHM_RND标记

连接的地址会自动向下调整为SHMLBA的整数倍;

公式:shmaddr - (shmaddr % SHMLBA)

SHMLBA为内存页面的大小(4K)

shmflg=SHM_RDONLY

只读共享内存, 不然的话就是可读,可写的, 注意: 此处没有可读,可写这个概念


shmdt

当一个进程完成某个共享内存区的使用时,调用shmdt断开这个内存区:

[cpp]  view plain  copy
  1. int shmdt(const void *shmaddr);  

参数:

   shmaddr: 由shmat所返回的指针

注意:将共享内存段与当前进程脱离不等于删除共享内存段

[cpp]  view plain  copy
  1. /** 示例: 将数据写入/读出共享内存  
  2. 程序write: 将数据写入共享内存  
  3. 程序read: 将数据读出共享内存(当然, 可以读取N多次)  
  4. **/    
  5. //write程序    
  6. struct Student    
  7. {    
  8.     char name[26];    
  9.     int age;    
  10. };    
  11. int main(int argc,char **argv)    
  12. {    
  13.     int shmid = shmget(0x1234, sizeof(Student), 0666|IPC_CREAT);    
  14.     if (shmid == -1)    
  15.         err_exit("shmget error");    
  16.     
  17.     // 以可读, 可写的方式连接该共享内存    
  18.     Student *p = (Student *)shmat(shmid, NULL, 0);    
  19.     if (p == (void *)-1)    
  20.         err_exit("shmat error");    
  21.     strcpy(p->name, "xiaofang");    
  22.     p->age = 22;    
  23.     shmdt(p);    
  24. }    
[cpp]  view plain  copy
  1. //read程序    
  2. int main(int argc,char **argv)    
  3. {    
  4.     int shmid = shmget(0x1234, 0, 0);    
  5.     if (shmid == -1)    
  6.         err_exit("shmget error");    
  7.     
  8.     // 以只读方式连接该共享内存    
  9.     Student *p = (Student *)shmat(shmid, NULL, 0);    
  10.     if (p == (void *)-1)    
  11.         err_exit("shmat error");    
  12.     
  13.     // 直接将其中的内容打印输出    
  14.     cout << "name: " << p->name << ", age: " << p->age << endl;    
  15.     shmdt(p);    
  16. }    
shmctl
[cpp]  view plain  copy
  1. int shmctl(int shmid, int cmd, struct shmid_ds *buf);   

设置/获取共享内存属性
参数:
   cmd:将要采取的动作(三个取值见下)
   buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

IPC_RMID:从系统中删除由shmid标识的共享内存区并拆除它
IPC_SET:给指定的共享内存区设置其shmid_ds结果成员
IPC_STAT:通过buff参数向调用者返回所指定共享内存区当前的shmid_ds结构

System V共享内存小结:

1)如果共享内存已经与所有访问它的进程断开了连接,则调用IPC_RMID子命令后,系统将立即删除共享内存的标识符,并删除该共享内存区,以及所有相关的数据结构;

2)如果仍有别的进程与该共享内存保持连接,则调用IPC_RMID子命令后,该共享内存并不会被立即从系统中删除,而是被设置为IPC_PRIVATE状态,并被标记为"已被删除"(使用ipcs命令可以看到dest字段);直到已有连接全部断开,该共享内存才会最终从系统中消失。

3)另外:一旦通过shmctl对共享内存进行了删除操作,则该共享内存将不能再接受任何新的连接,即使它依然存在于系统中!所以,可以确知,在对共享内存删除之后不可能再有新的连接,则执行删除操作是安全的;否则,在删除操作之后如仍有新的连接发生,则这些连接都将可能失败。

[cpp]  view plain  copy
  1. /** 示例: 删除共享内存 **/    
  2. int main(int argc,char *argv[])    
  3. {    
  4.     int shmid = shmget(0x1234, 0, 0);    
  5.     if (shmid == -1)    
  6.         err_exit("shmget error");    
  7.     
  8.     if (shmctl(shmid, IPC_RMID, NULL) == -1)    
  9.         err_exit("shmctl IPC_RMID error");    
  10.     cout << "share memory remove success" << endl;    
  11. }    

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/80106585