问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

进程通信——共享内存

创作时间:
作者:
@小白创作中心

进程通信——共享内存

引用
CSDN
1.
https://blog.csdn.net/qq_34799070/article/details/141752555

进程间通信(IPC)是操作系统中的一个重要概念,它允许不同的进程之间交换数据和信息。在各种IPC机制中,共享内存因其高效性而备受关注。本文将详细介绍共享内存的基本概念、工作原理以及具体的使用方法,并通过代码示例帮助读者更好地理解这一机制。

1. 基本认识

1.1 概念介绍

不同进程的资源通常是独立的,他们所占用的内存互相独立,不可互相访问。而共享内存允许多个进程访问同一块内存区域,从而实现数据的共享和交换。这是一种高效的进程通信方法。

在共享内存中,多个进程可以将同一块物理内存,映射到它们各自的虚拟地址中,使它们可以直接读写该内存的内容,而无需通过消息传递等其它通信方式,也就是“完全无需额外的拷贝”。这种直接的内存访问,使得数据交换更高效。

1.2 主要原理

要理解共享内存,首先要理解“虚拟内存”这一概念。虚拟内存是进程直接操作的地址,不同进程可以存在相同的虚拟内存地址,因为实际所代表的物理地址并不相同。

每个进程内的虚拟地址是连续的,但实际映射在物理地址上,却不是连续的。通常来说各进程之间不会共用物理内存地址,如下图所示:

进程A和进程B各自的虚拟内存,分别映射在实际物理内存的不同区域上。

那么,共享内存就是改变这种映射关系,让不同的进程“共用物理内存”。如下图所示:

这个时候,进程A的第3块虚拟内存,与进程B的第1块虚拟内存,就指向同一块物理内存了。也就是它们共享内存了。之后其中一个改写了这块内存的内容,另一个就可以直接读到,省去了中间(系统内核)的数据拷贝。这就是所谓“共享内存效率最高”的原因。

但从中我们不难看出,共享内存也是有缺点的,那就是大家都是读写同一块内存,很容易冲突。所以共享内存通常还需要搭配其它同步机制使用。

2. 使用方法

共享内存的使用并不难,就是改变内存映射,获取到同一个指向共享内存的地址,然后读写数据。

2.1 创建共享内存shmget

第一步是创建一个共享内存区域,并分配一块内存来存储数据。

2.1.1 shmget

#include <sys/ipc.h>
#include <sys/shm.h>
/***********************************
* @para key 共享内存的键值,用于标识共享内存段。通常使用 ftok 函数生成键值。
* @para size 共享内存段的大小,以字节为单位
* @para shmflg 共享内存的标志位,用于指定创建共享内存的权限和行为
* @return 成功时,返回共享内存的标识符(即共享内存的ID)
* 		  失败时,返回-1,并设置相应的错误码
* 
*********************************/
int shmget(key_t key, size_t size, int shmflg);
//example
key_t key = ftok(".", 123); //详见下一节
int shmid = shmget(key, 1024, IPC_CREAT | 0666);

2.1.2 ftok

上述出现了一个ftok函数,这个函数返回了一个key_t。这个有什么作用呢?其实,这个 key 是不同进程间使用同一片物理内存的关键。如果要进行共享,就大家都得使用同一个 key。否则,系统如何知道哪几个进程之间要共享呢。

而 ftok 则是使用一个“路径(文件或目录)”加一个“立即数”,来产生一个 key。只要大家的这两个参数相同,计算出来的 key 就是一致的。

其原型如下:

#include <sys/types.h>
#include <sys/ipc.h> 
key_t ftok(const char *pathname, int proj_id);
参数:
pathname:一个存在的文件路径名,用于生成键值。通常选择一个已经存在的文件。
proj_id:一个整数,用于区分不同的IPC资源。通常选择一个非零的整数。
返回值:
如果成功,返回一个键值(key)。
如果失败,返回-1,并设置相应的错误码。

关于ftok,还有几个要点:

  • ftok 的路径可以随便设置,但必须是实际存在的
  • ftok 只是想取文件 inode 这个唯一数值,和文件权限无关
  • proj_id 在一些系统(如 UNIX)上,实际取值是1到255。

为什么对 ftok 说那么多,因为这个 key 对很多进程间通信都是一个关键。不只是共享内存,其它诸如消息队列、信号量之类的方法,也会用到。

2.2 映射地址空间shmat

第二步就是,将创建出的共享内存空间,映射到本进程的虚拟地址空间。映射使用的是 shmat,原型如下:

#include <sys/types.h>
#include <sys/shm.h>
char *shmaddr = shmat(shmid, NULL, 0);

2.3 访问共享内存

这一步比较简单,就是向共享内存(也可以理解为自己的地址空间)写数据和读数据。

memcpy(shmaddr, source_buff, source_length); //写 
memcpy(target_buff, shmaddr, target_length); //读

2.4 释放

shm_mdt

2.5 同步和互斥

之前提到过共享内存的不足。由于多个进程可以同时访问共享内存,需要使用同步和互斥机制来确保数据的一致性和正确性。常见的同步机制包括信号量、互斥锁和条件变量等。

2.5.1 信号量

信号量

2.5.2 互斥锁

当互斥锁被定义在共享内存时,可以在不同进程之间使用。

#include <pthread.h>
#define KEY_NUM 126
#define FILE_PATH "./share.txt"
struct T_data
{
    char buff[1000];
    pthread_mutex_t mut_lock;
};
int main()
{
    key_t key = ftok(filepath, key_num);
    if(key < 0)
    {
        return 0;
    }
    int shmid = shmget(key, 1200, IPC_CREATE|0666);//666代表可读可写
    if(shmid < 0)
    {
        return 0;
    }
    char *mem = shmat(shm_id, NULL, 0);
    if(mem < 0)
    {
        printf("shmat error!\n");
        return mem;
    }
    //使用前先加锁
    T_data* = (T_data*)mem;
    pthread_mutex_init(&T_data->mut_lock, NULL);
    pthread_mutex_lock(&T_data->mut_lock);
    pthread_mutex_unlock(&T_data->mut_lock);
    pthread_mutex_destroy(&T_data->mut_lock);
    
    return 0;
}

3. 接口封装

3.1 创建shm_create

#include <sys/types.h>
#include <sys/ipc.h> 
key_t ftok(const char *pathname, int proj_id);
#define KEY_NUM 126
#define FILE_PATH "./share.txt"
int shm_create_with_key(const char *filepath, int len, int key_num)
{
    key_t key = ftok(filepath, key_num);
    if(key < 0)
    {
        printf("create key error!\n");
        return key;
    }
    int shmid = shmget(key, len, IPC_CREATE|0666);//666代表可读可写
    if(shmid < 0)
    {
        return shmid;
        printf("create share mem error!\n")
    }
    return shmid;
}
int shm_create(int len)
{
    return shm_create_with_key(FILE_PATH, len, KEY_NUM);
}

3.2 映射shm_connect

char* shm_connect(int shm_id)
{
    char *mem = shmat(shm_id, NULL, 0);
    if(mem < 0)
    {
        printf("shmat error!\n");
        return mem;
    }
    return mem;
}

3.3 多进程共享内存

#include <sys/types.h>
#include <sys/ipc.h> 
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define KEY_NUM 126
#define FILE_PATH "./share.txt"
//使用文件路径作为共享内存
int shm_create_with_key(const char *filepath, int len, int key_num)
{
    key_t key = ftok(filepath, key_num);
    if(key < 0)
    {
        printf("create key error!\n");
        return key;
    }
    int shmid = shmget(key, len, IPC_CREAT|0666);//666代表可读可写
    if(shmid < 0)
    {
        return shmid;
        printf("create share mem error!\n");
    }
    return shmid;
}
int shm_create(int len)
{
    return shm_create_with_key(FILE_PATH, 100, KEY_NUM);
}
char* shm_connect(int shm_id)
{
    char *mem = (char*)shmat(shm_id, NULL, 0);
    if(mem < 0)
    {
        printf("shmat error!\n");
        return NULL;
    }
    return mem;
}
int main()
{
    int shm_id = shm_create(10);
    if(shm_id < 0)
    {
        printf("shm_create error!\n");
        return -1;
    }   
    char *mem = shm_connect(shm_id);
    if(!mem)
    {
        printf("shm_connect error!\n");
        return -1;
    }
    int i = 10;
    while (--i)
    {
        printf("%s\n", mem);
        sleep(1);
    }
    shmdt(mem);
    return 0;
}
#include <sys/types.h>
#include <sys/ipc.h> 
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "string.h"
#define KEY_NUM 126
#define FILE_PATH "./share.txt"
int shm_create_with_key(const char *filepath, int len, int key_num)
{
    key_t key = ftok(filepath, key_num);
    if(key < 0)
    {
        printf("create key error!\n");
        return key;
    }
    int shmid = shmget(key, len, IPC_CREAT|0666);//666代表可读可写
    if(shmid < 0)
    {
        return shmid;
        printf("create share mem error!\n");
    }
    return shmid;
}
int shm_create(int len)
{
    return shm_create_with_key(FILE_PATH, 100, KEY_NUM);
}
char* shm_connect(int shm_id)
{
    char *mem = (char*)shmat(shm_id, NULL, 0);
    if(mem < 0)
    {
        printf("shmat error!\n");
        return NULL;
    }
    return mem;
}
int main()
{
    int shm_id = shm_create(10);
    if(shm_id < 0)
    {
        printf("shm_create error!\n");
        return 0;
    }   
    char *mem = shm_connect(shm_id);
    if(!mem)
    {
        printf("shm_connect error!\n");
        return 0;
    }
    //初始化
    memcpy(mem, "hello", 6);
    int i = 0;
    while (i<10)
    {
        mem[0] = '1'+i;
        ++i;
        sleep(1);
    }
    shmdt(mem);
    return 0;
}
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号