进程间通信之共享内存及其shm函数的使用【Linux】

简介: 进程间通信之共享内存及其shm函数的使用【Linux】


什么是共享内存

  共享内存区是最快的IPC(Inter-Process Communication,进程间通信)形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据.

  匿名管道和命名管道都需要通过系统调用接口来实现进程间通信,因为管道属于文件,属于内核中的一种特殊数据结构,由OSweihu,所以只能使用系统调用。而共享内存存在堆栈之间,可以直接读写。双方进程若要进行通信,直接进行内存级的读写即可。

从下图可以更加直观的看到在内核照中的运行方式:

  • 共享内存的提供者是操作系统。
  • 共享内存缺乏访问控制,用户(进程)双方都不会影响对方,甚至不知道对方的存在,容易造成接受的数据是不完整的。
  • 共享内存 = 共享内存块 + 对应的共享内存的内核数据结构

共享内存的内核数据结构

如何实现共享内存

共享内存函数

shmget函数

功能:用来创建共享内存

原型: int shmget(key_t key, size_t size, int shmflg);

参数:

key:这个共享内存段名字

size:共享内存大小

shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

  参数key要保证在系统中的唯一性,key值相同,就是同一块共享内存,保证key的唯一性可以使用ftok函数实现:

ftok函数

所需要的头文件:

#include <sys/types.h>

#include <sys/ipc.h>

函数原型:

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

参数:

pathname:指定的文件,此文件必须存在且可存取

proj_id:计划代号(project ID),可以在[0, 255]中随机取一个数字。

函数返回值

  • 成功:返回key_t值(即IPC 键值)
  • 出错:-1,错误原因存于error中
    ftok函数把从pathname导出的信息与id的低序8位组合成一个整数IPC键(也就是shmget函数中需要的key值)。

  注意:如果在使用ftok的这段时间里,pathname指向的文件或者目录被删除而且又重新创建,ftok是能够成功返回的,但是由于inode可能不同了,返回值也就不同于所希望的值,由于key值相同,使用同一块共享内存,这时候就可能会导致key值不同,双方无法共享一块内存,所以这个pathname一定要是一个不会被修改的文件路径。


  对于shmget的第二个参数size,代表这个共享内存的大小,最好是页(PAGE:4096byte)的整数倍,(OS和磁盘进行I/O操作的基本单位也是4096byte),如果设置为4097byte,虽然只是多了一个字节,但是系统底层可能会创建4096 * 2的空间,即使这样,也只能使用自己申请的大小(4097byte),不能使用4096*2的空间。

第三个参数shmflg:

  • 该参数用于确定共享内存属性。
  • 使用上为:标志位 | 内存权限
  • 标志位参数有两种:IPC_CREAT、IPC_EXCL
  • 使用TPC_CREAT | 0666(内存权限):创建共享内存若底层存在,就获取,并返回,若不存在,就新建,并返回。
  • 使用TPC_CREAT | IPC_EXCL | 0666(内存权限):创建共享内存若底层存在,则出错返回。若不存在,则新建,并返回。所以返回的一定是一个全新的shm.

shmat函数

功能:将共享内存段连接到进程地址空间

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

参数:

shmid: 共享内存标识

shmaddr:指定连接的地址

shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

使用这个函数建立物理内存和虚拟内存的映射。

shmaddr设置为nullptr表示让内核自己确定位置。

shmflg设置为0为默认方式。

使用shmat函数可以简单的这样:

char* shmaddr = (char*)shmat(shmid, nullptr, 0);

  • shmaddrNULL,核心自动选择一个地址
  • shmaddr不为NULLshmflgSHM_RND标记,则以shmaddr为连接地址。
  • shmaddr不为NULLshmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
  • shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离

原型:int shmdt(const void *shmaddr);

参数: shmaddr: 由shmat所返回的指针

返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段功能

shmctl函数

功能:用于控制共享内存

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

参数:

shmid:由shmget返回的共享内存标识码

cmd:将要采取的动作(有三个可取值)

buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0;失败返回-1

 cmd的三个命令:

命令 说明
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID 删除共享内存段

shmctl常用IPC_RMID,用于删除共享内存空间。

代码实现

//server.cpp
// 由server再共享内存里面读取信息
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <string>
#include <cstdlib>
const int SIZE = 4094;
int main()
{
    key_t key = ftok(".", 113);
    if (key < 0)
    {
        perror("ftok");
        exit(1);
    }
    int shmid = shmget(key, SIZE, IPC_CREAT | 0666);
    if (shmid < 0)
    {
        perror("shmget");
        exit(2);
    }
    char *addr = (char *)shmat(shmid, NULL, 0);
    int i = 0;
    while (i < 20)
    {
        i++;
        printf("client# %s\n", addr);
        fflush(stdout);
        memset(addr, 0, sizeof(addr)); // 每次写入都清空,保证共享内存中没有上一次的数据残留。
        sleep(1);
    }
    shmdt(addr);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}
// client.cpp
// 由client再共享内存里面发送信息
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <cstdlib>
const int SIZE = 4094;
int main()
{
    key_t key = ftok(".", 113);
    if (key < 0)
    {
        perror("ftok");
        exit(1);
    }
    int shmid = shmget(key, SIZE, IPC_CREAT | 0666);
    if (shmid < 0)
    {
        perror("shmget");
        exit(2);
    }
    char *addr = (char *)shmat(shmid, NULL, 0);
    int i = 0;
    while (i < 15)
    {
        fgets(addr, 100, stdin);
        fflush(stdin);
        sleep(1);
        i++;
    }
    shmdt(addr);
    return 0;
}
// makeflie
.PHONY:all
all:server client
server:server.cpp
  g++ -o $@ $^
client:client.cpp
  g++ -o $@ $^
.PHONY:clean
clean:
  rm -f server client

  上面的代码是实现一个由客户端进行在终端上进行写入信息到共享内存,服务端从共享内存读出到终端打印到屏幕上的一个过程。


    😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

  • List item
相关文章
|
27天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
31 3
|
5天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
26 4
linux进程管理万字详解!!!
|
2天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
4天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
24 4
|
5天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
6天前
|
消息中间件 存储 Linux
|
8天前
|
存储 缓存 监控
|
13天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
15 1
|
18天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
27天前
|
存储 缓存 监控
Linux中内存和性能问题
【10月更文挑战第5天】
36 4