文章目录
- 共享内存
- 常用的接口
- 指令
- 利用命名管道实现同步机制
- 总结
System V的IPC资源的生命周期都是随内核的。
共享内存
共享内存也是为了进程间进行通信的,因为进程间具有独立性,通信的本质是两个不同的进程看到同一份公共资源,所以共享内存一定是由OS提供的,共享内存的本质就是,在内存中开辟一块空间,然后映射在需要通过这片空间的进程的地空间中堆和栈之间共享区,通过这样的方式让两个不同的进程看到同一份公共资源。如下图:
一个共享内存可以满足很多进程进行通信,它可以挂载很多进程,但是OS中的进程是很多的,不同的进程和不同的进程之间需要通信的内容和方式也不太一样,所以OS中一定会存在大量的共享内存,因此OS一定要对共享内存进行管理(先描述,在组织),所以OS中一定有描述共享内存的各种属性的结构体,然后用链表等数据结构把OS中存在的共享内存管理起来,这样对共享内存的管理就变成了对链表的增删查改。
那么如何保证两个不同进程能看到同一个共享内存呢?
OS是让两个进程通过约定的方式让不同的进程看到同一块共享内存,两个进程约定一个key值,这个key值会被写入shm结构体中,然后不同的进程拿着相同的key值,就可以看到同一份共享内存了,但是系统中的共享内存会存在很多个,这个key值每个共享内存应该是不同的,所以我们需要尽可能的保证key的唯一性,但是让用户来进行这个操作,明显是不靠谱的,所以有个系统调用的函数,可以来帮助我们形成这个key值。
只需要传进去一个字符串和一个整型变量,这个函数就会通过自身的算法给我们形成一个key值,两个不同的进程拿着相同的字符串和相同的整型变量再通过相同的算法,形成的key值一定是一样的。这个key值最终是要被写进内核数据结构的。
这个__key就是我们用户自己传进去的key。共享内存 = 共享内存空间 + 共享内存属性
常用的接口
shmget
第一个参数就是我们之前自己形成的key,最后一个最常用的标志位就两个,IPC_CREAT和IPC_EXCL
- IPC_CREAT :不存在就创建,存在就获取
- IPC_EXCL:单独使用没有任何意义,和IPC_CREAT 一起使用,不存在就创建,存在就报错,这两个参数一起使用可以保证一定是第一次创建共享内存。
第三个参数的标志位后面可以直接跟共享内存的权限,具体可以参考下面的代码。
它的返回值就类似于文件描述符,方便用户操作,并且后面的节后都需要用这个shmid来标识自己创建的共享内存,我们的key是内核里面用的为了标识共享内存的唯一性,我们用户用的都是shmid,并且指令删除共享内存也是要用shmid不能使用key。
shmat
创建成功后,我们会拿到一个指针,这个指针就和malloc开辟的空间一样,可以直接拿着指针对这块内存进行操作。第二个参数我们一般为nullptr就行,最后一个参数一般为0。
shmdt
shmctl
指令
常用相关的函数就是这些,在Linux中如果我们要查看已经创建的共享内存有哪些,可以使用ipcs -m 指令
想要删除一个共享内存可以使用ipcrm -m shmid 的方式删除
最后的数字应该为具体的shmid。
利用命名管道实现同步机制
共享内存不会提供同步机制,但是他是需要同步机制的,我们可以使用管道来实现它的同步机制,接下来我们可以利用前面的命名管道尝试实现一下这个同步机制。
我们需要两个独立的进程一个server,一个client,所以我们需要一个可以形成两个可执行程序的Makefile。
.PHONY:all
all:client server
client:Client.cc
g++ -o $@ $^ -std=c++11
server:Server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf client server
然后我们需要一个Client.cc和Server.cc,但是我们需要形成同一个key,所以我们可以把两个源文件中需要两个进程看到的同一份资源抽出来,Com.hpp。
Com.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
const std::string path = "/home/ysl/long/class_dragon/test_5_8/shm";
const std::string filename = "fifo";
const int proj_id = 0x11223344;
const int num = 4096;
key_t getkey()
{
key_t key = ftok(path.c_str(), proj_id);
if(key < 0)
{
std::cerr << "getkey error" << std::endl;
exit(-1);
}
return key;
}
int _getshm(key_t key, int flag)
{
int shmid = shmget(key,num,flag);
if(shmid < 0)
{
std::cerr << "shm error" << std::endl;
exit(-1);
}
return shmid;
}
int Create_shm(key_t key)
{
return _getshm(key,IPC_CREAT|IPC_EXCL|0644);
}
int getshm(key_t key)
{
return _getshm(key,IPC_CREAT);
}
bool Create_fifo()
{
int n = mkfifo(filename.c_str(),0666);
if(n < 0)
{
std::cerr << "mkfifo error" << std::endl;
return false;
}
return true;
}
Server.cc
#include "Com.hpp"
class Init
{
public:
Init()
{
//创建命名管道
Create_fifo();
// 获取key
key_t key = getkey();
// 创建共享内存
shmid = Create_shm(key);
std::cout << shmid << std::endl;
std::cout << "chuangjian" << std::endl;
// 挂载
addr = (char *)shmat(shmid, nullptr, 0);
std::cout << "guazai" << errno << std::endl;
fd = open(filename.c_str(),O_RDONLY);
}
~Init()
{
shmdt(addr);
std::cout << "qu" << std::endl;
// 删除共享内存
shmctl(shmid, IPC_RMID, nullptr);
std::cout << "shan" << std::endl;
close(fd);
unlink(filename.c_str());
}
public:
int shmid;
char *addr;
int fd;
};
Init init;
int main()
{
while(true)
{
int code = 0;
ssize_t n = read(init.fd,&code,sizeof code);
if(n > 0)
{
std::cout << init.addr << std::endl;
}
else if(n == 0)
{
break;
}
//sleep(1);
}
return 0;
}
Client.cc
#include "Com.hpp"
class Init
{
public:
Init()
{
// 获取key
key_t key = getkey();
// 创建共享内存
shmid = getshm(key);
// 挂载
addr = (char *)shmat(shmid, nullptr, 0);
}
~Init()
{
shmdt(addr);
}
public:
int shmid;
char *addr;
};
Init init;
int main()
{
int fd = open(filename.c_str(),O_WRONLY);
for(char ch = 'a'; ch <= 'z'; ++ch)
{
int code = 1;
write(fd,&code,sizeof code);
init.addr[ch-'a'] = ch;
sleep(1);
}
return 0;
}
我们是可以通过命名管道让共享内存拥有同步机制的。
总结
- 共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有的使用者的,所以一定要注意共享内存的使用安全问题。
- 共享内存可以提供较大的空间。
- 共享内存是所有进程间通信中速度最快的。可以减少数据拷贝的次数。
为什么说共享内存是最快的?
这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,也就会减少很多的数据的拷贝。