进程间通信——System V IPC与共享内存

  什么是System V IPC?

  System V是一种Unix操作系统,此系统引入了三种高级进程间的通信机制:消息队列,共享内存,信号量。System V IPC是以下三种IPC的合称:

  System V 消息队列     System V 信号量      System V 共享内存

  消息队列、信号量、共享内存也称为IPC对象。通过ipcs可以查看当前的IPC对象,通过ipcrm可以删除IPC对象。

1   ipcs -q: 只显示消息队列。
2   ipcs -s: 只显示信号量。
3   ipcs -m: 只显示共享内存。
4   ipcs –help: 其他的参数

  IPC对象存在于内核中而不是文件系统中,由用户控制释放,不像管道的释放由内核控制

       IPC对象通过其标识符来引用和访问,所有IPC对象在内核空间有唯一性标志ID,在用户空间的唯一性标识符称为key

  IPC对象函数创建

  Unix系统中,一切皆文件,很多IPC机制中的操作都是针对文件描述符的,但system V是针对IPC对象的ID操作的,而ID(标识符)是由key(键)生成的。

   在IPC的通信模式下,不管是使用消息队列还是共享内存,甚至是信号量,每个IPC的对象(object)都有唯一的名字,称为“键”(key)。通过“键”,进程能够识别所用的对象。“键”与IPC对象的关系   就如同文件名称之于文件,通过文件名,进程能够读写文件内的数据,甚至多个进程能够共用一个文件。而在IPC的通讯模式下,通过“键”的使用也使得一个IPC对象能为多个进程所共用。

  ftok() ---> key ------>  ID

  接下来看一下ftok函数:

  ftok()函数

1 #include <sys/types.h>
2 #include <sys/ipc.h>
3 
4 key_t ftok(const char *pathname, int proj_id);
5 //pathname为文件路径名,必须存在且可访问,常为当前路径名“.”
6 //proj_id 子ID,用于生成key的数字,范围1-255
7 

  System V IPC详见:https://blog.csdn.net/qq_38211852/article/details/80475818

共享内存

  共享内存允许不同的进程访问同一个逻辑内存,对内存直接读写,而不需要任何数据的拷贝,对于消息队列、管道等通信方式,需要在内核空间进行四次数据拷贝,而高兴内存只需要拷贝两次,一次输入文件到共享内存区,另一次从共享内存区到输出文件。。

  共享内存在内核空间创建,可以被映射到用户空间访问,使用灵活。(映射即建立关联:如在内核中一块内存地址为1,通过映射到用户空间后为a,之后在用户空间访问a即访问内核空间1一样。)

  共享内存并未提供同步机制,所以需要用其他机制实现不同进程对共享内存的访问。

    共享内存使用步骤

1 创建/打开共享内存
2 映射共享内存,即把指定的共享内存映射到进程地址空间用于访问
3 读写共享内存
4 撤销共享内存映射
5 删除共享内存对象

  1、共享内存创建—shmget

 1 #include <sys/ipc.h>
 2 #include <sys/shm.h>
 3 
 4 int shmget(key_t key, size_t size, int shmflg);
 5 
 6 //成功返回共享内存的ID,失败返回EOF
 7 //参数:
 8 //key---可由fotk生成,为共享内存段命令,函数返回与key相关标识符
 9 //size---以字节为单位指定需要的共享内存容量
10 //shmflg---权限标志,如:IPC_CREAT|0666

详解:

//key_t key
-----------------------------------------------
    key标识共享内存的键值: 0/IPC_PRIVATE。 当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中设置了IPC_PRIVATE这个标志,则同样将创建一块新的共享内存。

//int size(单位字节Byte)
-----------------------------------------------
    size是要建立共享内存的长度。所有的内存分配操作都是以页为单位的。所以如果一段进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE=4096字节)这样,新创建的共享内存的大小实际上是从size这个参数调整而来的页面大小。即如果size为1至4096,则实际申请到的共享内存大小为4K(一页);4097到8192,则实际申请到的共享内存大小为8K(两页),依此类推。

//int shmflg
-----------------------------------------------
    shmflg主要和一些标志有关。其中有效的包括IPC_CREAT和IPC_EXCL,它们的功能与open()的O_CREAT和O_EXCL相当。
    IPC_CREAT   如果共享内存不存在,则创建一个共享内存,否则打开操作。
    IPC_EXCL     只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。

    如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。

    如果将IPC_CREAT和IPC_EXCL标志一起使用,shmget()将返回一个新建的共享内存的标识符;如果该共享内存已存在,或者返回-1。

     IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。

推荐使用这个:
           可以使用0666|IPC_CREAT,来作为shmflg的值。

//返回值
-----------------------------------------------
成功返回共享内存的标识符;不成功返回-1,errno储存错误原因。
    EINVAL           参数size小于SHMMIN或大于SHMMAX。
    EEXIST           预建立key所致的共享内存,但已经存在。
    EIDRM            参数key所致的共享内存已经删除。
    ENOSPC        超过了系统允许建立的共享内存的最大值(SHMALL )。
    ENOENT        参数key所指的共享内存不存在,参数shmflg也未设IPC_CREAT位。
    EACCES        没有权限。
    ENOMEM       核心内存不足。

引用于:http://blog.csdn.net/lanmanck/archive/2010/12/22/6092995.aspx
shmget参数详解

示例1:创建一个私有的共享内存,大小为512字节,权限为0666

1 int shmid;
2 
3 if((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0)
4 {
5       perror("shmget");
6       exit(-1);            
7 }

  示例2:创建/打开一个和KEY关联的共享内存,大小为1024字节,权限为0666

 1 key_t key;
 2 int shmid;
 3 
 4 if((key = ftok(".", 'm')) == -1)
 5 {
 6      perror("ftok");
 7      exit(-1);
 8 }
 9 if((shmid = shmget(key, 1024, IPC_CREAT|0666)) < 0)
10 {
11       perror("shmget");
12       exit(-1);            
13 }

  2、共享内存的映射—shmat

 1 #include <sys/ipc.h>
 2 #include <sys/shm.h>
 3 
 4 void *shmat(int shmid, const void *shmaddr, int shmflg);
 5 
 6 //成功返回映射后的地址,失败返回(void *)-1
 7 //参数:
 8 //shmid---要映射的共享内存ID
 9 //shmaddr---映射后的地址,NULL表示由系统自动映射
10 //shmflg---标志位,0表可读写;SHM_RDONLY表只读

  shmat--share memory attach,应用层无法直接访问内核内存,通过系统调用,将内核中的共享内存映射到用户空间。

  3、写共享内存

  通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型

  示例:在共享内存中存放键盘输入的字符串

 1 char *addr;
 2 int shmid;
 3 ...
 4 if((addr = (char *)shmat(shmid, NULL, 0)) == (char *)-1)
 5 {
 6       perror("shmat");
 7       exit(-1);  
 8 }
 9 fgets(addr, N, stdin);
10 ...

  4、共享内存撤销映射—shmdt

  将共享内存从当前进程中分离,分离并不是删除,只是使该共享内存对当前进程不再可用。

 1 #include <sys/ipc.h>
 2 #include <sys/shm.h>
 3 
 4 int shmdt(const void *shmaddr);
 5 
 6 //成功时返回0,失败时返回EOF
 7 //参数:
 8 //shmaddr---为shmat函数返回的地址指针
 9 //不使用共享内存时应当撤销映射
10 //进程结束时会自动撤销

  5、共享内存控制—shmctl

  用于控制共享内存

 1 #include <sys/ipc.h>
 2 #incude  <sys/shm.h>
 3 
 4 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
 5 
 6 //成功返回0,失败返回EOF
 7 //参数
 8 //shmid---要操作的共享内存id
 9 //cmd---要执行的操作  
10 //       IPC_STAT:用共享内存的当前关联值覆盖shmid_ds的值。
11 //       IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
12 //       IPC_REID:删除共享内存段

注意事项:

  每块共享内存的大小有限制,可以通过下命令查看

  ipcs -l

  cat /proc/sys/kernel/shmmax

  共享内存删除的时间点

  shmctl(shmid, IPC_RMID, NULL);添加删除标记

  当nattach变成0时才真正删除。

测试程序

通过shm_write 和shm_read这两个进程实现对共享内存的读写

shm_wirte.c

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/ipc.h>
 5 #include <sys/shm.h>
 6 
 7 int main()
 8 {
 9     key_t key;
10     int shmid;
11     char *addr; //定义映射后的地址
12     key = ftok(".",23);
13     if(key == -1)
14     {
15         perror("ftok");
16         return -1;
17     }
18     shmid = shmget(key,1024,IPC_CREAT|0666); //创建共享内存
19     if(shmid==-1)
20     {
21         perror("shmget");
22         return -1;
23     }
24     
25     addr = shmat(shmid,NULL,0); //映射共享内存
26 
27     strcpy(addr,"this is share memory"); //读写共享内存
28 
29     shmdt(addr); //撤销共享内存映射
30 
31 
32 }

shm_read.c

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/ipc.h>
 5 #include <sys/shm.h>
 6 
 7 int main()
 8 {
 9     key_t key;
10     int shmid;
11     char *addr; //定义映射后的地址
12     key = ftok(".",23);
13     if(key == -1)
14     {
15         perror("ftok");
16         return -1;
17     }
18     shmid = shmget(key,1024,IPC_CREAT|0666); //创建共享内存
19     if(shmid==-1)
20     {
21         perror("shmget");
22         return -1;
23     }
24     
25     addr = shmat(shmid,NULL,0); //映射共享内存
26 
27 //    strcpy(addr,"this is share memory"); //读写共享内存
28     printf("get share memory = %s
",addr);
29     shmdt(addr); //撤销共享内存映射
30 
31 
32 }

结果:

共享内存详解优秀博客:https://blog.csdn.net/Al_xin/article/details/38602093

原文地址:https://www.cnblogs.com/y4247464/p/12109332.html