一、共享内存(what)的原理
进程A和进程B需要通信,两者需要看到同一块地方(让不同的进程看到同一个资源)。在进程的虚拟地址中,有共享区,可以让两个进程的共享区通过页表指向同一块物理空间,这样就实现了进程之间的通信。

共享内存也可能是有许多个的,所以需要组织管理,所以就需要先描述(结构体),再组织。
所以:共享内存 = 结构体 + 内存块
二、共享内存使用
1.创建
2.关联挂接
3.使用
4.去关联
5.释放共享内存
1.创建
shmget 函数
bash
NAME
shmget - allocates a System V shared memory segment
LIBRARY
Standard C library (libc, -lc)
SYNOPSIS
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
RETURN VALUE
On success, a valid shared memory identifier is returned. On error, -1 is returned, and errno is set to indicate the error.
**作用:**申请一块内存空间
参数:
key:共享内存的key值,在内核中,用来标识共享内存的唯一性,需要用户来传(用ftok函数来生成)。
细节:这里的key值为什么不能让os自己生成呢?
答:进程A生成内存块,那么进程A可以得到key值,进程B怎么可以得到呢?
进程B要得到,进程之间就需要进程间通信,???虽然可以通过匿名管道或者命名管道获得,但是要实现一种通信还需要依靠其它通信方法,太糟糕了。
**size:**申请空间的大小,是4096的整数倍
shmflg:传标志位和权限(用 | 来合并)
下面介绍2个标志位
bashIPC_CREAT Create a new segment. If this flag is not used, then shmget() will find the seg‐ ment associated with key and check to see if the user has permission to access the segment. IPC_EXCL This flag is used with IPC_CREAT to ensure that this call creates the segment. If the segment already exists, the call fails.**IPC_EXCL:**不能单独使用
**IPC_CREAT:**可以单独传递,如果创建的共享内存不存在,就创建,如果存在,就获取它、
(可以用来获取shmid)
**IPC_CREAT | IPC_EXCL:**如果创建的共享内存不存在,就创建,如果存在,出错返回
(创建共享内存)
返回值:
bash
On success, a valid shared memory identifier is returned. On error, -1 is returned, and errno is set to indicate the error.
成功:返回内存块的身份标识 shmid,这个身份标识用户可以来使用
失败:返回 -1
ftok 函数
bash
NAME
ftok - convert a pathname and a project identifier to a System V IPC key
LIBRARY
Standard C library (libc, -lc)
SYNOPSIS
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
RETURN VALUE
On success, the generated key_t value is returned. On failure -1 is returned, with errno
indicating the error as for the stat(2) system call.
作用:生成key值
参数
pathname:一个字符串,最好是存在的路径
proj_id:一个数字,用户设置,用来生成这个共享内存的key值
其实就是通过一些计算生成一个key值
细节:多个共享内存的key值有可能重复吗。
答:有可能,不过shget函数会有调用错误
命令:查看共享内存块
bashipcs
bashzhangsan@hcss-ecs-f571:~$ ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
bashipcs -m
bashzhangsan@hcss-ecs-f571:~$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status
命令:删除
bash
zhangsan@hcss-ecs-f571:~$ ipcrm -m shmid
细节:用户不主动删除ipc资源的话,ipc的生命周期随OS,会一直存在,除非重启操作系统
理解shmid和key值
1.key只在内核中,标识共享内存的唯一性!用户使用共享内存,不使用key值
2.shmid 在用户中使用,使用shmid来访问共享内存
(了解过文件系统的话 可以类比文件描述符fd和inode的关系)
2.控制、删除
shmctl 函数(shell memeny control)
bash
NAME
shmctl - System V shared memory control
LIBRARY
Standard C library (libc, -lc)
SYNOPSIS
#include <sys/shm.h>
int shmctl(int shmid, int op, struct shmid_ds *buf);
**作用:**可以控制shmid共享内存块(删除,获取,设置共享内存的属性)。
参数:
**shmid:**共享内存块的id
**op:**操作方式

IPC_RMID 用删除内存块
其他操作可以查看手册。
**buf:**共享内存块中结构体中的内容(属性)
cpp
struct shmid_ds {
struct ipc_perm shm_perm; /* 操作权限 */
int shm_segsz; /* 共享内存段大小 */
__kernel_time_t shm_atime; /* 最后一次attach时间 */
__kernel_time_t shm_dtime; /* 最后一次detach时间 */
__kernel_time_t shm_ctime; /* 最后一次change时间 */
__kernel_ipc_pid_t shm_cpid; /* 创建者pid */
__kernel_ipc_pid_t shm_lpid; /* 最后操作的pid */
unsigned short shm_nattch; /* 当前attach的进程数 */
unsigned short shm_unused; /* 保留字段 */
void *shm_unused2; /* 保留字段 */
void *shm_unused3; /* 保留字段 */
};
删除时可以设置为nullptr
3.关联挂接、去关联
shmat 函数(attach)
cpp
NAME
shmat, shmdt - System V shared memory operations
LIBRARY
Standard C library (libc, -lc)
SYNOPSIS
#include <sys/shm.h>
void *shmat(int shmid, const void *_Nullable shmaddr, int shmflg);
**作用:**将共享内存链接到进程虚拟地址空间
参数:
**shmid:**共享内存块的id
**shmaddr:**指定链接的地址(这里一般让os自己来决定,设置为nullptr)
**shmflg:**关联的权限(这里可以设置为0,因为shmget创建共享内存块的时候可以把权限设置好,这里有特殊要求可以查看参考手册来设置)
**返回值:**成功,返回一个指针,指向共享内存第一个节;失败,返回 -1
shmdt 函数 (detach)
cpp
NAME
shmat, shmdt - System V shared memory operations
LIBRARY
Standard C library (libc, -lc)
SYNOPSIS
#include <sys/shm.h>
int shmdt(const void *shmaddr);
**作用:**将共享内存段与当前进程脱离
参数:shmat的返回值
**返回值:**成功返回 0,失败返回 -1
三、共享内存的优缺点
- 访问特性:访问共享内存,不需要系统调用,因为 shm 已经映射到了进程的用户共享区了!
- 实时性:写端数据拷贝到 shm,其他端立马能看到!!
- 性能优势 :共享内存,是所有进程间通信方式中,速度最快的!!
- 底层原理:拷贝次数少,直接映射,不需要系统调用!
- 缺点 :没有资源的保护机制,没有同步或者互斥!
- 补充说明:sem(信号量)由用户自己完成保护!
四、小demo,用共享内存实现两个进程之间的通信
Shm.hpp
cpp
#pragma once
#include <cstdio>
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/shm.h>
const int gsize = 128;
// 用户指明,本质:等价于;命名管道里面的文件路径
// PATHNAME, PROJ_ID:我们两个就能看到同一份资源!
#define PATHNAME "/tmp"
#define PROJ_ID 0x66
class Shm
{
public:
Shm():_shmid(-1),_size(gsize),_start_addr(nullptr)
{}
~Shm()
{}
void Delete()
{
int n = shmctl(_shmid, IPC_RMID, nullptr);
}
// void Get()
// {
// key_t k = Getkey();
// if(k < 0)
// {
// std::cerr << "Getkey error";
// exit(1);
// }
// printf("key = 0x%x,key = %d\n", k, k);
// // _shmid = shmget(k,_size,IPC_CREAT);
// }
void Attach()
{
_start_addr = shmat(_shmid, nullptr, 0);
if(_start_addr == (void*)(-1)) exit(3);
}
void PrintAttr()
{
struct shmid_ds ds;
int n = shmctl(_shmid, IPC_STAT, &ds);
if(n < 0)
{
perror("shmctl");
exit(4);
}
printf("key:0x%x\n", ds.shm_perm.__key);
printf("shm_nattch:%ld\n", ds.shm_nattch);
printf("segsz:%ld\n", ds.shm_segsz);
}
void Detach()
{
shmdt(_start_addr);
}
void Get()
{
GetHelper(IPC_CREAT);
}
void Create()
{
GetHelper(IPC_CREAT | IPC_EXCL | 0666);
}
void* Addr()
{
return _start_addr;
}
int Size()
{
return _size;
}
private:
void GetHelper(int shmflg)
{
//1.构建键值
key_t k = Getkey();
if(k < 0)
{
std::cerr << "Getkey error";
exit(1);
}
printf("key = 0x%x,key = %d\n", k, k);
//2.创建新的共享内存
_shmid = shmget(k,_size,shmflg);
if(_shmid < 0)
{
perror("shmget");
exit(2);
}
printf("key = 0x%x,key = %d,_shmid = 0x%x\n", k, k, _shmid);
}
key_t Getkey()
{
return ftok(PATHNAME, PROJ_ID);
}
private:
int _shmid;
int _size;
void* _start_addr;
};
Server.cc
cpp
#include <iostream>
#include "Shm.hpp"
int main()
{
Shm sharedmem;
sharedmem.Create();
sharedmem.Attach();
sleep(2);
sharedmem.PrintAttr();
char *shm_start = (char *)sharedmem.Addr();
int sz = sharedmem.Size();
while (1)
{
// 本质是读取共享内存
for (int i = 0; i < sz; i++)
{
std::cout << shm_start[i] << " ";
}
std::cout << std::endl;
sleep(1);
}
sharedmem.Detach();
sharedmem.Delete();
return 0;
}
Client.cc
cpp
#include <iostream>
#include "Shm.hpp"
int main()
{
Shm sharedmem;
sharedmem.Get();
sharedmem.Attach();
sleep(2);
sharedmem.PrintAttr();
char* shm_start = (char*)sharedmem.Addr();
int sz = sharedmem.Size();
int index = 0;
while(true)
{
std::cout << "please Enter@ ";
char ch;
std::cin >> ch;
*(shm_start + index) = ch;
shm_start[index++] = ch;
index %= sz;
sleep(1);
}
sharedmem.Detach();
return 0;
}