C语言 多进程编程(六)共享内存

简介: 本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。

多进程(六)

共享内存

  • 共享内存是将分配的物理空间直接映射到进程的⽤户虚拟地址空间中, 减少数据在内核空间缓存
  • 共享内存是⼀种效率较⾼的进程间通讯的⽅式

在 Linux 系统中通过 ipcs -m 查看所有的共享内存

共享内存的模型:

img_44.png

共享内存的创建

  • 使用 shmget() 函数创建共享内存

函数头文件:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget (key_t __key, size_t __size, int __shmflg)
  • key 是一个整数值, 用于标识共享内存块, 必须唯一
  • size 是一个整数值, 表示共享内存块的大小, 单位为字节 4K(一个内存页)的整数倍
  • shmflg 共享内存标志,是一个整数值, 用于设置共享内存的访问权限, 可以取值:
    • IPC_CREAT :创建共享内存块,如果 key 已经存在,则返回错误
    • IPC_EXCL :和 IPC_CREAT 相反,如果 key 已经存在,则返回错误
    • 0 :访问权限为读写

返回值:

  • 如果成功, 则返回一个非负整数, 该整数是共享内存块的标识符
  • 如果出错, 则返回 -1, 并设置 errno 变量

示例:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define SHM_PATH "/home/gopher"
#define SHM_ID 1234
int main() {
   
   

    key_t key;//消息队列的key
    //通过文件路径和ID生成key,
    key= ftok(SHM_PATH,SHM_ID);
    if(key==-1){
   
   
        printf("ftok() error\n");
        exit(EXIT_FAILURE);
    }
    printf("key: %d\n",key);

    //创建共享内存
    int shmid;
    shmid=shmget(key,1024,IPC_CREAT|0666);
    printf("shmid: %d\n",shmid);
    return 0;
}

运行结果:

key: -769608541
shmid: 1

img_45.png

共享内存删除

可以通过命令删除共享内存:

ipcrm -m <共享内存标识符>

也可以通过 shmctl() 函数删除共享内存:

函数头文件:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl (int __shmid, int __cmd, struct shmid_ds *__buf)

函数功能:
共享内存控制函数,功能由具体的功能命令字决定, 用于控制共享内存的创建、删除、设置和获取信息等。

  • shmid 共享内存标识符, 由 shmget() 函数返回
  • cmd 功能命令字, 用于指定控制命令, 可以取值:
    • IPC_RMID :删除共享内存块,使用时候第三个参数为 NULL ,标记为删除,实际上会等到,其他映射都删除才会删除
    • IPC_SET :设置共享内存块的属性
    • IPC_STAT :获取共享内存块的属性
  • buf 指向 shmid_ds 结构体(共享内存数据结构)的指针, 用于设置或获取共享内存块的属性
  • 返回值:
    • 如果成功, 则返回 0
    • 如果出错, 则返回 -1, 并设置 errno 变量
  • shmid_ds 结构体:
  • struct shmid_ds {
         
         
      struct ipc_perm shm_perm; /* 共享内存权限 */  
      size_t shm_segsz; /* 共享内存段大小 */
      pid_t shm_lpid; /* 最后一个 attach 进程的 PID */
      pid_t shm_cpid; /* 当前 attach 进程的 PID */
      unsigned long shm_nattch; /* 当前 attach 进程的数量 */
      time_t shm_atime; /* 上次 attach 时间 */
      time_t shm_dtime; /* 上次 detach 时间 */
      time_t shm_ctime; /* 创建时间 */
      void *shm_internal; /* 内部指针 */
    };
    

删除示例:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define SHM_PATH "/home/gopher"
#define SHM_ID 1234
int main() {
   
   

    key_t key;//消息队列的key
    //通过文件路径和ID生成key,
    key= ftok(SHM_PATH,SHM_ID);
    if(key==-1){
   
   
        printf("ftok() error\n");
        exit(EXIT_FAILURE);
    }
    printf("key: %d\n",key);

    //创建共享内存
    int shmid;
    shmid=shmget(key,1024,IPC_CREAT|0666);
    printf("shmid: %d\n",shmid);


    int  ret=shmctl(shmid,IPC_RMID,NULL); //删除共享内存
    if(ret==-1){
   
   
        printf("shmctl() error\n");
        exit(EXIT_FAILURE);
    }



    return 0;


}

共享内存映射

  • 共享内存映射是将共享内存中的数据映射到进程的虚拟地址空间中, 使得进程可以直接访问共享内存中的数据
  • 共享内存映射是⼀种效率较⾼的进程间通讯的⽅式

共享内存映射的创建

  • 使用 shmat() 函数创建共享内存映射
  • 函数头文件:
  • #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    void *shmat (int __shmid, const void *__shmaddr, int __shmflg)
    

    函数功能:
    将进程地址空间中的一个区域映射到共享内存中, 并返回映射的地址。

  • shmid 共享内存标识符, 由 shmget() 函数返回

  • shmaddr 映射到共享内存中的地址, 可以为 NULL, 表示由系统选择映射地址
  • shmflg 映射标志, 可以取值:
    • SHM_RDONLY :只读映射
    • SHM_RND :映射地址随机
    • SHM_REMAP :允许映射到已存在的共享内存
    • SHM_EXEC :允许映射到可执行内存
    • 0 :默认值, 允许读写映射

返回值:

  • 如果成功, 则返回映射到共享内存中的地址
  • 如果出错, 则返回 (void *)-1, 并设置 errno 变量

解除共享内存映射

  • 使用 shmdt() 函数解除共享内存映射
  • 函数头文件:
  • #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmdt (const void *__shmaddr)
    
    函数功能:

将进程地址空间中的一个区域与共享内存的映射解除。

  • shmaddr 映射到共享内存中的地址
  • 返回值:
  • 如果成功, 则返回 0
  • 如果出错, 则返回 -1, 并设置 errno 变量

示例:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define SHM_PATH "/home/gopher"
#define SHM_ID 1234
int main() {
   
   

    key_t key;//消息队列的key
    //通过文件路径和ID生成key,
    key= ftok(SHM_PATH,SHM_ID);
    if(key==-1){
   
   
        printf("ftok() error\n");
        exit(EXIT_FAILURE);
    }
    printf("key: %d\n",key);

    //创建共享内存
    int shmid;
    shmid=shmget(key,1024,IPC_CREAT|0666);
    printf("shmid: %d\n",shmid);

//    int  ret=shmctl(shmid,IPC_RMID,NULL); //删除共享内存
//    if(ret==-1){
   
   
//        printf("shmctl() error\n");
//        exit(EXIT_FAILURE);
//    }

    //映射共享内存
    void* addr=NULL;
    addr=shmat(shmid,NULL,0);
    if(addr==(void*)-1){
   
   
        printf("shmat() error\n");
        exit(EXIT_FAILURE);
    }
    printf("addr: %p\n",addr);
    //修改共享内存中的数据
    memset(addr,'A',10);

    //解除映射
    int ret=shmdt(addr);
    if(ret==-1) {
   
   
        printf("shmdt() error\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

写入和读取共享内存中的数据

写入:


#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define SHM_PATH "/home/gopher"
#define SHM_ID 1234
int main() {
   
   

    key_t key;//消息队列的key
    //通过文件路径和ID生成key,
    key= ftok(SHM_PATH,SHM_ID);
    if(key==-1){
   
   
        printf("ftok() error\n");
        exit(EXIT_FAILURE);
    }
    printf("key: %d\n",key);

    //创建共享内存
    int shmid;
    shmid=shmget(key,1024,IPC_CREAT|0666);
    printf("shmid: %d\n",shmid);

//    int  ret=shmctl(shmid,IPC_RMID,NULL); //删除共享内存
//    if(ret==-1){
   
   
//        printf("shmctl() error\n");
//        exit(EXIT_FAILURE);
//    }

    //映射共享内存
    void* addr=NULL;
    addr=shmat(shmid,NULL,0);
    if(addr==(void*)-1){
   
   
        printf("shmat() error\n");
        exit(EXIT_FAILURE);
    }
    printf("addr: %p\n",addr);
    //修改共享内存中的数据
    memset(addr,'A',10);

    //解除映射
    int ret=shmdt(addr);
    if(ret==-1) {
   
   
        printf("shmdt() error\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

读取:


#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define SHM_PATH "/home/gopher"
#define SHM_ID 1234
int main() {
   
   

    key_t key;//消息队列的key
    //通过文件路径和ID生成key,
    key= ftok(SHM_PATH,SHM_ID);
    if(key==-1){
   
   
        printf("ftok() error\n");
        exit(EXIT_FAILURE);
    }
    printf("key: %d\n",key);

    //创建共享内存
    int shmid;
    shmid=shmget(key,1024,IPC_CREAT|0666);
    printf("shmid: %d\n",shmid);





//    int  ret=shmctl(shmid,IPC_RMID,NULL); //删除共享内存
//    if(ret==-1){
   
   
//        printf("shmctl() error\n");
//        exit(EXIT_FAILURE);
//    }

    //映射共享内存
    void* addr=NULL;
    addr=shmat(shmid,NULL,0);
    if(addr==(void*)-1){
   
   
        printf("shmat() error\n");
        exit(EXIT_FAILURE);
    }
    printf("addr: %p\n",addr);
    //读取共享内存中的数据
    char str[1024];
    memcpy(str,addr,1024);
    printf("str: %s\n",str);
    printf("strlen(str): %d\n",strlen(str));
    //解除映射
    int ret=shmdt(addr);
    if(ret==-1) {
   
   
        printf("shmdt() error\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

运行结果

key: -769608541
shmid: 2
addr: 0x7f724c65a000
str: AAAAAAAAAA
strlen(str): 10

大致操作流程:

img_46.png

相关文章
|
3天前
|
存储 Linux 调度
深入理解操作系统:从进程管理到内存分配
【8月更文挑战第44天】本文将带你深入操作系统的核心,探索其背后的原理和机制。我们将从进程管理开始,理解如何创建、调度和管理进程。然后,我们将探讨内存分配,了解操作系统如何管理计算机的内存资源。最后,我们将通过一些代码示例,展示这些概念是如何在实际操作系统中实现的。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
|
7天前
|
存储 并行计算 算法
CUDA统一内存:简化GPU编程的内存管理
在GPU编程中,内存管理是关键挑战之一。NVIDIA CUDA 6.0引入了统一内存,简化了CPU与GPU之间的数据传输。统一内存允许在单个地址空间内分配可被两者访问的内存,自动迁移数据,从而简化内存管理、提高性能并增强代码可扩展性。本文将详细介绍统一内存的工作原理、优势及其使用方法,帮助开发者更高效地开发CUDA应用程序。
|
8天前
|
安全 开发者 Python
揭秘Python IPC:进程间的秘密对话,让你的系统编程更上一层楼
【9月更文挑战第8天】在系统编程中,进程间通信(IPC)是实现多进程协作的关键技术。IPC机制如管道、队列、共享内存和套接字,使进程能在独立内存空间中共享信息,提升系统并发性和灵活性。Python提供了丰富的IPC工具,如`multiprocessing.Pipe()`和`multiprocessing.Queue()`,简化了进程间通信的实现。本文将从理论到实践,详细介绍各种IPC机制的特点和应用场景,帮助开发者构建高效、可靠的多进程应用。掌握Python IPC,让系统编程更加得心应手。
12 4
|
12天前
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
12天前
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
12天前
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。
|
12天前
|
Linux C语言
C语言 多进程编程(七)信号量
本文档详细介绍了进程间通信中的信号量机制。首先解释了资源竞争、临界资源和临界区的概念,并重点阐述了信号量如何解决这些问题。信号量作为一种协调共享资源访问的机制,包括互斥和同步两方面。文档还详细描述了无名信号量的初始化、等待、释放及销毁等操作,并提供了相应的 C 语言示例代码。此外,还介绍了如何创建信号量集合、初始化信号量以及信号量的操作方法。最后,通过实际示例展示了信号量在进程互斥和同步中的应用,包括如何使用信号量避免资源竞争,并实现了父子进程间的同步输出。附带的 `sem.h` 和 `sem.c` 文件提供了信号量操作的具体实现。
|
2月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
2月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
64 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
1月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。

相关实验场景

更多