2026/6/20 5:10:37
网站建设
项目流程
淮北哪有做网站的,包头seo营销公司,seo外包多少钱,网站的公关和广告活动怎么做1.共享内存的原理共享内存是最快的进程间通信IPC的方式#xff0c;相对于管道而言#xff0c;需要经历数据从用户态到内存#xff0c;内存到用户态的两次拷贝#xff0c;共享内存则是直接对物理内存进行操作#xff0c;不需要拷贝#xff0c;一旦这样的内存映射到共享它的…1.共享内存的原理共享内存是最快的进程间通信IPC的方式相对于管道而言需要经历数据从用户态到内存内存到用户态的两次拷贝共享内存则是直接对物理内存进行操作不需要拷贝一旦这样的内存映射到共享它的进程的地址空间这些进程间进行数据传递就不再涉及到内核换句话来说进程不再通过执行进入内核的系统调用来完成传递彼此的数据如何理解呢请看下图进行进程间通信的本质就是要让不同进程看到同一份资源那么共享内存又是如何做的呢请看下图我们先以进程A为例进行讲解如果要使用共享内存顾名思义首先就要有内存即先使用某种手段在物理内存上申请内存当有了内存之后那么就要将这个内存通过页表建立物理地址与虚拟地址的映射关系映射到进程地址空间的共享区位置然后向用户层返回共享内存的首地址即在地址空间上起始的虚拟地址那么接下来进程B就来了它想要通过共享内存与进程A进行通信此时共享内存已经有了那么对于进程B来将就只需要获取这个共享内存将这个内存通过页表建立物理地址与虚拟地址的映射关系映射到进程地址空间的共享区位置然后向用户层返回共享内存的首地址即在地址空间上起始的虚拟地址此时进程A和进程B作为不同进程就可以看到同一份资源进行通信了通常的共享内存也是进行单向通信的共享内存可以进行双向通信具体看用户需求如果我们要释放共享内存那么步骤只需要和建立共享内存的步骤相反不就行了即1. 清除页表上关于共享内存建立的虚拟地址到物理地址的映射关系2. 释放共享内存操作系统不相信任何人操作系统对上层提供系统调用接口所以进程如果想要创建共享内存挂接到进程地址上等操作都要通过系统调用接口告诉操作系统让操作系统去做那么也就会有很多进程会创建共享内存所以操作系统也需要去管理这些共享内存怎么管理先描述再组织给每一个共享内存使用一个struct结构体struct shmid_ds结构体来描述属性然后采取某个数据结构进行组织起来。ps共享内存的生命周期是随内核的要是不去关闭它就会一直存在要么内核重启要么用户手动关闭。2.共享内存的接口2.1 shmget 创建共享内存其中sh的意思是share共享m是memory内存shmget的意思即为共享内存获取/分配的意思int shmget(key_t key, size_t size, int shmflg);返回值共享内存标识符在进程方面标记资源的唯一性对于进程来说对于共享内存的后续操作都需要通过返回值来进行操作对于操作系统来说可以通过返回值来管理共享内存的属性大小权限关联进程数。keykey_t 类型其实就是int类型需要通过ftok函数获取在ftok里面细说size代表申请的共享内存的大小共享内存的大小一般都是设置为物理内存的一块大小的整数倍物理内存是以块为基本单位的如果你申请的假如是 4097但是块的大小是 4096物理内存给你的还是 4096*2但是你具体使用只可以使用 4097。shmflg标志位设置权限和申请方法IPC_CREAT单独使用如果你申请的共享内存不存在那么就创建。如果你申请的共享内存存在那么就获取并返回。IPC_EXCL不单独使用而是和IPC_CREAT结合使用。IPC_CREAT | IPC_EXCL如果你申请的共享内存不存在那么就创建。如果存在那么就出错返回。这样可以确保我们申请了一个共享内存这个共享内存一定是新的。2.2 ftok函数官方文档中ftok的作用也就是将一个文件路径名pathname和一个项目标识符project identifier转换为一个 System V 进程间通信IPC的键值key。ftok里面的两个参数全部都交给用户来决定根据用户给与的参数会生成一个整数进程就可以拿到这个整数做为ftok的返回值。但是针对这两个pathname以及proj_id也是有要求的其中pathname路径要求这个路径是存在的proj_id要求不为0所以针对用户想要进行通信的两个进程用户约定这两个进程使用的都是同一个pathname以及proj_id那么同时ftok函数使用同一套算法进行计算所以计算出来的整数一定是相同的并且这个key值具有唯一性。对于进程来说进程需要通过key来找到同一份共享内存对于操作系统来说需要通过key来确定共享内存是否已经建立所以shmget的返回值就像是共享内存的操作符ftok的返回值就像是共享内存的身份证2.3 shmat和shmdtvoid *shmat(int shmid, const void *shmaddr, int shmflg);shmat作用是建立共享内存物理内存与进程虚拟地址空间的关系shmidshmget的返回值。shmaddr想要让物理内存挂接到你的虚拟地址空间共享区的哪一块地方填 nullptr让操作系统来分配。shmflg想要以什么权限来挂接这一块共享内存可以设置为只读什么的填 0的话就会使用我们在shmget是设置的权限。int shmdt(const void *shmaddr);解除共享内存于进程虚拟地址空间的关系。2.4 shmctl对共享内存的操作函数执行查询修改删除等操作shmidshmget的返回值cmd执行的操作着重看一个第三个参数观察这个struct shmid_ds类型元数据的结构体中还嵌套有一个类型为struct ipc_perm的结构体这个嵌套的结构体中包含共享内存的部分属性包括keymode权限等那么struct shmid_ds类型的结构体中还包含共享内存的大小shm_segsz等等其实里面也会有共享内存的所在块的信息在使用shmat时候不是建立了物理内存与虚拟地址空间的关系了吗所以这个结构体里面其实也记载了物理内存的位置。所以对于共享内存要进行删除的话shmctl(shmid,IPC_RMID,nullptr);第三个参数就没有什么用也就是第三个参数设为nullptr因为操作系统会根据shmid来映射找到对应共享内存段的struct shmid_ds结构体获取里面的属性找到对应的物理内存将其标记为待删除还记得我们之前提到的在操作系统中删除就是支持覆盖其他的进程想要连接上这个共享内存也就不行了当struct shmid_ds里面的连接数为0时这块物理内存也就可以被其他进程使用了然后将struct shmid_ds里面数据清空。2.5 命令行查看共享内存ipcs -m可以查看共享内存的一些属性ipcrm -m shmid 删除共享内存3.共享内存的建立到删除生成 IPC 键值进程调用 ftok(pathname, proj_id) 生成唯一的 key_t 键值作为共享内存的全局标识进程可以通过生成的key_t 键值来找到共享内存。创建/获取共享内存段进程调用 shmget(key, size, IPC_CREAT | 权限位) 操作系统会根据key来判断共享内存是否存在之后内核创建共享内存元数据 struct shmid_ds 返回 shmid 此时仅分配元数据未分配物理内存。映射共享内存到进程地址空间进程调用 shmat(shmid, NULL, 0) 内核按需分配物理内存块的位置建立进程虚拟地址与物理页帧的映射返回映射起始地址 shm_addr 多个进程映射同一 shmid 会指向同一块物理内存元数据也会记录物理内存和虚拟地址空间的映射。写入/读取共享内存内容进程通过 shm_addr 直接读写物理内存用户态操作无数据拷贝如 strcpy(shm_addr, hello shared memory) 其他映射的进程可直接读取到更新后的内容。解除地址映射进程不再使用时调用 shmdt(shm_addr) 断开当前进程虚拟地址与物理内存的关联内核减少 struct shmid_ds 中的 shm_nattch 关联进程数计数。、标记共享内存删除任意进程调用 shmctl(shmid, IPC_RMID, NULL) 内核将该共享内存标记为待删除状态新进程无法再通过原 key 获取此段。物理内存与元数据回收当 shm_nattch 计数减至 0所有进程都已解除映射内核回收共享内存对应的物理页帧和元数据共享内存内容彻底消失。4.代码实现share.hpp#include sys/ipc.h #include sys/shm.h #include sys/types.h #include sys/ipc.h #include iostream #include string #includestdio.h #includeunistd.h using namespace std; const string path/home/wjp/sharemem; int proj_id0x66; const int SIZE4096; int Getkey() { int kftok(path.c_str(),proj_id); if(k0) { exit(1); } return k; } int GetSharemem(int flag)//创建一块物理内存 { int kGetkey(); int shmidshmget(k,SIZE,flag); //shmid:内核对共享内存的唯一标识符 if(k0) { exit(2); } return shmid; } int Create() { return GetSharemem(IPC_CREAT|IPC_EXCL|0666); } int Get() { return GetSharemem(IPC_CREAT); }creat.cpp一个进程创建共享内存并一直打印共享内存的数据负责共享内存的删除#include./share.hpp int main() { int shmidCreate(); char*shmaddr(char*)shmat(shmid,nullptr,0); if((long long)shmaddr-1) { perror(shmat); return 1; } while(true) { coutsay shmaddrendl; sleep(1); } shmdt(shmaddr); shmctl(shmid,IPC_RMID,nullptr); return 0; }get.cpp一个进程负责连接共享内存并且从键盘接收数据打印到共享内存。#include./share.hpp int main() { int shmidGet(); char*shmaddr(char*)shmat(shmid,nullptr,0); while(true) { coutPlease enter endl; fgets(shmaddr,SIZE,stdin); } shmdt(shmaddr); return 0; }运行结果这里要注意要先运行./create,先运行./get后续运行./create就会因为(IPC_CREAT|IPC_EXCL|0666);共享内存存在而出错返回。可以看出来现在的共享内存是没有同步和互斥的。进程create作为读端没有等资源就绪就开始读了。5.代码改进#ifndef __SHAREMEM_HPP__ #define __SHAREMEM_HPP__ #include sys/stat.h #include sys/types.h #include sys/ipc.h #include iostream #include sys/shm.h #include stdlib.h #include string #include unistd.h #include fcntl.h using namespace std; const int size 4096; const string pathname /home/wjp/sharemem/makefile; const int proj_id 0x6666; key_t Getkey() { int k ftok(pathname.c_str(), proj_id); if (k 0) { exit(1); } return k; } int Getsharemem(int flag) { int k Getkey(); int shmid shmget(k, size, flag); if (shmid 0) { exit(2); } return shmid; } int Create() { return Getsharemem(IPC_CREAT | IPC_EXCL | 0666); } int Get() { return Getsharemem(IPC_CREAT); } #define FIFO_FILE ./mkfifo #define MODE 0664 enum { FIFO_FILE_CREATE_ERR 1, FIFO_DELETE_ERR, FIFO_OPEN_ERR, }; class Init { public: Init() { int n mkfifo(FIFO_FILE, MODE); if (n -1) { cout1endl; perror(mkfifo); exit(FIFO_FILE_CREATE_ERR); } } ~Init() { int munlink(FIFO_FILE); if(m-1) { perror(unlink); exit(FIFO_DELETE_ERR); } } }; #endif#includesharemem.hpp int main() { int shmidGet(); char*shmaddr(char*)shmat(shmid,nullptr,0); int fdopen(FIFO_FILE,O_WRONLY); if(fd0) { couterrorendl; } while(true) { coutplease enter endl; fgets(shmaddr,4096,stdin); write(fd,c,1); } shmdt(shmaddr); close(fd); return 0; }#includesharemem.hpp int main() { Init init; int shmidCreate(); char*shmaddr(char*)shmat(shmid,nullptr,0); if ((long long)shmaddr -1){ perror(shmat); return 1; } int fdopen(FIFO_FILE,O_RDONLY); if(fd0) { exit(FIFO_OPEN_ERR); } while(true) { char c; ssize_t sread(fd,c,1); if(s0) break; coutsay shmaddrendl; // sleep(1); } shmdt(shmaddr); shmctl(shmid,IPC_RMID,nullptr); close(fd); return 0; }管道的双方是存在同步性的在读写端正常时一段关闭另一端就会阻塞可以在create进程中让进程去读取文件由于get进程没有向文件中写入数据就会阻塞只有等到get进程启动向向共享内存写入数据然后向文件中写入数据create进程才会去共享内存读取数据。ps当我们要使用ctrlc结束进程时一定要在get端使用不能在create端否则操作系统杀死进程时并没有执行shmctl对共享内存的删除也就意味着共享内存还存在。运行结果