Linux进程通信之System V共享内存
前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而System V共享内存区则是调用shmget创建共享内存区然后调用shmat进行内存区的映射。
对每个System V共享内存区,内核会维护一个shmid_ds的数据结构,Linux 2.6.18 中的定义如下:
<bits/shm.h> /* 连接共享内存区的进程数的数据类型 */
typedef unsigned long int shmatt_t; struct shmid_ds
{
struct ipc_perm shm_perm; /* operation permission struct */
size_t shm_segsz; /* 共享存储段的最大字节数 */ __time_t shm_atime; /* time of last shmat() */
__time_t shm_dtime; /* time of last shmdt() */
__time_t shm_ctime; /* time of last change by shmctl() */ __pid_t shm_cpid; /* pid of creator */
__pid_t shm_lpid; /* pid of last shmop */ shmatt_t shm_nattch; /* 连接共享内存区的进程数 */ //保留字段
#if __WORDSIZE == 32
unsigned long int __unused1;
unsigned long int __unused2;
unsigned long int __unused3;
#endif
unsigned long int __unused4;
unsigned long int __unused5;
};
1 System V共享内存区的创建和打开
下面是shmget函数的接口以及说明:
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//成功返回共享内存标识符,失败返回-1
shmget函数用于创建或打开一个共享内存区对象,shmget成功调用会返回一个共享内存区的标识符,供其它的共享内存区操作函数使用。
key:用于创建共享内存区的键值,这个在前面其他System IPC创建的时候已经讨论过了,System IPC都有一个key,作为IPC的外部标识符,创建成功后返回的描述符作为IPC的内部标识符使用。key的主要目的就是使不同进程在同一IPC汇合。key具体说可以有三种方式生成:
- 不同的进程约定好的一个值;
- 通过相同的路径名和项目ID,调用ftok()函数,生成一个键;
- 还可以设置为IPC_PRIVATE,这样就会创建一个新的,唯一的IPC对象;然后将返回的描述符通过某种方式传递给其他进程;
size:指定创建共享内存区的大小,单位是字节。如果实际操作为创建一个共享内存区时,必须指定一个非0值,如果实际操作是访问一个已存在的共享内存区,那么size应为0。
shmflg:指定创建或打开消息队列的标志和读写权限(ipc_perm中的mode成员)。我们知道System V IPC定义了自己的操作标志和权限设置标志,而且都是通过该参数传递,这和open函数存在差别,open函数第三个参数mode用于传递文件的权限标志。System V IPC的操作标志包含:IPC_CREAT,IPC_EXCL。读写权限如下图:
图1 System V共享内存区的读写权限标志
System V共享内存区在创建后,该size大小的内存区会被初始化为0,这和POSIX共享内存不同,POSIX标准并没有规定新创建的POSIX共享内存区的初始内容。
2 System V共享内存区的连接和断接
通过shmctl创建或打开共享内存区对象后,并没有将该共享内存区映射到调用进程的地址空间中,所以无法访问该共享内存区,需要通过shmat函数,将该共享内存区连接到调用进程的地址空间中,才能进行访问,当该进程完成对该共享内存区的访问后,可以调用shmdt断接这个共享内存区,当然进程结束后会自动断接所有连接的共享内存区。下面是shmat和shmdt函数的接口以及说明:
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//成功返回映射区的起始地址,失败返回-1
int shmdt(const void *shmaddr);
//成功返回0,失败返回-1
shmat用于将一个共享内存区连接到调用进程的地址空间中。
shmid:打开的System V共享内存对象的标识符;
shmaddr和shmflg参数共同决定了共享内存区连接到调用进程的具体地址,规则如下:
- shmaddr为空指针:连接的地址由系统内核决定,这是推荐的方法,具有可移植性。
- shmaddr非空:此时还要根据shmflg参数是否指定SHM_RND标志进行判断:
- 没有指定SHM_RND:共享内存区连接到调用进程的shmaddr指定的地址;
- 指定SHM_RND:共享内存区连接到shmaddr指定的地址向下舍入SHMLBA的位置。
shmflg:除了上面说的SHM_RND外,还有可以指定SHM_RDONLY标志,限定只读访问。一般该标志置为0。
shmdt用于将一个共享内存区从该进程内断接,当一个进程终止时,它连接的所有共享内存区会自动断接。
3 System V共享内存区的控制操作
shmctl函数可以对共享内存区进行多种控制操作,下面是shmctl函数的接口以及说明:
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//成功返回0,失败返回-1
shmid:共享内存区的标识符。
cmd:对共享内存区控制操作的命令,Open Group 的SUS定义了一下三个操作命令:
- IPC_SET:按第三个参数buf所指定的内容,设置共享内存区的操作权限字段的:shm_perm.uid,shm_perm.gid和shm_perm.mode。此命令只能由以下进程执行:有效用户ID等于shm_perm.uid或shm_perm.cuid,以及有超级用户权限的进程。
- IPC_STAT:获取共享内存区的shmid_ds结构,存放到传入的第三个参数中。
- IPC_RMID:从系统内核中删除该共享内存区。因为每个共享内存区有一个连接数据的计数,除非连接该共享内存区的最后一个进程断接或终止,否则该共享内存区不会被实际删除。这和其他的System V IPC,例如System V消息队列的删除差别很大,倒是和POSIX IPC的xxx_unlink删除操作很相识。调用该命令后,该共享内存区标识符不能再继续被连接。此命令也只能由以下进程执行:有效用户ID等于shm_perm.uid或shm_perm.cuid,以及有超级用户权限的进程。
在Linux中还定义了其他的控制命令,如:IPC_INFO,SHM_INFO,SHM_LOCK,SHM_UNLOCK等,具体可以参考Linux手册。
下面是创建System V共享内存区和查看其属性的测试代码:
#include <iostream>
#include <cstring>
#include <errno.h> #include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h> using namespace std; #define PATH_NAME "/tmp/shm" int main()
{
int fd; if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
{
cout<<"open file "<<PATH_NAME<<"failed.";
cout<<strerror(errno)<<endl;
return -1;
} close(fd); key_t key = ftok(PATH_NAME, 0); int shmID; if ((shmID = shmget(key, sizeof(int), IPC_CREAT | 0666)) < 0)
{
cout<<"shmget failed..."<<strerror(errno)<<endl;
return -1;
} shmid_ds shmInfo;
shmctl(shmID, IPC_STAT, &shmInfo); cout<<"shm key:0x"<<hex<<key<<dec<<endl;
cout<<"shm id:"<<shmID<<endl;
cout<<"shm_segsz:"<<shmInfo.shm_segsz<<endl;
cout<<"shm_nattch:"<<shmInfo.shm_nattch<<endl; return 0;
}
执行结果如下:
shm key:0x80e7
shm id:5898284
shm_segsz:4 //共享内存区的大小
shm_nattch:0 //共享内存区的连接数目
通过ipcs命令查看该新创建的共享内存区对象:
[root@idcserver program]# ipcs -m -i 5898284
Shared memory Segment shmid=5898284
uid=0 gid=0 cuid=0 cgid=0
mode=0666 access_perms=0666
bytes=4 lpid=0 cpid=16422 nattch=0
att_time=Not set
det_time=Not set
change_time=Tue Aug 13 15:34:35 2013
4 System V共享内存区的限制
和其中的System V IPC一样,System V共享内存也存在系统的限制,关于系统范围内对共享内存的限制,在Linux 2.6.18 <bits/shm.h>中定义了shminfo结构,该结构显示了系统内核的限制,如下:
#include <bits/shm.h>
struct shminfo
{
unsigned long int shmmax; //一个共享内存区的最大字节数
unsigned long int shmmin; //一个共享内存区的最小字节数
unsigned long int shmmni; //系统范围内的共享内存区对象的最大个数
unsigned long int shmseg; //每个进程连接的最大共享内存区的数目
unsigned long int shmall; //系统范围内的共享内存区的最大页数
unsigned long int __unused1;
unsigned long int __unused2;
unsigned long int __unused3;
unsigned long int __unused4;
};
在Linux 下shmctl中可以指定IPC_INFO来获取上面结构所示的系统范围内的限制。在Linux下,具体的限制值可以通过sysctl来查看,如下:
[root@idcserver program]# sysctl -a | grep shm
...
kernel.shmmni = 4096 //系统范围内的共享内存区对象的最大个数
kernel.shmall = 4294967296 //系统范围内的共享内存区的最大页数
kernel.shmmax = 68719476736 //一个共享内存区的最大字节数
一般情况下不需要对System V共享内存区的系统限制进程修改,因为基本可以满足应用需求,如果要在系统范围内对内核限制进行修改,在Linux下面可以通过修改/etc/sysctl.conf 内核参数配置文件,然后配合sysctl命令来对内核参数进行设置。例如下面示例:
[root@idcserver program]#echo "kernel.shmmni= 1000" >>/etc/sysctl.conf
[root@idcserver program]#sysctl -p
[root@idcserver program]#sysctl -a |grep shm
kernel.shmmni = 1000
kernel.shmall = 4294967296
kernel.shmmax = 68719476736
5 System V共享内存区的使用
下面是System V共享内存区的使用示例,进程1通过共享内存区向进程2发送一条消息,对共享内存区的同步采用System V信号量来完成。如下:
//process 1
#include <iostream>
#include <cstring>
#include <errno.h> #include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/sem.h> using namespace std; #define PATH_NAME "/tmp/shm" union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
}; int main()
{
int fd; if ((fd = open(PATH_NAME, O_RDONLY | O_CREAT, 0666)) < 0)
{
cout<<"open file "<<PATH_NAME<<"failed.";
cout<<strerror(errno)<<endl;
return -1;
} close(fd); key_t keyShm = ftok(PATH_NAME, 0);
key_t keySem = ftok(PATH_NAME, 1); int shmID, semID; if ((shmID = shmget(keyShm, sizeof(int), IPC_CREAT | 0666)) < 0)
{
cout<<"shmget failed..."<<strerror(errno)<<endl;
return -1;
} int *buf = (int *)shmat(shmID, 0, 0); if ((semID = semget(keySem, 1, IPC_CREAT | 0666)) < 0)
{
cout<<"semget failed..."<<strerror(errno)<<endl;
return -1;
} semun arg;
arg.val = 0; //初始化信号量资源的数目为0 if (semctl(semID, 0, SETVAL, arg) < 0)
{
cout<<"semctl error "<<strerror(errno)<<endl;
return -1;
} struct sembuf buffer;
buffer.sem_num = 0;
buffer.sem_op = 1;
buffer.sem_flg = 0; *buf = 111;
cout<<"process 1:send "<<*buf<<endl; //将信号量资源加1,以表示共享内存区内已有资源
semop(semID, &buffer, 1); return 0;
} //process 2
#include <iostream>
#include <cstring>
#include <errno.h> #include <unistd.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/sem.h> using namespace std; #define PATH_NAME "/tmp/shm" union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
}; int main()
{
int fd; if ((fd = open(PATH_NAME, O_RDONLY)) < 0)
{
cout<<"open file "<<PATH_NAME<<"failed.";
cout<<strerror(errno)<<endl;
return -1;
} close(fd); key_t keyShm = ftok(PATH_NAME, 0);
key_t keySem = ftok(PATH_NAME, 1); int shmID, semID; if ((shmID = shmget(keyShm, sizeof(int), 0)) < 0)
{
cout<<"shmget failed..."<<strerror(errno)<<endl;
return -1;
} int *buf = (int *)shmat(shmID, 0, 0); if ((semID = semget(keySem, 1, 0)) < 0)
{
cout<<"semget failed..."<<strerror(errno)<<endl;
return -1;
} struct sembuf buffer;
buffer.sem_num = 0;
buffer.sem_op = -1;
buffer.sem_flg = 0; //获得信号量资源
semop(semID, &buffer, 1); cout<<"process 2:recv "<<*buf<<endl; return 0;
}
测试结果为:
# ./send
process 1:send 111
# ./recv
process 2:recv 111
Aug, 13 PM 22:18 @lab
今天貌似是个悲伤的日子。。。你们都在那啪啪啪,我们只能在这撸呀撸。。。实验室今天还一起吃饭,好久没喝酒,喝了不到两瓶啤的就晕了,哎,老了。。。15号就要放假了,好好看书,准备找工作了。。。
Linux进程通信之System V共享内存的更多相关文章
- Linux IPC实践(9) --System V共享内存
共享内存API #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int ...
- linux网络编程之system v共享内存
接着上次的共享内存继续学习,这次主要是学习system v共享内存的使用,下面继续: 跟消息队列一样,共享内存也是有自己的数据结构的,system v共享内存也是随内核持续的,也就是说当最后一个访问内 ...
- php进程(线程)通信基础--System V共享内存
PHP默认情况没有开启功能,要支持该功能在编译PHP的时候要加入下面几个选项 System V消息,--enable-sysvmsg System V信号量支持,--enable-sysvsem ...
- Linux进程通信之System V消息队列
System V消息队列是Open Group定义的XSI,不属于POSIX标准.System V IPC的历史相对很早,在上个世70年代后期有贝尔实验室的分支机构开发,80年代加入System V的 ...
- 阐述linux IPC(五岁以下儿童):system V共享内存
[版权声明:尊重原创.转载请保留源:blog.csdn.net/shallnet 要么 .../gentleliu,文章学习交流,不用于商业用途] system V共享内存和posix ...
- Linux system v 共享内存
system v 共享内存 #include <sys/types.h> #include <sys/shm.h> int shmget(key_t key, size_t s ...
- System V 共享内存区
1.概述 系统调用mmap通过映射一个普通文件实现共享内存.System V 则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信.也就是说,每个共享内存区域对应特殊文件系统shm中的一个文 ...
- System V共享内存介绍
(一)简单概念 共享内存作为一种进程间通信的方式,其相较于其他进程间通信方式而言最大的优点就是数据传输速率快.其内部实现的方式采用了Linux进程地址空间中的mmap文件映射区,将文件内容直接映射到各 ...
- UNIX环境高级编程——System V 共享内存区
共享内存区域是被多个进程共享的一部分物理内存.如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信.共享内存是进程间共享数据的一种最 ...
随机推荐
- AppDomain与进程、线程、Assembly之间关系
AppDomain是CLR的运行单元,它可以加载Assembly.创建对象以及执行程序 AppDomain是CLR实现代码隔离的基本机制. 每一个AppDomain可以单独运行.停止:每个AppD ...
- python 包导入规则
python 包导入规则,恶心了一天,终于搞清楚了 1.目录 speed data __init__.py __init__.py static templates view __init__.py ...
- iOS: 学习笔记, 透过Boolean看Swift(译自: https://developer.apple.com/swift/blog/ Aug 5, 2014 Boolean)
透过Boolean看Swift 一个简单的Bool类型内部就包含了许多Swift主要功能, 如何构建一个简单类型是有趣的演示. 本文将创建一个与Bool类型在设计与实现上非常相似的新MyBool类型. ...
- Sharepoint 问题集锦 - external list (外部列表)
使用Sharepoint开发过程中遇到的问题总结. 错误1: Unable to display this Web Part. To troubleshoot the problem, open th ...
- const变量与define定义常量的区别
一.概念性区别 const 变量就是在普通变量前边加上一个关键字const,它赋值的唯一机会就是“定义时”,此变量不能被程序修改,存储在rodata区. define定义的是常量,不是变量,所以编译器 ...
- Spire.XLS
又一款Excel处理神器Spire.XLS,你值得拥有(二) 前言:上篇 C#组件系列——又一款Excel处理神器Spire.XLS,你值得拥有 介绍了下组件的两个功能,说不上特色,但确实能解决我 ...
- Reactor Cooling
sgu194:http://acm.sgu.ru/problem.php?contest=0&problem=194 题意:题目大意:给n个点,及m根pipe,每根pipe用来流躺液体的,单向 ...
- <转>SFTP 和FTPS的区别是什么?
SFTP 和FTPS都是为ftp连接加密,协议非常相似. 一个是借助ssl协议加密,一个时借助ssh加密. ssl是为http/smtp等加密设计的,ssh是为telnet/ftp等加密.建立传输通道 ...
- vs查看虚函数表和类内存布局
虚继承和虚基类 虚继承:在继承定义中包含了virtual关键字的继承关系: 虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:class CSubClass : publ ...
- bzoj2653
CLJ神牛的可持久化论文的题目,果然厉害其实第一步能想到后面就还是很简单的首先是二分答案,转化为判定性问题然后对于区间内的数,比他大的标为1,小的标为-1显然,如果存在一个左右端点符合的区间使得这个区 ...