品牌网站建设荐选蝌蚪做地方门户网站不备案可以吗

当前位置: 首页 > news >正文

品牌网站建设荐选蝌蚪,做地方门户网站不备案可以吗,百度推广需要多少钱,互联网保险模式本篇文章接着上篇文章通信 | 管道通信#xff08;匿名管道 命名管道#xff09;进行讲解。本篇文章的中点内容是共享内存。 文章目录 一、初识与创建共享内存 1、1 什么是共享内存 1、2 共享内存函数 1、2、1 创建共享内存 shmget 1、2、2 ftok 生成 key 1、2、3 获取共…  本篇文章接着上篇文章通信 | 管道通信匿名管道 命名管道进行讲解。本篇文章的中点内容是共享内存。 文章目录  一、初识与创建共享内存 1、1 什么是共享内存 1、2 共享内存函数 1、2、1 创建共享内存 shmget 1、2、2 ftok 生成 key 1、2、3 获取共享内存 shmget 1、3 demo 代码 二、对共享内存进行相关操作  2、1 查看/删除 共享内存资源 2、2 共享内存挂接和访问 2、2、1 共享内存的挂接 shmat 2、2、2 共享内存的访问 2、3 删除共享内存 shmctl  三、完整共享内存通信 demo 代码 3、1 Log.hpp 日志 3、2 comm.hpp 3、3 shmClient.cpp 3、4 shmServer.cpp ♂️ 作者Ggggggtm ♂️  专栏Linux从入门到精通   标题共享内存  ❣️ 寄语与其忙着诉苦不如低头赶路奋路前行终将遇到一番好风景 ❣️ 一、初识与创建共享内存 1、1 什么是共享内存 我们在之前学管道通信时是怎么实现通信的呢匿名管道通信的方式是子进程继承父进程的内核数据结构使得父子进程能够看到同一块空间。命名管道通信是让不同进程打开同一份文件。我们发现通信的前提就是让不同的进程看到同一份“资源”。当然共享内存也不例外。    每个进程都有自己独立的地址空间所以它们彼此之间不能直接访问对方的内存。而共享内存则提供了一种特殊的内存区域允许多个进程可以同时访问和操作同一块内存。具体如下图 这里再次解释一下上图。我们创建共享内存的过程一个进程在内核空间申请一个共享内存对象让后通过页表建立与物理内存的映射。让后另一个进程通过特殊的方法和算法来找到该共享内存并且与其建立映射。下面我们会对上述过程进行详细解释。  1、2 共享内存函数 1、2、1 创建共享内存 shmget shmget函数用于创建一个新的共享内存段或者获取现有的共享内存段的标识符。 函数原型为 int shmget(key_t key, size_t size, int shmflg); 参数说明 key用于标识共享内存段的键值。可以使用ftok函数生成。size指定共享内存段的大小以字节为单位。shmflg用于指定共享内存段的访问权限和标志位可以使用IPC_CREAT、IPC_EXCL等宏进行设置。 返回值 如果成功返回共享内存段的标识符即共享内存ID。如果失败返回-1并设置errno。   更加详细的如下图 我们在这里具体解释一下第三个参数 shmflg。这个参数是可以有多个选择的。底层是利用了位图的思想。主要是IPC_CREAT和IPC_EXCL两个选项。   IPC_CREAT和IPC_EXCL是在shmget函数中使用的标志位用于指定共享内存段的访问权限和标志。它们在shmget函数的第三个参数shmflg中使用。 IPC_CREAT该标志用于创建一个新的共享内存段。如果指定的key对应的共享内存段不存在则创建一个新的共享内存段。如果共享内存段已经存在则返回该共享内存段的标识符即共享内存ID。 IPC_EXCL该标志与IPC_CREAT一起使用在创建共享内存段时起作用。如果指定的key对应的共享内存段已经存在则shmget函数会失败并返回-1并且置errno为EEXIST资源已存在。    我们接下来再看一下 shmget 的具体使用例子。 int shmid shmget(key, size, IPC_CREAT | permission_flags);   上述代码中IPC_CREAT标志位用于创建共享内存段。如果指定的key对应的共享内存段已经存在那么shmget函数会返回该共享内存段的标识符如果共享内存段不存在则会创建一个新的共享内存段并返回新创建的共享内存段的标识符。 1、2、2 ftok 生成 key 在函数shmget中key值是用于标识或检索共享内存段的关键值。它在创建或访问共享内存时起到重要作用。具体来说key值用于以下两个目的 当多个进程需要访问同一个共享内存段时它们可以使用相同的key值来标识这个共享内存段。如果一个共享内存段已经存在并且其他进程想要访问它那么只需要提供相同的key值即可找到该共享内存段。   那在使用 shmget 函数之前我们应该使用 ftok 函数生成key值来表示这个共享内存段。由于是标示共享空间所以应该确定唯一性。至于key的值是多少并不关键。那我们看一下ftok 函数的用法。具体如下 参数说明 pathname一个包含一个现有文件的路径名用于生成k值。最好是有权访问这个文件。proj_id不同的proj_id可以被用作区分不同类型的通信方式或不同的ipc资源来生成不同的k值。其实就是一个任意整型值。   我们再看一下其具体的例子 #include stdio.h #include sys/types.h #include sys/ipc.hint main() {key_t key;char *path ./example.txt;int proj_id 0x66;// 使用ftok函数生成k值key ftok(path, proj_id);printf(Generated key: %d\n, key);return 0; }   另一个进程也可以用相同的方法生成相同的key值。ftok函数根据给定的路径名和proj_id生成k值。当路径名和proj_id相同时生成的k值也相同。这是因为ftok函数内部使用了哈希运算将路径名和proj_id转化为一个唯一的整数。尽管可能存在哈希冲突即不同的路径名和proj_id生成相同的k值但概率非常低。通常情况下不同的进程可以使用相同的路径名和proj_id生成相同的k值是非常罕见的。即使出现相同的k值由于进程间通信中还有其他参数的限制如消息队列标识、共享内存标识等不同进程之间的IPC通信仍然可以正常进行。     生成 key 值后我们就可以用key值创建共享内存或者来获取共享内存。下面我们看一下获取使用key值来获取共享内存的方法。 1、2、3 获取共享内存 shmget shmget 函数还可用来获取共享内存。当生成的key值已经有对应的共享内存时shmget 函数就会返回这段共享内存的标识码。我么不只需要将第三个参数修改为0就是来获取对应key值的共享内存。 1、3 demo 代码 我们接下来写一段代码测试和总结一下我们上面所学到的函数。下面为实例 // Log.hpp #include iostream #include ctime#define Debug 0 #define Notice 1 #define Warning 2 #define Error 3const std::string msg[] {Debug,Notice,Warning,Error };std::ostream Log(std::string message, int level) {std::cout | (unsigned)time(nullptr) | msg[level] | message;return std::cout; }// comm.hpp #pragma once#include iostream #include cstdio #include unistd.h #include sys/types.h #include sys/ipc.h #include sys/shm.h #include cassert #include cstring #include sys/types.h #include sys/stat.h #include fcntl.h #include Log.hppusing namespace std; #define PATH_NAME /home/gtm #define PROJ_ID 0x66 #define SHM_SIZE 4096 //共享内存的大小最好是页(PAGE: 4096)的整数倍#define FIFO_NAME ./fifo// shmClient.cc #includecomm.hppint main() {Log(child pid is : , Debug) getpid() endl;key_t k ftok(PATH_NAME, PROJ_ID);if (k 0){Log(create key failed, Error) client key : k endl;exit(1);}Log(create key done, Debug) client key : k endl;// 获取共享内存int shmid shmget(k, SHM_SIZE, 0);if(shmid 0){Log(create shm failed, Error) client key : k endl;exit(2);}Log(create shm success, Error) client shmid : shmid endl;return 0; }// shmServer.cc #include comm.hppstring TransToHex(key_t k) {char buffer[32];snprintf(buffer, sizeof buffer, 0x%x, k);return buffer; }int main() {// 我们之前为了通信所做的所有的工作属于什么工作呢让不同的进程看到了同一份资源(内存)// 1. 创建公共的Key值key_t k ftok(PATH_NAME, PROJ_ID);assert(k ! -1);Log(create key done, Debug) server key : TransToHex(k) endl;// 2. 创建共享内存 – 建议要创建一个全新的共享内存 – 通信的发起者int shmid shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //if (shmid -1){perror(shmget);exit(1);}Log(create shm done, Debug) shmid : shmid endl;return 0; }   shmServer.cc文件中的代码是服务端代码。首先调用ftok函数生成一个唯一的键值并将其转换为十六进制字符串表示。然后使用shmget函数创建一个共享内存段创建时指定了IPC_CREAT标志用于新建共享内存段。如果创建成功返回一个共享内存标识符shmid。   shmClient.cc文件中的代码是客户端代码。首先也是调用ftok函数生成一个唯一的键值。然后通过shmget函数打开已存在的共享内存段打开时不需要指定IPC_CREAT标志而是提供即将打开的共享内存段的键值和大小。如果打开成功返回一个共享内存标识符shmid。   Log.hpp文件定义了一个宏和一个Log函数。宏定义了四个日志级别分别对应Debug、Notice、Warning和Error四个字符串。Log函数负责输出日志信息接受一个字符串信息和一个日志级别参数。Log函数将时间戳、日志级别和消息内容输出到标准输出流中。 上述代码就是完成了创建共享内存的功能并且在其中打印了一些日志信息。我们不妨来看一下运行结果。具体如下图   我们发现对应的shmid是相同的说明Server和Client确实获得了相同的共享内存块。他们所生成的key值相同吗其实是相同的如下图 二、对共享内存进行相关操作  2、1 查看/删除 共享内存资源 当我们再次运行时就会发生错误。具体如下图   为什么呢原因是我们刚刚创建的共享内存依然存在。当进程结束时共享内存并不会自动释放。为什么呢我们可以认为共享内存是属于操作系统。所以共享内存的生命周期随操作系统这时我们可以手动关闭共享内存。在关闭前首先要查看共享内存指令ipcs -m。具体如下图   当然查到共享内存后可以用指令进行删除。那么问题来了使用key值删除呢还是用shmid 进行删除呢我们这里需要注意共享内存中的shmid和key值是两个不同的概念。 shmidShared Memory ID是共享内存的标识符由操作系统分配并作为一个非负整数对共享内存进行引用。在使用共享内存时我们需要通过shmid来进行操作如创建、附加、访问和删除等。shmid可以看作是内核用于标识某个特定共享内存段的一个唯一值。 key值是用户定义的一个标识符通常是一个整数值。在创建共享内存时我们可以使用ftok函数生成一个key值以便其他进程可以通过这个key值来获取相同的共享内存区域。key值是用于标识共享内存的用户级别的标识符不同于shmid其值不受内核控制。   所以删除共享内存我们使用的shmid。具体指令ipcrm -m shmid。如下图   当我们删除共享内存后再次进行查找发现就没有了且程序能够正常运行。 2、2 共享内存挂接和访问 2、2、1 共享内存的挂接 shmat 在调用shmget()函数时内核会在内部维护一个共享内存表格其中包含了共享内存的相关信息包括共享内存的大小、权限等。当调用成功后将返回一个唯一的共享内存标识符该标识符可以用于后续的共享内存操作。   那么正常来说我们访问共享内存是需要通过系统调用的。但是我们这里可以将内核级别的共享内存挂接到进程的地址空间。然后用户就可以直接进行访问。 进程可以使用系统提供的函数如shmat()将自己的地址空间映射到共享内存。也可以理解为shmat()函数将共享内存附加到进程的虚拟地址空间中使得进程可以访问该共享内存所指向的物理内存区域。具体用法如下   参数说明 shm_id共享内存标识符通过调用 shmget 获取。shm_addr内存段的地址通常传入 NULL表示由系统自动选择一个适合的地址。shmflg控制共享内存段的附加方式和权限可以使用 IPC_CREAT 标志创建新的共享内存段。通常传入0。   返回值 如果成功返回指向共享内存段第一个字节的指针如果失败返回 void * 类型的错误值 -1。   其实我们看完其使用方法后有没有发现与 malloc 很相似。malloc 申请空间成功后会返回所申请空间的起始地址。否则就会返回NULL。shmat 与其确实有些相似。我们可结合如下例子一起理解 #include sys/types.h #include sys/ipc.h #include sys/shm.h #include stdio.hint main() {int shm_id;key_t key;int *shared_memory;// 获取共享内存标识符key ftok(。/file, 0x66);shm_id shmget(key, sizeof(int), IPC_CREAT | 0666);// 将共享内存段附加到进程的地址空间中shared_memory shmat(shm_id, NULL, 0);// 访问共享内存printf(共享内存中的值为%d\n, *shared_memory);// 分离共享内存段shmdt(shared_memory);return 0; }   我们也看到了最后是有一个去关联的 shmdt 函数。参数就是我们所获取的共享内存的起始地址这里就不再过多解释此函数。 这里有会有一个问题将内核级别的共享内存挂接到进程地址空间的哪里了呢我们看如下图    我们之前学进程地址空间时知道堆和栈的中间有大量的镂空而这段位置就是内核级别的共享内存所挂接到的位置 2、2、2 共享内存的访问 到这里我们已经学习了共享内存的大部分内容。只差对共享内存的访问了。当我们对共享内存进行挂接后 就可以得到共享内存挂接后的起始地址。我们用户可以对其进行直接访问写入/读取。我们给出如下伪代码 // shmServer.cppchar *shmaddr (char *)shmat(shmid, nullptr, 0);Log(attach shm done, Debug) shmid : shmid endl;for(;;){printf(%s\n, shmaddr);if(strcmp(shmaddr, quit) 0) break;sleep(1);}// shmClient.cpp // 挂接并获得共享内存起始地址char *shmaddr (char *)shmat(shmid, nullptr, 0);if(shmaddr nullptr){Log(attach shm failed, Error) client key : k endl;exit(3);}char a a;for(; a c; a){// 我们是每一次都向shmaddr[共享内存的起始地址]写入snprintf(shmaddr, SHM_SIZE - 1,\hello server, 我是其他进程,我的pid: %d, inc: %c\n,\getpid(), a);sleep(3);}   对上述代码是一个使用共享内存进行进程间通信的伪代码。下面对代码进行详解 首先在服务端shmServer.cpp中通过shmat函数将共享内存连接到当前进程的地址空间。shmat函数的第一个参数是共享内存的标识符shmid第二个参数为NULL表示让系统自动选择合适的地址分配给共享内存第三个参数为0表示以默认权限进行操作。连接完成后返回共享内存的起始地址并赋值给shmaddr指针。 在服务器端的for循环中通过printf函数将shmaddr指向的共享内存内容输出到标准输出读取。然后通过strcmp函数判断共享内存中的内容是否为quit如果是则跳出循环结束程序。否则通过sleep函数暂停1秒钟。 在客户端shmClient.cpp中同样通过shmat函数连接到共享内存并将共享内存的起始地址赋给shmaddr指针。若连接失败shmaddr为nullptr则输出错误信息并退出程序。 在客户端的for循环中使用snprintf函数将格式化的字符串写入shmaddr指向的共享内存中写入。该字符串包含了客户端进程的PID进程标识符和一个递增的字符以展示多次写入的内容。然后通过sleep函数暂停3秒钟。 运行结果如下   我们通过运行结果发现在客户端没有写入的情况下服务端进行读取时也会读到内容。读到的是空字符串共享内存默认会初始化为0。我们发现共享内存的读写并没有访问控制。我们知道命名管道通信是由访问控制的。但是当一个进程写入时另一个就能够马上看到写入的内容。所以共享内存是所有进程间通信(IPC)速度最快的不需要过多的拷贝不需要将数据给操作系统。如果我想一定程度的访问控制呢?可以在共享内存读写的过程中加入命名管道来控制。 2、3 删除共享内存 shmctl  上面我们了解了可以使用Linux指令对共享内存进行删除我们也可以使用系统调用 shmctl函数 对其进行删除。具体使用如下  参数说明 shmid共享内存标识符通过shmget函数获取得到。cmd表示对共享内存进行的操作类型可以选择的参数有 IPC_STAT获取共享内存的状态信息将共享内存的属性保存在buf所指向的结构体中。IPC_SET设置共享内存的属性使用buf所指向的结构体中的值进行设置。IPC_RMID删除共享内存。buf指向一个struct shmid_ds结构体的指针用于存储共享内存的属性信息。通常使用nullptr。   shmctl函数可以用于对共享内存段进行控制操作。它能够实现共享内存的创建、删除、以及获取和修改共享内存的属性。但是我们该函数最常用删除共享内存。使用IPC_RMID操作可以删除指定的共享内存段并释放系统资源。这个操作会立即删除共享内存段以及与它关联的任何进程中的键和id。 三、完整共享内存通信 demo 代码 3、1 Log.hpp 日志 #include iostream #include ctime#define Debug 0 #define Notice 1 #define Warning 2 #define Error 3const std::string msg[] {Debug,Notice,Warning,Error };std::ostream Log(std::string message, int level) {std::cout | (unsigned)time(nullptr) | msg[level] | message;return std::cout; } 3、2 comm.hpp #pragma once#include iostream #include cstdio #include unistd.h #include sys/types.h #include sys/ipc.h #include sys/shm.h #include cassert #include cstring #include sys/types.h #include sys/stat.h #include fcntl.h #include Log.hppusing namespace std; //不推荐#define PATH_NAME /home/whb #define PROJ_ID 0x66 #define SHM_SIZE 4096 //共享内存的大小最好是页(PAGE: 4096)的整数倍#define FIFO_NAME ./fifoclass Init { public:Init(){umask(0);int n mkfifo(FIFO_NAME, 0666);assert(n 0);(void)n;Log(create fifo success,Notice) \n;}~Init(){unlink(FIFO_NAME);Log(remove fifo success,Notice) \n;} };#define READ O_RDONLY #define WRITE O_WRONLYint OpenFIFO(std::string pathname, int flags) {int fd open(pathname.c_str(), flags);assert(fd 0);return fd; }void Wait(int fd) {Log(等待中…., Notice) \n;uint32_t temp 0;ssize_t s read(fd, temp, sizeof(uint32_t));assert(s sizeof(uint32_t));(void)s; }void Signal(int fd) {uint32_t temp 1;ssize_t s write(fd, temp, sizeof(uint32_t));assert(s sizeof(uint32_t));(void)s;Log(唤醒中…., Notice) \n; }void CloseFifo(int fd) {close(fd); } 3、3 shmClient.cpp #include comm.hppint main() {Log(child pid is : , Debug) getpid() endl;key_t k ftok(PATH_NAME, PROJ_ID);if (k 0){Log(create key failed, Error) client key : k endl;exit(1);}Log(create key done, Debug) client key : k endl;// 获取共享内存int shmid shmget(k, SHM_SIZE, 0);if(shmid 0){Log(create shm failed, Error) client key : k endl;exit(2);}Log(create shm success, Error) client key : k endl;// sleep(10);char *shmaddr (char *)shmat(shmid, nullptr, 0);if(shmaddr nullptr){Log(attach shm failed, Error) client key : k endl;exit(3);}Log(attach shm success, Error) client key : k endl;int fd OpenFIFO(FIFO_NAME, WRITE);// client将共享内存看做一个char 类型的bufferwhile(true){ssize_t s read(0, shmaddr, SHM_SIZE-1);if(s 0){shmaddr[s-1] 0;Signal(fd);if(strcmp(shmaddr,quit) 0) break;}}CloseFifo(fd);// 去关联int n shmdt(shmaddr);assert(n ! -1);Log(detach shm success, Error) client key : k endl;return 0; } 3、4 shmServer.cpp #include comm.hppInit init; string TransToHex(key_t k) {char buffer[32];snprintf(buffer, sizeof buffer, 0x%x, k);return buffer; }int main() {// 1. 创建公共的Key值key_t k ftok(PATH_NAME, PROJ_ID);assert(k ! -1);Log(create key done, Debug) server key : TransToHex(k) endl;// 2. 创建共享内存 – 建议要创建一个全新的共享内存 – 通信的发起者int shmid shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666); //if (shmid -1){perror(shmget);exit(1);}Log(create shm done, Debug) shmid : shmid endl;// 3. 将指定的共享内存挂接到自己的地址空间char *shmaddr (char *)shmat(shmid, nullptr, 0);Log(attach shm done, Debug) shmid : shmid endl;int fd OpenFIFO(FIFO_NAME, READ);for(;;){Wait(fd);// 临界区printf(%s\n, shmaddr);if(strcmp(shmaddr, quit) 0) break;// sleep(1);}// 4. 将指定的共享内存从自己的地址空间中去关联int n shmdt(shmaddr);assert(n ! -1);(void)n;Log(detach shm done, Debug) shmid : shmid endl;// 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接依旧删除共享内存n shmctl(shmid, IPC_RMID, nullptr);assert(n ! -1);(void)n;Log(delete shm done, Debug) shmid : shmid endl;CloseFifo(fd);return 0; } 上述共享内存代码是结合了命名管道通信进行了访问控制。