【Linux】进程间通信(3):共享内存

news2025/1/21 3:02:15

目录

一、共享内存概述

二、共享内存相关函数

1、shmget函数

 2、ftok函数

3、shmctl函数 

4、 shmat函数

5、 shdt函数

三、使用共享内存相关函数管理共享内存的一般过程 

1. 生成唯一的键值

2. 创建或获取共享内存段

3. 连接到共享内存段

4. 操作共享内存

5. 断开与共享内存段的连接

6. 删除共享内存段(可选)

总结

四、基于共享内存进行进程间通信的代码实践

五、共享内存相关命令行指令集

1. ipcs — 显示系统的 IPC(进程间通信)状态

2. ipcrm — 删除 IPC 对象

3、ipcs -m 输出解释


一、共享内存概述

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间,进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种互斥与同步的机制,互斥锁和信号量都可以。【注意:互斥锁与信号量将会在后续多线程部分着重讲解,此处同步机制由命名管道进行协助实现,因为管道的读写阻塞自带同步机制。】

采用共享内存通信的一个显而易见的好处就是效率高,因为进程可以直接读写内存,而不需要复制任何数据。而管道和消息队列等通信方式,则需要在内核和用户空间进行4次数据复制:

管道通信在Linux等操作系统中,需要在用户空间和内核空间进行四次数据复制。这四次数据复制的具体过程如下:

  1. 用户空间到内核空间(写入管道):当一个进程向管道写数据时,数据从用户空间被复制到内核空间中的管道缓冲区。

  2. 内核空间到内核空间(在管道缓冲区中):数据被复制到内核空间后,内核会将这些数据存储在管道的内部缓冲区中。这个缓冲区是内核为管道通信特别设置的一部分内存区域,用于临时存储待传输的数据。

  3. 内核空间到内核空间(从管道缓冲区到读取缓冲区):当另一个进程从管道读取数据时,数据从管道缓冲区复制到内核中的读取缓冲区。这一步确保了读取进程能接收到数据。

  4. 内核空间到用户空间(读取管道):最后,数据从内核中的读取缓冲区复制到读取进程的用户空间中。

综上所述,管道通信的四次数据复制分别是:从用户空间到内核空间、内核空间内部存储(尽管这一步通常不被单独视为一次复制,但它是数据传递的关键环节)、以及从内核空间到用户空间。这些步骤共同构成了管道通信中数据在用户和内核空间之间的高效传递机制。

而共享内存只需要复制两次数据:1、从输入文件到共享内存区;2、从共享内存区到输出文件。

实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,等到有新的通信时再重新建立共享内存区,而是保持共享内存区,直到通信完毕为止,如此,数据内容就一直保存在共享内存中,并没有写回文件。因此,采用共享内存的通信方式的效率是非常高的。

本章节主要对 System V 共享内存 进行讲解。

共享内存内核原理(了解):

page cache 和swap cache 中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache 中,一个页面的所有信息由struct page 来描述。struct page中有一个域为指针mapping,它指向一个struct address_space 类型结构。page cache 或swap cache中的所有页面就是根据address_space 结构和一个偏移量来区分的。
文件与address_space 结构的对应:在打开一个具体的文件后,内核会在内存中为之建立一个 struct inode 结构,其中的imapping 域指向一个address space结构。这样,一个文件就对应一个address_space 结构,一个address_space与一个偏移量能够确定一个page cache或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件和数据在文件内的偏移量找到相应的页面。
当进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,在第一次访问该空间时,会引发缺页异常。
对于共享内存映射的情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space 和偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区(Swap Area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中,进程最终将更新进程页表。

(注:对于映射普通文件的情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space和数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有被读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。)
所有进程在映射同一个共享内存区时的情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区对应的物理页面。
(注:一个共享内存区可以看作特殊文件系统shm中的一个文件,shm的安装点在交换区上.
上面涉及了一些数据结构,围绕数据结构理解问题会容易一些。)
 

二、共享内存相关函数

1、shmget函数

shmget 是一个用于操作共享内存的系统调用函数,主要用于在 Linux 操作系统中创建或获取共享内存段。下面是对 shmget 函数的详细解释:

函数原型

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

参数说明

  • key: 共享内存的键值,通常是一个整型值。这个键值用于标识共享内存段。通常使用 ftok 函数生成这个键值

  • size: 共享内存段的大小,以字节为单位。这个大小应该是大于 0 的整数。如果共享内存段已经存在,size 参数会被忽略。

  • shmflg: 控制标志,用于指定创建共享内存的权限和行为。常见的标志有:

    • IPC_CREAT:如果共享内存段不存在,则创建一个新的共享内存段。
    • IPC_EXCL:必须与 IPC_CREAT 一起使用,如果共享内存段不存在,则创建共享内存段;如果共享内存段已经存在,则返回错误。
    • 权限标志:如 06000666 等,表示共享内存段的访问权限(只读、只写、读写等)。

返回值

  • 成功:返回一个非负整数,这是共享内存段的标识符(shmid)
  • 失败:返回 -1,并设置 errno 来指示错误原因。

【说明】:key值由用户将通过调用ftok函数生成的返回值作为参数传给key,用于使操作系统生成一个唯一的共享内存的标识符,即 shmget 的返回值。可以理解为key是用户层面用来生成唯一的共享内存的标识,而shmid则是操作系统根据用户提供的唯一的key值生成的唯一的共享内存段的标识符,是系统层面的。进而当需要管理共享内存时,需要通过系统层面的唯一标识符shmid来对其标识的共享内存进行管理。

示例代码

下面是一个简单的示例,展示了如何使用 shmget 函数:

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

int main() {
    key_t key = ftok("shmfile", 65);  // 生成一个唯一的键值
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

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

    printf("Shared memory segment created with ID: %d\n", shmid);

    return 0;
}

常见用法

  1. 创建共享内存:使用 shmget 和 IPC_CREAT 标志来创建一个新的共享内存段。
  2. 访问现有共享内存:如果共享内存已经存在,使用 shmget 获取现有的共享内存段的标识符。
  3. 检查权限:通过 shmflg 参数设置对共享内存段的访问权限。

 2、ftok函数

ftok 函数是一个用于生成唯一键值的函数,常用于创建 IPC(进程间通信)机制中的共享内存、消息队列或信号量。

函数原型

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

参数说明

  • pathname: 指定的路径名,用于生成唯一的键值。这个路径名必须是系统上存在的文件路径,一般是一个用于标识进程间通信对象的文件。例如,可以使用当前目录下的某个文件。

  • proj_id: 项目标识符,是一个字符(通常是 int 类型),用于在同一路径下生成不同的键值。它可以是任意的,但通常选择不同的字符来区分不同的 IPC 对象。

返回值

  • 成功: 返回一个 key_t 类型的唯一键值。
  • 失败: 返回 -1,并设置 errno 以指示错误原因。常见的错误包括:
    • EINVAL:提供的路径名无效。
    • ENOENT:指定的文件不存在。

示例代码

以下是一个简单的示例,展示了如何使用 ftok 函数生成一个唯一的键值:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <errno.h>

int main() {
    key_t key = ftok("examplefile", 'a');  // 使用路径 "examplefile" 和项目标识符 'a'
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    printf("Generated key: %d\n", key);

    return 0;
}

使用说明

  1. 文件路径pathname 参数必须是系统上存在的文件。一般来说,这个文件不需要有特殊权限,只需要存在即可。常用的做法是选择一个项目目录中的文件或临时文件。

  2. 项目标识符proj_id 参数可以是 0 到 255 之间的整数,这样可以在同一个文件路径下生成不同的键值。通常选择 ASCII 字符作为项目标识符来提高可读性。

错误处理

  • 文件不存在: 确保提供的 pathname 参数指向一个存在的文件。
  • 路径无效: 确保路径名参数格式正确且有效。

ftok 是在 Unix-like 系统中处理 IPC 机制时非常有用的函数,它帮助生成唯一的标识符来避免不同 IPC 对象之间的冲突。

3、shmctl函数 

shmctl 是一个系统调用函数,用于对共享内存段进行控制和操作。它允许程序获取共享内存段的信息、修改共享内存段的属性,或删除共享内存段。

函数原型

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

参数说明

  • shmid: 共享内存段的标识符,通常是通过 shmget 函数获得的。

  • cmd: 控制命令,指定 shmctl 函数应执行的操作。常见的控制命令包括:

    • IPC_RMID:删除共享内存段。
    • IPC_STAT:获取共享内存段的信息。
    • IPC_SET:设置共享内存段的属性(如权限)。
    • SHM_LOCK:锁定共享内存段(在某些系统上可用)。
    • SHM_UNLOCK:解锁共享内存段(在某些系统上可用)。
  • buf: 指向 shmid_ds 结构体的指针,用于存储共享内存段的信息或设置属性。这个结构体的定义如下:

    struct shmid_ds {
        struct ipc_perm shm_perm;  // 共享内存段的权限
        size_t shm_segsz;          // 共享内存段的大小
        time_t shm_atime;          // 最后一次附加时间
        time_t shm_dtime;          // 最后一次分离时间
        time_t shm_ctime;          // 最后一次改变时间
        unsigned short shm_cpid;   // 创建共享内存段的进程 ID
        unsigned short shm_lpid;   // 最后一次操作共享内存的进程 ID
        unsigned short shm_nattch; // 连接到共享内存段的进程数
    };
    

返回值

  • 成功: 对于 IPC_STAT 和 IPC_SET 命令,返回 0。对于 IPC_RMID 命令,返回 0
  • 失败: 返回 -1,并设置 errno 以指示错误原因。

常见命令的使用

  1. 删除共享内存段 (IPC_RMID)

    这个命令用于标记共享内存段以待删除。共享内存段会在所有进程都分离之后被实际删除。

    #include <sys/shm.h>
    #include <stdio.h>
    
    int main() {
        int shmid = /* obtain shared memory ID */;
        if (shmctl(shmid, IPC_RMID, NULL) == -1) {
            perror("shmctl");
            return 1;
        }
        return 0;
    }
    
  2. 获取共享内存段的信息 (IPC_STAT)

    这个命令用于获取共享内存段的状态信息。你需要提供一个 shmid_ds 结构体的指针来存储这些信息。

    #include <sys/shm.h>
    #include <stdio.h>
    
    int main() {
        int shmid = /* obtain shared memory ID */;
        struct shmid_ds shm_info;
        if (shmctl(shmid, IPC_STAT, &shm_info) == -1) {
            perror("shmctl");
            return 1;
        }
        printf("Shared memory segment size: %zu bytes\n", shm_info.shm_segsz);
        return 0;
    }
    
  3. 设置共享内存段的属性 (IPC_SET)

    这个命令用于修改共享内存段的权限和其他属性。你需要提供一个包含新属性的 shmid_ds 结构体。

    #include <sys/shm.h>
    #include <stdio.h>
    
    int main() {
        int shmid = /* obtain shared memory ID */;
        struct shmid_ds shm_info;
        // 先获取当前信息
        if (shmctl(shmid, IPC_STAT, &shm_info) == -1) {
            perror("shmctl");
            return 1;
        }
        // 修改权限
        shm_info.shm_perm.mode = 0666;  // 设为读写权限
        if (shmctl(shmid, IPC_SET, &shm_info) == -1) {
            perror("shmctl");
            return 1;
        }
        return 0;
    }
    

错误处理

  • EINVAL: 提供的命令无效或 buf 参数无效。
  • EIDRM: 共享内存段已经被删除。
  • ENOMEM: 内存不足,无法完成操作。

shmctl 函数提供了对共享内存段的全面控制,可以在进程间通信中对共享内存进行管理和操作。

4、 shmat函数

shmat 是一个系统调用函数,用于将共享内存段附加到当前进程的地址空间中。这使得进程可以直接访问共享内存段的内容,从而实现进程间通信。下面是 shmat 函数的详细说明。

函数原型

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

参数说明

  • shmid: 共享内存段的标识符,由 shmget 函数返回。它标识了要附加的共享内存段。

  • shmaddr: 请求附加共享内存段的地址。这个参数的意义因平台而异:

    • NULL:表示让系统选择附加地址。
    • 非 NULL:请求将共享内存段附加到指定的地址。如果指定的地址不适合或不可用,通常会返回 -1
  • shmflg: 附加共享内存段的标志。常用的标志包括:

    • SHM_RDONLY:以只读模式附加共享内存段。如果不设置此标志,则默认以读写模式附加。
    • 0:默认情况下,以读写模式附加共享内存段。

返回值

  • 成功: 返回指向共享内存段的映射地址的指针。这个地址是进程的虚拟地址空间中的一个有效地址。
  • 失败: 返回 -1,并设置 errno 以指示错误原因。如果失败,shmat 不会对 shmaddr 参数做任何修改。

错误处理

  • EACCES: 权限不足,无法以指定的模式附加共享内存段。
  • EINVALshmid 无效,或 shmaddr 地址无效。
  • ENOMEM: 内存不足,无法附加共享内存段。

示例代码

以下是一个示例,展示了如何使用 shmat 函数将共享内存段附加到进程的地址空间中:

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int shmid;
    void *shmaddr;
    size_t size = 1024;  // 共享内存段的大小
    key_t key = 1234;    // 共享内存段的键值

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

    // 附加共享内存段
    shmaddr = shmat(shmid, NULL, 0);  // 让系统选择附加地址
    if (shmaddr == (void *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 使用共享内存
    printf("Shared memory attached at address %p\n", shmaddr);
    snprintf((char *)shmaddr, size, "Hello, Shared Memory!");

    // 分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(EXIT_FAILURE);
    }

    return 0;
}

注意事项

  1. 地址选择:如果 shmaddr 参数为 NULL,系统会选择一个合适的地址来附加共享内存段。如果提供了一个非 NULL 地址,系统会尝试将共享内存段附加到该地址,如果无法附加,则 shmat 会失败。

  2. 权限控制:使用 SHM_RDONLY 标志可以以只读模式附加共享内存段,确保不会修改共享内存中的数据。这对于只需要读取数据的进程很有用。

  3. 分离共享内存:使用 shmdt 函数可以将共享内存从进程的地址空间中分离。确保在不再使用共享内存时调用 shmdt 以释放资源。

  4. 多进程访问:多个进程可以同时附加同一个共享内存段,允许它们通过共享内存进行数据交换。然而,要注意同步和互斥问题,以避免数据竞争和不一致。

shmat 是在进行进程间通信时,将共享内存映射到进程的虚拟地址空间中,使得进程能够直接读取和写入共享内存中的数据。

5、 shdt函数

shmdt 是一个标准的系统调用函数,用于从进程的地址空间中分离共享内存段。

函数原型

int shmdt(const void *shmaddr);

参数说明

  • shmaddr: 指向要分离的共享内存段的地址。这个地址是通过之前调用 shmat 函数获得的。

返回值

  • 成功: 返回 0
  • 失败: 返回 -1,并设置 errno 以指示错误原因。

错误处理

  • EINVALshmaddr 不在当前进程的地址空间中。
  • EPERM: 当前进程没有足够的权限执行该操作。

示例代码

以下是一个简单的示例,展示如何使用 shmdt 函数将共享内存段从进程的地址空间中分离:

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/types.h>

int main() {
    int shmid;
    void *shmaddr;
    size_t size = 1024;  // 共享内存段的大小
    key_t key = 1234;    // 共享内存段的键值

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

    // 附加共享内存段
    shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (void *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 使用共享内存
    printf("Shared memory attached at address %p\n", shmaddr);

    // 从进程的地址空间中分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(EXIT_FAILURE);
    }

    return 0;
}

解释

  1. 附加共享内存: 之前调用 shmat 函数将共享内存段附加到进程的地址空间中。
  2. 使用共享内存: 进程可以通过 shmaddr 指针访问共享内存中的数据。
  3. 分离共享内存: 调用 shmdt 函数将共享内存段从进程的地址空间中分离,释放了对该内存区域的访问。

shmdt 是管理共享内存的重要函数,确保在不再需要访问共享内存时调用它以释放资源。

三、使用共享内存相关函数管理共享内存的一般过程 

使用共享内存相关函数管理共享内存的一般过程可以分为以下几个步骤:

1. 生成唯一的键值

使用ftok函数生成一个唯一的键值(key_t),用于标识共享内存段。这一过程通常如下:

key_t key = ftok("/path/to/some/file", 'R');

这里,/path/to/some/file是一个存在的文件路径,'R'是项目标识符(通常可以是任意字符)。

2. 创建或获取共享内存段

使用shmget函数创建或获取共享内存段。shmget的调用形式如下:

int shmid = shmget(key, size, IPC_CREAT | 0666);
  • key:由ftok生成的唯一键值。
  • size:共享内存段的大小(以字节为单位)。
  • IPC_CREAT:如果共享内存段不存在,则创建它。
  • 0666:设置共享内存的权限(读写权限)。

shmget函数返回一个共享内存段的标识符(shmid),用于后续操作。

3. 连接到共享内存段

使用shmat函数将共享内存段附加到调用进程的地址空间。这样,进程可以访问共享内存。shmat的调用形式如下:

void *shm_addr = shmat(shmid, NULL, 0);
  • shmid:由shmget返回的共享内存标识符。
  • NULL:系统会选择一个合适的地址进行映射。
  • 0:指定附加的选项(通常为0,表示默认行为)。

shmat函数返回一个指向共享内存的指针(shm_addr),通过它,进程可以访问共享内存段的内容。

4. 操作共享内存

现在,你可以通过shm_addr指针读取或写入共享内存中的数据。操作共享内存就像操作普通内存一样。例如,可以将shm_addr 当作转换为char*类型的字符数组的指针,使用snprintf函数进行写入。

5. 断开与共享内存段的连接

当进程不再需要访问共享内存时,使用shmdt函数断开共享内存段:

shmdt(shm_addr);
  • shm_addr:之前通过shmat函数获得的共享内存地址。

6. 删除共享内存段(可选)

如果不再需要共享内存段,可以使用shmctl函数删除它。删除操作通常由共享内存段的所有者执行。shmctl的调用形式如下:

shmctl(shmid, IPC_RMID, NULL);
  • shmid:由shmget返回的共享内存标识符。
  • IPC_RMID:请求删除共享内存段。
  • NULL:不需要提供额外的信息。

总结

  1. 生成键值:使用ftok函数生成唯一的键值。
  2. 获取共享内存段:使用shmget创建或获取共享内存段。
  3. 连接共享内存:使用shmat将共享内存附加到进程地址空间。
  4. 操作共享内存:通过指针访问和修改共享内存中的数据。
  5. 断开连接:使用shmdt断开共享内存段。
  6. 删除共享内存段(可选):使用shmctl删除共享内存段。

四、基于共享内存进行进程间通信的代码实践

当获取了共享内存的虚拟地址后,可以将其强制转换为期望的指针类型(比如 char*int* 等),就可以将这块内存区域当作数组来使用。进而我们可以直接使用下标来读取和修改共享内存中的数据。例如:

1. 创建和写入共享内存的程序

对于共享内存的数据的写入,

1、我们可以使用strcpy/strncpy函数将字符串拷贝给共享内存。

2、我们可以使用sprintf/snprintf函数实现向共享内存中进行数据写入。

3、也可以通过使用数组下标的方式来读取和修改共享内存中的数据,需要注意的是数组下标不能超过共享内存的容量所允许的最大下标,即不能超过SHM_SIZE-1。

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

#define SHM_KEY 0x1234      // 共享内存的唯一标识符
#define SHM_SIZE 1024       // 共享内存的大小

int main() {
    int shm_id;
    char *shm_ptr;

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

    // 将共享内存段附加到当前进程的地址空间
    shm_ptr = (char*)shmat(shm_id, NULL, 0);
    if (shm_ptr == (char*)-1) {
        perror("shmat");
        exit(1);
    }

    // 1、通过strcpy或snprintf函数修改共享内存中的数据
    strcpy(shm_ptr, "Hello, System V shared memory!");

    // 2、使用下标方式修改共享内存的内容
    shm_ptr[0] = 'H';
    shm_ptr[1] = 'e';
    shm_ptr[2] = 'l';

    // 打印共享内存中的内容
    printf("Shared memory contains: %s\n", shm_ptr);

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

    return 0;
}

2. 读取共享内存的程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define SHM_KEY 0x1234      // 共享内存的唯一标识符
#define SHM_SIZE 1024       // 共享内存的大小

int main() {
    int shm_id;
    char *shm_ptr;

    // 获取共享内存段的标识符
    shm_id = shmget(SHM_KEY, SHM_SIZE, 0666);
    if (shm_id == -1) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存段附加到当前进程的地址空间
    shm_ptr = (char*)shmat(shm_id, NULL, 0);
    if (shm_ptr == (char*)-1) {
        perror("shmat");
        exit(1);
    }

    // 读取共享内存中的内容
    printf("Shared memory contains: %s\n", shm_ptr);

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

    return 0;
}
3、模拟服务端与客户端的信息交互,使用命名管道协助共享内存实现同步机制。

代码整体思路:1、服务端负责创建共享内存与命名管道。2、客户端负责向共享内存中写入信息,当写入完成后,通过对管道写入信息来唤醒服务端进行数据的读取。3、当服务端的管道接收到信息后,再通过共享内存接收服务端写入的信息。4、服务端负责共享内存和命名管道的创建与销毁。5、共享内存在服务端进程和客户端进程的地中空间中都需要进行连接和断开连接的操作。

namedpipe.hpp:

#pragma once
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <cstring>

#define PATHNAME "./myfifo"

#define Creater 0
#define User 1
#define LIMIT 0666
#define BUFFER_SIZE 256

class NamePipe
{
private:
    std::string _path;
    int _fd;
    int _identity;
public:
    NamePipe(const std::string& path, int identity)
    :_path(path), _identity(identity), _fd(-1)
    {
        if(_identity == Creater){
            if(mkfifo(PATHNAME, LIMIT) == -1){
                perror("create fifo fail!");
                exit(-1);
            }
        }
    }
    ~NamePipe(){
        if (_identity == Creater)
        {
            int res = unlink(_path.c_str());
            if (res != 0)
            {
                perror("unlink fail!");
            }
        }
        Close();
    }
    void OpenForRead()
    {
        if((_fd = open(_path.c_str(), O_RDONLY, LIMIT)) == -1){
                perror("open for read fail!");
                exit(-1);
            }
            else{
                std::cout << "客户端准备进行通信..." << std::endl;
            }
        
    }
    int Read()
    {
        int r_num = 0;
        char buffer[BUFFER_SIZE];
        memset(buffer, 0, sizeof(buffer));
        if((r_num = read(_fd, buffer, sizeof(buffer) - 1)) == -1){
            perror("read fail!");
            return -1;
        }
        else if(r_num == 0)
        {
            std::cout << "客户端关闭,通信终止..." << buffer << std::endl;
            return 0;
        }
        else {
            buffer[r_num] = '\0';
        }
        return r_num;
    }
    void OpenForWrite()
    {
        if((_fd = open(_path.c_str(), O_WRONLY, LIMIT)) == -1){
                perror("open for write fail!");
                exit(-1);
            }
        else{
            std::cout << "服务端端准备进行通信..." << std::endl;
        }
    }
    void Write(const std::string& buffer)
    {
        int w_num = 0;
        if((w_num = write(_fd, buffer.c_str(), buffer.size())) == -1){
            perror("read fail!");
            exit(-1);
        }
    }
    void Close()
    {
        close(_fd);
    }
};

 shared_memory.hpp:

#pragma once
#include <unistd.h>
#include <cerrno>
#include <cstdio>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>

#ifndef Creater
#define Creater 0
#endif

#ifndef User
#define User 1
#endif

#define LIMIT 0666
#define SHM_SIZE 4096
#define PATH_SIZE 128

char path_name[PATH_SIZE] = {0};

class shm
{
private:
    int _identity;
    int _shmid;
    void *_shm_addr;

public:
    // 获取当前文件路径,作为ftok函数的路径参数
    char *GetCwd()
    {
        
        char *ptr = nullptr;
        if ((ptr = getcwd(path_name, sizeof(path_name))) == nullptr)
        {
            perror("getcwd fail!!!");
            exit(errno);
        }
        return ptr;
    }

    key_t GetKey()
    {
        key_t k = 0;
        if ((k = ftok(GetCwd(), Creater)) == -1)
        {
            perror("ftok fail!!!");
            exit(errno);
        }
        return k;
    }

    int GetShmIdForCreater()
    {
        int id = 0;
        if ((id = shmget(GetKey(), SHM_SIZE, IPC_CREAT | IPC_EXCL | LIMIT)) == -1)
        {
            perror("shmget for creater fail!!!");
            exit(errno);
        }
        return id;
    }

    int GetShmIdForUser()
    {
        int id = 0;
        if ((id = shmget(GetKey(), SHM_SIZE, IPC_CREAT | LIMIT)) == -1)
        {
            perror("shmget for user fail!!!");
            exit(errno);
        }
        return id;
    }

    void *ShmAttch()
    {
        void *res = shmat(_shmid, nullptr, 0);
        if (res == (void *)-1)
        {
            perror("shmat fail!!!");
            exit(errno);
        }
        return res;
    }

    void WriteShm(const char *str)
    {
        size_t len = strlen(str);
        if (len >= SHM_SIZE)
        {
            perror("Input string is too large for shared memory\n");
            exit(errno);
        }
        if (snprintf(static_cast<char *>(_shm_addr), SHM_SIZE, "%s", str) < 0)
        {
            perror("WriteShm fail!!!");
            exit(errno);
        }
    }

    void ReadShm(char *str)
    {
        if (str == nullptr)
        {
            fprintf(stderr, "Output buffer is null\n");
            exit(EXIT_FAILURE);
        }
        strncpy(str, static_cast<char *>(_shm_addr), SHM_SIZE - 1);
        str[SHM_SIZE - 1] = '\0'; // 确保字符串结尾符
    }

    shm(int identity)
        : _identity(identity)
    {
        if (_identity == Creater)
        {
            _shmid = GetShmIdForCreater();
            std::cout << "Shared memory has been created..." << std::endl;
        }
        else
        {
            _shmid = GetShmIdForUser();
            std::cout << "Shared memory has been used..." << std::endl;
        }
        _shm_addr = ShmAttch();
    }

    ~shm()
    {
        if (shmdt(_shm_addr) == -1)
        {
            perror("shmdt fail!!!");
            exit(errno);
        }
        std::cout << "Shared memory mounting has been released..." << std::endl;
        if (_identity == Creater)
        {
            if (shmctl(_shmid, IPC_RMID, nullptr) == -1)
            {
                perror("shmctl fail!!!");
                exit(errno);
            }
            std::cout << "Remove Shared_Memory Successfully !!!" << std::endl;
        }
    }
};

server.cc:

#include "shared_memory.hpp"
#include "namedpipe.hpp"

int main()
{
    shm t(Creater);
    std::string fifo_path = std::string(t.GetCwd()) + std::string("/myfifo");
    NamePipe p(fifo_path.c_str(), User);
    std::cout << "Waiting for the client to suspend... " << std::endl; 
    sleep(5);
    p.OpenForRead();
    while(true)
    {
        if(p.Read() == 0) break;
        char buffer[SHM_SIZE];
        t.ReadShm(buffer);
        std::cout << "server read : " << buffer << std::endl; 
    }
    return 0;   
}

client.cc:

#include "shared_memory.hpp"
#include "namedpipe.hpp"
#include <signal.h>

int main()
{
    shm t(User);
    std::string fifo_path = std::string(t.GetCwd()) + std::string("/myfifo");
    NamePipe p(fifo_path.c_str(), Creater);
    p.OpenForWrite();
    char ch = 'a';
    std::string s;
    while(ch <= 'g')
    {
        s += ch++;
        std::cout <<"client write : " << s << std::endl; 
        t.WriteShm(s.c_str());
        p.Write("User");//向共享内存中写完信息后,再向管道中写入信息,唤醒服务端读取共享内存的信息
        sleep(1);
    }
    return 0;   
}

 makefile:

.PHONY:all
all:server client

server:server.cc
	g++ server.cc -o server

client:client.cc
	g++ client.cc -o client

.PHONY:clean
clean:
	rm -f client server

五、共享内存相关命令行指令集

在 Linux 操作系统中,处理共享内存的常用命令的包括 ipcs 和 ipcrm。这些工具可以用于查看和管理系统中的共享内存段。以下是这些命令的详细用法:

1. ipcs — 显示系统的 IPC(进程间通信)状态

ipcs 命令用于显示当前系统中各种 IPC 对象的状态,包括共享内存段、消息队列和信号量。

语法

ipcs [options]

常用选项

  • -m: 显示共享内存段的信息。
  • -q: 显示消息队列的信息。
  • -s: 显示信号量的信息。
  • -a: 显示所有 IPC 对象的信息(共享内存段、消息队列、信号量)。
  • -l: 显示 IPC 资源的限制信息。

示例

  1. 显示所有共享内存段

    ipcs -m
    
  2. 显示所有 IPC 对象(包括共享内存段、消息队列和信号量)

    ipcs -a
    
  3. 显示 IPC 资源的限制

    ipcs -l
    

2. ipcrm — 删除 IPC 对象

ipcrm 命令用于删除系统中的 IPC 对象,如共享内存段、消息队列和信号量。

语法

ipcrm [options] id

常用选项

  • -m: 删除共享内存段。

  • -q: 删除消息队列。

  • -s: 删除信号量集。

参数

  • id: IPC 对象的标识符(可以通过 ipcs 命令获得)。

示例

  1. 删除指定的共享内存段

    ipcrm -m shmid
    

    其中 shmid 是共享内存段的标识符。

  2. 删除指定的消息队列

    ipcrm -q msgid
    

    其中 msgid 是消息队列的标识符。

  3. 删除指定的信号量集

    ipcrm -s semid
    

    其中 semid 是信号量集的标识符。

使用示例

假设你创建了一个共享内存段,其标识符是 12345,可以通过 ipcs -m 查看共享内存段的详细信息:

ipcs -m

输出示例(部分):

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x12345678 12345      user       666        1024       2

如果你想删除这个共享内存段,可以使用 ipcrm 命令:

ipcrm -m 12345

这将删除标识符为 12345 的共享内存段。

总结

  • ipcs 命令用于查看共享内存段及其他 IPC 对象的信息。
  • ipcrm 命令用于删除指定的共享内存段、消息队列或信号量集。

3、ipcs -m 输出解释

在 ipcs -m 命令的输出中,各列的含义如下:

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status

1. key

  • 含义: 共享内存段的键值(key_t 类型)。这是一个整数值,用于标识共享内存段。可以使用 ftok 函数生成键值,确保在不同进程间唯一。

2. shmid

  • 含义: 共享内存段的标识符(shmid)。这是系统分配给共享内存段的唯一标识符,供进程在使用 shmatshmdt 和 shmctl 等系统调用时引用。

3. owner

  • 含义: 共享内存段的拥有者。显示拥有该共享内存段的用户的用户名或用户ID(UID)。

4. perms

  • 含义: 权限设置,显示共享内存段的权限。这通常是一个三位八进制数(类似文件权限),其中每一位表示不同用户组的权限。权限位的含义如下:

    • 第一位表示所有者的权限(读、写、执行)。

    • 第二位表示同组用户的权限(读、写、执行)。

    • 第三位表示其他用户的权限(读、写、执行)。

    例如,666 表示所有用户都具有读写权限,但没有执行权限。

5. bytes

  • 含义: 共享内存段的大小(以字节为单位)。表示分配给共享内存段的总内存大小。

6. nattch

  • 含义: 当前附加到共享内存段的进程数量。表示有多少个进程当前附加了这个共享内存段。进程通过 shmat 系统调用将共享内存附加到其地址空间中。

7. status

  • 含义: 共享内存段的状态。通常显示为 (没有状态信息)或 (指示是否是共享内存段的创建/删除状态的标志,具体视系统和情况而定)。在一些系统中,这一列可能为空或不显示特定的状态信息。

示例解释

假设 ipcs -m 命令的输出如下:

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x12345678 12345      user       666        1024       2

这个输出表示:

  • 键值 (key)0x12345678,这是共享内存段的标识符键值。
  • 共享内存标识符 (shmid)12345,这是系统分配的共享内存段的唯一标识符。
  • 拥有者 (owner)user,表示该共享内存段的拥有者是用户名为 user 的用户。
  • 权限 (perms)666,表示所有用户都有读写权限,没有执行权限。
  • 大小 (bytes)1024,共享内存段的大小为 1024 字节。
  • 附加进程数量 (nattch)2,表示有 2 个进程当前附加到这个共享内存段。
  • 状态 (status): 空或无特定状态,具体信息可能依赖于系统。

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

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

相关文章

2024 NVIDIA开发者社区夏令营环境配置指南(Win Mac)

2024 NVIDIA开发者社区夏令营环境配置指南(Win & Mac) 1 创建Python环境 首先需要安装Miniconda&#xff1a; 大家可以根据自己的网络情况从下面的地址下载&#xff1a; miniconda官网地址&#xff1a;https://docs.conda.io/en/latest/miniconda.html 清华大学镜像地…

对提高数据和指令缓存命中率方法的验证

对于如何写出让CPU跑得更快的代码这一问题&#xff0c;我看到了两种方法&#xff0c;分别是&#xff1a;1、提高数据缓存命中率&#xff1b;2、提高指令缓存命中率。 本文对这两种方法进行了简单的验证&#xff0c;并提供了示例代码。 本文基于2.3 如何写出让 CPU 跑得更快的代…

大规模深度学习推理引擎是什么?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 首先&#xff0c;我们来理解 “深度学习”&#xff1a;它是人工智能的一个重要分支&#xff0c;模仿人脑的神经网络结构&#xff0c;运用多层神经网络来学习和解析数据。这些神经网络能够从庞大的数据集…

二、版本更新与插件安装

版本更新 点击版本管理 点击刷新列表后点击一键更新&#xff0c;即可完成更新&#xff08;注意完成更新必须要关闭正在运行的服务&#xff09; 更新拓展&#xff08;更新插件&#xff09; 点击拓展-刷新列表-一键更新 即可 注意&#xff1a;操作完成以后必须重新启动方可生效…

(六)activiti-modeler 设计器属性编辑弹窗bug修复

BUG重现 在使用流程设计器时&#xff0c;经常碰到弹窗不小心关闭&#xff0c;比如不小心点击了灰色背景上&#xff0c;此时BUG就出现了。弹窗被关闭了&#xff0c;分配用户属性被置空了&#xff0c;以前有数据也被清空了&#xff0c;还无法再次点击弹窗编辑。 不仅仅是分配用…

deque和优先级队列

咱学完栈和队列之后&#xff0c;又了解到了vector和list&#xff0c;更深入的了解到了它们各自的优势&#xff0c;那么有没有可能结合它们的优点摒弃弱点呢&#xff0c;其实是有人这么试过的&#xff0c;不过咱还在学vector和list就证明他可能没成功&#xff0c;不过并不影响我…

python中类class的魔法方法

开始介绍之前&#xff0c;我们先看下之前文章我们介绍过的内置类merryview的一些方法&#xff0c;如下图所示&#xff1a; 有很多双下划线开始和结束的method&#xff0c;这么多method是做啥子用的呢&#xff1f; 其实这些方法就是我们常说的魔法方法&#xff0c;也是python中的…

【代码随想录】二分查找

文章为代码随想录的学习笔记&#xff0c;链接&#xff1a; 代码随想录 只要看到面试题中给出的数组是有序数组&#xff0c;都可以想一想是否可以使用二分法。 基本概念 二分查找要求线性表必须采用顺序存储结构&#xff0c;而且表中元素按关键字有序排列。‘ 查找过程&…

2024年华数杯数学建模竞赛——赛题浅析

本次华数杯与国赛同频【第一天下午六点发题&#xff0c;第四天下午八点收卷】&#xff0c;一共74小时。难度约为国赛的0.8&#xff0c;题量约为国赛的0.8-0.9.非常适合作为国赛前的练手赛。下面为大家带来本次华数杯的选题建议&#xff0c;希望对大家有所帮助。 一图流 选题人…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 卢小姐的字符串解压缩(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题…

36-38 关系数据库入门

关系数据库入门 关系数据库概述 数据持久化 - 将数据保存到能够长久保存数据的存储介质中&#xff0c;在掉电的情况下数据也不会丢失。 数据库发展史 - 网状数据库、层次数据库、关系数据库、NoSQL数据库。 1970年&#xff0c;IBM的研究员E.F.Codd在Communication of the ACM…

【Material-UI】Autocomplete中的禁用选项:Disabled options

文章目录 一、简介二、基本用法三、进阶用法1. 动态禁用2. 提示禁用原因3. 复杂的禁用条件 四、最佳实践1. 一致性2. 提供反馈3. 优化性能 五、总结 Material-UI的Autocomplete组件提供了丰富的功能&#xff0c;包括禁用特定选项的能力。这一特性对于限制用户选择、提供更好的用…

长短期记忆网络LSTM

目录 一、LSTM提出的背景&#xff1a;1.RNN存在的问题&#xff1a;2.LSTM的思想&#xff1a;2.1回顾GRU的提出&#xff1a;2.2LSTM在GRU上的改进&#xff1a; 二、遗忘门、输入门、输出门&#xff1a;三、LSTM网络架构&#xff1a;1.候选记忆单元C~t&#xff1a;2.遗忘门、输入…

顶刊TPAMI 2024!无需全标注,仅用少量涂鸦标注即可获得确定和一致的语义分割预测结果...

本文介绍了山东大学&#xff0c;北京大学和纽约州立大学石溪分校合作开展的一项工作。该工作面向图像涂鸦弱标注语义分割任务&#xff0c;重点关注采用涂鸦弱标注时语义分割网络的不确定性和不一致性问题。 作者提出最小化熵损失函数和网络嵌入的随机游走过程来分别改善分割网络…

Altera之FPGA器件系列简介

目录 一、前言 二、命名规则 2.1 MAX V系列 2.2 Cyclone 系列 2.3 Arria 系列 2.4 Stratix 系列 2.5 Agilex 系列 三、器件划分 3.1 工艺制程 3.2 使用领域 四、参考 一、前言 Altera是作为FPGA领域的头部企业&#xff0c;是一家老牌的技术公司&#xff0c;成立于19…

【一图学技术】7.削峰与限流防刷技术解决方案及限流算法图解

削峰与限流防刷技术 一、削峰技术 ✈解决问题&#xff1a;解决流量大的问题&#xff0c;限制单机流量 &#x1f680;核心技术&#xff1a; 秒杀令牌&#xff1a;颁发给用户令牌&#xff0c;给予操作特权 秒杀大闸&#xff1a;限制令牌数量 队列泄洪&#xff1a;队列增加缓…

4_损失函数和优化器

教学视频&#xff1a;损失函数与反向传播_哔哩哔哩_bilibili 损失函数&#xff08;Loss Function&#xff09; 损失函数是衡量模型预测输出与实际目标之间差距的函数。在监督学习任务中&#xff0c;我们通常希望模型的预测尽可能接近真实的目标值。损失函数就是用来量化模型预…

神经网络基础--激活函数

&#x1f579;️学习目标 &#x1f579;️什么是神经网络 1.神经网络概念 2.人工神经网络 &#x1f579;️网络非线性的因素 &#x1f579;️常见的激活函数 1.sigmoid激活函数 2.tanh激活函数 3.ReLU激活函数 4.softmax激活函数 &#x1f579;️总结 &#x1f57…

计算机基础(Windows 10+Office 2016)教程 —— 第5章 文档编辑软件Word 2016(上)

第5章 文档编辑软件Word 2016 5.1 Word 2016入门5.1.1 Word 2016 简介5.1.2 Word 2016 的启动5.1.3 Word 2016 的窗口组成5.1.4 Word 2016 的视图方式5.1.5 Word 2016 的文档操作5.1.6 Word 2016 的退出 5.2 Word 2016的文本编辑5.2.1 输入文本5.2.3 插入与删除文本5.2.4 复制与…

二进制与进制转换与原码、反码、补码详解--内含许多超详细图片讲解!!!

前言 今天给大家分享一下C语言操作符的详解&#xff0c;但在此之前先铺垫一下二进制和进制转换与原码、反码、补码的知识点&#xff0c;都非常详细&#xff0c;也希望这篇文章能对大家有所帮助&#xff0c;大家多多支持呀&#xff01; 操作符的内容我放在我的下一篇文章啦&am…