【Linux】进程间的通信之共享内存

简介: 【Linux】进程间的通信之共享内存

一、system V 内存共享原理

利用内存共享进行进程间的通信的原理其实分为以下几个步骤:

  1. 在物理内存中创建一块共享内存。
  2. 将共享内存链接到要通信的进程的页表中,并通过页表进行进程地址空间的映射。
  3. 进程地址空间映射完毕以后返回首个虚拟地址,以便于进程之间进行通信。

根据共享内存的原理,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,这也就大大的提高了进程间通信的速度。


系统中可以用共享内存进行通信,但是系统中可能不是只有一对进程使用共享内存,在任何一个时刻,可能有多个共享内存在被用来进行通信,所以系统中一定会存在很多共享内存块,操作系统也要整体管理所有的共享内存。

所以共享内存,不是我们想的那样,只要在内存中开辟空间即可,系统也要为了管理共享内存,构建对应的描述共享内存的结构体对象! 在Linux中这个结构体是struct shmid_ds,这个结构体里面存放了共享内存的各种属性。

所以:共享内存 = 共享内存的内核数据结构(struct shmid_ds) + 真正开辟的内存空间

二、共享内存的使用

共享内存的使用涉及很多的系统调用,在这里我们边实现代码边进行讲解。

1、ftok函数

key_t是一个inttypedef

这个函数的作用就是根据你传递的两个参数,结合函数内部的算法生成一个key_t类型的值,这里我们暂时把这个值记为 key,这个key几乎不可能出现:参数的不同,结果生成相同的key值。

这个key值单独看起来是没有任何作用的,它要与其他的系统调用结合起来才会发挥作用,未来要通信的两个进程就可以在函数ftok输入相同的相同的参数,生成相同的key值,然后通过这个key值就能确定它们想要通信的共享内存是哪一个了。

  • 参数:第一个是路径名,第二个是项目ID(只有最低8位可以使用),我们可以给这两个参数随意赋值,但是这个文件路径必须是现有的,可以访问的的文件,项目ID最低8位必须有效,而且必须非零。
  • 返回值:返回一个key_t类型的值,如果调用失败,返回-1,错误码被设置。

2、shmget函数

这个函数的作用是:在物理内存中申请一块共享内存,并返回一个和参数key相关联的这块共享内存的标识符。

  • 参数
  1. 第一个参数是用ftok函数生成的key值。
  2. 第二个参数是设置共享内存的大小(你实际得到的大小),但是这个共享内存的大小实际上操作系统是按照PAGE_SIZE(4KB)对齐,来分配的。
  3. 第三个参数是标志位,这里主要介绍三个标志:IPC_CREATIPC_EXCLmode_flags
    IPC_CREAT:如果与key有关共享内存不存在,就创建,如果已经存在就直接返回共享内存的标识符。

    IPC_EXCL: 这个标志要与IPC_CREAT一起使用,表示共享内存如果不存在就创建,如果存在就直接报错,通过这两个标志位的组合,我们能够保证我们拿到的共享内存一定是最新的,而不是以前其他进程可能使用过的。

    mode_flags : 最低9位有效,表示申请的共享内存的权限。
  • 返回值:调用成功,返回一个和参数key相关联的这块共享内存的标识符,调用失败就返回 -1,错误码被设置。

3、shmat函数

这个函数的作用就是将我们申请得到的共享内存与进程地址空间进行映射,映射完毕以后返回给我们一个映射好以后的首个虚拟地址,通过这个虚拟地址我们就可以进行进程间通信了。

  • 参数
  1. 来自于shmget函数得到的共享内存标识符。
  2. 一个虚拟地址,我们想要将共享内存的地址在进程地址空间的哪里开始映射,如果这里设置为NULL操作系统会自动选择一个合适的地址进行映射。
  3. 标志位,当这里传入SHM_RDONLY时,表示映射以后的进程对于此段空间只有读权限,没有写权限,当我们在这里传入0时,表示读写权限都有。
  • 返回值
    如果函数调用成功就返回映射好以后的首个虚拟地址,以便于进行读取和写入,如果调用失败就会返回一个(void *) -1的值

4、shmdt函数

当我们在进程地址空间中挂起了一个块共享内存之后当我们不需要使用时,也要将进程地址空间中的链接关系进行取消,shmdt函数的作用就是如此。

  • 参数shmat函数返回的地址。
  • 返回值: 如果调用成功就返回0,调用失败就返回-1

5、shmctl函数

此函数的功能很强大,里面有许多的标志位,可以完成许多不同的工作,这个函数主要用来控制共享内存。

  • 参数
  1. 来自于shmget函数得到的共享内存标识符。
  2. 是标志位,里面有许多标志,这里我们就介绍两个: IPC_RMIDIPC_STAT

    IPC_RMID: 设置这个标记表示要销毁共享内存,调用者必须是共享内存的创建者,或者是特权用户。

    IPC_STAT: 将shmid相关的内核数据结构中的信息复制到第三个参数buf所指向的shmid_ds结构中,调用者必须要有读权限。
  3. 一个struct shmid_ds类型的指针,在标志位中设置了IPC_STAT,指针所指向的变量里面就能拿到相关的内核信息,如果不关心内核信息可以设置为nullptr
  • 返回值:一般来说,成功返回是0,错误返回结果是-1

6、代码使用

command .hpp

#ifndef __COMMAND_HPP__
#define __COMMAND_HPP__
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#define NUM 4096
const std::string pathname = "./";
const int proj_id = 66;
#endif

sysserve.cpp

#include "command.hpp"
int main()
{
    // 1.生成相同的key值
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key == -1)
    {
        std::cerr << "错误码" << errno << " " << strerror(errno) << std::endl;
        exit(-1);
    }
    // 2.申请共享内存
    umask(0);
    int shmid = shmget(key, NUM, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        std::cerr << "错误码" << errno << " " << strerror(errno) << std::endl;
        exit(-1);
    }
    // 停顿3s观察连接数变为 1
    sleep(2);
    // 3.将共享内存挂接到进程地址空间
    char *s = (char *)shmat(shmid, nullptr, 0);
    // 4.进行进程间的通信
    sleep(6); // 启动另外一个要通信进程,看到连接数变为2
    // 5.取消挂接
    int err = shmdt(s);
    if (err == -1)
    {
        std::cerr << "错误码" << errno << " " << strerror(errno) << std::endl;
    }
    sleep(3);
    // 6.删除共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}

sysclient.cpp

#include "command.hpp"
int main()
{
    // 1.生成相同的key值
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key == -1)
    {
        std::cerr << "错误码" << errno << " " << strerror(errno) << std::endl;
        exit(-1);
    }
    // 2.得到共享内存标识符
    int shmid = shmget(key, NUM, IPC_CREAT);
    if (shmid == -1)
    {
        std::cerr << "错误码" << errno << " " << strerror(errno) << std::endl;
        exit(-1);
    }
    // 3.将共享内存挂接到进程地址空间
    char *s = (char *)shmat(shmid, nullptr, 0);
    // 4.进行进程间的通信
    sleep(3);
    // 5.取消挂接
    int err = shmdt(s);
    if (err == -1)
    {
        std::cerr << "错误码" << errno << " " << strerror(errno) << std::endl;
    }
    return 0;
}

Linux的命令行中我们可以使用ipcs -m命令查看共享内存的详细信息。

可以看到第一个是共享内存的key值,第二个是共享内存的标识符,第三个是拥有者,第四个是权限,第五个是共享内存的字节数,第六个是共享内存的连接数,第七个是共享内存的状态。

运行上面的代码,先启动sysserve再启动sysclient,我们可以看到连接数由0->1 ->2 ->1 ->0 ->被删除

三、一些细节的补充

  1. 共享内存的生命周期是随内核的,即进程退出以后如果没有删除共享内存,则共享内存不会消失!
  2. 共享内存没有任何保护机制(同步和互斥)
相关文章
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
140 16
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
87 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
119 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
85 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
83 0
|
7月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
7月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
353 5
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
428 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。