建设软件网站,哪里有做空包网站的,花瓣网是仿国外那个网站做的,淘宝淘宝网页版登录入口⭐前言#xff1a;在前面的博文中分析了什么的进程间通信和进程间通信的方式之一#xff1a;管道#xff08;匿名管道和命名管道#xff09;。接下来分析第二种方式#xff1a;共享内存。 要实现进程间通信#xff0c;其前提是让不同进程之间看到同一份资源。所谓共享内存… ⭐前言在前面的博文中分析了什么的进程间通信和进程间通信的方式之一管道匿名管道和命名管道。接下来分析第二种方式共享内存。 要实现进程间通信其前提是让不同进程之间看到同一份资源。所谓共享内存那就是不同进程之间可以看到内存中同一块资源这就是共享内存的概念。
共享内存原理
用户通过操作系统提供的系统调用让操作系统帮助用户去申请一块空间跟C语言中malloc函数、C的new的意思差不多。创建好后将创建好的内存映射到进程地址空间中然后返回这个地址的起始地址给用户。最后当结束通信后就会取消进程和内存的映射关系去掉然后释放这段内存空间
而这段内存就称为共享内存进程与内存关联的行为称为挂接。取消进程与内存的映射关系称为去关联。释放这段内存叫做释放共享内存。 理解共享内存的开辟
①用户申请开辟共享内存空间的系统接口是专门为了进程间通信而设计出来的可以让不同进程同时跟其建立关联。跟mallocnew等等的函数不一样它们虽然也可以在物理内存上开辟空间但是只能用于本身进程。
②共享内存是一种通信方式意味着所有想通信的进程都可以使用它。
③既然共享内存是一种通信方式因此在OS中一定存在多个共享内存
实例代码
共享内存函数
按照上图的步骤第一步创建共享内存。以下是创建共享内存的两个函数。
①shmget函数 功能用来创建共享内存 原型int shmget(key_t key, size_t size, int shmflg); 头文件#includesys/ipc.h #includesys/shm.h 参数 key : 这个共享内存段名字。 size : 共享内存大小 shmflg : 由九个权限标志构成它们的用法和创建文件时使用的mode模式标志是一样 其中重要的两个 IPC_CREAT如果不存在创建之。如果存在获取之。 IPC_EXCL无法单独使用。需要与IPC_CREAT结合使用 IPC_CREAT | IPC_EXCL如果不存在创建之。如果存在出错并返回。如果创建成功那么一定是一个新的共享内存。 返回值成功返回一个非负整数即该共享内存段的标识码失败返回 - 1 shmget函数中的参数key它能够标定唯一性因为需要保证一个进程去申请共享内存另外的进程去获取这个共享内存它们的共享内存是同一个共享内存而获取key是通过ftok函数来获取的。
②ftok函数 功能将一个路径明和一个项目标识符转化成一个IPC的key 原型key_t ftok(const char* pathname , int proj_id); 头文件#includesys/ipc.h #includesys/types.h 参数 pathname传进来的字符串 proj_id项目标识符 返回值成功返回key失败返回-1 只要不同进程在调用ftok的时候参数一模一样获取相同的key再去调用shmget函数通过同一个key就能访问同一个共享内存。
补充说明
共享内存物理内存块共享内存的相关属性
上面谈到OS中一定存在多个共享内存而OS必须要对这些用户申请开辟的空间进行管理即先描述再组织因此OS会对开辟的共享内存创建一个数据结构一个共享内存一个数据结构然后通过链表链接起来统一管理。于是在谈到申请开辟一块共享内存就需要想到共享内存 物理内存块 共享内存的相关属性
key值被包含在了共享内存的属性中。
共享内存的相关属性被包含在共享内存的数据结构中而其中的key值也包含在了里面。即key值是在shmget函数创建出来后被设置进入共享内存的属性当中用来表示该共享内存并表示该共享内存在内核中的唯一性
shmid和key的关系区分
shmget函数返回值假设命名为shmid。那么shmid与key的关系就如同在文件IO中的文件描述符fd和inode的关系一样inode是一个文件一个inode表示文件的唯一性key是一个共享内存一个表示的是共享内存的唯一性它们都是底层访问目标的工具。但是上层是不用key或inode的而是使用shmid和fd这样一个特定的整数来访问。一句话来说一个是用户的一个是系统的两个互不干扰这是它的好处。
查看共享内存指令 ipcs -m ipc资源的特征
共享内存的生命周期是随操作系统的不是随进程的即使进程终止了但没有去释放这段共享内存那么它就会一直存在。
删除共享内存 ipcrm -m shmid 按照上图所示以下是删除共享内存的函数。
③shmctl函数 功能用于控制共享内存即删除共享内存设置共享内存属性等等 原型int shmctl(int shmid, int cmd, struct shmid_ds *buf); 头文件#includesys/ipc.h #includesys/shm.h 参数 shmid:由shmget返回的共享内存标识码。 cmd:将要采取的动作有三个可取值 动作 ①IPC_STAT获取共享内存属性 ②IPC_SET设置共享内存属性 ③IPC_RMID删除共享内存 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构 返回值成功返回0失败返回-1 按照上图所示以下是将共享内存映射到进程地址空间的函数。
④shmat函数 功能将共享内存段连接到进程地址空间 原型void *shmat(int shmid, const void *shmaddr, int shmflg); 头文件#includesys/shm.h #includesys/types.h 参数 shmid: 共享内存标识,即想和哪个共享内存关联起来 shmaddr:指定连接的地址。就是想把这个共享内存映射到哪个进程地址空间中给出这个进程地址。 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY 返回值成功返回一个指针指向共享内存失败返回-1 使用完后不直接删除共享内存而是先去关联。以下是去关联的函数。
⑤shmdt函数 功能将共享内存段与当前进程脱离 原型int shmdt(const void *shmaddr); 头文件#includesys/shm.h #includesys/types.h 参数shmaddr: 由shmat所返回的指针 返回值成功返回0失败返回-1 注意将共享内存段与当前进程脱离不等于删除共享内存段 示例代码代码如下
代码思路创建一段共享内存创建两个没有亲属关系的进程client进程负责写入server进程负责读取。
头文件comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include iostream
#include cerrno
#include cstring
#include cstdlib
#include cstdio
#include sys/ipc.h
#include sys/shm.h#define PATHNAME .
#define PROJ_ID 0X66//设置共享内存大小建议为4KB的整数倍
//因为系统分配共享内存是以4KB为单位的
#define MAX_SIZE 4096//获取key
key_t getKey()
{//通过ftok函数获取keykey_t k ftok(PATHNAME,PROJ_ID);//获得同一个keyif(k 0){std::cerrerrno:strerror(errno)std::endl;exit(1);}return k;
}
//创建共享内存
int getShmHelper(key_t k,int flags)
{//通过shmget函数创建共享内存。//第一个参数是key第二个参数是共享内存的大小。第三个参数是权限标志int shmid shmget(k,MAX_SIZE,flags);//创建共享内存if(shmid0){std::cerrerrno:strerror(errno)std::endl;exit(2);}return shmid;
}
//通过封装函数给用户去使用只需传入key值即可。
//获取共享内存不一定要新的因为不用调用它的进程去创建新的
int getShm(key_t k)
{return getShmHelper(k,IPC_CREAT);
}
//创建共享内存,使用IPC_CREAT | IPC_EXCL,确定创建的共享内存一定是新的。需要给权限0600
int createShm(key_t k)
{return getShmHelper(k,IPC_CREAT | IPC_EXCL | 0600);
}//进程地址空间与共享内存相联
void* attachShm(int shmid)
{//通过shmat函数将共享内存段连接到进程地址空间//传入shmid和指定连接的进程地址的地址但是这个一般不填系统会自动去填//第三个参数是权限标志是对内存只读还是读写。//在Linux系统中一般是64位。我们这里需要将shmat函数返回的指针判断是否关联成功//强行转化为longlongvoid *men shmat(shmid,nullptr,0);if((long long)men-1L){std::cerrerrno:strerror(errno)std::endl;exit(3);}return men;//返回起始地址}void detachShm(void* start)
{//通过shmdt函数去关联if(shmdt(start)-1){std::cerrerrno:strerror(errno)std::endl;}
}void delShm(int shmid)
{//通过shmctl函数删除共享内存//第一个参数是函数是需要对哪个共享内存操作那个共享内存//第二个参数是需要进行什么样的操作//第三个参数一般给nullptrif(shmctl(shmid,IPC_RMID,nullptr)-1){std::cerrerrno:strerror(errno)std::endl;}
}
负责写入的进程程序代码client.cc
#includecomm.hpp
#includeunistd.hint main()
{//第一步创建key创建共享内存key_t k getKey();//获取keyprintf(key: 0x%x\n,k);//查看key值int shmid getShm(k);//创建共享内存printf(shmid:%d\n,shmid);//查看shmid//第二步关联内存和进程地址空间char* start (char*)attachShm(shmid);printf(attach success,address start: %p\n,start);//查看起始地址//开始使用//写下需要往共享内存段写入的数据const char* message hello server,我是另一个进程正在和你通信;pid_t id getpid();int cnt 1;while(true){sleep(5);//写入到共享内存段将共享内存段当字符串不需要额外char buffer[];snprintf(start,MAX_SIZE,%s[pid:%d][消息编号:%d],message,id,cnt);}//去关联detachShm(start);//这个工程项目不需要删除共享内存return 0;
}
负责读取的进程的程序代码server.cc
#includecomm.hpp
#includeunistd.hint main()
{key_t k getKey();//获取key值printf(key: 0x%x\n,k);//查看key值int shmid createShm(k);//创建共享内存,必须是新的printf(shmid: %d\n,shmid);//查看共享内存//关联char* start (char*)attachShm(shmid);printf(attach success, address start: %p\n, start);//使用while(true){//读取共享内存中的数据printf(client say: %s\n,start);//获取共享内存中的属性数据部分struct shmid_ds ds;shmctl(shmid,IPC_STAT,ds);printf(获取属性: size: %d, pid: %d, myself: %d, key: 0x%x,\ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);sleep(1);}//去关联detachShm(start);//删除共享内存delShm(shmid);return 0;
} 结果如下在第一个五秒时共享内存中没有任何数据。第二个五秒消息编号为1。第三个五秒消息编号为2...... 对于从内核数据结构中获取共享内存的属性发现没有直接显示key值。但实际上key值是在这个内核数据结构中里面的另外一个结构体里面。 共享内存的优缺点
优点所有使用共享内存的进程通信速度是最快的能大大减少数据拷贝的次数并且生命周期是随系统的那么如果我们考虑到同样一份代码分别使用管道和共享内存的话并且考虑键盘输入和显示器输出那么管道有几次拷贝共享内存有几次拷贝 如图管道的话需要创建buffer来获取数据然后通过管道进行通信。而共享内存不需要因为共享内存可以作为字符串空间直接写入和读取数据。因此根据上图所示管道是6次拷贝共享内存是4次拷贝。当然代码不同拷贝的次数也不会同。
缺点共享内存没有同步和互斥