本篇博客介绍第二种进程间通信的方式 –
System V
System V 有三种方式:
共享内存
消息队列
信号量
本篇博客对于系统调用的函数,会进行一定的封装
进程间具有独立性,不同进程之间无法直接获取对方的数据,所有通信都需要有
第三方
的介入,管道是以文件
的方式,提供共享的文件。
匿名管道建立在子进程继承父进程的进程信息
的特性,可以获得父进程创建的匿名管道的文件描述符
,仅适用于具有“亲戚关系”
的进程;
命名管道本质同匿名管道一样,只不过是在当前目录下创建一个管道文件
,不同进程可以以不同方式
打开这个管道文件,进行读写
,适用于任何不同进程
两个管道都是内存级文件
,不会进行刷盘
那么System V 共享内存是以什么方式使得不同进程进行通信的呢?
进程间通信的前提都是:
让不同进程看到同一份“资源”
管道通过文件,而System V通过内存实现
System V创建的共享内存通过页表映射放到不同进程的共享区
创建共享内存的函数是shmget()
shared memory get
- 第一个参数稍后详细讲解
- 第二个参数是创建的共享内存的大小
- 第三个参数是一个
位图
,可传的参数有三种情况:
(1)IPC_CREAT
:没有共享内存就创建,有,就返回这个共享内存的shmid
(2)IPC_CREAT | IPC_EXCL
:没有共享内存就创建,有,就直接报错,返回-1并设置错误码。其作用是确保创建的共享内存是最新的
(3)以上两种 | 权限
注意:IPC_EXCL不能单独使用
,没有效果返回值是创建的共享内存的
共享内存的标识符
(和文件描述符不同)
下面我们详细讲解一下第一个参数
我们创建的共享内存,
需要不同进程双方都能找到
,并且创建新的共享内存时不和其他共享内存冲突
,所有需要由系统生成一个key值
,尽量的避免冲突,保证不同进程的通信
生成key值的函数是:ftok()
内存中可能同时有多个共享内存,操作系统也会对其有相应的结构体,对他们进行管理。这也是先描述,再组织思想的体现。
所以我们就需要确保不同进程能找到同一个共享内存
第一个参数:路径字符串;第二个参数:项目ID
ftok的这两参数,在创建后会保存在共享内存结构体的属性中
,这样操作系统才能帮我们找到我们要使用的共享内存。
而需要进行通信的进程,需要确保这两个参数传的一样
,才能找到同一个共享内存。
具体内容是什么其实不重要(pathname需要是能找到的文件路径
),只要确保要通信的进程传的一样就好
获取key值的函数
#define PATHNAME "."
#define PROJID 0x6666
//获取key值
key_t getKey()
{
key_t k=ftok(PATHNAME,PROJID);
if(k==-1)
{
cerr<<errno<<":"<<strerror(errno)<<endl;
exit(1);
}
return k;
}
//将key值转换成十六进制
string toHex(key_t k)
{
char buffer[64];
snprintf(buffer,sizeof(buffer),"0x%x",k);
return buffer;
}
这样我们就得到了相同的key值。
接下来就是创建内存空间了。
因为创建共享内存和获取共享内存只有最后一个参数有所不同,所以我们可以进行一定的封装
//进行封装
static int createShmHelper(key_t key,int size,int flag)
{
int shmid = shmget(key,size,flag);
//出错判断
if(shmid==-1)
{
cerr<<"error: "<<errno<<" : "<<strerror(errno)<<endl;
exit(2);
}
return shmid;
}
//创建共享内存
int createShm(key_t key,int size)
{
//因为是创建,我们就用,保证创建最新的内存空间
return createShmHelper(key,size,IPC_CREAT | IPC_EXCL);
}
//获取共享内存
int getShm(key_t key,int size)
{
//只要获取就好,所以只要IPC_CREAT
return createShmHelper(key,size,IPC_CREAT);
}
我们成功创建了共享内存,那么接下来有个疑问:我们这两个程序很简单,没有任务循环,运行一下就结束了,那
相应创建的共享内存还存在吗?
我们如果再运行shmServer会发现以下现象
因为在shmServer.cpp中,我们调用shmget()使用的是IPC_CREAT | IPC_EXCL,所以当共享内存已经存在时,会创建失败,返回-1,设置错误码。
==================================================================================
共享内存的查看
我们还可以使用ipcs
指令查看进程间通信的相关信息
第一个是消息队列,第二个共享内存,第三个是信号量数组
ipcs -m
指令表示只查看共享内存
key
:共享内存对应的key值,类比文件inode编号
,内核使用
shmid
:共享内存的id,类比文件描述符fd
,用户使用
owner
:创建该共享内存的用户
perms
:该共享内存的权限(同文件)
bytes
:大小(以字节为单位)
nattch
:关联(链接)该共享内存的进程数
status
:
以上两种现象,都表明,即使创建共享内存的进程已经退出,相应的共享内存其实还存在。
==================================================================================
共享内存的删除
我们可以通过ipcrm -m shmid
删除我们创建的共享内存
我们再创建共享内存就会分配新的shmid给我们创建的共享内存了
函数是:
shmctl()
第一个参数是shmid
;第二个是对共享内存的操作
,操作有多种,删除,修改个别属性...
;第三个参数是输出型参数
,是内核中管理共享内存的结构体
,可以通过传结构体获取该共享内存的属性
共享内存的删除
修改操作的宏是IPC_RMID
//删除共享内存
void delShm(int shmid)
{
int n=shmctl(shmid,IPC_RMID,NULL);
assert(n!=-1);
(void)n;
}
==================================================================================
获取共享内存属性
//查看共享内存属性
struct shmid_ds ds;
int n=shmctl(shmid,IPC_STAT,&ds);
if(n!=-1)
{
cout<<"perm: "<<toHex(ds.shm_perm.__key)<<endl;
cout<<"create pid: "<<ds.shm_cpid<<" : "<<getpid()<<endl;
}
注意:查看共享内存需要有权限
调用者必须要有这个共享内存的读权限
权限需要在创建时确定,其实只需要在shmget的第三个参数加上对应的权限就好
//创建共享内存
int createShm(key_t key,int size)
{
//因为是创建,我们就用,保证创建最新的内存空间
umask(0);//将掩码设置为0,保证预期的权限
return createShmHelper(key,size,IPC_CREAT | IPC_EXCL | 0666);
}
以上我们完成了进程间通信的周边工作,但是我们还没有真正实现通信。我们
只创建了共享内存
,但其实此时共享内存还并没有在我们进程的task struct中,我们只是成功的创建了。为此我们还需要关联这个共享内存
,才会将共享内存映射到进程的共享区
,才可以真正的使用。
第一个参数是
shmid
;第二个参数是共享区的虚拟地址
,可以通过这个参数指定将共享内存挂接到指定位置;第三个参数是挂接方式(读写)
返回值是挂接的共享区的虚拟地址
//关联共享内存
char * attchShm(int shmid)
{
char*start=(char*)shmat(shmid,NULL,0);
return start;
}
在(三)中,我们就讲了,nattch是关联该共享内存的进程数
,这个测试也验证了这一点。0->1->2->1->0
取关联的函数是shmdt()
//取关联共享内存
void detachShm(char*start)
{
int n=shmdt(start);
assert(n!=-1);
(void)n;
}
接下来,我们就可以使用共享内存进行通信了。
不过我们还可以将上述的代码再进行一个封装,封装成一个类
#define SERVER 1
#define CLIENT 0
const int gsize=4096;
class Init
{
public:
Init(int t):type(t)
{
//获取key值
key_t k = getKey();
//创建/获取共享内存
if(type == SERVER)
{
//服务端创建
shmid = createShm(k, gsize);
}
else
{
//客户端获取
shmid = getShm(k, gsize);
}
//关联共享内存
start = attachShm(shmid);
}
//获取读写字符串
char *getStart()
{
return start;
}
~Init()
{
//取关联
detachShm(start);
if(type == SERVER)
{
//如果是服务端,还需要销毁共享内存
delShm(shmid);
}
}
private:
char *start;//读写字符串
int type; //表示是服务端还是客户端
int shmid;//共享内存的id
};
本篇知识记录较杂,请多谅解。本着记笔记分享的目的,望佬指点。
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。