什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?

共享内存(shared memory)

  • 共享内存
    • 1、背景
    • 2、定义
    • 3、两种方式(mmap上一篇博客说明)
      • 3.2、shmget
        • 3.2.1、使用流程和基本原理
        • 3.2.2、函数原型(创建共享内存)
        • 3.2.3、其他相关AP函数
          • 3.2.3.1、shmat ( ):挂接共享内存
          • 3.2.3.2、shmdt ( ):去关联共享内存
          • 3.2.3.3、shmctl ( ):控制共享内存
        • 3.2.4、应用实例
    • 4、mmap和shm的区别
  • 参考

共享内存

1、背景

当存在客户-服务程序中复制文件时候,其数据流如下,要经历四次数据复制,开销很大。具体如下:

进程调用read或是write后会陷入内核,因为这两个函数都是系统调用,进入系统调用后,内核开始读写文件,

  • 假设内核在读取文件,内核首先把文件读入自己的内核空间,
  • 读完之后进程在内核回归用户态,内核把读入内核内存的数据再copy进入进程的用户态内存空间。
  • 实际上我们同一份文件内容相当于读了两次,先读入内核空间,再从内核空间读入用户空间。

什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?_第1张图片
如果采用共享内存的方式,那么将大大优化IO操作,数据流变成了如下,数据只复制两次,[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。

  • Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改,实现对文件的读取和修改;
  • 普通文件映射到进程地址空间后,进程可以向访问内存的方式对文件进行访问,不需要其他系统调用(read,write)去操作。

什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?_第2张图片

2、定义

  • 共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存
  • 不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。
  • 如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
  • 这是最快的进程间通信方式,但是不提供同步功能(需要我们信号量实现)。

什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?_第3张图片

3、两种方式(mmap上一篇博客说明)

mmap参考链接

这里简单对mmap() 和 shmget()基本原理简单说明
mmap() 基本原理:可以把一个文件映射到进程的地址空间(进程使用的虚拟内存),这样进程就可以通过读写这个进程地址空间来读写这个文件。

  • mmap()通过映射一个普通文件实现共享内存

shmget() 基本原理:内核里存在着一个特殊的文件系统(shm),这个文件系统的存储介质不是别的,正是 RAM。

  • shmget() 调用之后,系统会为你在这个文件系统上创建一个文件,但是这个时候仅仅是创建了这个文件。
  • 然后你就应该调用 shmat() 了,调用 shmat() 之后,内核会这个文件映射到你的进程地址空间,这个时候你就能直接读写映射后的地址了。

总结

  • 创建共享内存。通过函数shmget()从内存中获取一块共享内存区域,该函数返回值为共享内存的ID。
  • 映射共享内存。通过函数shmat()将上一步获取的共享内存映射到具体的内存空间。
    什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?_第4张图片

3.2、shmget

3.2.1、使用流程和基本原理

使用流程

  • 1、进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。
  • 2、系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符
  • 3、内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel结构体
  • 4、内核在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentryinode结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。

所有这一切都是系统调用shmget完成的。(下面的图结合上面的流程,理解起来可能稍微容易一些)

基本原理

每一个共享内存区都有一个控制结构struct shmid_kernelshmid_kernel是共享内存区域中非常重要的一个数据结构,它是存储管理和文件系统结合起来的桥梁。

struct shmid_kernel /* private to the kernel */
{   
    struct kern_ipc_perm    shm_perm;
    struct file *       shm_file;
    int         id;
    unsigned long       shm_nattch;
    unsigned long       shm_segsz;
    time_t          shm_atim;
    time_t          shm_dtim;
    time_t          shm_ctim;
    pid_t           shm_cprid;
    pid_t           shm_lprid;
};

该结构中最重要的一个域应该是shm_file,它存储了将被映射文件的地址。
什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?_第5张图片
内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。
1、上图中的shm_ids.entries变量指向一个ipc_id结构数组,而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针
2、对于系统V共享内存区来说,kern_ipc_perm的宿主是shmid_kernel结构shmid_kernel是用来描述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。
3、在shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件,这样,共享内存区域就与shm文件系统中的文件对应起来。

3.2.2、函数原型(创建共享内存)

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

返回值:

成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1并设置错误码。

参数说明:

  • key:非0整数,它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
  • size:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。
  • shmflg:如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
    • 举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

3.2.3、其他相关AP函数

3.2.3.1、shmat ( ):挂接共享内存

第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);

返回值:

调用成功时返回一个指向共享内存第一个字节的指针,并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);出错返回-1。

参数说明:

  • 1、shm_id:由shmget()函数返回的共享内存标识。
  • 2、shm_addr:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址
  • 3、shm_flg:是一组标志位,通常为0
3.2.3.2、shmdt ( ):去关联共享内存

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

int shmdt(const void *shmaddr);

返回值:

调用成功时返回0,失败时返回-1.

参数说明:

  • shmaddr:shmat()函数返回的地址指针
3.2.3.3、shmctl ( ):控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

返回值:
成功返回0,失败返回-1。

参数说明:

  • 1、shm_id:shmget()函数返回的共享内存标识符。
  • 2、command:要采取的操作,它可以取下面的三个值 :
    • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
    • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
    • IPC_RMID:删除共享内存段
  • 3、buf:一个结构指针,它指向共享内存模式和访问权限的结构,设置为NULL即可。

3.2.4、应用实例

实例:进程间通信
shmread.c:创建共享内存,并读取其中的信息,
shmwrite.c:向共享内存中写入数据。
shmdata.h:定义结构shared_use_st中的written作为一个可读或可写的标志,非0:表示可读,0:表示可写,text则是内存中的文件。

shmdata.h的源代码

#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
 
#define TEXT_SZ 2048
 
struct shared_use_st
{
    int written; // 作为一个标志,非0:表示可读,0:表示可写
    char text[TEXT_SZ]; // 记录写入 和 读取 的文本
};
 
#endif

shmread.c的源代码

#include 
#include 
#include 
#include 
#include 
#include 
#include "shmdata.h"
 
int main(int argc, char **argv)
{
    void *shm = NULL;
    struct shared_use_st *shared; // 指向shm
    int shmid; // 共享内存标识符
 
    // 创建共享内存
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
    if (shmid == -1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
 
    // 将共享内存连接到当前进程的地址空间
    shm = shmat(shmid, 0, 0);
    if (shm == (void *)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
 
    printf("\nMemory attached at %X\n", (int)shm);
 
    // 设置共享内存
    shared = (struct shared_use_st*)shm; // 注意:shm有点类似通过 malloc() 获取到的内存,所以这里需要做个 类型强制转换
    shared->written = 0;
    while (1) // 读取共享内存中的数据
    {
        // 没有进程向内存写数据,有数据可读取
        if (shared->written == 1)
        {
            printf("You wrote: %s", shared->text);
            sleep(1);
 
            // 读取完数据,设置written使共享内存段可写
            shared->written = 0;
 
            // 输入了 end,退出循环(程序)
            if (strncmp(shared->text, "end", 3) == 0)
            {
                break;
            }
        }
        else // 有其他进程在写数据,不能读取数据
        {
            sleep(1);
        }
    }
 
    // 把共享内存从当前进程中分离
    if (shmdt(shm) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
 
    // 删除共享内存
    if (shmctl(shmid, IPC_RMID, 0) == -1)
    {
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }
 
    exit(EXIT_SUCCESS);
}

shmwrite.c的源代码

#include 
#include 
#include 
#include 
#include 
#include "shmdata.h"
 
int main(int argc, char **argv)
{
    void *shm = NULL;
    struct shared_use_st *shared = NULL;
    char buffer[BUFSIZ + 1]; // 用于保存输入的文本
    int shmid;
 
    // 创建共享内存
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
    if (shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }
 
    // 将共享内存连接到当前的进程地址空间
    shm = shmat(shmid, (void *)0, 0);
    if (shm == (void *)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
 
    printf("Memory attched at %X\n", (int)shm);
 
    // 设置共享内存
    shared = (struct shared_use_st *)shm;
    while (1) // 向共享内存中写数据
    {
        // 数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
        while (shared->written == 1)
        {
            sleep(1);
            printf("Waiting...\n");
        }
 
        // 向共享内存中写入数据
        printf("Enter some text: ");
        fgets(buffer, BUFSIZ, stdin);
        strncpy(shared->text, buffer, TEXT_SZ);
 
        // 写完数据,设置written使共享内存段可读
        shared->written = 1;
 
        // 输入了end,退出循环(程序)
        if (strncmp(buffer, "end", 3) == 0)
        {
            break;
        }
    }
 
    // 把共享内存从当前进程中分离
    if (shmdt(shm) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
 
    sleep(2);
    exit(EXIT_SUCCESS);
}

什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?_第6张图片
分析:

  • 1、程序shmread创建共享内存,然后将它连接到自己的地址空间。

    • 在共享内存的开始处使用了一个结构struct_use_st。该结构中有个标志written
      • 当共享内存中有其他进程向它写入数据时,共享内存中的written被设置为0,程序等待。
      • 当它不为0时,表示没有进程对共享内存写入数据,程序就从共享内存中读取数据并输出,然后重置设置共享内存中的written为0,即让其可被shmwrite进程写入数据。
  • 2、程序shmwrite取得共享内存并连接到自己的地址空间

    • 检查共享内存中的written,是否为0
      • 不为0,表示共享内存中的数据还没有被完,则等待其他进程读取完成,并提示用户等待。
      • 为0,表示没有其他进程对共享内存进行读取,则提示用户输入文本,
      • 再次设置共享内存中的written为1,表示写完成,其他进程可对共享内存进行读操作

4、mmap和shm的区别

linux中的两种共享内存。一种是我们的IPC通信System V版本的共享内存(shm),另外的一种就是内存映射I/O(mmap函数)

  • 1、mmap()通过映射一个普通文件实现共享内存,shmget():内核里存在着一个特殊的文件系统(shm),这个文件系统的存储介质不是别的,正是 RAM。
    • 所以,mmap可以看到文件的实体,而 shmget 对应的文件在交换分区上的 shm 文件系统内,无法直接 cat 查看
    • 速度比较:shm保存在RAM,这样读写的速度要比磁盘要快,但是存储量不是特别大。mmap是在磁盘上建立一个文件
    • 安全比较: mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。
  • 2、mmap是在磁盘上建立一个文件,每个进程地址空间中都会开辟出一块空间进行文件-内存的映射
    而对于shm而言,shm每个进程最终会映射到同一块物理内存。
    • mmap 方式下各进程映射文件的相同部分可以共享内存, shmget 时各个进程共享同一片物理内存区

参考

1、https://www.cnblogs.com/huxiao-tee/p/4660352.html
2、https://blog.csdn.net/mj813/article/details/52082499
3、https://www.cnblogs.com/52php/p/5861372.html
4、https://blog.csdn.net/woaiclh13/article/details/106409361

你可能感兴趣的:(操作系统,linux)