共享内存区是最快的IPC(进程间通信)形式
。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
在物理内存中申请一块空间
,将创建好的内存映射进进程的地址空间
中,然后将地址空间这块区域的起始地址返回给上层用户
,用户最终可以通过访问起始地址的方式实现对内存的访问。
照猫画虎,同样的一块内存空间可以映射进另一个进程的地址空间中,返回起始地址给用户,另一个进程的上层用户也可以实现的同一块内存的访问,最终实现进程间通信。
未来不想通信
:a.取消进程和内存的映射关系; b.释放内存。
这一块被进程共享的内存称为 共享内存
。将创建好的进程映射进进程的地址空间,我们称这个步骤为进程和共享内存挂接
。而取消进程与共享内存的映射我们称之为 去关联
。
———— 我是一条知识分割线 ————
通过让不同进程,看到同一个内存块的方式,我们就称之为:共享内存
。
常识补充:shm - 共享内存
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
共享内存 = 物理内存块 + 共享内存的相关属性
,我们在申请共享内存的时候,也要对共享内存做管理,管理方法:将共享内存的属性对象用数据结构(struct shmid_ds)的方式管理起来。
注意:
key的信息会被 shmget函数设置进共享内存的属性对象中保存起来
。
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
ipcs -m #查看被用户使用的共享内存
ipcrm -m shmip #删除特定shmip的共享内存
共享内存的特征:
共享内存的生命周期是跟随OS的,不是跟随进程的
。也就是说,我们需要在指令行下对自己创建的用户级共享内存进行手动删除。
# ls
client.c comm.c comm.h Makefile server.c
# cat Makefile
.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
#ifndef COMM_H //把头文件的内容都放在#ifndef和#endif中,可避免头文件被多个文件引用产生的冲突
#define COMM_H
#include
#include
#include
#include
#define PATHNAME "."
#define PROJ_ID 0x6666
int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);
#endif
#include "comm.h"
static int commShm(int size, int flags)
{
key_t _key = ftok(PATHNAME, PROJ_ID);
if (_key < 0) {
perror("ftok");
return -1;
}
int shmid = 0;
if ((shmid = shmget(_key, size, flags)) < 0) {
perror("shmget");
return -2;
}
return shmid;
}
int destroyShm(int shmid)
{
if (shmctl(shmid, IPC_RMID, NULL) < 0) {
perror("shmctl");
return -1;
}
return 0;
}
int createShm(int size)
{
return commShm(size, IPC_CREAT | IPC_EXCL | 0666);
}
int getShm(int size)
{
return commShm(size, IPC_CREAT);
}
#include "comm.h"
int main()
{
int shmid = createShm(4096);
char* addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while (i++ < 26) {
printf("client# %s\n", addr);
sleep(1);
}
shmdt(addr);
sleep(2);
destroyShm(shmid);
return 0;
}
#include "comm.h"
int main()
{
int shmid = getShm(4096);
sleep(1);
char* addr = shmat(shmid, NULL, 0);
sleep(2);
int i = 0;
while (i < 26) {
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
# ./server
shmget: File exists
# ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x66026a25 688145 root 666 4096 0
# ipcrm -m 688145 #删除shm ipc资源,注意,不是必须通过手动来删除,这里只为演示相关指令,删除IPC资源是进
程该做的事情
优点:在所有进程间通信的方法中,共享内存的速度是最快的
。
如果使用管道,用户拷贝 6次
,键盘的输入数据 -> 语言级缓冲区 -> 进程对应的文件缓冲区 -> 管道 -> 另一个进程对应的文件缓冲区 -> 语言级缓冲区 -> 屏幕。
如果使用共享内存,用户拷贝 4次
,键盘的输入数据 -> 语言级缓冲区 -> 共享内存 -> 语言级缓冲区 -> 屏幕。
我们知道,在企业的数据通信中,数据往往非常庞大,此时使用共享内存可以大大降低拷贝次数
。
缺点:共享内存不会给我们进行同步和互斥操作,没有对我们的数据做任何保护
。
也就是说,共享内存没有对写端在写、读端不读,写端不写、读端一直读,共享内存数据已满等情况,做相应的阻塞或其他保护。
通常情况下,工程师们会对共享内存进行一定的封装,实现对共享内存的保护。
下面是共享内存暴露给用户的用户级数据结构:
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
在上文中我们就曾学习过,在内核层面上,为了帮助不同的进程能找到同一块共享内存,我们有 key存在于共享内存的数据结构对象中,它可以让不同进程确定是否看到的是同一块共享内存
。
此时问题来了,为什么我没有在你的数据结构中找到 key的信息呢?事实上,我们看不到 key,是因为设计者对其做了相应的封装,它就被保存在了下面这个结构体中:
struct ipc_perm shm_perm; /* operation perms */
程序获取共享内存部分数据结构展示:
printf("获取属性: size: %d, pid: %d, myself: %d, key: 0x%x", \
ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);
输出结果如下:
system V - 共享内存 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!