【Linux】System V 共享内存、消息队列、信号量

这篇具有很好参考价值的文章主要介绍了【Linux】System V 共享内存、消息队列、信号量。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🍎作者:阿润菜菜
📖专栏:Linux系统编程


system V共享内存介绍

  1. System V 共享内存是一种进程间通信的机制,它允许多个进程共享一块物理内存区域(称为“段”)。System V 共享内存的优点是效率高,因为进程之间不需要复制数据;缺点是需要进程之间进行同步,以避免数据的不一致性
  2. 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到
    内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

示意图:
【Linux】System V 共享内存、消息队列、信号量

理解共享内存IPC

  1. 进程凭什么独立?每个进程拥有自己独立的进程地址空间mm_struct,自己独立的映射的物理内存空间。进程独立性的实现主要依赖于操作系统和硬件的支持。操作系统通过为每个进程分配独立的虚拟地址空间,使得每个进程都有自己的代码和数据空间,这样不会被其他进程干扰。硬件通过内存管理单元(MMU)来实现虚拟地址到物理地址的映射,以及分页或分段的方式来划分内存空间。
  2. 操作系统还通过进程调度算法来控制每个进程的执行顺序和时间片,以及通过进程同步和通信机制来协调多个进程之间的关系。
  3. 实现进程间通信的第一个前提就是如何让不同的进程看到同一份资源,匿名管道我们是通过子进程继承父进程打开的资源,命名管道是通过两个进程都打开具有唯一性标识的命名管道文件,而共享内存其实是通过OS创建一块shm(共享内存块),然后通过MMU将shm的地址分别映射到两个进程的各自地址空间当中,那么两个进程就可以通过这份虚拟起始地址来进行进程间通信。
    在应用层也就是用户层,我们只能操作虚拟地址,但内核中会有MMU进行虚拟地址的映射,所以进程在IPC时,只需要操纵虚拟地址即可,从虚拟地址中读取或向虚拟地址中进行写入,这样就完成了共享内存式的IPC。
  4. 所以通过让不同的进程,看到同一份物理内存块的方式,就叫做共享内存!
  5. 为什么说共享内存是最快的IPC形式
    共享内存是一种进程间通信(IPC)的方式,它允许多个进程访问同一块逻辑内存从而实现数据的快速交换共享内存是最快的IPC形式,因为它避免了数据在进程间的复制,而是直接在内存中读写。要使用共享内存,需要用到一些函数,如shmget, shmat, shmdt, shmctl等。这些函数可以创建、映射、分离、控制共享内存段。共享内存的优点是高效和灵活,缺点是没有提供同步机制,需要借助其他手段来实现进程间的同步访问,而且共享内存没有任何保护机制 那管道呢?系统接口有封装。
    【Linux】System V 共享内存、消息队列、信号量

实现共享内存IPC

认识接口

命令查看 共享内存是否已经存在
ipcs -m 查看共享内存
ipcrm -m 用于删除共享内存 — 注意使用shmid进行删除 类比于文件描述符

System V 共享内存的API包括以下几个系统调用:

  • shmget(2):创建一个新的段或获取一个已存在的段的标识符(ID)。这个ID是用来在其他API中引用段的。
  • shmat(2):将一个已存在的段映射到调用进程的虚拟地址空间中。这样,进程就可以通过指针来访问共享内存中的数据。
  • shmdt(2):将一个段从调用进程的虚拟地址空间中解除映射。这样,进程就不能再访问共享内存中的数据。
  • shmctl(2):对一个段进行控制操作,例如修改它的权限、获取它的状态信息、删除它等。

1.shmget()函数是用来创建或打开一块共享内存的,它的原型是int shmget (key_t key, size_t size, int shmflg);

第一个参数key是一个非零整数,它为共享内存段提供一个外部名,可以用IPC_PRIVATE或ftok()函数生成。shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1。

第二个参数size是以字节为单位指定需要共享的内存容量。所有的内存分配操作都是以页为单位的,所以如果申请的内存大小不是页的整数倍,会被向上取整到最近的页大小。

第三个参数shmflg是一组标志位,它可以指定权限标志、创建标志和排他标志。权限标志与文件的读写权限一样,如0644表示允许创建者读写,其他用户只读。创建标志IPC_CREAT表示如果共享内存不存在,则创建一个新的共享内存,否则打开已有的共享内存。排他标志IPC_EXCL表示只有在共享内存不存在时,才创建新的共享内存,否则返回错误。

共享内存的大小是以4kb为单位的,这是巧合吗?是这样的(对应磁盘文件系统):这里第二个参数是共享内存的大小,一般建议将开辟的共享内存大小设置为4KB的整数倍,内存划分内存块的基本单位是Page,大小刚好是4KB,所以建议将大小设置为4KB的整数倍,如果你设置成4097什么的,有点浪费内存,因为实际内核会开辟8KB大小的空间。

2.shmat()函数用来将共享内存段连接到进程的地址空间,它的原型是void * shmat (int shmid, const void *shmaddr, int shmflg);

第一个参数shmid是由shmget()函数返回的共享内存标识符。第二个参数shmaddr指定共享内存连接到当前进程中的地址位置,通常为NULL,表示让系统来选择共享内存的地址。第三个参数shmflg是一组标志位,可以指定SHM_RDONLY表示共享内存只读,或者默认为0表示可读可写。调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1。

3.shmdt()函数用来将共享内存从当前进程中分离,它的原型是int shmdt (const void *shmaddr);

参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。

4.shmctl()函数用来控制共享内存的状态,它的原型是int shmctl (int shmid, int command, struct shmid_ds *buf);

第一个参数shmid是shmget()函数返回的共享内存标识符。第二个参数command是要采取的操作,可以取以下三个值:

IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。

IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值。

IPC_RMID:删除共享内存段。

第三个参数buf是一个结构指针,它指向共享内存模式和访问权限的结构。调用成功时返回0,失败时返回-1。

代码实现

下面是一个使用System V 共享内存的示例程序,它由两个部分组成:writer.c和reader.c。writer.c负责创建一个共享内存段,并向其中写入一些字符串;reader.c负责读取共享内存段中的字符串,并打印出来。

writer.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE 1024 // size of shared memory

int main() {
    int shmid; // shared memory ID
    key_t key; // key to locate shared memory
    char *shm; // pointer to shared memory

    // create a key using a file name and a char
    if ((key = ftok("writer.c", 'A')) == -1) {
        perror("ftok");
        exit(1);
    }

    // create a shared memory segment
    if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666)) == -1) {
        perror("shmget");
        exit(1);
    }

    // attach the shared memory segment to the process
    if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // write some strings to the shared memory
    strcpy(shm, "Hello, world!");
    shm += strlen("Hello, world!");
    strcpy(shm, "This is an example of System V shared memory.");
    shm += strlen("This is an example of System V shared memory.");
    strcpy(shm, "Goodbye!");

    // wait until reader finishes reading
    while (*shm != '*')
        sleep(1);

    // detach the shared memory segment from the process
    if (shmdt(shm) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

reader.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE 1024 // size of shared memory

int main() {
    int shmid; // shared memory ID
    key_t key; // key to locate shared memory
    char *shm; // pointer to shared memory
    char *s;   // pointer to traverse shared memory

    // create a key using a file name and a char
    if ((key = ftok("writer.c", 'A')) == -1) {
        perror("ftok");
        exit(1);
    }

    // get the shared memory segment
    if ((shmid = shmget(key, SHM_SIZE, 0)) == -1) {
        perror("shmget");
        exit(1);
    }

    // attach the shared memory segment to the process
    if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // read the strings from the shared memory
    s = shm;
    while (*s != '\0') {
        printf("%s\n", s);
        s += strlen(s) + 1;
    }

    // write a '*' to the shared memory to indicate reading is done
    *shm = '*';

    // detach the shared memory segment from the process
    if (shmdt(shm) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

为了运行这个示例程序,我们需要先编译writer.c和reader.c,然后先运行writer,再运行reader。运行结果如下:

$ gcc writer.c -o writer
$ gcc reader.c -o reader
$ ./writer &
[1] 1234
$ ./reader
Hello, world!
This is an example of System V shared memory.
Goodbye!
[1]+  Done                    ./writer
$

实现共享内存服务端和客户端的通信:查看我的代码仓库

补充知识 — 命令操作

ipcs 查看进程间通信资源/ipcrm 删除进程间通信资源

  -m 针对共享内存的操作

 -q	针对消息队列的操作
 
-s	针对信号量的操作

-a	针对所有资源的操作

深入理解

什么是同步与互斥

  • 同步是指协调多个进程的执行顺序,使得某些进程在执行某些操作之前必须等待其他进程完成一些操作。例如,一个进程要读取一个文件,必须等待另一个进程写入该文件。
  • 互斥是指保证多个进程对共享资源的访问不会发生冲突,使得某些资源在一个时间段内只能被一个进程使用。例如,一个进程要打印一份文档,必须等待打印机空闲。

OS中常用的同步与互斥机制有:

  • 信号量与PV操作:信号量是一个整数变量,表示可用资源的数量。PV操作是两个原子操作,用于对信号量进行加减操作。P操作表示申请一个资源,如果资源不足则阻塞;V操作表示释放一个资源,如果有等待的进程则唤醒。
  • 管程:管程是一种高级的同步机制,它是一种封装了共享数据和对数据操作的过程的数据结构。管程内部有一个互斥锁和若干条件变量,用于实现对共享数据的互斥访问和同步控制。

本节介绍互斥的四个相关概念 :

【Linux】System V 共享内存、消息队列、信号量

  1. 什么是临界资源?
  • 临界资源是指一次仅允许一个进程使用的共享资源,如打印机、磁带机等。临界资源需要互斥访问,即同一时间只能有一个进程访问,否则会导致数据不一致或资源冲突。
  • 临界区是指每个进程中访问临界资源的那段代码,如对打印机的操作。临界区需要设置进入区和退出区,以检查和控制对临界资源的访问。进入区要判断是否可以进入临界区,如果可以则设置标志或锁;退出区要释放标志或锁,以便其他进程可以进入。

2.什么是原子性?

  • 原子性是指一个操作或一组操作要么全部执行成功,要么全部不执行,不会被其他进程或中断打断。原子性是实现临界区互斥的一个重要条件,因为如果在进入区或退出区被打断,就会导致死锁或饥饿等问题。原子性可以通过硬件指令或软件方法来实现。

共享内存的内核数据结构

下面是OS给用户暴露的一部分shm的内核数据结构,因为OS要进行管理,所以实际在底层中其结构更为复杂,里面的key被封装到ipc_perm结构体里面,ipc_perm又被封装到shmid_ds{}结构体内部。

【Linux】System V 共享内存、消息队列、信号量
内核中的ipc_perm结构体是用来描述IPC对象的权限和所有者的
它的定义如下:

struct ipc_perm {
    key_t key; // 调用shmget()时给出的关键字
    uid_t uid; // 共享内存所有者的有效用户ID
    gid_t gid; // 共享内存所有者所属组的有效组ID
    uid_t cuid; // 共享内存创建者的有效用户ID
    gid_t cgid; // 共享内存创建者所属组的有效组ID
    unsigned short mode; // 权限 + SHM_DEST和SHM_LOCKED标志
    unsigned short seq; // 序列号
};

结构中的mode域类似于文件的stat结构的mode域,但是不可以有执行权限。mode值描述如下:

操作者 写(更改 更新)
用户 0400 0200
0040 0020
其他 0004 0002

IPC对象包括共享内存、消息队列和信号量,它们都是用来进行进程通信的一些资源。
IPC对象的创建、访问和控制都需要使用ipc_perm结构体中信息

IPC资源的组织方式(多态)

对于System V标准的IPC资源组织方式来说,资源的获取与释放操作,他们的接口相似度非常高,
所以OS要对这些同一标准的各个通信机制进行管理,他们都有各自的内核数据结构,但都非常的相似,OS系统可以通过数组的方式对这些System V标准的IPC资源进行管理。

结构体的第一个成员地址,在数字上和结构体对象本身的地址是相同的。虽然他们类型不同,但是地址的字面值是相同的,所以我们可以只存储这些内核数据结构的第一个字段的地址,用一个指针数组来进行存储,因为虽然这些IPC资源的内核数据结构不同,但是他们的第一个字段的类型都是相同的,都是struct ipc_perm,所以我们可以用指针数组来进行管理。
当要访问具体的某个IPC资源的内核数据结构时,我们可以将数组中的内容拿出来,将其强转成对应的IPC资源内核数据结构的类型,也就是转成结构体类型,那么此时这个指针指向的就不再是struct ipc_perm类型的结构体了,而是变为struct shmid_ds或struct semid_ds或struct msqid_ds这几种IPC内核数据结构类型的结构体,此时我们就可以具体的访问某个IPC资源了。
上面能够这么做的原因其实是因为,结构体的地址和结构体中第一个字段的地址 在字面值上是相同的,只是他们类型不同罢了,我们可以通过类型强转的方式,让指针指向不同的结构体。
3.
下面组织IPC资源的方式不就是多态吗?右边三个资源就是派生类,左边是存储基类指针的指针数组,基类指针指向哪个派生类结构体,就调用哪个派生类结构体里的方法成员,只不过在Linux这里是通过指针类型强转的方式来实现的。
【Linux】System V 共享内存、消息队列、信号量

共享内存的优点和缺点(管道和shm分别数据拷贝次数)

共享内存的优点:所有进程间通信中速度最快的,只要向shmat返回的虚拟地址写入数据,另一个进程直接就可以通过他自己的shmat返回的虚拟地址读取到共享内存中的数据,效率非常的高,因为共享内存能大大减少数据的拷贝次数

综合考虑管道和共享内存,考虑键盘输入和显示器输出,管道和共享内存分别有几次数据拷贝呢?如果细算的话其实是6次和4次,如果不细算的话是4次和2次。
有一种说法,喜欢把缓冲区分为内核缓冲区和程序缓冲区,程序缓冲区指的是语言级别你所能见到的所有能够存放数据的空间,这些都可以叫做程序缓冲区,是一种笼统的叫法。
管道由于要调用read和write接口,则必须定义buffer,在读端和写端分别都定义出一个buffer,实际数据会先从stdin到buffer里面,再从buffer到pipe的内核级缓冲区中,然后再从内核级缓冲区到读端的buffer中,最后再从读端的buffer拷贝到stdout的用户级缓冲区,这样算就是4次。
共享内存无须调用read或write接口,shmat会直接返回虚拟地址,所以只需将stdin的数据拷贝到虚拟地址里面,然后MMU会将虚拟地址进行映射,另一端的进程可以直接通过虚拟地址看到左边进程映射到shm的数据,所以另一端进程也只需要将虚拟地址的数据拷贝到stdout的缓冲区即可,这样算就是2次。
但我们知道键盘输入的缓冲区实际上是先到内核标准输入缓冲区中的,cin或scanf等标准输入都是从内核标准输入缓冲区中拿数据的。并且在输出时,printf或cout等标准输出其实是先将数据输出到内核标准输出缓冲区的,然后才是将数据输出到stdout也就是显示器文件内部的用户级缓冲区。所以如果把这两步考虑上,那么管道和共享内存将各自增加两次的数据拷贝。
【Linux】System V 共享内存、消息队列、信号量

认识信号量

现实生活中,我们在看电影之前,一定要先买票,看电影,看完走人。
在OS内部访问临界资源:一定要先申请信号量资源,在使用,然后释放信号量资源 。
信号量的本质是一个计数器,通常用来表示公共资源中,资源数量多少的问题。当访问没有保护的公共资源时,会产生数据不一致的问题,我们将被保护起来的公共资源称为临界资源,但大部分资源其实都是独立的。公共资源(内存,文件,网络等)都是要通过代码来进行访问的,这些代码我们称为临界区,其余未访问公共资源的代码称为非临界区。

当信号量为0时(count),OS挂起阻塞进程。那么是谁在申请信号量? 进程在申请!那前提就是 所有进程都要看到同一块信号量啊! 那就得是共享资源,必须保证自己的++ 操作 — 是原子性的

只要访问公共资源,我们就必须对公共资源进行保护。所有的进程在访问公共资源之前,都必须申请sem信号量,申请sem信号量不就需要先看到同一份sem信号量吗?那么其实sem信号量本身就是公共资源,所以信号量也必须保证自身操作的安全性,那么信号量的++或- -等操作也都必须得是原子性的,要么做成功,要么就回到最初状态
【Linux】System V 共享内存、消息队列、信号量

System V 消息队列

1.消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

2.每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

3.IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

内核也给我们提供了获取消息队列和控制消息队列的系统接口
【Linux】System V 共享内存、消息队列、信号量文章来源地址https://www.toymoban.com/news/detail-444833.html

到了这里,关于【Linux】System V 共享内存、消息队列、信号量的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux】进程间通信之共享内存/消息队列/信号量

    共享内存是通过让不同的进程看到同一个内存块的方式。 我们知道,每一个进程都会有对应的PCB-task_struct ,独立的进程地址空间,然后通过页表将地址映射到物理内存中。此时我们就可以让OS在内存中申请一块空间,然后将创建好的内存空间映射到进程的地址空间中,两个需

    2024年02月05日
    浏览(40)
  • Linux进程间通信【消息队列、信号量】

    ✨个人主页: 北 海 🎉所属专栏: Linux学习之旅 🎃操作环境: CentOS 7.6 阿里云远程服务器 在 System V 通信标准中,还有一种通信方式: 消息队列 ,以及一种实现互斥的工具: 信号量 ;随着时代的发展,这些陈旧的标准都已经较少使用了,但作为 IPC 中的经典知识,我们可

    2024年02月08日
    浏览(29)
  • linux中互斥锁,自旋锁,条件变量,信号量,与freeRTOS中的消息队列,信号量,互斥量,事件的区别

    对于目前主流的RTOS的任务,大部分都属于并发的线程。 因为MCU上的资源每个任务都是共享的,可以认为是单进程多线程模型。 【freertos】003-任务基础知识 在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变

    2024年02月11日
    浏览(28)
  • 【STM32】FreeRTOS消息队列和信号量学习

    一、消息队列(queue) 队列是一种用于实现任务与任务之间,任务与中断之间消息交流的机制。 注意:1.数据的操作是FIFO模式。 2.队列需要明确数据的大小和队列的长度。 3.写和读都会出现堵塞。 实验:创建一个消息队列,两个发送任务,一个接收任务。 其中任务一任务三

    2024年02月13日
    浏览(27)
  • 学习系统编程No.22【消息队列和信号量】

    北京时间:2023/4/20/7:48,闹钟6点和6点30,全部错过,根本起不来,可能是因为感冒还没好,睡不够吧!并且今天是星期四,这个星期这是第二篇博客,作为一个日更选手,少些了两篇博客,充分摆烂,但是摆烂具体也是有原因的,星期一的时候莫名高烧,头昏脑涨的感觉,睡

    2023年04月27日
    浏览(33)
  • linux(system V标准)信号量

    目录:             1.什么是信号量             2.信号量的本质 1.什么是信号量   2.信号量的本质  什么是临界资源呢?? 凡是倍多个执行流同时访问的资源就是临界资源!!! 我们看一个问题,我们fork()之后创建一个子进程,那么我们的全局变量,是不是我们父

    2024年02月07日
    浏览(29)
  • 【C++】Windows下共享内存加信号量实现进程间同步通信

    目录 一,函数清单 1.CreateFileMapping 方法 2.OpenFileMapping 方法 3.MapViewOfFile 方法 4.UnmapViewOfFile 方法 5.CreateSemaphore 方法 6. OpenSemaphore 方法 7.WaitForSingleObject 方法 8.ReleaseSemaphore 方法 9.CloseHandle 方法 10.GetLastError 方法 二,单共享内存单信号量-进程间单向通信 共享内存管理文

    2024年02月08日
    浏览(30)
  • 【Linux】进程间通信——System V信号量

    目录 写在前面的话 一些概念的理解 信号量的引入 信号量的概念及使用            System V信号量是一种较低级的IPC机制 ,使用的时候需要手动进行操作和同步。在现代操作系统中,更常用的是 POSIX信号量 (通过 sem_* 系列的函数进行操作)或更高级的同步原语(如互斥锁

    2024年02月11日
    浏览(35)
  • 【设计模式】C语言使用共享内存和信号量,完美实现生产者与消费者模式

    生产者和消费者模式适用于生产者和消费者之间存在数据交换的场景。在这种模式中,生产者负责生产数据并将其放入缓冲区,而消费者负责从缓冲区中取出数据并进行处理。这种模式的优点是可以实现生产者和消费者之间的解耦,使得它们可以独立地进行操作,从而提高了

    2024年02月03日
    浏览(28)
  • 【linux】POSIX信号量+基于环形队列的生产消费模型

    喜欢的点赞,收藏,关注一下把! 上篇文章最后我们基于BlockQueue生产者消费者模型写了代码,测试什么的都通过了。最后我们说代码还有一些不足的地方,由这些不足从而引入了我们接下来要学的信号量! 我们在看一看不足的地方 1.一个线程,在操作临界资源的时候,必须

    2024年02月01日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包