目录
1. 概念
system V 是Linux自己支持的一套通信体系,不同于管道通信基于文件的形式,而是独立的一套系统函数调用接口。
通信的本质是两不同的进程能够看到同一份资源,这个资源可以是文件,也可以单单就是一块内存。如果两个进程可以像malloc申请一块空间一样,对申请同一块空间,并对空间进行读写,是不是也可以达到通信的目的呢?
第一步,向操作系统申请共享内存空间,第二步,建立映射
共享内存块,在操作系统中也是一种资源,如果被大量的申请,是不是也需要把这些共享内存块管理起来呢?肯定是要的,所以要对这些块,进行描述和组织,形成共享内存对应的数据结构
共享内存 = 共享内存块 + 对应共享内存的数据结构
在这些数据结构中就会保存共享内存的一些属性,例如,唯一标识符,权限,大小等等
查看系统中的共享内存资源
ipcs -m
2. 使用方法
2.1 申请空间
shmget
函数作用:
向系统申请共享内存,拿到这个共享内存的标识符
参数:
- key: key_t是一个整形,他的数据内容不重要,重要的是他要具有唯一性,就像一把钥匙,只有有这把🔑的进程,才能打开这个共享内存。这个可以通过系统提供的算法函数ftok来生成钥匙。
- size:申请共享内存的大小,最好是页(4096byte)的整数倍,因为操作系统会按照页的整数倍申请空间。
- shmflg:创建规则,和创建文件的属性
IPC_CREAT(0) 如果不存在,创建并返回,如果存在,直接返回存在空间的shmid IPC_CREAT | IPC_EXCL 如果不存在,创建并返回,如果存在,返回错误 | 空间属性(例如0664) 如果没有,权限全0,和文件类似的权限 返回值:
成功返回 shmid(用户层的共享内存标识符,类似于文件标识符fd)
失败返回 -1
ftok
函数作用:
通过算法生成重复率极低的key
参数:
- pathname:文件或者目录的路径,必须真实存在,有inode,内容是什么不重要
- proj_id:int类型的整数,必须非0,一样内容是什么不重要
原理:
重要的是,这个函数会根据文件的inode和proj_id的低8位,通过它的算法生成一个key,只要传进去的两个参数相同,返回的key值也一定是确定的,根本目的是为了保障生成一个重复率极低的key
返回值:
成功:返回key_t值失败:返回-1
shmctl
函数作用:
控制shm共享内存,主要作用是删除,也可以获取shm数据结构的信息,或者对其设置
参数:
- shmid:共享内存的描述符shmget的返回值
- cmd:设置IPC_RMID代表删除
- buf:输出型参数,用于获取shm数据结构中的属性(删除设置为nullptr就行)
返回值:
成功:返回0失败:返回-1
注意:
1. 如果不删除共享内存,他就会一直存在在你的操作系统中,shm的生命周期随内核,只要你不主动关闭,只有重启才能解决它了
除了这种删除方式,还有命令行方式
ipcrm -m shmid
2. 即便还有进程在挂接当前shm,删除是强制执行的,不管有没有挂接
2.2 建立映射
shmat
函数作用:
shm attach挂接,通过建立页表映射,建立虚拟地址和物理地址的链接,拿到物理地址的起始地址
参数:
- shmid:共享内存的描述符shmget的返回值
- shmaddr:你可以给他一个起始地址,然后他会向后找可用的空间,建立映射并返回,一般建议,设为0,让他自己在共享区建立映射
- 读写权限设置,0表示读写,SHM_RDONLY表示只读
返回值:
成功:返回起始地址void*失败:返回(void*)-1
shmdt
函数作用:
shm detach拆除,拆除挂接
参数:
- shmaddr:虚拟内存中shm的起始地址,shmat的返回值
返回值:
成功:返回0失败:返回-1
3. 优势
是速度对快的IPC通信方式
重要原因是拷贝次数少,为什么拷贝次数少呢?
相比于管道通信基于文件,共享内存是基于用户空间的内存的,文件属于内核级空间内,如果要读写,都需要利用read和write文件系统调用接口,而使用这些接口就必须,先把内容存在一个缓冲区buffer,这就必须要多拷贝一次,读写各一次
而shm共享内存,是用户空间上的内容,不需要再调用系统接口写入,直接写就可以,减少了拷贝
4. 弊端
- 共享内存缺乏访问控制,会产生并发问题(并发问题,信号量,后面会写)
- 操作复杂,而且相当于另起炉灶,和Linux下一切皆文件的思想冲突
小实验:
log.hpp
#pragma once
#include <ctime>
#include <iostream>
#include <string>
enum status
{
Debug,
Notice,
Warning,
Error
};
const std::string str[] = {
"Debug",
"Notice",
"Warning",
"Error"
};
std::ostream& log(std::string message, status level)
{
std::cout << " | " << str[level] << " | " << (unsigned int)time(nullptr) << " | " << message << "\t";
return std::cout;
}
head.hpp
#pragma once
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/stat.h>
#include<sys/shm.h>
#include<fcntl.h>
#include"log.hpp"
using namespace std;
#define PATH_NAME "."
#define SIZE 4096
#define ID 066
#define FIFO_NAME "./fifo"
#define READ O_RDONLY
#define WRITE O_WRONLY
class Init
{
public:
Init()
{
int n = mkfifo(FIFO_NAME, 0664);
if(n == -1)
{
perror("mkfifo");
exit(1);
}
}
~Init()
{
int n = unlink(FIFO_NAME);
assert(n != -1);
}
};
int Open(string pathname, int flags)
{
int fd = open(pathname.c_str(), flags);
if(fd < 0)
{
perror("open");
exit(1);
}
return fd;
}
void Wait(int fd)
{
log("waiting....", Notice) << endl;
uint32_t temp = 0;
ssize_t sz = read(fd, &temp, sizeof(temp));
assert(sz == sizeof(temp));
}
void Signal(int fd)
{
uint32_t temp = 1;
ssize_t sz = write(fd, &temp, sizeof(temp));
assert(sz == sizeof(temp));
log("signal....", Notice) << endl;
}
void Close(int fd)
{
close(fd);
}
shm_client.cc
#include "head.hpp"
int main()
{
key_t key = ftok(PATH_NAME, ID);
if (key == -1)
{
perror("ftok");
exit(1);
}
log("ftok key success", Debug) << "key: " << key << endl;
// 链接共享内存
int shmid = shmget(key, SIZE, IPC_CREAT);
if (shmid == -1)
{
perror("shmid");
exit(2);
}
log("shmget success", Debug) << "shmid: " << shmid << endl;
// sleep(10);
// 挂接shm
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
if (shmaddr == (char *)-1)
{
perror("shmat");
exit(3);
}
log("shmat success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
// sleep(10);
// 进行写操作
int fifo_fd = Open(FIFO_NAME, WRITE);
while(true)
{
ssize_t sz = read(0, shmaddr, SIZE - 1);
assert(sz > 0);
shmaddr[sz - 1] = '\0';
Signal(fifo_fd);
if(strcmp("quit", shmaddr) == 0)
break;
}
// while (true)
// {
// ssize_t sz = read(0, shmaddr, SIZE - 1);
// assert(sz > 0);
// shmaddr[sz - 1] = '\0';
// if(strcmp("quit", shmaddr) == 0)
// break;
// }
// char a = 'a';
// for(; a <= 'z'; a++)
// {
// shmaddr[a - 'a'] = a;
// //snprintf(shmaddr, SIZE, "我是client,我的pid:%d,信息:%c", getpid(), a);
// sleep(1);
// }
// strcpy(shmaddr, "quit");
log("client quit", Notice) << endl;
// 分离挂接
int ret = shmdt(shmaddr);
assert(ret != -1);
log("shmdt success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
// 不需要删除
return 0;
}
shm_server.cc
#include "head.hpp"
string TRANSTOHEX(int x) // 转16进制
{
char str[35]; // 32+2+1
snprintf(str, sizeof(str), "0x%x", x);
return str;
}
Init init;
int main()
{
key_t key = ftok(PATH_NAME, ID);
if (key == -1)
{
perror("ftok");
exit(1);
}
log("ftok key success", Debug) << "key: " << TRANSTOHEX(key) << endl;
// sleep(10);
// 创建共享内存
int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0664);
if (shmid == -1)
{
perror("shmid");
exit(2);
}
log("shmget success", Debug) << "shmid: " << shmid << endl;
// sleep(10);
// 挂接shm
char *shmaddr = (char *)shmat(shmid, nullptr, 0);
if (shmaddr == (char *)-1)
{
perror("shmat");
exit(3);
}
log("shmat success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
// sleep(10);
// 进行读操作
// 读,利用管道,构建访问控制
int fifo_fd = Open(FIFO_NAME, READ);
while(true)
{
Wait(fifo_fd);
cout << shmaddr << endl;
if (strcmp(shmaddr, "quit") == 0)
{
log("server quit", Notice) << endl;
break;
}
}
// while (true)
// {
// cout << shmaddr << endl;
// if (strcmp(shmaddr, "quit") == 0)
// {
// log("server quit", Notice) << endl;
// break;
// }
// sleep(1);
// }
// 分离挂接
int ret = shmdt(shmaddr);
assert(ret != -1);
log("shmdt success", Debug) << "shmaddr: " << (void *)shmaddr << endl;
// sleep(10);
// 删除共享内存
int n = shmctl(shmid, IPC_RMID, nullptr);
assert(n != -1);
log("shmrm success", Debug) << "shmid: " << shmid << endl;
return 0;
}
完。