【Linux系统篇】:System V IPC核心技术解析---从共享内存到消息队列与信号量

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:c++篇–CSDN博客

在这里插入图片描述

文章目录

  • 一.System V共享内存(重点)
    • 1.基本概念和原理
    • 2.系统调用函数
      • shmget函数(创建/获取共享内存)
      • ftok函数(生成键值key)
      • 创建测试
      • shmat函数(挂接共享内存)
      • shmdt函数(去关联共享内存)
      • shmctl函数(控制共享内存,可以用来删除)
      • 挂接,去关联与删除测试
    • 3.进程间通信测试
    • 4.共享内存的特性
    • 5.共享内存的内核数据结构
  • 二.System V消息队列(了解即可)
  • 三.System V信号量(线程做铺垫)
  • 四.IPC资源的内核数据结构

一.System V共享内存(重点)

1.基本概念和原理

共享内存是一种进程间通信(IPC)机制,允许多个进程访问同一块物理内存区域。它是所有IPC方式中速度最快的,因为数据无需在进程间复制。

底层原理

1.虚拟内存映射机制

进程A的虚拟地址空间     物理内存     进程B的虚拟地址空间
+---------------+     +-------+     +---------------+
|               |     |       |     |               |
+---------------+     +-------+     +---------------+
| 0x8000-0x8FFF | --> | 数据  | <-- | 0xB000-0xBFFF |
+---------------+     +-------+     +---------------+
|               |     |       |     |               |
+---------------+     +-------+     +---------------+
  • 每个进程都有自己的虚拟地址空间
  • 共享内存通过将不同进程的虚拟地址映射到相同的物理内存页来实现
  • 进程可能在不同的虚拟地址上访问相同的物理内存

2.页表映射

进程A页表                   进程B页表
+-------------------+      +-------------------+
| 虚拟页 | 物理页帧  |      | 虚拟页 | 物理页帧  |
+-------+---------+        +-------+---------+
| ...   | ...     |        | ...   | ...     |
| 0x8   | 0x1A    | -----> | 0xB   | 0x1A    |
| ...   | ...     |        | ...   | ...     |
+-------------------+      +-------------------+
  • 每个进程除了有自己的虚拟地址空间,还有一个页表
  • 在物理内存上申请一块空间后,通过页表建立虚拟地址到物理地址的映射
  • 不同进程的共享内存在虚拟页不同,但通过页表会映射到相同的物理页上

通过共享内存进行通信的进程都会有一个共享内存,如果系统中存在大量的共享内存,系统就要对所有的共享内存进行管理,如何管理?先描述再组织!也就是说每个共享内存都会有一个描述该共享内存的结构体对象,然后系统会对这些结构体对象做管理。

此外上面所有的都不是直接由进程来做,而是由操作系统来完成;进程表示的是用户,当用户和系统进行交互,只能通过系统调用接口来实现。

2.系统调用函数

shmget函数(创建/获取共享内存)

#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);

参数

  • key:共享内存的键值,由ftok函数生成(后面讲)

  • size:共享内存的大小(字节为单位);创建时必须指定;获取已存在的共享内存时可以为0;一般建议共享内存的大小设置为4096字节的倍数

  • shmflg

    IPC_CREAT:如果共享内存不存在则创建;存在就获取并返回(可以单独使用)。

    IPC_CREAT|IPC_EXCL:如果共享内存不存在则创建;存在就出错放回;确保共享内存是新创建的(IPC_EXCL不可以单独使用)。

返回值

  • 成功返回共享内存的标识符(shmid;类似于文件描述符)
  • 失败返回-1,并设置errno

ftok函数(生成键值key)

#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);

功能

  • 输入文件路径和项目ID,根据相应的算法生成对应的键值key返回。key是一个数字,具有唯一性(因为文件路径具有唯一性)

参数

  • pathname:必须是一个存在且可访问的文件,通常选择稳定的系统文件或应用配置文件。
  • proj_id:项目ID,通常用字符,如’A’;只使用低8位,范围1-255

返回值

  • 成功返回生成的键值key
  • 失败返回-1,并设置errno(文件路径必须存在,否则失败)

键值key和共享内存标识符shmid的作用

键值(key)的作用

1.全局命名:键值是一个系统级别的标识符,用于在系统范围内唯一标识一个共享内存对象

2.进程间共识:不相关的进程可以通过约定相同的key来访问同一个共享内存

3.持久标识:key可以持久存在,即使系统重启,相同的ftok参数会生成相同的key

4.IPC资源定位:操作系统通过key在IPC表中查找相应的共享内存

标识符(shmid)的作用

1.内部引用:是系统内核为特定共享内存段分配的唯一标识符

2.句柄作用:进程获取shmid后,使用它进行后续的共享内存操作

3.临时性:shmid只在当前系统运行期间有效,系统重启后会改变

4.权限控制:系统通过shmid管理对共享内存的访问权限

当程序调用shmget()

  • 内核分配物理内存页
  • 创建共享内存标识符shmid
  • 在内核的IPC资源表中记录此共享内存段信息

创建测试

对于使用共享内存通信的两个进程,并不需要两个进程都创建,只需要第一个创建后,第二个之后的进程直接获取共享内存标识符直接使用即可:

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <cstdlib>
#include "log.hpp"#define SIZE 1024const std::string pathname = "/home/zmh01";
const int proj_id = 0x6666;Log log;key_t GetKey(){// 生成共享内存的键值keykey_t k = ftok(pathname.c_str(), proj_id);if(k < 0){log(Fatal, "create key error: %s", strerror(errno));exit(1);}log(INFO, "create key success, key: %d", k);return k;
}int GetShareMemHelper(int flag){int shmid = shmget(GetKey(), SIZE, flag);if (shmid < 0){log(Fatal, "create share memory error: %s", strerror(errno));exit(2);}log(INFO, "create share memory success, shmid: %d", shmid);return shmid;
}// 创建共享内存接口
int CreateShm(){return GetShareMemHelper(IPC_CREAT | IPC_EXCL);
}// 获取共享内存接口
int GetShm(){return GetShareMemHelper(IPC_CREAT);
}#endif

第一个进程调用CreateShm函数创建共享内存,第二个之后的进程调用GetShm函数就可以直接使用已经创建好的共享内存

先用进程a调用CreatemShm函数创建共享内存进行测试:

prcoessa.cc文件:

#include "comm.hpp"
#include "log.hpp"using namespace std;int main(){sleep(3);int shmid = CreateShm();sleep(5);log(INFO, "processa quit!");return 0;
}

在这里插入图片描述

共享内存的生命周期

共享内存的生命周期随内核,不会随进程的生命周期结束而结束:

在这里插入图片描述

需要手动删除(后面会讲解使用函数调用删除):

#查看共享内存资源
ipcs -m#删除指定的共享内存
ipcrm -m <shmid>

在这里插入图片描述

共享内存的权限

共享内存也是需要权限的,设置权限需要在创建共享内存时设置:

int GetShareMemHelper(int flag){int shmid = shmget(GetKey(), SIZE, flag);if (shmid < 0){log(Fatal, "create share memory error: %s", strerror(errno));exit(2);}log(INFO, "create share memory success, shmid: %d", shmid);return shmid;
}// 创建共享内存接口
int CreateShm(){return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}

加上对应的权限后,创建出来的共享内存就具备了权限:

在这里插入图片描述

shmat函数(挂接共享内存)

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数

  • shmid:共享内存标识符,由shmget函数返回
  • shmaddr:如果为nullptr系统会自动选择合适的虚拟地址挂接;如果非nullptr请求挂接到指定地址(通常不建议使用)
  • shmflg:控制标志,常用值:
    • 0:默认读写权限
    • SHM_RDONLY:只读方式挂接
    • SHM_REMAP:强制覆盖已有映射

返回值

  • 成功返回共享内存被挂接的起始地址
  • 失败返回(void)-1,并设置errno

当程序调用shmat函数时

  • 系统修改进程的页表,建立虚拟地址到共享物理地址的映射
  • 返回映射的起始地址(虚拟地址)给进程使用
  • 共享内存的挂接数加一

shmdt函数(去关联共享内存)

int shmdt(const void *shmaddr);

参数

  • shmaddr:共享内存被挂接的起始地址(描述共享内存的结构体对象中包含了共享内存的大小,系统只需要知道起始地址再根据大小,就可以将整个共享内存去关联)

返回值

  • 成功返回0;失败返回-1,并设置errno

注意点

  • 去关联只是取消进程与共享内存的关联,不会删除共享内存
  • 去关联后不能再通过原指针进行访问

当程序调用shmdt函数时

  • 系统移除进程页表中对应的映射关系
  • 进程无法再根据起始地址访问共享内存
  • 共享内存的挂接数减一

shmctl函数(控制共享内存,可以用来删除)

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数

  • shmid:共享内存标识符,shmget函数返回值
  • cmd:控制命令,删除时使用IPC_RMID(标记共享内存删除)
  • buf:指向描述共享内存结构体的指针(删除时设置为nullptr即可)

返回值

  • 成功返回0;失败返回-1,并设置errno

当程序调用shmctl(IPC_RMID)时

  • 系统标记该共享内存段为待删除
  • 当挂接该共享内存的所有进程都分离后,物理内存被释放

挂接,去关联与删除测试

两个文件表示不同的两个程序

processa.cc文件:

#include "comm.hpp"
#include "log.hpp"using namespace std;int main(){// 创建int shmid = CreateShm();log(Debug, "creat shm done");sleep(1);// 挂起char *shmaddr = (char *)shmat(shmid, nullptr, 0);log(Debug, "shmat shm done");sleep(2);// 去关联shmdt(shmaddr);log(Debug, "shmdt shm done");sleep(2);// 释放shmctl(shmid, IPC_RMID, nullptr);log(Debug, "shmctl shm done");sleep(2);return 0;
}

processb.cc文件:

#include "comm.hpp"
#include "log.hpp"using namespace std;int main(){// 获取int shmid = GetShm();log(Debug, "creat shm done");sleep(1);// 挂起char *shmaddr = (char *)shmat(shmid, nullptr, 0);log(Debug, "shmat shm done");sleep(2);// 去关联shmdt(shmaddr);log(Debug, "shmdt shm done");sleep(2);return 0;
}

在这里插入图片描述

程序a和程序b启动后,程序a先创建共享内存此时挂接数(nattch)为0,之后从0到1再到2表示两个程序调用shmat函数挂接共享内存;然后挂接数从2变成1再到0表示两个程序调用shmdt函数去关联共享内存;最后由程序a调用shmctl函数删除共享内存。

3.进程间通信测试

processa.cc文件:

进程a用来读取共享内存中写入的数据

#include "comm.hpp"
#include "log.hpp"using namespace std;int main(){// 创建int shmid = CreateShm();// 挂起char *shmaddr = (char *)shmat(shmid, nullptr, 0);// IPC codewhile(true){cout << "client say@" << shmaddr << endl;sleep(1);}// 去关联shmdt(shmaddr);// 释放shmctl(shmid, IPC_RMID, nullptr);return 0;
}

processb.cc文件:

进程b用来向共享内存中写入数据

#include "comm.hpp"
#include "log.hpp"using namespace std;int main(){// 获取int shmid = GetShm();// 挂起char *shmaddr = (char *)shmat(shmid, nullptr, 0);//IPC codewhile(true){cout << "Please Enter@";// 不需要借助缓冲区,直接写入到共享内存中即可fgets(shmaddr, SHMSIZE, stdin);}// 去关联shmdt(shmaddr);return 0;
}

在这里插入图片描述

进程b写入时,自定义每次从共享内存的起始位置开始写入(写入位置可以自己设置),一旦进程b把数据写入到共享内存后,进程a就会立即看到共享内存中的数据,就可以直接读取,不需要再调用系统接口来读取。

此外进程a每次读取并不会关心另一个进程b是否写入新的数据,而是一直按照自己的读取方式来读取(也就是说没有同步和互斥机制)。

4.共享内存的特性

1.共享内存没有同步和互斥的保护机制

对于共享内存,系统并没有设置对应的同步和互斥保护机制,需要用户自己设计同步和互斥机制。

2.共享内存具有高性能特性,是所有进程间通信中,速度最快的

共享内存是零拷贝机制,数据无需在进程间复制,直接访问同一物理内存即可;一旦建立映射,访问速度接近于常规内存操作;通常适合大量数据交换,性能不会随数据量增加而显著降低;同时可以减轻CPU负载,减少了数据搬运和上下文切换的开销。

3.共享内存内部的数据,由用户自己来维护

进程读取和写入的位置可以由用户自己来设置,根据起始位置和大小来进行维护

5.共享内存的内核数据结构

前面提到过系统要对每个共享内存进行管理,所以内核中存在描述共享内存的内核数据结构,也就是strcut shmid_ds

struct shmid_ds {struct ipc_perm shm_perm;   // 权限信息size_t          shm_segsz;  // 共享内存段大小(字节)time_t          shm_atime;  // 最后附加(attach)时间time_t          shm_dtime;  // 最后分离(detach)时间time_t          shm_ctime;  // 最后修改时间(如权限更改)pid_t           shm_cpid;   // 创建者 PIDpid_t           shm_lpid;   // 最后操作(attach/detach)的 PIDshmatt_t        shm_nattch; // 当前附加的进程数// ... 其他内核内部字段
};

其中第一个字段包含了另一个内核数据结构strcut ipc_perm

struct ipc_perm {key_t  __key;    // IPC 对象的键值(通过 ftok 生成)uid_t  uid;      // 所有者的用户 IDgid_t  gid;      // 所有者的组 IDuid_t  cuid;     // 创建者的用户 IDgid_t  cgid;     // 创建者的组 IDmode_t mode;     // 访问权限(如 0666)// ... 其他内核内部字段
};

该数据结构中存放的则是共享内存的键值key以及各种权限信息。

通过指令ipcs -m即可查看所有共享内存的属性信息。

也可以通过shmctl函数来获取struct shmid_ds结构体:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

二.System V消息队列(了解即可)

System V消息队列是一种进程间通信(IPC)机制,允许不相关进程通过消息交换数据,基本原理如下:

1.消息队列是内核中的链表结构,这个内核数据结构一定是由系统创建的,和共享内存一样属于共享资源;既然由系统创建,用户和系统进行交互只能通过系统调用来实现,所以和共享内存一样,消息队列也有一批相关的系统调用函数。

2.不同的进程之间要想进行通信,需要先看到同一份资源,也就是要标识同一个消息队列,如何表示同一个?和共享内存一样,通过唯一的键值key标识,同样也是由ftok函数生成。

3.进行通信之前,进程需要使用msgget函数(等同于shmget函数)创建或获取队列,生成一个msgid返回(等同于shmid),后续所有的操作通过这个id来实现。

4.发送消息使用msgsnd函数,接收消息使用msgrcf函数;发送的数据会生成一个消息节点存放在队列中,接收消息时从队列中获取,如果每个进程发送的数据不进行区分直接存放到消息队列中,就会导致无法正确接收到目标信息。因此在每个消息节点中,除了包含数据部分,还有类型字段部分,用来表示当前消息的特定类型。接收消息时按消息类型选择性接受即可。

5.消息队列的生命周期也是随系统的,持久性的存在于内核中,直到显示删除或系统重启。通过msgctl函数可以控制消息队列的删除(等价于shmctl函数)。

系统也要对每个消息队列进行管理,所以也存在描述消息队列的内核数据结构对象struct msqid_ds

struct msqid_ds {struct ipc_perm msg_perm;   // 权限信息struct msg     *msg_first;  // 队列中第一条消息的指针(内核维护)struct msg     *msg_last;   // 队列中最后一条消息的指针time_t          msg_stime;  // 最后发送消息的时间time_t          msg_rtime;  // 最后接收消息的时间time_t          msg_ctime;  // 最后修改时间(如权限更改)unsigned long   msg_cbytes; // 当前队列中消息总字节数msgqnum_t       msg_qnum;   // 当前队列中的消息数量msglen_t        msg_qbytes; // 队列最大允许字节数(由 msgmnb 限制)pid_t           msg_lspid;  // 最后发送消息的 PIDpid_t           msg_lrpid;  // 最后接收消息的 PID// ... 其他内核内部字段
};

该结构体中的第一个字段同样是strcut ipc_perm

消息队列和共享内存大致相同,因为都是属于System V通信标准中的IPC资源,包括之后的信号量,也是相同的机制。

三.System V信号量(线程做铺垫)

信号量也是System V进程通信的一种范畴。这里只讲解相关概念和原理,为之后的线程学习做铺垫,相关的系统调用函数在之后的线程部分再讲解使用。

  • 前置相关名词解释

以共享内存为例,前面讲解过共享内存没有同步和互斥机制,也就是没有任何保护机制;假设当前进程a和进程b通过共享内存来通信,当进程a正在写入,写入了一半时,就被进程b读取拿走了,就会导致双方发和收的数据不完整,这就是数据不一致问题

1.如果进程a和进程b看到同一份资源,也就是共享资源,如果不加以保护,就会导致数据不一致问题

2.如何解决数据不一致问题?加锁—也就是互斥访问;什么是互斥访问?任何时刻,只允许一个执行流(一个进程)访问共享资源,这就是互斥

3.对于共享资源,任何时刻只允许一个执行流访问(就是执行访问代码)的资源,叫做临界资源(一般是内存空间)。

4.举例:一个程序有100行代码,可能只有5到10行代码才在访问临界资源,而访问临界资源的代码,叫做临界区

明白了上面的,就可以解释一个现象

当多个进程往显示器文件写入内容时,会发生内容错乱,混乱,甚至和命令行混在一起,为什么会有这种现象?

这是因为往显示器文件写入时,需要先写入到显示器文件的文件缓冲区,多个进程往同一个文件缓冲区写入,相当于多个进程共享同一个文件缓冲区,而文件缓冲区并没有同步和互斥保护机制,就会造成数据不一致问题,如果想要解决这种情况,就要将显示器文件变成临界资源。

显示器文件也是一种共享资源!!!上面的现象是典型的数据不一致问题。

  • 理解什么是信号量

结论:信号量本质上就是一个计数器,类似但不等于int cnt = n,用来描述临界资源中资源数量的多少

如何理解临界资源中资源数量的多少这句话?

先举一个例子来讲解:

假设现在有一个电影院,电影院其中一个放映厅有100个座位,也就是说这个放映厅最多可以卖出100张票。

当我们要去看电影的时候,还没去看电影,需要先买票,买完票后,在电影放映的时段,这个座位就是属于我们自己的,即使可能买完票没有去,这个座位也要空着,因为我们提前买过票了,在这个时间段,这个座位就是我们的。

而买票的本质就是对资源的预定机制

除此之外,这个放映厅最多可以卖出100张票,一旦这100张全部买外,之后其他人在想买票就没有空余的座位了。

我们可以认为存在一个票数计数器,每卖出一张票,计数器就要减一,表示放映厅中的资源减少一个

当票数的计数器减到0后,就没有空余的座位,表示放映厅中的资源已经申请完毕了

通过上面这个电影院的例子再来理解临界资源中的资源数量这个概念:

假如一整块临界资源可以分为多个小块,当进程a只访问其中一块资源时,就可以把这一小块资源给进程a;而当进程b访问另一块资源时,因为和进程a访问的不是同一块,也可以把进程b要访问的这一块资源给他。系统没必要把整个临界资源锁住,只要把各自要访问的不同资源给他们即可。

这种情况下就可以允许多个执行流(进程)同时访问临界资源,提高多进程执行流访问临界资源的并发度,在一定程度上提高效率

但是上面这种情况最怕的就是两种情况:

1.多个执行流访问同一个资源;

2.整个临界资源分成n个资源,但是有n+1个执行流访问,同样也会导致第一种情况。

如何解决?通过一个引用计数器来解决

假设整个临界资源分成了n块资源,所以引用计数就是int cnt = n,每当有执行流访问时,就要申请计数器资源,引用计数减一;一旦引用计数cnt< = 0表示整个资源被申请完了,再有执行流申请,就不给了。

1.申请计数器成功,引用计数减一,就表示当前执行流具有访问资源的权限了。

2.申请了计数器资源,并没有立即访问申请的资源,就和买票一样,申请了计数器资源是对资源的预定机制。

3.计数器可以有效保证进入共享资源的执行流数量。

4.每一个执行流想访问共享资源中的一部分的时候,并不是直接访问,而是先申请计数器资源,就比如看电影先买票。

上面一直在讲的计数器资源,就叫做信号量

还是以上面电影院的例子来讲解:

如果现在电影院放映厅中只有一个座位,我们就只需要一个值为1的计数器。对于这一个座位,只有一个人能抢到,只有一个人能进放映厅看电影,表示看电影期间只有一个执行流在访问临界资源,这种情况就是上面提到的互斥

把值只能为0和1的两态计数器叫做二元信号量—本质上就是一个锁!!!

计数器为1,资源为1的本质—其实就是将临界资源不要分成很多块了,而是当作一个整体使用。整体申请,整体释放,每次最多只有一个执行流在使用

  • 信号量也是一种共享资源

当执行流要访问临界资源时,先要申请信号量计数器资源,这个信号量计数器资源,也是一种共享资源!

如果存在多个执行流,每个执行流都要知道当前的信号量计数器资源还有多少,比如执行流a申请了一个计数器资源,计数器减一,减一之后,需要同步的让其他执行流知道当前的计数器资源还剩多少。否则就会造成多个执行流访问同一个资源的情况。

而计数器资源存在的目的,就是为了保证其他共享资源的安全问题,而要保护别人的安全,首先需要保证自己的安全。

对于计数器资源减一的操作cnt--,并不是安全的!

在C语言上cnt--就是一条语句;

而变成汇编语句后,就会变成多条汇编语句(一般是3条):

a.将cnt变量的内容从内存拿到CPU寄存器中

b.CPU内进行cnt--操作

c.将计算后的结果写回cnt变量的内存位置

而进程在运行的时候,是可以随时被切换的,一旦当前进程正在执行这三条指令时被切换,被其他进程打断,就会导致cnt变量的错乱,因此对于计数器资源减一的操作,并不是安全的

(关于计数器资源不安全的问题,到之后的线程部分再具体讲解,这里只需要了解即可)

既然信号量是一种共享资源,又是如何保证自身的安全的

信号量有两种操作:

1.申请信号量,本质是对计数器资源的减一操作—这个操作叫做P操作;

2.释放资源,释放信号量,本质是对计数器资源的加一操作—这个操作叫做V操作

申请和释放信号量的操作叫做PV操作----具有原子性

什么是原子性?

要么不做,要么就做完—是两态的;没有“正在做”的概念

原子性放到汇编中,就是只有一条汇编语句,所以CPU执行该汇编语句时要么不做,要么就做完,当前进程不会正在做时被切换,被其他进程打断。

  • 信号量为什么也是进程通信的一种

对于进程通信,不仅仅是对数据之间的相互传输,相互协同也是通信的范畴。

要相互协同,本质上也是通信,信号量首先需要被所有的通信进程看到,也是一种共享资源。

  • 总结

1.信号量本质上是一把计数器,申请和释放信号量的操作叫做PV操作—具有原子性

2.执行流申请临界资源,必须先申请信号量资源,得到信号量之后,才能访问临界资源

3.信号量值只有0和1两态的,叫做二元信号量,本质上就是一个锁,具有互斥功能

4.申请信号量资源的本质:是对临界资源的预定机制

四.IPC资源的内核数据结构

在 System V IPC(进程间通信)机制中,struct shmid_ds(共享内存)、struct msqid_ds(消息队列)和 struct semid_ds(信号量)是内核用于管理不同 IPC 资源的内核数据结构。尽管它们服务于不同的 IPC 机制,但它们的核心设计遵循统一的模式,包含相似的字段,同时针对各自的功能扩展了特定成员。

三个IPC资源的内核数据结构的第一个字段都包含了一个struct ipc_perm结构体,这个结构体中存放的都是用于管理IPC对象的权限和所有者信息。

系统要对所有的IPC资源对象做管理,就要借助一个stcut ipc_perm *array[]数组,用于管理和跟踪所有的IPC对象(包括共享内存,消息队列和信号量)。

每一个IPC资源将结构体对象中第一个字段struct ipc_perm结构体指针存放到数组中,存放的位置就是xxxid(每个IPC资源的标识符)

每个IPC资源通过标识符就可以到数组对应的位置中,找到struct ipc_perm指针,根据这个指针就可以找到第一个字段struct ipc_perm结构体对象,当需要访问IPC资源结构体对象stcut xxx_ds中的其他资源时,只需要将struct ipc_perm*强制转换成strcut xxx_ds*即可(系统可以区分指针指向的类型,就可以转换成目标类型)。

这种数据结构设计就是C++中的多态,其中stcut ipc_perm是基类,struct xxx_ds是子类

在这里插入图片描述

以上就是关于System V中三种IPC资源的讲解,重点是共享内存,对于消息队列和信号量了解即可;如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79546.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ 20 信号量详解

C 20 信号量详解 一、信号量类型 C20 标准中定义了两种信号量&#xff1a; std::counting_semaphore<Max>&#xff1a;计数信号量&#xff08;允许资源池最多有 Max 个资源&#xff09;std::binary_semaphore&#xff1a;二进制信号量&#xff08;等价于 std::countin…

Vue3中provide和inject的用法示例

在 Vue3 中&#xff0c;provide 和 inject 用于实现跨层级组件通信。以下是一个简单的示例&#xff1a; 1. 父组件 (祖先组件) - 提供数据 javascript 复制 // ParentComponent.vue import { provide, ref, reactive } from vue;export default {setup() {// 提供静态数据p…

Spring数据访问全解析:ORM整合与JDBC高效实践

目录 一、Spring ORM集成深度剖析 &#x1f31f; ORM模块架构设计 核心集成特性&#xff1a; 整合MyBatis示例配置&#xff1a; 二、Spring JDBC高效实践指南 &#x1f31f; 传统JDBC vs Spring JDBC对比 &#x1f31f; JdbcTemplate核心操作示例 批量操作优化&#xf…

UE快速预览材质节点快捷键

开始预览节点 添加快捷键 然后按R就能快速预览 不用再右键了 非常方便

Java漏洞原理与实战

一、基本概念 1、序列化与反序列化 (1)序列化:将对象写入IO流中&#xff0c;ObjectOutputStream类的writeobject()方法可以实现序列化 (2)反序列化:从IO流中恢复对象&#xff0c;ObjectinputStream类的readObject()方法用于反序列化 (3)意义:序列化机制允许将实现序列化的J…

每日算法【双指针算法】(Day 1-移动零)

双指针算法 1.算法题目&#xff08;移动零&#xff09;2.讲解算法原理3.编写代码 1.算法题目&#xff08;移动零&#xff09; 2.讲解算法原理 数组划分&#xff0c;数组分块&#xff08;快排里面最核心的一步&#xff09;只需把0改为tmp 双指针算法&#xff1a;利用数组下标来…

SQL Server 的鎖機制

SQL Server 的鎖機制是為了確保數據的一致性和事務的隔離性而設計的。以下是針對讀寫操作的鎖定行為的詳細說明&#xff1a; 1. 鎖的基本類型 SQL Server 的鎖主要分為以下幾類&#xff1a; 共享鎖&#xff08;Shared Lock, S Lock&#xff09; 用於讀操作&#xff08;如 S…

AIP目录

专注于开发灵活API的设计文档。 AIP是总结了谷歌API设计决策的设计文档&#xff0c;它也为其他人提供了用文档记录API设计规则和实践的框架和系统。 基础1AIP目的和指南2AIP编号规则3AIP版本管理200先例8AIP风格与指导9术语表流程100API设计评审常见问题205Beta版本发布前置条…

CSS进度条带斑马纹动画(有效果图)

效果图 .wxml <view class"tb"><view class"tb-line" style"transform:translateX({{w%}})" /> </view> <button bind:tap"updateLine">增加进度</button>.js Page({data: {w:0,},updateLine(){this.…

【工具-Krillin AI】视频翻译、配音、语音克隆于一体的一站式视频多语言转换工具~

Krillin AI 是全能型音视频本地化与增强解决工具。这款简约而强大的工具&#xff0c;集音视频翻译、配音、语音克隆于一身&#xff0c;支持横竖屏格式输出&#xff0c;确保在所有主流平台&#xff08;哔哩哔哩&#xff0c;小红书&#xff0c;抖音&#xff0c;视频号&#xff0c…

zset.

zset 有序集合 zset 保留了 set 不能有重复元素的特点 zset 中的每个元素都有一个唯一的浮点类型的分数&#xff08;score&#xff09;与之关联&#xff0c;使得 zset 内部的元素是可以维护有序性的。但是这个有序不是用下标作为排序依据的&#xff0c;而是根据分数&#xf…

Spring 数据库编程

Spring JDBC 传统的JDBC在操作数据库时&#xff0c;需要先打开数据库连接&#xff0c;执行SQL语句&#xff0c;然后封装结果&#xff0c;最后关闭数据库连接等资源。频繁的数据库操作会产生大量的重复代码&#xff0c;造成代码冗余&#xff0c;Spring的JDBC模块负责数据库资源…

492Q 型气缸盖双端面铣削组合铣床总体设计

一、引言 492Q 型气缸盖是发动机的重要组成部分&#xff0c;其双端面的加工精度对发动机的性能和可靠性有着重要影响。设计一款适用于 492Q 型气缸盖双端面铣削的组合铣床&#xff0c;能够提高加工效率和质量&#xff0c;满足发动机生产的需求。 二、总体设计要求 加工精度&…

颚式破碎机的设计

一、引言 颚式破碎机作为矿山、建材等行业的重要破碎设备&#xff0c;其性能优劣直接影响物料破碎效率与质量。随着工业生产规模的扩大和对破碎效率要求的提高&#xff0c;设计一款高效、稳定、节能的颚式破碎机具有重要意义。 二、设计需求分析 处理能力&#xff1a;根据目…

第三阶段面试题

Nginx nginx常用模块以及其功能 proxy模块&#xff0c;进行代理功能 ssl模块&#xff0c;进行HTTPS协议的使用 gzip模块&#xff0c;进行传输数据的压缩 upstream模块&#xff0c;进行反向代理时使用 static模块&#xff0c;静态资源进行访问的模块 cache模块&#xff0…

鸿蒙NEXT开发键盘工具类(ArkTs)

export declare type KeyboardCallBack (show: boolean, height: number) > void; import { AppUtil } from ./AppUtil; import { LogUtil } from ./LogUtil; import { ArrayUtil } from ./ArrayUtil;/*** 键盘工具类* author 鸿蒙布道师* since 2025/04/18*/ export class…

基于 LabVIEW 的电液伺服阀测试台开发

开发了一种基于 LabVIEW 图形编程语言的自动测试系统&#xff0c;能够完成电液伺服阀的空载流量特性、压力增益特性、内泄漏特性等静态特性的自动测试。针对测试过程中干扰信号频段与正常信号频段接近&#xff0c;普通数字滤波器滤波效果不佳的问题&#xff0c;采用迭代滤波分解…

【uniapp】vue2 使用 Vuex 状态管理

创建store文件夹&#xff1a;store/index.js // index.js import Vue from vue import Vuex from vuex import address from ./modules/address.jsVue.use(Vuex)const store new Vuex.Store({modules: {address} })export default store 创建modules文件夹&#xff1a;modul…

c# 简单实现将Message的内容保存到txt中,超过100个则清理旧文件

using System; using System.IO; using System.Threading;public static class LogManager {private static readonly object _fileLock new object(); // 线程安全锁private const int MaxFiles 100; // 最大文件数限制private const string LogDire…

阿里云镜像加速仅支持阿里云产品了

最近在拉取docker镜像时一直报超时的错误&#xff1a; docker pull hello-world Using default tag: latest Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exce…