【Linux】进程通信——共享内存+消息队列+信号量

在这里插入图片描述

欢迎来到Cefler的博客
博客主页:折纸花满衣
个人专栏:题目解析
推荐文章:【LeetCode】winter vacation training

【Linux】进程通信——共享内存+消息队列+信号量_第1张图片


目录

  • 共享内存
  • 关于共享内存的接口函数
  • ipcs命令
  • ipcrm命令
  • 共享内存实现进程间通信代码示例
  • 消息队列
    • 概念
    • 进程之间的消息队列通信
  • 信号量
    • semget,semop,semctl
  • IPC

共享内存

共享内存(Shared Memory)是一种进程间通信(IPC)的机制,用于在不同进程之间共享数据。

在共享内存中,多个进程可以将相同的内存区域映射到它们各自的地址空间中这意味着这些进程可以直接访问相同的物理内存位置,而无需进行复制或通过消息传递来传递数据。这种直接的内存共享方式使得进程间的数据交换更加高效和快速。

在使用共享内存进行进程间通信时,需要注意以下几点

  1. 同步机制:由于多个进程可以同时访问共享内存,因此需要使用同步机制(如信号量、互斥锁)来协调进程之间对共享内存的访问,避免数据冲突和竞争条件。
  2. 内存管理:共享内存需要由操作系统进行管理和分配。在创建共享内存时,需要申请足够的内存空间,并在使用完毕后及时释放,以防止资源泄漏。
  3. 数据一致性:由于多个进程可以同时访问共享内存,需要确保数据的一致性。可以使用同步机制来保证数据的正确读写顺序,或者通过采用特定的算法和协议来处理并发访问产生的冲突。
    【Linux】进程通信——共享内存+消息队列+信号量_第2张图片
    共享内存的优势在于它可以实现高效的数据共享,避免了进程间频繁的数据拷贝;同时,由于数据直接存储在物理内存中,共享内存的访问速度也比其他进程间通信方式更快。
    共享内存就是在物理内中开辟好内存空间,将地址映射到页表中,再映射到进程地址空间中。

共享内存比管道通信快的原因主要有以下几个方面

  1. 数据传输方式:在共享内存中,数据直接存储在物理内存中,不需要经过内核空间,进程可以直接读写内存,因此速度较快。而在管道通信中,数据需要在内核空间和用户空间之间进行来回拷贝,增加了额外的开销和延迟。

  2. 数据复制次数:在共享内存中,数据只需要被复制到共享内存区域一次,之后各个进程可以直接访问这块内存,而在管道通信中,每次数据传输都需要进行一次复制,这增加了额外的开销。

  3. 同步机制开销:在管道通信中,由于数据传输是基于文件描述符的读写操作,需要使用系统调用来进行读写和同步,而共享内存的访问是直接基于内存地址的,避免了频繁的系统调用,因此同步机制的开销较小。

关于共享内存的接口函数

共享内存是一种高效的进程间通信方式,Linux系统提供了一系列函数用于进行共享内存的创建、映射、卸载和控制。下面分别介绍这些函数的作用:

  1. shmget函数

shmget函数用于创建或打开一个共享内存段。它的原型为:

#include 
#include 

int shmget(key_t key, size_t size, int shmflg);

其中,参数key是一个键值,用于标识共享内存段,size是共享内存段的大小,shmflg是一个位掩码,用于指定创建共享内存段时的权限和选项。函数返回值是一个共享内存的标识符,如果出错则返回-1。

以下是shmflg的选项:
shmflg是在使用System V共享内存函数时用于设置标志的参数。它是一个整数值,可以通过按位或(|)运算将多个选项组合在一起。

  1. IPC_CREAT:如果指定此选项,当共享内存不存在时将创建一个新的共享内存对象。如果共享内存已经存在,则忽略该选项。通常与IPC_EXCL选项结合使用。
  2. IPC_EXCL:如果指定此选项,只有在共享内存不存在时才创建新的共享内存对象(保证返回的共享内存内容一定是全新的)。如果共享内存已经存在,则返回错误。通常与IPC_CREAT选项结合使用
  3. SHM_RDONLY:以只读模式打开共享内存对象,只能读取共享内存中的数据,不能修改。
  4. SHM_RSHM_WSHM_RW:表示共享内存的权限。SHM_R表示只读权限,SHM_W表示可写权限,SHM_RW表示可读可写权限。
  5. SHM_LOCKSHM_UNLOCK:用于对共享内存进行锁定和解锁操作。锁定共享内存可以防止其被交换到磁盘上,提高访问效率。
  6. SHM_HUGETLB:使用大页面分配共享内存,可以提高性能和效率。

2 shmat函数

shmat函数用于将共享内存段映射到进程的地址空间中。它的原型为:

#include 

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

其中,参数shmid是共享内存的标识符,shmaddr是指定的映射地址,如果为NULL,则由系统自动分配一个地址;shmflg是一个位掩码,用于指定映射共享内存时的选项。函数返回值是映射后的共享内存地址,如果出错则返回-1

  1. shmdt函数

shmdt函数用于将共享内存段从进程的地址空间中卸载。它的原型为:

#include 

int shmdt(const void *shmaddr);

其中,参数shmaddr是共享内存的地址,即shmat函数的返回值。函数返回值为0表示成功,如果出错则返回-1。

  1. shmctl函数

shmctl函数用于对共享内存进行控制操作,例如删除共享内存段等。它的原型为:

#include 

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

其中,参数shmid是共享内存的标识符,cmd是一个命令码,用于指定要执行的操作,buf是一个指向共享内存信息结构体的指针,用于传递相关参数和返回信息。函数返回值为0表示成功,如果出错则返回-1。

其中cmd参数表示要执行的操作类型,具体选项如下

  • IPC_STAT:获取共享内存的状态信息,并将其写入到共享内存区域的shmid_ds结构中。
  • IPC_SET:设置共享内存的状态信息,使用shmid_ds结构中的值对共享内存进行更新。
  • IPC_RMID:删除共享内存标识符所代表的共享内存段。如果在删除之前有进程仍然附加到该共享内存段,则它不会被删除,直到最后一个进程离开后才会被删除。
  • SHM_LOCK:锁定共享内存区域,防止被交换到磁盘上。
  • SHM_UNLOCK:解锁共享内存区域。
  • SHM_STAT:获取共享内存的状态信息,并将其写入到共享内存区域的shmid_ds结构中。与IPC_STAT功能相同。
  • SHM_INFO:获取系统中共享内存的统计信息,并将其写入到shminfo结构中。
  • SHM_STAT_ANY:与SHM_STAT功能相同,但不需要对共享内存区域具有所有者或创建者权限。

这些选项可用于对共享内存进行各种操作,例如获取共享内存的状态信息、设置共享内存的状态信息、删除共享内存、锁定共享内存区域等。具体使用哪个选项取决于需要执行的操作类型。

5.fotk函数
ftok函数是一个用于生成System V IPC(Inter-Process Communication,进程间通信)中的键值的函数。它的原型为:

key_t ftok(const char *pathname, int proj_id);

该函数接受一个路径名和一个项目ID作为参数,并返回一个用于标识System V IPC资源的键值

路径名指定了一个现有的文件的路径,通常选择一个存在于文件系统中的文件。该文件用于关联一个唯一的键值,以便不同进程可以通过该键值来访问同一个System V IPC资源。如果路径名对应的文件不存在或不可访问,ftok函数将返回-1。

项目ID是一个用户定义的整数,用于进一步区分不同的System V IPC资源。在使用ftok函数时,应确保不同的资源具有不同的项目ID,以避免冲突。

ftok函数根据路径名和项目ID计算出一个32位的键值,并返回该键值。该键值可用于创建共享内存、消息队列和信号量等System V IPC对象。

ipcs命令

ipcs命令是用于查看和管理System V IPC(Inter-Process Communication,进程间通信)对象的工具。它可以列出系统中当前存在的共享内存、消息队列和信号量等IPC对象的信息。

ipcs命令的基本语法如下:

ipcs [options]

常用的选项包括:

  • -m:列出所有共享内存对象的信息。
  • -q:列出所有消息队列的信息。
  • -s:列出所有信号量的信息。
  • -a:列出所有IPC对象(共享内存、消息队列和信号量)的信息。
  • -p:显示与每个IPC对象关联的进程ID和进程所有者的信息。
  • -l:显示System V IPC的限制和参数信息。

ipcs命令输出的信息包括对象的ID、键值、所有者、权限、大小等。通过查看ipcs的输出,可以了解系统中当前存在的IPC对象的状态、使用情况和其他相关信息。

对于管理员或有足够权限的用户,ipcs命令还可以用于删除或清理无用的IPC对象。例如,可以使用ipcrm命令结合ipcs的输出来删除某个特定的IPC对象。

ipcrm命令

ipcrm命令用于删除System V IPC(Inter-Process Communication,进程间通信)对象,包括共享内存、消息队列和信号量等。

ipcrm命令的基本语法如下:

ipcrm [options] <shmid>

其中,是要删除的IPC对象的标识符。标识符可以是IPC对象的ID、键值或名称,具体取决于IPC对象的类型。

常用的选项包括:

  • -m :删除指定ID的共享内存对象。
  • -q :删除指定ID的消息队列。
  • -s :删除指定ID的信号量。

需要注意的是,使用ipcrm命令删除IPC对象需要具有足够的权限。通常只有管理员或创建IPC对象的用户才能删除它们。

在使用ipcs命令查看IPC对象信息后,可以得到IPC对象的ID,然后使用ipcrm命令删除指定的IPC对象。例如,通过以下命令可以删除一个共享内存对象:

ipcrm -m <ID>

请注意,在删除IPC对象之前,请确保没有进程正在使用该对象,否则可能会导致意外行为或数据丢失。

共享内存实现进程间通信代码示例

以下是一个使用C++语言实现的简单进程间通信的例子,使用共享内存:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SHM_SIZE 1024  // 共享内存大小

int main() {
    int shmid;
    char *shmaddr;
    key_t key = ftok(".", 'a');  // 生成key值

    // 创建共享内存
    if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT|0666)) == -1) {
        perror("shmget error");
        exit(1);
    }

    // 将共享内存映射到进程地址空间
    if ((shmaddr = (char *)shmat(shmid, NULL, 0)) == (char *)-1) {
        perror("shmat error");
        exit(1);
    }

    // 写入数据到共享内存
    std::string message = "Hello, shared memory!";
    strncpy(shmaddr, message.c_str(), SHM_SIZE);

    // 创建子进程
    pid_t pid = fork();

    if (pid < 0) {  // fork失败
        perror("fork error");
        exit(1);
    } else if (pid == 0) {  // 子进程
        // 从共享内存中读取数据
        std::cout << "Child get message: " << shmaddr << std::endl;

        // 解除共享内存映射
        if (shmdt(shmaddr) == -1) {
            perror("shmdt error");
            exit(1);
        }
    } else {  // 父进程
        // 等待子进程结束
        wait(NULL);

        // 解除共享内存映射
        if (shmdt(shmaddr) == -1) {
            perror("shmdt error");
            exit(1);
        }

        // 删除共享内存
        if (shmctl(shmid, IPC_RMID, NULL) == -1) {
            perror("shmctl error");
            exit(1);
        }
    }

    return 0;
}

消息队列

概念

消息队列是一种在分布式系统中常用的通信模式,它允许应用程序之间通过异步方式传递消息。简单来说,消息队列就是一个存储消息的容器,生产者将消息发送到队列中,而消费者则从队列中获取消息进行处理。

消息队列的设计基于生产者-消费者模型。生产者负责产生消息并将其发送到队列中,而消费者则从队列中读取消息并进行相应的处理。这种模型解耦了消息的发送方和接收方,使得系统更加灵活、可扩展和可靠。

使用消息队列有多个优点。首先,它实现了异步通信,生产者和消费者之间不需要实时交互,可以独立进行处理。这对于处理高并发和处理延迟敏感任务非常有帮助。

其次,消息队列提供了解耦的能力。生产者只需将消息发送到队列中,而不需要关心具体的消费者是谁以及何时处理消息。消费者可以根据自己的需求从队列中获取消息,并且可以根据处理速度调整消费的速率

此外,消息队列还具备削峰填谷的功能。当系统面临突发的请求压力时,消息队列可以缓冲请求,并根据系统的处理能力逐渐消化积压的消息,避免系统过载。

常见的消息队列系统有 RabbitMQ、Apache Kafka、ActiveMQ 等。它们提供了丰富的功能和灵活的配置选项,可以根据不同的场景选择合适的消息队列系统。

总结起来,消息队列是一种重要的分布式通信机制,通过解耦、异步和削峰填谷等特性,它能够帮助构建高性能、可扩展和可靠的系统。在现代应用开发中,消息队列被广泛应用于日志处理、任务调度、实时消息推送等各种场景。

进程之间的消息队列通信

在进程之间,可以使用消息队列实现通信。每个进程都可以创建一个或多个消息队列,用来存放发送和接收的消息。

在 Linux 系统中,系统调用 msgget() 可以用来创建或获取一个消息队列的标识符。创建消息队列时需要指定一些参数,如消息队列的权限、大小等。创建完成后,进程可以使用 msgsnd() 向消息队列中添加消息,使用 msgrcv() 从消息队列中读取消息。

在发送消息时,需要指定消息类型、消息正文和消息长度。消息类型是一个正整数,用于区分不同的消息。当接收消息时,也需要指定要接收的消息类型。如果消息队列中有匹配的消息,则将其从队列中删除并返回给接收进程。

使用消息队列进行进程间通信的好处是它允许异步通信,并且可以通过消息队列来解耦发送方和接收方的耦合关系。同时,由于消息队列是基于内核实现的,因此可以跨越不同的进程,甚至可以跨越不同的机器。

但是使用消息队列进行进程间通信也存在一些问题。例如,消息队列的容量有限,如果消息队列已满,则无法再向其中添加消息。此外,消息队列的消息类型只能是正整数,这种限制可能无法满足某些特定的需求。


msgget
msgget 是一个系统调用函数,用于创建一个消息队列或获取一个已存在的消息队列的标识符。

函数原型如下:

#include 
#include 
#include 

int msgget(key_t key, int msgflg);

参数说明:

  • key:消息队列的键值,可以使用 ftok() 函数生成。这个键值在系统中必须是唯一的,不同进程通过相同的键值可以访问同一个消息队列。
  • msgflg:消息队列的标志位,用于指定消息队列的创建和访问权限。常见的标志位包括 IPC_CREAT(创建消息队列)和 IPC_EXCL(与 IPC_CREAT 配合使用,如果消息队列已经存在则报错)。

返回值:

  • 成功时,返回消息队列的标识符(非负整数),可以用于后续操作;
  • 失败时,返回 -1,并设置 errno 错误码。

以下是一个示例代码,演示如何使用 msgget 创建或获取一个消息队列:

#include 
#include 
#include 
#include 
#include 

int main() {
    key_t key;
    int msgid;

    // 生成一个消息队列的键值
    key = ftok("path/to/a/file", 'A');
    if (key == -1) {
        perror("ftok");
        exit(1);
    }

    // 创建或获取消息队列
    msgid = msgget(key, IPC_CREAT | 0666);
    if (msgid == -1) {
        perror("msgget");
        exit(1);
    }

    printf("Message queue created or obtained with ID: %d\n", msgid);

    return 0;
}

在上述示例中,我们通过 ftok() 函数生成一个键值,该键值与指定的文件路径和标识字符 ‘A’ 相关联。然后使用 msgget 创建或获取一个消息队列,并将其标识符存储在 msgid 变量中。最后,打印出消息队列的标识符。

需要注意的是,在实际应用中,需要确保不同进程使用相同的键值来创建或获取同一个消息队列

信号量

信号量是一种用于进程间同步和互斥的机制。它主要用于解决多个进程或线程之间共享资源的并发访问问题。

由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种
关系为进程的互斥

信号量的基本原理是在共享资源上设置一个计数器用于记录可用资源的数量。当进程需要使用共享资源时,首先检查信号量的计数器。如果计数器大于零,则表示有可用的资源,进程可以继续执行并将计数器减一。如果计数器为零,则表示没有可用的资源,进程需要等待,直到其他进程释放资源并增加计数器。

在 Linux 系统中,信号量通常使用 semget()semop()semctl() 等系统调用函数来创建、操作和控制。

  • semget():用于创建或获取一个信号量集的标识符。
  • semop():用于对信号量进行操作,如增加、减少等。
  • semctl():用于控制信号量,如获取当前计数器的值、设置计数器的值、删除信号量等。

信号量的操作主要包括 P 操作(等待信号量)和 V 操作(释放信号量):

  • P 操作(等待信号量):如果信号量的计数器大于零,则将计数器减一,进程继续执行;否则,进程进入等待状态。
  • V 操作(释放信号量):将信号量的计数器加一,唤醒等待该信号量的其他进程。

通过合理地使用 P 操作和 V 操作,可以实现对共享资源的互斥访问和进程间的同步。

以下是一个示例代码,演示如何使用信号量进行进程间互斥访问:

#include 
#include 
#include 
#include 
#include 

#define SEM_KEY 1234

void P(int semid) {
    struct sembuf sb;
    sb.sem_num = 0;  // 信号量集中的第一个信号量
    sb.sem_op = -1;  // 执行 P 操作
    sb.sem_flg = 0;
    semop(semid, &sb, 1);
}

void V(int semid) {
    struct sembuf sb;
    sb.sem_num = 0;  // 信号量集中的第一个信号量
    sb.sem_op = 1;  // 执行 V 操作
    sb.sem_flg = 0;
    semop(semid, &sb, 1);
}

int main() {
    key_t key;
    int semid;

    // 创建或获取信号量集的标识符
    key = ftok("path/to/a/file", SEM_KEY);
    if (key == -1) {
        perror("ftok");
        exit(1);
    }
    semid = semget(key, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        exit(1);
    }

    // 对信号量进行操作
    P(semid);  // 进入临界区,执行互斥操作
    printf("Critical section\n");
    V(semid);  // 离开临界区,释放资源

    // 删除信号量集
    semctl(semid, 0, IPC_RMID);

    return 0;
}

semget,semop,semctl

semget、semop和semctl是Linux系统中用于控制信号量的三个关键函数。

  1. semget:该函数用于创建或获取一个信号量集。其原型如下:

    #include 
    #include 
    #include 
    
    int semget(key_t key, int nsems, int semflg);
    
    • key:用于标识信号量集的键值,通常通过ftok函数生成。
    • nsems:指定需要创建的信号量集中的信号量数量。
    • semflg:设置信号量集的访问权限和其他标志位。
  2. semop:该函数用于对信号量进行操作,例如等待信号量、释放信号量等。其原型如下:

    #include 
    #include 
    #include 
    
    int semop(int semid, struct sembuf *sops, size_t nsops);
    
    • semid:信号量集的标识符,由semget函数返回。
    • sops:指向一个sembuf结构体数组,每个sembuf结构体表示对一个信号量的操作。
    • nsops:指定sops数组中sembuf结构体的数量。
  3. semctl:该函数用于对信号量进行控制操作,例如获取信号量的值、设置信号量的值等。其原型如下:

    #include 
    #include 
    #include 
    
    int semctl(int semid, int semnum, int cmd, ...);
    
    • semid:信号量集的标识符,由semget函数返回。
    • semnum:指定操作的信号量在信号量集中的索引,一般为0表示第一个信号量。
    • cmd:指定要执行的控制命令。
    • …:可选参数,根据不同的控制命令可能需要提供额外参数。

这些函数允许进程间通过信号量进行同步与互斥操作,确保资源的正确共享和访问。通过semget函数创建或获取信号量集,使用semop函数进行信号量的操作,使用semctl函数进行信号量的控制。


semctl函数的cmd选项
semctl函数的cmd选项用于指定对信号量进行的控制命令。下面是一些常用的cmd选项:

  • IPC_RMID:删除信号量集。该命令将删除指定的信号量集以及相关的数据结构。

    semctl(semid, semnum, IPC_RMID);
    
  • SETVAL:设置信号量的值。该命令将指定的信号量设置为给定的值。

    union semun {
      int val;               // value for SETVAL
      struct semid_ds *buf;  // buffer for IPC_STAT, IPC_SET
      unsigned short *array; // array for GETALL, SETALL
      struct seminfo *__buf; // buffer for IPC_INFO (Linux-specific)
    };
    
    union semun arg;
    arg.val = 5; // 设置信号量的值为5
    semctl(semid, semnum, SETVAL, arg);
    
  • GETVAL:获取信号量的值。该命令将返回指定信号量的当前值。

    int value = semctl(semid, semnum, GETVAL);
    
  • SETALL:设置所有信号量的值。该命令将指定信号量集中所有信号量的值设置为给定的数组。

    unsigned short values[3] = {2, 4, 6};
    semctl(semid, 0, SETALL, values);
    
  • GETALL:获取所有信号量的值。该命令将返回指定信号量集中所有信号量的当前值。

    unsigned short values[3];
    semctl(semid, 0, GETALL, values);
    
  • IPC_STAT:获取信号量集的状态信息。该命令将返回一个semid_ds结构体,包含有关信号量集的详细信息。

    struct semid_ds buf;
    semctl(semid, 0, IPC_STAT, &buf);
    

IPC

IPC(Inter-Process Communication)资源是指在操作系统中用于进程间通信的各种资源。它们是为了满足不同进程之间的信息交换、同步和互斥等需求而提供的机制和数据结构

常见的IPC资源包括:

  1. 信号量(Semaphore):用于多个进程或线程之间进行同步和互斥操作的计数器。

  2. 共享内存(Shared Memory):允许多个进程共享同一块物理内存区域,使得进程可以直接读写该内存区域,实现高效的数据交换。

  3. 消息队列(Message Queue):进程通过将消息发送到队列中,实现进程间的异步通信。

  4. 管道(Pipe):一种半双工的通信方式,可以在两个相关进程之间传输数据。

  5. 套接字(Socket):用于在网络中进行进程间通信的接口。

  6. 信号(Signal):用于通知进程发生了某个事件,例如中断信号、异常信号等。

这些IPC资源提供了不同的通信方式和机制,可以满足不同场景下进程间通信的需求。通过使用这些资源,进程可以进行数据的共享、同步操作、消息的传递以及事件的通知等,从而实现了进程间的有效交互和协作。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长

【Linux】进程通信——共享内存+消息队列+信号量_第3张图片
在这里插入图片描述

你可能感兴趣的:(Linux,linux,共享内存,消息队列,信号量)