您的位置:首页 > 运维架构 > Linux

Unix/Linux C++应用开发-进程通信共享内存

2016-10-25 23:12 351 查看
    共享内存首先为一块实际物理内存,该块物理内存地址被影射到对应的不同进程地址空间中,如此不同的进程都可见共享内存中的所有数据。由于不同的进程操作同一块共享内存时,需要保证同一时间的操作不能搞乱内存中的数据,一定程度上共享内存操作需要增加进程操作同步机制。

    从操作系统原理角度来讲,某种形式的资源竞争为防止造成死锁等异常情况,需要一种同步机制来保证竞争相同资源时能够有序进行。共享内存使用中同步机制的某种形式上增加了该种进程通信方式的难度,但是从大型软件系统角度讲,共享内存的应用往往是进程通信的最好选择,尤其是需要处理大数据量的分布式实时系统。

    Linux系统下针对共享内存应用支持两种不同的标准,即Posix共享内存以及SystemV共享内存两种不同的方式。下面将会通过一个简单的Linux系统IPC应用图讲述两种共享内存标准方式的异同,两种标准方式对于Linux系统来讲都能够很好的支持应用,IPC应用发展见图所示。



    Linux系统中进程通信IPC基本从Unix系统继承而来。由于Unix系统最初发展产生分支,主要由当前AT&T贝尔实验室以及BSD两个机构分别实现两个版本的Unix系统。从IPC进程通信的角度来讲,AT&T版本的Unix主要发展形成SystemV标准的IPC通信方式,该方式仅仅限制于单台主机内部不同进程通信;而BSD机构版本的Unix则发展出可以进行不同主机不同进程通信基于socket的进程通信方式。

    为了摆脱不同版本的Unix差异性便于不同版本之间的可移植性,发展而来的新独立的Posix标准。Linux操作系统由于是基于Unix操作系统基础上发展而来的,除了支持Unix不同标准外,也很好的遵循了Posix标准。从进程通信的角度看,Linux系统共支持三种不同标准通信方式。第一个是继承至AT&T机构Unix版本的SystemV的IPC方式,第二个支持BSD版本Unix基于socket不同主机通信方式,另外还支持Posix标准下的IPC通信方式。

Linux系统下共享内存通常是单独创建作为公共存储空间存在的,一般情况下共享内存创建之后并不会随着单次的进程数据通信完成而销毁。由于共享内存是随内核持续的,因此共享内存可以长存系统中,直至系统重启。对于大数据量应用的生产型系统,Linux下共享内存有着广泛的应用。

1.Linux上共享内存创建管理

    Linux系统中共享内存为一块实际分配的内存区域,该内存区域可以被映射到不同进程的地址空间,被不同的进程所共享。共享内存是进程IPC通信中最快的方式,读写只需将通信的数据直接从映射的共享内存执行单次拷贝即可。

    通常情况下系统中每个共享内存对象都对应着一个特殊文件,在Linux内核中通常是通过特殊文件系统中创建一个同共享内存名称的特殊文件,而共享内存到进程地址空间的映射也是映射到相应文件系统中同名特殊文件的过程。与mmap系统调用的共享内存方式不同的是,该特殊文件并不能当作普通文件使用读写操作来访问,而是通过将特殊文件映射到进程地址空间,直接采用内存访问操作方式来访问应用的。

    正由于此原因,共享内存并没与针对其读写操作提供相应的系统调用操作,因为映射到进程地址空间的共享内存完全可以通过操作内存拷贝的方式来访问对应的共享空间。系统中针对每块共享内存,即每个共享内存操作对象都分配有相应的一个名为shmid_kernel的控制结构,存储着与相应特殊文件系统相关联的信息。Linux内核中针对共享内存中该结构体信息如下所示。

struct shmid_kernel
{
struct kern_ipc_perm shm_perm;
struct file* shm_file;
int id;
unsigned long shm_nattch;
unsigned long shm_segsz;
time_t shm_atim;
time_t shm_dtim;
time_t shm_ctim;
pid_t shm_cprid;
pid_t shm_lprid;
};

该结构体中shm_file存储了将要被映射的文件地址,每个共享内存区域都对应着一个特殊文件,内核中通过结构体ipc_ids定义的shm_ids对象来维护系统中所有的共享内存块信息,shm_ids可以通过内核实现方式访问所有的共享内存区域。
1)共享内存创建

    Linux操作系统针对共享内存操作提供了一系列的系统调用操作接口,主要包括shmget、shmat以及shmdt三种,另外还包含共享内存控制系统调用shmctl将会在后续章节介绍。

    下面将会主要针对shmget(共享内存创建)、shmat(共享内存到进程地址的映射)以及shmat(解除共享内存与进程地址的映射)三种系统调用作详细的介绍,后面将会针对这三个系统调用应用使用完整实例来进一步加深共享内存操作的印象。

    首先介绍共享内存有关的第一个系统调用shmget接口方法,系统中针对shmget方法提供原型如下。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key,int size,int shmflg);    共享内存接口方法使用同样需要包含一些头文件定义,上述两个头文件包含确保应用程序中可以正确使用Linux系统提供的共享内存操作方法接口。shmget方法为创建共享内存的操作接口,该方法拥有三个函数参数,分别表示创建共享内存的键、需要创建的共享内存大小以及创建的共享内存的存储权限。
    该方法接口调用会返回一个整型值,如果系统调用创建共享内存失败,那么该函数将会返回值为-1。如果系统调用创建共享内存成功,则会返回新的共享内存块的标识号即id。shmget方法不仅仅针对系统中不存在的共享内存块的创建工作,当当前需要指定创建的共享内存已经存在时,则可以使用该方法获取该共享内存块的id,获取该值用于应用程序中其它部分处理。

a.共享内存创建方法接口三个参数分别有着不同的作用。第一个参数为共享内存的key,唯一标识系统中一块共享内存,也称为共享内存唯一名字。共享内存key值为一个整型数,该值可以取值为0,创建一块私用的共享内存。在实际应用系统中,可以通过配置文件设定相应的共享内存key值,通过统一指定值来管理系统中共享内存创建使用工作。

b.该方法第二个参数为共享内存创建时指定的大小,参数size为整型值表示共享内存创建的长度,该参数表示的大小是以字节为单位的,如果需要创建一个10M大小的共享内存则该参数大小设定为10000*1024字节。

c.第三个参数shmflg主要指定共享内存创建时的标志,主要包含IPC_CREAT与IPC_EXCL两个标志,其中IPC_CREAT表示如果系统中指定的共享内存不存在,则创建一块新的共享内存,否则打开获取共享内存的操作符。IPC_EXCL则表示只有共享内存不存在的时,新的共享内存才会创建,否则将会报错。通常IPC_CREAT与IPC_EXCL两个标志值会配合一起使用,中间采用或运算来连接,保证创建的共享内存为新的,此时如果需要创建的共享内存已经存在的,则该方法会返回-1,而不会打开已有的共享内存操作符。

该参数中上述标志值还会配合SHM_W与SHM_R两个控制模式用于根据需求指定新创建的共享内存读写的许可。其中两个标志值范围不同,表示的可读写的权限的不同。通常标志值大于整数3则表示当前组内读写许可权限,而标志值大于6则表示全局可读写权限的许可,该标志值可以根据实际需求在应用程序中使用#define来指定并使用。

shmget系统调用方法在应用程序中会返回一个整型值,如果该返回值为-1则表示创建共享内存不成功,可以通过系统中提供的错误值来简单判断创建过程中所产生的错误。系统提供的错误值标识为errno,该值有几个表示常见错误的值说明,比如EINVAL表示创建过程中指定的共享内存大小size参数超过设定的范围,因为不同的系统中针对共享内存默认创建的最大与最小有某种规约的设定,如果指定的大小超出该范围,则会报出相应错误号。

实际开发的应用程序中,可以针对标准提供的错误号作简单的创建后的判断,为系统程序在执行时错误的排除提供一定的线索。
2)共享内存映射

    Linux系统下共享内存创建完毕之后,自然是进程需要链接到相应共享内存之上,从而通过共享内存来实现不同进程之间的数据通信。共享内存创建之后,仅仅为一块实际的内存空间,此时并没有任何进程挂接在上面。通过系统提供的方法接口shmat方法,应用进程可以attach到相应的共享内存块上,从而进行相应的通信处理。下面将会针对共享内存的映射方法接口作详细的讲述。

#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid,const void *shmaddr,int shmflg);    该系统接口方法主要用于将指定id的共享内存映射到当前应用进程的地址空间中,其中共享内存可操作的地址将会由参数shmaddr与参数shmflg共同来设置决定。如果shmaddr参数默认设定为NULL值,那么系统将会选择一个合适的共享内存链接到进程空间的首地址。

    参数shmid为共享内存在系统中的标识值,该值为shmget方法创建并返回的共享内存标识号。参数shmaddr为指向共享内存映射到进程空间的首地址,如果设定为NULL,则系统会返回一个合适的共享内存可操作的地址供应用程序读写操作使用。第三个参数shmflg为系统调用中提供的标志值设定,该标志值有三种。标志值为SHM_RND表示如果参数shmaddr为非NULL,则返回的共享内存操作地址将会在低边界可用地址上,可以是低边界地址的整数倍。

    如果shmaddr地址指定为非空,那么标志值SHM_RND未指定,则将会通过指定的shmaddr地址来链接共享内存。而第二个标志值SHM_RDONLY表示指定shmid的共享内存块以只读的方式映射到进程地址空间,获取的首地址只能读取其中的数据。第三个标志值SHM_REMAP表示允许调用者将一块共享内存区域映射到进程自身的内存空间中,如果该标志未被设置,用户传入的地址shmaddr就必须不能与进程中拥有的任何内存地址重合,如果设定了该值则意味着替换掉与指定shmaddr地址重合的共享内存段的映射。

    该系统调用方法如果执行成功,会返回相应的映射的共享内存可操作首地址供attach上共享内存的进程读写应用。而调用失败则会返回(void*)-1值,同样调用该方法后也可以针对系统提供的基本常见错误标志作一些判断处理工作。该方法错误标志可以为EACCES,表示操作的进程没有足够的操作共享内存块的权限,ENOMEM内存不足,没有办法为其分配相应的页表等错误。
3)共享内存释放

    与shmat方法相反,Linux系统调用提供了shmdt方法用于解除共享内存与当前进程之间的映射关系,即将进程与共享内存相互分离的过程。Linux提供的该方法的系统调用函数原型如下所示。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void* shmaddr);
    shmdt操作主要用于将当前进程与共享内存之间的映射关系解除,对于共享内存状态中会有专门保存当前attach上该内存的进程数。当应用进程调用该方法时,系统会自动将attach上共享内存的进程数减1,而调用shmat时会自动增加1表示当前连接到共享内存上操作的进程数。

    该方法只有一个参数,要求该参数必须为shmat映射时返回的可操作的共享内存首地址,成功解除映射时该函数会返回整型值0,否则返回-1。需要注意的是,该方法的调用并不会将系统中创建的共享内存删除,仅仅是用于分离当前进程地址空间与共享内存块的地址映射关系。另外attach上去的进程也会随着消亡而释放与共享内存之间的映射关系,与attach操作相反的是该方法称为detach共享内存操作。

4)共享内存操作工具ipcs

    系统中存在的共享内存信息,依然可以通过Linux提供的ipcs工具来操作。ipcs工具主要用于Linux系统下IPC资源操作,针对消息队列、信号量以及共享内存都有相关的选项来操作系统中相应的IPC方式的资源。

    对于共享内存,ipcs工具则提供了-m选项功能。用户使用ipcs命令配合-m选项可以查看系统中相应共享内存的基本信息。当前Linux系统shell下敲ipcs –m命令,回车之后出现的基本信息界面如下。
[developer@localhost test]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 developer 777 393216 2 dest
0x00000000 32769 root 644 106496 2 dest
0x00977998 98306 developer 600 2000 0
    当前系统中存在三个共享内存块,其中属于root用户下指定权限为644的共享内存块,以及一个shmid为0的特殊共享内存块,第三个则为当前开发用户下通过应用程序创建的key值为0x00977998,相应的执行权限为600,对应的大小为2000个字节的共享内存块,并且通过其中nattch字段信息显示表明当前并没有进程attach映射到该共享内存块进行可能的读写操作。

    通过前面介绍的释放方法接口并不能真正的删除系统中的共享内存块,在ipcs工具中可以通过ipcrm命令配合-m选项,并且指定相应需要删除的共享内存shmid号即可。如上用户创建的shmid为98306的共享内存块,可以通过如下命令真正的删除释放系统中共享内存资源,删除信息如下所示。
[developer@localhost test]$ ipcrm -m 98306
[developer@localhost test]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 developer 777 393216 2 dest
0x00000000 32769 root 644 106496 2 dest    通过上述删除命令后,查询相应的共享内存信息会发现,用户创建的共享内存块资源已经被删除。或者也可以通过ipcrm命令配合-M选项,后面紧跟需要删除的共享内存块的key值实现同样的功能。
5)共享内存创建完成实例

    Linux系统下共享内存创建通过系统调用shmget方法实现,该接口方法提供的参数为3个,分别为共享内存的key值、共享内存的指定大小以及共享内存创建的标记。通常应用程序中共享内存操作离不开系统调用的三个操作步骤,通过shmget方法创建共享内存块,通过shmat方法映射共享内存与进程地址空间,以及读写操作共享内存,最后通过shmdt方法来释放这种映射关系。

    下面通过一个完整的实例,讲述Linux系统下共享内存创建基本操作过程,实例代码编辑如下所示。
a.准备实例

    打开UE工具,创建新的空文件并且另存为chapter2501.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**
* 实例chapter2501
* 源文件chapter2501.cpp
* 共享内存创建应用实例
*/
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;
#ifndef SHM_FAILED //定义方法调用出错常量符号
#define SHM_FAILED -1
#endif
#define FLAG IPC_CREAT|IPC_EXCL|SHM_R|SHM_W //定义共享内存创建标志
//内存数据记录结构定义
typedef struct
{
int id; //数据编号
char data[100]; //具体数据空间定义
}recordData;

int main()
{
int shmid; //共享内存描述符
recordData *memoryAddr; //记录结构指针对象
shmid = shmget(0x00000003,10*1024,FLAG); //共享内存创建方法调用,返回值初始化描述符
if(shmid == SHM_FAILED) //判断创建工作是否成功
{
cout<<"share memory create fail!"<<endl; //不成功则打印输出提示信息
exit(1); //退出进程
}
memoryAddr = (recordData*)shmat(shmid,NULL,0); //进程attach到共享内存,获取其操作首地址
char temp; //定义字符临时变量
temp = 65; //为该变量初始化ASCII值
for(int i = 0;i < 5;i++) //循环写入数据
{
memcpy((*(memoryAddr+i)).data,&temp,1); //拷贝数据进入共享内存
(*(memoryAddr+i)).id = i; //数据id赋值
++temp; //递增temp值
}
if(shmdt(memoryAddr) !=0) //操作完毕后detach共享内存
{
cout<<"share memory detach fail!"<<endl; //执行失败提示信息
exit(1); //退出进程
}
return 0;
}
b.准备Makefile
    Linux平台下需要编译的源文件为chapter2501.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter2501.o
CC=g++

chapter2501: $(OBJECTS)
$(CC) $(OBJECTS) -g -o chapter2501
clean:
rm -f chapter2501 core $(OBJECTS)
submit:
cp -f -r chapter2501 ../bin    上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
c.编译运行程序

    当前shell下执行make命令,生成可执行程序文件,随后通过makesubmit命令提交程序文件至本实例bin目录,通过cd命令定位至bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$ make
g++ -c -o chapter2501.o chapter2501.cpp
g++ chapter2501.o -g -o chapter2501
[developer @localhost src]$ make submit
cp -f -r chapter2501 ../bin
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter2501
[developer @localhost bin]$ ipc -m
-bash: ipc: command not found
[developer @localhost bin]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000003 0 billing 600 10240 0
    本实例中主要演示利用系统调用,创建并写入数据至共享内存的过程。首先使用相关共享内存系统调用需要包含相应的头文件,并且通过宏自定义SHM_FAILED表示共享内存创建错误标志值。同时定义符号FLAG,用于表示共享内存创建时的标志值,该标志值表示如果系统中不存在相应key值得共享内存,则创建共享内存并且允许对该共享内存读写。

    在共享内存中写入记录式数据,需要定义结构体来表示。结构体recordData主要包含两个成员变量,分为为id与data表示记录编号以及记录数据。主应用程序中,定义整型变量shmid表示共享内存shmget方法调用后返回的id。同时定义记录结构体对象指针,用于获取可操作的共享内存首地址。

    调用shmget系统函数,根据传入的参数创建key值为0x00000003,大小为10KB的共享内存,并且标志值为FLAG。将调用结果赋值给shmid变量,随后判断该系统调用是否成功,如果shmid值为-1表明共享内存创建失败,否则程序继续执行下去。

    调用shmat方法,将共享内存地址映射到进程空间,并且返回可操作共享内存的首地址。由于该方法返回的是void*类型的地址指针,所以实际应用中需要额外的强制转换,将其转换为需要存放的记录指针形式。调用该方法,传入shmget函数返回的共享内存id,并且选择由系统自动提供一个可操作共享内存的首地址,赋值给当前的记录对象指针,即此时该对象指针指向了可操作共享内存的首地址。

    下面就可以采用该对象指针来操作所指向的内存空间,首先定义字符变量temp,初始化值为65,即为字符‘A’。通过for循环控制结构,开始向共享内存空间中写入数据5条记录数据。循环体中整型记录数据可以通过直接赋值,而字符数组数据将会通过memcpy内存直接拷贝的方式来写入。

    通过首地址memoryAddr偏移来将相应的数据放入对应的记录中,其中变量memoryAddr+i即实现每写入一个结构记录,操作的地址指针将会移向下一个记录空间供写入数据。最后不要忘记操作完共享内存后,将进程地址空间与共享内存之间的映射关系解除。

    上述程序执行完毕之后,通过ipcs –m命令组合可以查询到新建的共享内存基本信息,从而确定共享内存创建是否成功。

    至于数据写入是否正确,下面将会通过共享内存读取程序实例来验证,代码如下。

a.准备实例

    打开UE工具,创建新的空文件并且另存为chapter2502.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**
* 实例chapter2502
* 源文件chapter2502.cpp
* 共享内存读取操作实例
*/
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;
#ifndef SHM_FAILED //方法调用错误返回值常量符号定义
#define SHM_FAILED -1
#endif
#define FLAG IPC_CREAT|0666 //创建方法标志常量符号定义
//共享内存数据记录结构定义
typedef struct
{
int id; //数据编号
char data[100]; //数据正文
}recordData;
int main()
{
int shmid; //共享内存描述符
recordData *memoryAddr; //记录结构指针对象
char buffer[1024]; //缓冲区定义
shmid = shmget(0x00000003,10*1024,FLAG); //调用创建共享内存方法,返回值初始化描述符
memoryAddr = (recordData*)shmat(shmid,NULL,0); //当前进程attach到共享内存,返回首地址
for(int i = 0;i < 5;i++) //循环从共享内存读取数据记录操作
{
cout<<"id:"<<(*(memoryAddr+i)).id<<" "; //打印输出记录编号
cout<<"data:"<<(*(memoryAddr+i)).data<<endl; //打印输出相应数据
}
if(shmdt(memoryAddr) !=0) //调用detach脱离共享内存
{
cout<<"share memory detach fail!"<<endl;
exit(1);
}
return 0;
}b.准备Makefile
    Linux平台下需要编译源文件为chapter2502.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter2502.o
CC=g++

chapter2502: $(OBJECTS)
$(CC) $(OBJECTS) -g -o chapter2502
clean:
rm -f chapter2502 core $(OBJECTS)
submit:
cp -f -r chapter2502 ../bin    上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
c.编译运行程序

    当前shell下执行make命令,生成可执行程序文件,随后通过makesubmit命令提交程序文件至本实例bin目录,通过cd命令定位至bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$ make
g++ -c -o chapter2502.o chapter2502.cpp
g++ chapter2502.o -g -o chapter2502
[developer @localhost src]$ make submit
cp -f -r chapter2502 ../bin
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter2502
id:0 data:A
id:1 data:B
id:2 data:C
id:3 data:D
id:4 data:E
    本实例主要演示了从进程attach共享内存中读取相应数据的过程,大致实现过程与创建写入数据过程类似,仅仅是获取到可操作共享内存地址之后,通过取地址内容操作,将相应的内存空间中的数据打印输出出来,程序运行结果正确的将输入的数据从共享内存中读取出来。

2.Linux上共享内存控制管理

    Linux系统共享内存操作提供了针对共享内存块相应的控制方法,主要可以通过指定相应的共享内存id,获取并设置系统中指定共享内存基本信息,并且可以删除指定共享内存块以及锁定等操作。下面将会详细介绍Linux下共享内存控制操作的应用情况。

1)共享内存基本信息结构

    Linux系统内核针对每块共享内存块都维护着一个基本信息结构体shmid_ds,共享内存控制方法可以操作该结构体获取相应指定共享内存基本信息,同时也可以重新设置该结构体中基本信息。

    系统中共享内存基本信息结构体shmid_ds为Linux系统内核维护,该结构体定义实现如下所示。

struct shmid_ds
{
struct ipc_perm shm_perm; //共享内存访问权限
int shm_segsz; //共享内存大小
pid_t shm_lpid; //共享内存最后操作用户进程id
pid_t shm_cpid; //共享内存创建用户进程id
shmatt_t shm_nattach; //当前共享内存进程attach数
unsigned short shm_cnattach;
time_t shm_atime; //最后一次映射共享内存的时间
time_t shm_dtime; //最后一次取消映射共享内存的时间
time_t shm_ctime; //最后一次修改共享内存时间
};    而共享内存基本信息获取设置则是通过操作该结构体基本信息实现的,采用共享内存控制方法可以在应用程序中实现类似ipcs工具所提供的功能操作。其中shmid_ds结构体中成员ipc_perm为结构体对象成员,该结构体主要为共享内存信息key值、所属用户id以及共享内存存取模式标志等。该结构体同样为Linux系统内核定义维护,该结构体基本定义如下所示。
struct ipc_perm
{
key_t key; //共享内存key
ushort uid; //共享内存所属用户euid与egid
ushort gid;
ushort cuid; //共享内存创建者euid与egid
ushort cgid;
ushort mode; //低9位存取共享内存模式
ushort seq; //sequence number
};    通过系统中定义维护相关结构体信息,用户可以通过对外提供的共享内存控制接口来获取并操作。下面将会通过系统调用分析以及对应的演示实例来讲述共享内存控制方法的应用。
2)共享内存控制

    系统中共享内存控制方法为shmctl,该方法根据传入具体的参数命令,实现控制共享内存的基本操作。共享内存控制中提供了相当多的参数分别表示对应的控制功能,系统调用中该方法原型如下所示。

#include <sys/ipc.h>
#include <sys/sem.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
    该系统调用主要根据传入的命令参数cmd,根据其指定相应的标志操作值,从而实现不同的共享内存控制功能。参数shmid主要为当前需要控制的共享内存id,结构体参数shmid_ds则为共享内存信息输入输出数据,其中参数cmd提供了几种标志值,分别表示调用该方法实现不同的控制功能。

    cmd参数中标志值主要有5种,分别表示控制功能中获取共享内存信息、设置共享内存信息、删除共享内存以及锁定和解锁共享内存。

    主要标志值为IPC_STAT,根据传入的共享内存标识id参数,获取该块共享内存基本信息,获取到的基本信息存放于结构体指针参数buf中,供应用获取处理。第二个标志值为IPC_SET,主要用于设置指定共享内存块基本信息,通过指定的共享内存id,将需要设置的共享内存信息通过结构体指针buf传入并设置。而标志值SHM_LOCK,则用户锁住当前标识为shmid的共享内存块,不允许其它用户使用,通常该选项设置必须是root用户才可以。与之对应的是SHM_UNLOCK标志值,表示功能正好相反。

    最后一个参数IPC_RMID则表示根据传入的共享内存shmid参数,删除系统中指定的共享内存块。该系统调用与前面共享内存操作相同,调用会返回一个整型值,失败返回-1,成功返回值为0。后面将会通过一个共享内存封装实例,其中部分方法演示共享内存控制shmctl方法在应用程序中的使用情况。

3.共享内存操作封装实例

    通过前面Linux系统下共享内存系统的介绍,下面将会针对共享内存创建操作封装实现一个完整的共享内存创建管理类,将共享内存创建、映射以及基本的控制管理实现在该类中。共享内存操作类通过对外提供公共接口,在外部应用程序中可以包含该类的头文件定义,直接使用该类对象实例操作共享内存基本创建以及管理,并且可以将封装操作创建成静态库的方式供应用程序中连接使用。

    该实例中主要提供共享内存创建、析构、挂接以及去挂接的接口方法,实例代码编辑如下所示。
//共享内存操作库头文件MyShareMemory.h
#ifndef MYSHAREMEMORY_H
#define MYSHAREMEMORY_H
#include <iostream>
#include <string>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
//共享内存操作类
class MyShareMemory
{
public:
MyShareMemory(int key, int size, int flag); //共享内存操作类构造函数
~MyShareMemory(); //共享内存操作类析构函数
bool createMemory(); //创建共享内存方法
bool destroyMemory(); //销毁共享内存方法
bool attachMemory(char*& pmemoryAddr); //attach共享内存方法
bool detachMemory(char* pmemoryAddr); //detach共享内存方法
private:
int m_key; //共享内存key
int m_size; //共享内存大小
int m_flag; //共享内存创建标志
int m_shmid; //共享内存描述符
char *m_memoryAddr; //共享内存地址指针
};
#endif
//共享内存操作库源文件MyShareMemory.cpp
#include "MyShareMemory.h"
#ifndef SHM_FAILED
#define SHM_FAILED -1
#endif
/*
* 函数功能:共享内存操作构造函数
* 函数参数:key,共享内存指定key值;size,共享内存大小;flag,共享内存创建标志
* 返 回 值:无
*/
MyShareMemory::MyShareMemory(int key, int size, int flag)
{
m_flag = flag; //初始化共享内存类数据成员
m_key = key ;
m_size = size;
m_flag = flag;
m_shmid = -1;
m_memoryAddr = NULL;
}
/*
* 函数功能:共享内存操作析构函数
* 函数参数:无
* 返 回 值:无
*/
MyShareMemory::~MyShareMemory()
{
if(m_memoryAddr != NULL) //获取共享内存地址不为空
{
detachMemory(m_memoryAddr); //将会调用detach方法来脱离共享内存
}
}
/*
* 函数功能:共享内存创建操作方法
* 函数参数:无
* 返 回 值:成功创建返回true,否则返回false
*/
bool MyShareMemory::createMemory()
{
m_shmid = shmget(m_key, m_size, m_flag); //调用创建共享内存方法,返回值初始化描述符
if (m_shmid == -1) //如果创建不成功
{
return false; //返回false
}
m_memoryAddr = reinterpret_cast<char*>(shmat(m_shmid, NULL, 0)); //attach到共享内存,返回首地址
if (m_memoryAddr == (char*)SHM_FAILED) //如果返回地址为空
{
return false; //说明attach不成功返回false
}
return true; //否则成功返回true
}
/*
* 函数功能:共享内存创销毁操作方法
* 函数参数:无
* 返 回 值:成功创建返回true,否则返回false
*/
bool MyShareMemory::destroyMemory()
{
if (m_shmid == -1) //判断相应的共享内存描述符是否为-1
return false; //为-1则直接返回false
if (m_memoryAddr != NULL) //判断相应首地址是否为空
{
if (shmdt(m_memoryAddr) != 0) //如果不为空则detach脱离共享内存
return false; //方法调用不成功则返回false
m_memoryAddr = NULL; //否则脱离之后将其直接置NULL空值
}
if (shmctl(m_shmid, IPC_RMID, NULL) != 0) //调用共享内存控制方法,指定删除命令操作
return false; //方法不成功返回false
m_shmid = -1; //设置相应的描述符为-1初始值
m_memoryAddr = NULL; //设置首地址指针为空
return true; //成功返回true
}
/*
* 函数功能:共享内存挂接方法
* 函数参数:pmemoryAddr,共享内存挂接获取的首地址指针
* 返 回 值:成功创建返回true,否则返回false
*/
bool MyShareMemory::attachMemory(char*& pmemoryAddr)
{
pmemoryAddr = NULL; //初始化设置传入参数地址指针指向为空
if (m_shmid == -1) //判断相应的共享内存id是否存在
{
m_shmid = shmget(m_key, 0, SHM_R|SHM_W); //不存在则根据其数据成员重新创建
}
m_memoryAddr = reinterpret_cast<char*>(shmat(m_shmid, NULL, 0)); //随后调用attach方法,返回地址
if (m_memoryAddr == (char*)SHM_FAILED) //判断地址是否为空
return false; //为空则返回false
pmemoryAddr = reinterpret_cast<char*>(m_memoryAddr); //否则强制转换类型赋给传入的指针参数
return true; //成功返回true
}
/*
* 函数功能:共享内存脱离方法
* 函数参数:pmemoryAddr,共享内存挂接获取的首地址指针
* 返 回 值:成功创建返回true,否则返回false
*/
bool MyShareMemory::detachMemory(char* pmemoryAddr)
{
if (m_shmid == -1 || m_memoryAddr == NULL) //判断当前共享内存描述符以及首地址是否有效
return true; //如果已经无效则直接返回true
if (pmemoryAddr != (m_memoryAddr)) //判断获取到的首地址与当前首地址是否同一指向
return false; //如果不同则说明不是脱离当前指定共享内存块
int nCode = shmdt(m_memoryAddr); //调用相应detach方法,返回值赋给nCode
if (nCode != 0) //如果该方法执行不成功,进行判断诊断
{
if(errno == EINVAL)
{
m_memoryAddr = NULL;
cout<<"*Already shmdt*"<<endl;
return true;
}
return false;
}
m_memoryAddr = NULL;
return true;
}    上述封装共享内存操作需要编译成静态库的源文件为MyShareMemory.cpp,编译过程如下所示。
[developer@localhost sharememory]$ g++ -O -c MyShareMemory.cpp
[developer@localhost sharememory]$ ar -rsv libsharememory.a MyShareMemory.o
a - MyShareMemory.o
[developer@localhost sharememory]$ ll *.a
-rwx------ 1 root root 3628 Sep 6 2008 libsharememory.a
    通过前面章节介绍创建静态库的过程,将共享内存操作封装实现的源文件程序封装成静态库,上述操作过后,装有共享内存操作的静态库创建完毕。下面将会演示使用该静态库操作共享内存的过程。

    一旦确定为静态库的内容源文件设计开发并创建完毕后,应用程序中直接通过连接库文件进行操作,下面将会通过程序实例来演示封装实现的共享内存操作接口的使用情况,实例代码编译如下所示。
#include "MyShareMemory.h"
#define FLAG IPC_CREAT|IPC_EXCL|SHM_R|SHM_W //共享内存创建标志常量符号

int main()
{
char *pAddrMemory; //定义字符指针用于指向获取共享内存首地址
MyShareMemory *shareMemory = new MyShareMemory(0x900999,2000,FLAG);//创建共享内存操作对象
if(shareMemory->createMemory() == false) //调用创建方法创建共享内存
{
cout<<"shareMemory->Create() false!"<<endl; //失败提示信息
}
if(shareMemory->attachMemory(pAddrMemory) == false) //attach挂接到共享内存
{
cout<<"shareMemory->attachMemory(pAddrMemory) false!"<<endl;
}
if(shareMemory->detachMemory(pAddrMemory) == false) //detach脱离共享内存
{
cout<<"shareMemory-> detachMemory (pAddrMemory) false"<<endl;
}
/*if(shareMemory->destroyMemory() == false) //销毁共享内存,实际应用可以去注视打开
{
cout<<"destroy share memory false!"<<endl;
}*/
delete shareMemory;
return 0;
}    本实例需要编译源文件为testShareMemory.cpp,实例连接共享内存静态库编译使用过程如下。
[developer@localhost sharememory]$ g++ -O -o testShareMemory testShareMemory.cpp ./libsharememory.a
[developer@localhost sharememory]$ ./testShareMemory
[developer@localhost sharememory]$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 developer 777 393216 2 dest
0x00000000 32769 root 644 106496 2 dest
0x00900999 786436 developer 600 2000 0
    依然按照前面章节介绍静态库连接使用方式,在应用实例中包含需要操作的静态库的头文件,编译程序时只需要连接需要的静态库即可。

    上述实例中主要演示了通过共享内存操作类对象接口,创建共享内存、映射并获取可操作共享内存的首地址以及分离共享内存与当前进程地址空间的过程。由于该封装实现的库可以应用于任何需要共享内存操作的应用中,将共享内存常见操作封装实现成库,方便开发人员只需一次实现共享内存操作,即可在需要的地方包含使用。

    操作接口中同时还提供了destroyMemory方法接口,通过共享内存控制真正的在程序结束后清除系统中的操作的共享内存。该方法调用在应用实例中注释的原因在于通过ipcs命令检验前面创建共享内存是否成功。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: