文章目录
- 前言
- 一、system V共享内存
- 原理
- 二、创建共享内存
- 关联和去关联共享内存
- 进行通信
- 三、基于管道进行共享内存通信
- 四、临界资源
- 五、信号量
- 总结
前言
基于上篇我们利用管道进行进程间通信的使用和实现,本篇将带大家通过共享内存进行进程间通信!
正文开始
一、system V共享内存
首先回顾上篇的内容
进程进通信的前提是,先让不同的进程,看到同一份资源!
原理
系统提供给了我们如下接口
1.创建共享内存------删除共享内存(OS完成)
2.关联共享内存------去关联共享内存(进程完成)
上面函数的第二个参数建议设置成为页(4KB)的整数倍!
那么问题来了
1.共享内存存在哪里呢?
存在内核中—内核会给我们维护共享内存的结构!!—所以内核中一定存在描述共享内存的内核数据结构—struct shmid_ds{};
共享内存要被管理–>struct shmid_ds{}–>struct ipc_perm—>key(shmget)—>共享资源的唯一值。
我怎么知道,这个共享内存到底存在还是不存在呢?
先有方法表示共享内存的唯一性!!(key)–>一般是由用户提供的!
共享内存,在内核中,让不同的进程看到同一份共享内存的做法是:让他们拥有同一个key即可!!!
类比于命名管道进行理解
命名管道–>约定好使用同一个文件,来进行通信的!!
共享内存–>约定好使用同一个唯一key,来进行通信的!!
那么如何生成我们的key呢,系统给我们提供了接口!!
二、创建共享内存
//Comm.hpp
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<cstdlib>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string>
#include"Log.hpp"
#define PATH_NAME "/home/hulu"
#define PROJ_ID 0x14
key_t CreateKey()
{
key_t key=ftok(PATH_NAME,PROJ_ID);
if(key<0)
{
std::cerr<<"ftok "<<strerror(errno)<<std::endl;
exit(1);
}
return key;
}
//Log.h
#pragma once
#include<iostream>
#include<ctime>
std::ostream& Log()
{
std::cout << "For Debug |" << " timestamp: " << (uint64_t)time(nullptr) << " | ";
return std::cout;
}
//IpcShmSer.cc
#include"Comm.hpp"
#include"Log.hpp"
#include<unistd.h>
using namespace std;
int main()
{
key_t key=CreateKey();
Log()<<"key: "<<key<< "\n";
Log()<<"create share memory begin!\n";
int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
if(shmid<0)
{
Log()<<"shmget: "<<strerror(errno)<< "\n";
return 2;
}
Log()<<"create shm success,shmid: "<<shmid<< "\n";
return 0;
}
当我们运行完毕创建全新的共享内存的代码后(进程退出),但是第二(n)次的时候,该代码无法运行,告诉我们file存在!—>我要创建的共享内存是存在的!—>system V下的共享内存,生命周期是随内核的!!—>如果不显式的删除,只能通过Kernel(OS)重启解决!
那么我怎么知道有哪些IPC资源呢?
ipcs -m
这个就是我们刚刚创建的共享内存!!
如何显式的删除呢?
ipcrm -m shmid
还可以使用系统接口删除
int main()
{
key_t key=CreateKey();
Log()<<"key: "<<key<< "\n";
Log()<<"create share memory begin!\n";
int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
if(shmid<0)
{
Log()<<"shmget: "<<strerror(errno)<< "\n";
return 2;
}
Log()<<"create shm success,shmid: "<<shmid<< "\n";
//删除
shmctl(shmid,IPC_RMID,nullptr);
Log()<<"delete shm : "<<shmid<<"success\n";
}
这个权限默认是0,证明其他进程无法访问,我在这里设置为666,以便我们接下来的实验!
关联和去关联共享内存
共享内存虽然是这个进程创建的,但是他不属于这个进程!
需要我们调用系统接口将他们关联起来!
你怎么使用malloc的空间,你就怎么使用共享内存的空间!
int main()
{
key_t key=CreateKey();
Log()<<"key: "<<key<< "\n";
Log()<<"create share memory begin!\n";
int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
if(shmid<0)
{
Log()<<"shmget: "<<strerror(errno)<< "\n";
return 2;
}
Log()<<"create shm success,shmid: "<<shmid<< "\n";
sleep(10);
//使用
//1.将共享内存和自己的进程产生关联attach
char* str=(char*)shmat(shmid,nullptr,0);
Log()<<"attach shm: "<<shmid<<"success\n";
sleep(10);
//2.去关联
shmdt(str);
Log()<<"detach shm: "<<shmid<<"success\n";
// //删除
shmctl(shmid,IPC_RMID,nullptr);
Log()<<"delete shm : "<<shmid<<"success\n";
}
进行通信
//IpcShmSer.cc
int main()
{
key_t key=CreateKey();
Log()<<"key: "<<key<< "\n";
Log()<<"create share memory begin!\n";
int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
if(shmid<0)
{
Log()<<"shmget: "<<strerror(errno)<< "\n";
return 2;
}
Log()<<"create shm success,shmid: "<<shmid<< "\n";
//使用
//1.将共享内存和自己的进程产生关联attach
char* str=(char*)shmat(shmid,nullptr,0);
Log()<<"attach shm: "<<shmid<<"success\n";
while(true)
{
//让读端进行等待
printf("%s\n",str);
sleep(1);
}
//2.去关联
shmdt(str);
Log()<<"detach shm: "<<shmid<<"success\n";
// //删除
shmctl(shmid,IPC_RMID,nullptr);
Log()<<"delete shm : "<<shmid<<"success\n";
}
//IpcShmCli.cc
using namespace std;
int main()
{
key_t key = CreateKey();
Log() << "key: " << key << "\n";
//获取共享内存
int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
if (shmid < 0)
{
Log() << "shmget: " << strerror(errno) << "\n";
return 2;
}
//挂接
char *str = (char *)shmat(shmid, nullptr, 0);
//使用
//竟然没有使用任何的系统调用接口
int cnt=0;
while(cnt<=26)
{
str[cnt]='A'+cnt;
cnt++;
str[cnt]='\0';
sleep(1);
}
//去关联
shmdt(str);
return 0;
}
我们把共享内存实际上是映射到了我们的进程地址空间的用户空间了(堆->栈之间),对每一个进程而言挂接到自己上下文中的共享内存,属于自己的空间,类似于堆空间和栈空间,可以被用户直接使用!!
共享内存,因为它自身的特性,他没有任何访问控制!共享内存被双方直接看到,属于双方的用户空间,但是不安全!
共享内存是所有进程间通信,速度最快的!
三、基于管道进行共享内存通信
//Comm.hpp
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<cstdlib>
#include<sys/shm.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string>
#include"Log.hpp"
#define PATH_NAME "/home/hulu"
#define PROJ_ID 0x14
key_t CreateKey()
{
key_t key=ftok(PATH_NAME,PROJ_ID);
if(key<0)
{
std::cerr<<"ftok "<<strerror(errno)<<std::endl;
exit(1);
}
return key;
}
void CreateFifo()
{
umask(0);
if(mkfifo(FIFO_FILE,0666)!=0)
{
Log()<<strerror(errno)<<"\n";
exit(2);
}
}
#define READER O_RDONLY
#define WRITER O_WRONLY
int Open(const std::string& filename,int flags)
{
return open(filename.c_str(),flags);
}
int Wait(int fd)
{
uint32_t values=0;
ssize_t s=read(fd,&values,sizeof(values));
return s;
}
int Signal(int fd)
{
uint32_t cmd=1;
write(fd,&cmd,sizeof(cmd));
}
int Close(int fd,const std::string filename)
{
close(fd);
unlink(filename.c_str());
}
//Log.h
#pragma once
#include<iostream>
#include<ctime>
std::ostream& Log()
{
std::cout << "For Debug |" << " timestamp: " << (uint64_t)time(nullptr) << " | ";
return std::cout;
}
//IpcShmSer.cc
#include"Comm.hpp"
#include"Log.hpp"
#include<unistd.h>
using namespace std;
#define FIFO_FILE ".fifo"
#define READER O_RDONLY
#define WRITER O_WRONLY
int main()
{
key_t key=CreateKey();
Log()<<"key: "<<key<< "\n";
Log()<<"create share memory begin!\n";
int shmid=shmget(key,MEM_SIZE,IPC_CREAT | IPC_EXCL|0666);
if(shmid<0)
{
Log()<<"shmget: "<<strerror(errno)<< "\n";
return 2;
}
Log()<<"create shm success,shmid: "<<shmid<< "\n";
CreateFifo();
int fd=Open(FIFO_FILE,READER);
assert(fd>=0);
//使用
//1.将共享内存和自己的进程产生关联attach
char* str=(char*)shmat(shmid,nullptr,0);
Log()<<"attach shm: "<<shmid<<"success\n";
while(true)
{
//让读端进行等待
if(Wait(fd)<=0) break;
printf("%s\n",str);
sleep(1);
}
//2.去关联
shmdt(str);
Log()<<"detach shm: "<<shmid<<"success\n";
// //删除
shmctl(shmid,IPC_RMID,nullptr);
Log()<<"delete shm : "<<shmid<<"success\n";
Close(fd,FIFO_FILE);
}
//IpcShmCli.cc
#include "Comm.hpp"
#include "Log.hpp"
#include <cstdio>
#include <unistd.h>
#define FIFO_FILE ".fifo"
#define READER O_RDONLY
#define WRITER O_WRONLY
using namespace std;
int main()
{
int fd=Open(FIFO_FILE,WRITER);
key_t key = CreateKey();
Log() << "key: " << key << "\n";
//获取共享内存
int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
if (shmid < 0)
{
Log() << "shmget: " << strerror(errno) << "\n";
return 2;
}
//挂接
char *str = (char *)shmat(shmid, nullptr, 0);
while(true)
{
printf("Please Enter#");
fflush(stdout);
ssize_t s=read(0,str,MEM_SIZE);
if(s>0)
{
str[s]='\0';
}
Signal(fd);
}
//去关联
shmdt(str);
return 0;
}
四、临界资源
能被多个进程同时看到的资源----临界资源
如果没有对临界资源进行任何保护,对于临界资源的访问,双方进程在进行访问的时候,就都是乱序的,可能会因为读写交叉而导致各种的乱码和废弃数据访问控制方面的问题!!!
与之前fork()创建子进程后,父子进程打印数据是乱序的,缺乏访问控制!
对多个进程而言,访问临界资源的代码—临界区!
我的进程代码中,有大量的代码。但是只有一部分代码,会访问临界区!
我们把一件事情,要不没做,要么做完了(没有中间状态)----原子性
任何时刻,只允许一个进程访问临界资源—互斥
五、信号量
- 什么是信号量?
信号量本质就是一个计数器!!!
当信号量为1的时候表示的就是互斥特性!
互斥信号量----二元信号量
常规信号量—多元信号量
每一个进程要访问临界资源,必须先申请信号量–>就要求每一个进程必须先看到这个信号量—>所以信号量本身也是临界资源!!!—>信号量对应的操作是原子的!
信号量对应的操作是PV操作!!
sem:–; 申请资源:P
sem:++;释放资源 V
共享内存没有访问控制,我们可以通过信号量进行对资源的保护!
总结
临界资源和信号量的主要内容会在线程部分着重带大家理解!
这里是带大家浅浅的认识一下!有个了解即可!
(本章完!)