进程间通信——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
详解:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//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
示例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\n",addr); 29 shmdt(addr); //撤销共享内存映射 30 31 32 }
结果:
共享内存详解优秀博客:https://blog.csdn.net/Al_xin/article/details/38602093