深入探索进程间通信:System V IPC的机制与应用

目录

1、System V概述

2.共享内存(shm)

2.1 shmget — 创建共享内存

2.1.2 ftok(为shmmat创建key值)

 2.1.3 为什么一块共享内存的标志信息需要用户来传递

2.2 shmat — 进程挂接共享内存

2.3 shmdt — 断开共享内存连接

2.4 shmctl — 删除共享内存

2.5 命令行查看共享内存

2.6 使用命令释放共享内存资源

2.7. 优缺点

2.8 管道和共享内存的比较(为什么共享内存是最快的)

3 消息队列的原理与概念

4 信号量

4.1 储备知识

4.2 原理与概念


1、System V概述

在Linux系统下,System V指的是一套由AT&T开发的UNIX操作系统版本及其相关的进程间通信(IPC)机制。

System V是UNIX操作系统的一个重要分支,它提供了一套丰富的系统调用和进程间通信机制。与BSD等其他UNIX版本相比,System V在IPC机制方面有着显著的不同和优势。

System V提供了三种主要的IPC机制,包括:

  1. 共享内存(Shared Memory)
  2. 消息队列(Message Queues)
  3. 信号量(Semaphores)

2.共享内存(shm)

进程之间通信的前提都是,通信的进程都可以看到同一份资源。

管道通信是让通信的双方,看到一个操作系统内核管理的缓冲区,通过文件描述符读写数据;。而共享内存则是让进程之间看到一个直接映射到进程地址空间的共享内存区域。

共享内存是有操作系统开辟和维护的,他的生命周期是随着内核的结束而结束的。

共享内存的工作原理基于操作系统的内存管理机制。操作系统为共享内存区域分配一块物理内存,并允许多个进程或线程通过映射这块物理内存到各自的虚拟地址空间来访问它。由于这些进程或线程访问的是同一块物理内存,因此它们可以直接读写这块内存中的数据,而无需进行数据拷贝。

OS中可以存在多个共享内存,共享内存也可以让多组需要通信的进程访问。


2.1 shmget — 创建共享内存

shmget是一个在Linux系统中用于创建或获取共享内存段的系统调用函数。以下是关于shmget的详细解释:

参数说明

  1. key:共享内存的键值,用于唯一标识共享内存对象。这个键值可以通过ftok函数生成,也可以直接使用IPC_PRIVATE来创建一个新的共享内存对象。
  2. size:共享内存的大小,以字节为单位。这个大小需要在系统允许的范围内,通常由系统内核参数shmmax和shmmin控制。
  3. shmflg:标志位,用于指定创建共享内存的权限和行为。下面的表格是常见的标志位
    IPC_CREAT如果共享内存不存在则创建它
    IPC_CREAT|IPC_EXCL

    IPC EXCL单独使用无意义:


    如果内存中不存在键值与key值相等的共享内存,则会创建一个新的共享内存
    反之,就出错返回


    综上: 如果shmget调用成功,必定创建的是一个全新的共享内存

    IPC CREAT |IPC EXCL|杈限(0xxx)用来指定创建的共享内存的权限

返回值

  • 成功时,shmget返回一个有效的共享内存标识符(shmid),用于后续对共享内存的操作。
  • 失败时,shmget返回-1,并将errno设置为相应的错误代码,以指示失败的原因。


2.1.2 ftok(为shmmat创建key值)

ftok 是一个在 Unix 和类 Unix 操作系统(如 Linux)中用于生成一个唯一键值(key)的函数,这个键值通常用于创建或访问 IPC(进程间通信)对象,如消息队列、信号量和共享内存。ftok 函数通过指定的路径名和一个标识符(通常是一个字符)来生成这个键值。

参数说明

  • pathname:一个指向文件系统路径名的指针。这个路径名必须指向一个已存在的文件,并且对该文件的访问权限会影响 ftok 函数的行为。如果 ftok 成功,它不会修改这个文件的内容或属性。
  • proj_id:一个标识符,通常是一个字符(在 ASCII 范围内,即 0 到 127)。这个标识符与路径名一起用于生成键值。

返回值

  • 成功时,ftok 返回一个唯一的键值(key_t 类型),该键值可以用于创建或访问 IPC 对象。
  • 失败时,ftok 返回 (key_t)-1,并设置 errno 以指示错误原因。


 2.1.3 为什么一块共享内存的标志信息需要用户来传递

只要通信双方事先约定好了参数,两个进程可以基于相同的文件路劲和项目标识符来生成同一个key值,当它们分别调用shmget函数并传入相同的key,就能够看到同一个共享内存,从而实现进程间通信。如果key是由内核设定,进程之间不知道对方创建共享内存的key值,因为进程具有独立性,从而无法建立通信。

eg:进程A创建共享内存,其key值如果由OS自动生成,进程具有独立性,进程B无法知道进程A创建的共享内存的key值,因此进程B无法访问进程A创建的共享内存,从而无法建立通信。


2.2 shmat — 进程挂接共享内存

hmat是一个在Linux系统中用于进程间通信的函数,特别是在共享内存的使用中扮演着重要角色。

一、函数原型

 

二、参数说明

  1. shmid:共享内存标识符,由shmget函数返回。这个标识符用于唯一标识一个共享内存段。
  2. shmaddr:指定共享内存连接到当前进程的地址空间的起始地址。如果这个参数为NULL,系统会自动选择一个合适的地址。
  3. shmflg:指定连接共享内存的权限标志。常用的权限标志有SHM_RDONLY(只读连接)等。如果省略此标志,则默认以读写方式附加。
    选项作用
    SHM_RDONLY关联共享内存后只进行读取操作
    SHM_RND若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
    0默认为读写权限

三、返回值

  • 成功时,shmat函数返回一个指向共享内存的指针。
  • 失败时,返回(void*)-1,并设置相应的错误码。


2.3 shmdt — 断开共享内存连接

shmdt 是一个在 Linux 和其他类 Unix 操作系统中用于进程间通信(IPC)的函数,特别是在共享内存的使用中。它的主要作用是将先前由 shmat 函数附加到进程地址空间的共享内存段分离(detach)出去。

 参数

  • shmaddr:这是一个指向先前由 shmat 返回的共享内存段在进程地址空间中的起始地址的指针。这个地址是 shmat 函数成功执行后返回的指针。

返回值

  • 成功时,shmdt 函数返回 0。
  • 失败时,返回 -1,并设置相应的错误码。

使用说明

  • 在调用 shmdt 函数之后,进程将不再能够通过该指针访问共享内存段。但是,这并不意味着共享内存段被销毁。只要还有其他进程附加(attach)到该共享内存段,或者该共享内存段被标记为持久(persistent),它将继续存在。
  • 调用 shmdt 是个好习惯,因为它可以释放进程对共享内存段的引用,从而允许操作系统在必要时回收相关资源。但是,请注意,shmdt 并不销毁共享内存段本身;要销毁共享内存段,需要使用 shmctl 函数并指定 IPC_RMID 命令。
  • 如果进程在退出前没有调用 shmdt 来分离共享内存段,操作系统通常会在进程终止时自动执行这一操作。然而,显式调用 shmdt 可以避免潜在的资源泄漏和不必要的系统开销。

错误处理

在使用 shmdt 函数时,可能会遇到以下错误:

  • EINVAL:无效的 shmaddr 参数,即该地址不是由 shmat 返回的有效共享内存段地址。

当遇到这些错误时,应根据错误码进行相应的处理,例如输出错误信息并采取适当的恢复措施。


2.4 shmctl — 删除共享内存

shmctl是一个在Linux系统中用于控制共享内存的函数。但是普遍用于删除内存。 

二、参数说明

  1. shmid:共享内存的标识符,该标识符由shmget函数返回。

  2. cmd:指定要执行的操作命令。常用的命令有:

    IPC_STAT获取共享内存的状态,将共享内存的shmid_ds结构复制到buf中。
    IPC_SET如果进程有足够的权限,就改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内。
    IPC_RMID删除共享内存段。需要注意的是,该命令实际上不从内核删除共享内存段,而是仅仅把这个段标记为删除。实际删除过程是发生在最后一个使用该共享内存的进程退出或者是解除映射之后。

  3. buf:指向shmid_ds结构的指针,用于设置或获取共享内存的属性。当cmd为IPC_STAT或IPC_SET时,需要使用此参数。

三、返回值

若执行成功,shmctl函数返回0;如果失败,返回-1并设置相应的错误码。

四、注意事项

  1. 在使用shmctl函数之前,必须确保已经成功获取了共享内存的标识符(即shmid)。
  2. 当使用IPC_RMID命令删除共享内存时,请确保没有其他进程正在使用该共享内存,否则可能会导致未定义的行为。
  3. 在编写涉及共享内存的程序时,务必注意同步和互斥问题,以避免数据竞争和不一致性。


2.5 命令行查看共享内存

单独使用ipcs命令时,会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看它们之间某一个的相关信息,可以选择携带以下选项:

  • -q:列出消息队列相关信息。
  • -m:列出共享内存相关信息。
  • -s:列出信号量相关信息。
标题含义
key系统区别各个共享内存的唯一标识
shmid共享内存的用户层id(句柄)
owner共享内存的拥有者
perms共享内存的权限
bytes共享内存的大小
nattch关联共享内存的进程数
status共享内存的状态

key和shmid的区别:都是确保共享内存的唯一性和可访问性的重要机制。


key在内核中用于标识共享内存,确保其在系统内的唯一性,即:key在内核层面上保证了共享内存的唯一性。

shmid是在成功创建共享内存后由系统返回,用于在用户层面上唯一地标识共享内存,并作为后续对共享内存操作的参数,即:shmid在用户层面上提供了对共享内存的唯一标识和操作接口。

注意: key是在内核层面上保证共享内存唯一性的方式,而shmid是在用户层面上保证共享内存的唯一性,key和shmid之间的关系类似于fd和FILE*之间的的关系。


2.6 使用命令释放共享内存资源

ipcrm [-m|-q|-s] shmid
  • 功能:删除已存在的IPC资源对象,包括共享内存、消息队列、信号量集。


2.7. 优缺点

优点:共享内存是所有进程间通信(IPC)方式中速度最快的。

  1. 减少拷贝次数:在管道通信中,数据通常要经过至少两次拷贝,数据从一个进程的缓冲区写入管道,然后从管道中读取到另一个进程的缓冲区中,这涉及两次数据在不同内存区域之间的复制操作。在共享内存中允许多个进程直接访问同一块内存区域,当一个进程将数据写入到共享内存中,其他进程可以立即看到,最多只会经历一次从进程的用户空间到共享内存的拷贝。
  2. 直接访问:进程可以直接对共享内存进行读写操作,无需通过OS进行数据中转,这大大减少了内核参与数据传输的开销,提高了通信效率。

缺点:共享内存不提供进程间协同的任何机制。

  1. 这会导致多个进程同时访问共享内存区域时,出现数据不一致和数据竞争等问题。因为没有内置的同步和互斥手段,不同进程可能在不可预测的时间点对共享内存进行读写操作,从而破坏数据的完整性。例如,一个进程正在写入数据时,另一个进程可能同时在读取,可能会读取到不完整的数据;或者两个进程同时写入,可能会导致数据覆盖混乱。所以需要额外的机制(管道、信号量等)来保证数据的完整性和一致性。
  2. 进程间协同机制:是确保多个进程在访问公共资源时能够正确地同步、互斥以及协调彼此的操作。协调彼此的操作则涉及更复杂的交互,例如一个进程等待另一个进程完成特定任务后再继续执行。

管道在操作系统中自带协同机制。如:管道的读写操作具有原子性,一次读写要么全部完成,要么全部失败,保证了数据的完整性。同时,阻塞机制也起到了协同的作用,当缓冲区满时,写操作被阻塞,防止数据溢出;当缓冲区为空时,读操作被阻塞,在一定程度上实现了同步和互斥的效果。


2.8 管道和共享内存的比较(为什么共享内存是最快的)

我们先来看看管道通信:

从这张图可以看出,使用管道通信的方式,将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作:

  1. 服务端将信息从输入文件复制到服务端的临时缓冲区中。
  2. 将服务端临时缓冲区的信息复制到管道中。
  3. 客户端将信息从管道复制到客户端的缓冲区中。
  4. 将客户端临时缓冲区的信息复制到输出文件中。

我们再来看看共享内存通信 

从这张图可以看出,使用共享内存进行通信,将一个文件从一个进程传输到另一个进程只需要进行两次拷贝操作:

  1. 从输入文件到共享内存。
  2. 从共享内存到输出文件。

所以共享内存是所有进程间通信方式中最快的一种通信方式,因为该通信方式需要进行的拷贝次数最少。


3 消息队列的原理与概念


        1.消息队列:一种进程间通信(IPC)的机制,允许多个进程通过发送和接收带有类型的数据块(消息)进行通信,这些消息在队列中按照先进先出(FIFO)的顺序存储。


发送进程将消息添加到队列的末尾,接收进程从队列的头部读取信息。

注:消息队列的生命周期随内核,即:System V IPC资源的生命周期随内核!!!

        2.特点
支持异步通信:是指在进行数据传输时,发送方和接收方无需同步,即:它们可以独立的工作,彼此之间无需等待对方的回应。如:发送方发送消息后继续执行,无需等待接收方的回应,接收方可以在任何时间接收消息,同时会触发一个事件来通知发送方,从而达到异步通信的目的。

提供了可靠的消息传递机制,确保了数据不会丢失。如:即使接收方暂时无法处理消息,消息也会保存到队列中,直到接收方读取成功。

灵活的消息格式:消息队列中的消息可以包含不同类型的数据,如:文本、二进制等。

        3.基本组件:每个消息队列都有唯一的标识符(msgqid)、消息队列中每个消息都包含一个类型字段和数据字段。
消息中的类型字段可以用于标识消息的类型,以便接收进程可以根据类型来筛选和处理消息,而数据字段则包含实际要传递的数据。

        4.发送方和接收方通过使用相同的key值来创建或获取消息队列,它们就可以访问到同一个消息队列,从而实现进程间通信。消息队列特别适用于异步消息传递和任务队列等场景。


4 信号量

 

4.1 储备知识

  1.在多执行流场景下,共享资源可能同时被多个执行流尝试访问、修改,如果不加以保护,这可能会导致数据不一致、资源竞争和死锁等问题。
常见的保护机制主要包括同步和互斥机制。同步机制确保执行流按照预定的顺序进行交互,互斥机制确保共享资源在任何一个时刻只能被一个执行流访问。

  2.临界资源:被保护起来且任何时刻只允许一个执行流访问的公共资源,称为临界资源。eg:一个全局变量在多个线程同时读写时,如果不加以保护,可能会出现数据错误,这个全局变量就称为临界资源。

  3.临界区:访问临界资源的代码称为临界区。 非临界区:除临界区之外的代码称为非临界区。

程序员需要特别关注和保护临界区,以确保在任何时刻只有一个执行流,能够进入临界区访问临界资源,以防止其他执行流同时访问。

  4.保护临界资源,本质是保护临界区,确保在任何时刻只有一个执行流能够访问临界区,从而保护数据的一致性和正确性。

  5.原子性:一个操作被认为是原子的,此操作要么完全执行成功,要么完全不执行,不存在中间状态,即:此操作不可分割(一旦开始执行,它必须连续执行完成,中途不能被打断),不能被其他执行流中断。

在并发编程中,原子性用于确保多个线程或进程对共享资源进行操作,不会导致数据不一致、不确定的结果。

4.2 原理与概念

信号量机制本质是对于资源的预订操作,线程或者进程预订了之后,确保未来有一段时间,资源是属于我的。

对于预订资源,会有一个最小单位,资源都是以这个最小单位为整体被使用的。

信号量需要做到:

  1. 限制进来的进程数(保证每一个进来请求使用资源的进程都有一块资源)
  2. 合理的分配资源

这里,由于是信号量的前导,我们简单的把信号量理解为一个计数器(是由OS维护的)。

我们这里对于这个信号量的计数器的设计,提出几个问题?

1.计数器能不能简单的设计成一个整型变量?

不行,因为整型变量在经过进程创建之后,任意一个进程对他进行改变的时候,会发生写时拷贝,导致两个进程看到的不是同一个计数器,这样信号量的第一个目的,限制进入的进程数也就失效了。

2.count++和count--不是原子的。

3.申请sem和释放sem来保护临界资源,是规则。这个规则的由来?

这个规则就是,程序员之间规定的规则,再使用多进程访问临界资源的时候,需要代码这样来保护临界资源。

4.所有的进程要访问临界资源,都需要先申请信号量,那么所有进程都需要看到同一个信号量,说明了信号量本身就是一个临界资源。那么我们需要利用临界资源去保护另一个临界资源,为了防止临界资源保护的嵌套,我们就需要保证信号量这个临界资源是安全的。

所以,信号量的申请(++)和信号量的释放(--)这两个操作都是原子的

5.如果,信号量的初始值是1?

那么,这个信号量不就是一个二元信号量(不就是一把锁吗)

6.我们前面提到了信号量也需要合理的分配资源,那么由谁来做呢?

这里,也是由程序员,在代码部分来完成这项目标。

7.pv操作

我们把原子性的申请信号量称为p操作,原子性的释放信号量称为v操作。

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

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

相关文章

多线程运行时,JVM(Java虚拟机)的内存模型

在多线程运行时,JVM(Java虚拟机)的内存模型主要涉及以下几个方面: 1. 主内存和工作内存 JVM内存模型定义了主内存和工作内存的概念。主内存是所有线程共享的内存区域,而工作内存是每个线程私有的内存区域。线程对变量…

Rust : 生成日历管理markdown文件的小工具

需求: 拟生成以下markdown管理小工具,这也是我日常工作日程表。 可以输入任意时间段,运行后就可以生成以上的markdown文件。 一、toml [package] name "rust-workfile" version "0.1.0" edition "2021"[d…

记一次 Golang pkg 性能提升 38147125738.8 倍之旅

我正在使用 linux-open-ports 项目来获取系统上当前打开的端口列表。不过我注意到实现速度比我预期的要慢一点,所以我开始调查、分析、修改并使整个过程,使其性能提升了 38147125738.8 倍。下面我对整个过程做一个详细叙述。 1 基线 Go 提供了通过基准测…

mean,median,mode,var,std,min,max函数

剩余的函数都放在这篇里面吧 m e a n mean mean函数可以求平均值 a a a为向量时, m e a n ( a ) mean(a) mean(a)求向量中元素的平均值 a a a为矩阵时, m e a n ( a , 1 ) mean(a,1) mean(a,1)求矩阵中各列元素的平均值; m e a n ( a , 2 )…

带Burst AOT Settings移植问题

报错 burst问题 Burst AOT Settings 是 Unity 的 Burst Compiler 的一部分,用于预编译程序集(AOT,Ahead-Of-Time Compilation),以便在不支持 JIT(即时编译)的平台上运行,例如 iOS 和…

Android studio 签名加固后的apk文件

Android studio打包时,可以选择签名类型v1和v2,但是在经过加固后,签名就不在了,或者只有v1签名,这样是不安全的。 操作流程: 1、Android studio 对项目进行打包,生成有签名的apk文件&#xff…

数据结构—队列

目录 一、队列的定义 二、队列的顺序储存结构 2.1顺序队列的定义 2.2循环队列定义 2.3循环队列的基本操作 三、队列的链式储存结构 3.1链队列的定义 3.2链队列的基本操作 一、队列的定义 队列是一种线性表,其特殊性在于队列的基本操作是线性表的子表。队列…

【计算机网络】实验2:总线型以太网的特性

实验 2:总线型以太网的特性 一、 实验目的 加深对MAC地址,IP地址,ARP协议的理解。 了解总线型以太网的特性(广播,竞争总线,冲突)。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实…

PHP RabbitMQ连接超时问题

问题背景 Error: The connection timed out after 3 sec while awaiting incoming data 看到这个报错,我不以为意,认为是我设置的超时时间不够导致的,那就设置长一点 Error: The connection timed out after 300 sec while awaiting incom…

asp.net core过滤器应用

筛选器类型 授权筛选器 授权过滤器是过滤器管道的第一个被执行的过滤器,用于系统授权。一般不会编写自定义的授权过滤器,而是配置授权策略或编写自定义授权策略。简单举个例子。 using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCo…

缓存与数据库数据一致性 详解

缓存与数据库数据一致性详解 在分布式系统中,缓存(如 Redis、Memcached)与数据库(如 MySQL、PostgreSQL)一起使用是提高系统性能的常用方法。然而,缓存与数据库可能因更新时序、操作失误等原因出现数据不一…

Linux DNS解释器

作用 DNS(Domain Name System)是互联网上的一项服务,用于将域名和IP地址进行相互映射,使人 更方便的访问互联网 正向解析:域名->IP 反向解析:IP->域名 连接方式 DNS使用53端口监听网络 查看方法&a…

3.STM32通信接口之SPI通信---SPI实战(W25Q64存储模块介绍)《精讲》

上一节介绍了SPI的通信过程和方法,接下来就要进行STM32与外围模块通信了,这个模块是一块非易失型存储芯片,能够提供8MB的存储空间。接下来跟着Whappy脚步,进行探索新大陆吧!【免费】W25Q64(中英文数据手册)资源-CSDN文…

嵌入式系统应用-LVGL的应用-平衡球游戏 part2

平衡球游戏 part2 4 mpu60504.1 mpu6050 介绍4.2 电路图4.3 驱动代码编写 5 游戏界面移植5.1 移植源文件5.2 添加头文件 6 参数移植6.1 4 mpu6050 4.1 mpu6050 介绍 MPU6050是一款由InvenSense公司生产的加速度计和陀螺仪传感器,广泛应用于消费电子、机器人等领域…

java将word docx pdf转换为图片(不需要额外下载压缩包,直接导入maven坐标)

(本代码实现的是将第1页转为图片,主要用于制作文件缩略图) pdf转图片容易 docx转图片麻烦,看其他博客可以直接导入maven坐标,但我知道那是需要付费且有时限的包 本着简单实用的心,我找到法子了 pdf转图片:有库直接转…

C#学写了一个程序记录日志的方法(Log类)

1.错误和警告信息单独生产文本进行记录; 2.日志到一定内存阈值可以打包压缩,单独存储起来,修改字段MaxLogFileSizeForCompress的值即可; 3.Log类调用举例:Log.Txt(JB.信息,“日志记录内容”,"通道1"); usi…

Qt—QLineEdit 使用总结

文章参考:Qt—QLineEdit 使用总结 一、简述 QLineEdit是一个单行文本编辑控件。 使用者可以通过很多函数,输入和编辑单行文本,比如撤销、恢复、剪切、粘贴以及拖放等。 通过改变 QLineEdit 的 echoMode() ,可以设置其属性,比如以密码的形式输入。 文本的长度可以由 m…

linux(centos) 环境部署,安装JDK,docker(mysql, redis,nginx,minio,nacos)

目录 1.安装JDK (非docker)1.1 将文件放在目录下: /usr/local/jdk1.2 解压至当前目录1.3 配置环境变量 2.安装docker2.1 验证centos内核2.2 安装软件工具包2.3 设置yum源2.4 查看仓库中所有docker版本,按需选择安装2.5 安装docker2.6 启动docker 并 开机…

电阻改善信号完整性

1.为什么电路端接电阻能改善信号完整性 由于电信号在PCB上传输,因此在PCB设计中可以把PCB走线认为是信号的通道,当该通道的 物理结构(线宽,线到参考面的距离等)发生变化,特别是有一些突变时,都会…

Java基础面试题,46道Java基础八股文(4.8万字,30+手绘图)

Java是一种广泛使用的编程语言,由Sun Microsystems(现为Oracle Corporation的一部分)在1995年首次发布。它是一种面向对象的语言,这意味着它支持通过类和对象的概念来构造程序。 Java设计有一个核心理念:“编写一次&am…