进程通讯—共享内存对象

38 篇文章 0 订阅
15 篇文章 0 订阅

进程通讯—共享内存

1.共享内存的定义与原理

1.1定义

共享内存是指允许多个不相关的进程访问同一个逻辑内存区域。这种内存区域在物理上通常是同一段内存,但在虚拟地址空间中,不同进程通过各自的页表将其映射到这一共同的物理内存区域。

1.2原理

在Linux系统中,每个进程都有自己的进程控制块(PCB)和地址空间(Addr Space),以及与之对应的页表。通过页表,进程的虚拟地址被映射到物理地址。当两个或多个进程通过页表将虚拟地址映射到同一块物理内存区域时,这块区域即成为共享内存。这样,当一个进程修改共享内存中的数据时,其他进程通过访问共享内存就能立即看到这些改动。

2.共享内存的特点

  1. 高效性:共享内存是进程间通信中最快的方式之一,因为它减少了数据复制的次数,直接在内存中进行数据交换。
  2. 灵活性:进程可以灵活地读写共享内存中的数据,实现复杂的数据交换和同步机制。
  3. 复杂性:共享内存的使用需要额外的同步机制(如信号量、互斥锁等)来避免数据竞争和条件竞争等问题。

2.创建共享内存对象

2.1库函数shm_open()和 shm_unlink()

shm_open() 可以开启一块内存共享对象,我们可以像使用一般文件描述符一般使用这块内存对象

2.2函数描述:

#include <sys/mman.h>
/**
* const char *name: 这是共享内存对象的名称,直接写一个文件名称,本身会保存在
/dev/shm 。名称必须是唯一的,以便不同进程可以定位同一个共享内存段。
* 命名规则:必须是以正斜杠/开头,以\0 结尾的字符串,中间可以包含若干字符,但
不能有正斜杠
* int oflag: 打开模式 二进制可拼接
* (1) O_CREAT:如果不存在则创建新的共享内存对象
* (2) O_EXCL:当与 O_CREAT 一起使用时,如果共享内存对象已经存在,则返
回错误(避免覆盖现有对象)
* (3) O_RDONLY:以只读方式打开
* (4) O_RDWR:以读写方式打开
* (5) O_TRUNC 用于截断现有对象至 0 长度(只有在打开模式中包含 O_RDWR 时
才有效)。
* mode_t mode: 当创建新共享内存对象时使用的权限位,类似于文件的权限模式,一般
0644 即可
* return: 成功执行,它将返回一个新的描述符;发生错误,返回值为 -1
*/
int shm_open(const char *name, int oflag, mode_t mode);
/**
* 
* 删除一个先前由 shm_open() 创建的命名共享内存对象。尽管这个函数被称为
“unlink”,但它并没有真正删除共享内存段本身,而是移除了与共享内存对象关联的名称,
使得通过该名称无法再打开共享内存。当所有已打开该共享内存段的进程关闭它们的描述
符后,系统才会真正释放共享内存资源
 * 
* char *name: 要删除的共享内存对象名称
* return: 成功返回 0 失败返回-1
*/
int shm_unlink(const char *name);

3.分配共享内存大小

3.1库函数truncate()和 ftruncate()

​ truncate 和 ftruncate 都可以将文件缩放到指定大小,二者的行为类似:如果文件 被缩小,截断部分的数据丢失,如果文件空间被放大,扩展的部分均为\0 字符。缩放前后 文件的偏移量不会更改。缩放成功返回 0,失败返回-1。

​ 不同的是,前者需要指定路径,而后者需要提供文件描述符;ftruncate 缩放的文件 描述符可以是通过 shm_open()开启的内存对象,而 truncate 缩放的文件必须是文件系 统已存在文件,若文件不存在或没有权限则会失败

3.2函数描述:

#include <unistd.h>
#include <sys/types.h>
/**
* 将指定文件扩展或截取到指定大小
* 
* char *path: 文件名 指定存在的文件即可 不需要打开
* off_t length: 指定长度 单位字节
* return: int 成功 0
* 失败 -1
*/
int truncate(const char *path, off_t length);
/**
* 将指定文件描述符扩展或截取到指定大小
* 
* int fd: 文件描述符 需要打开并且有写权限
* off_t length: 指定长度 单位字节
* return: int 成功 0
* 失败 -1
*/
int ftruncate(int fd, off_t length);

4.映射内存地址

4.1库函数mmap()

​ mmap 系统调用可以将一组设备或者文件映射到内存地址,我们在内存中寻址就相当于 在读取这个文件指定地址的数据。父进程在创建一个内存共享对象并将其映射到内存区后, 子进程可以正常读写该内存区,并且父进程也能看到更改。使用 man 2 mmap 查看该系统

4.2函数描述:

#include <sys/mman.h>
/**
* 将文件映射到内存区域,进程可以直接对内存区域进行读写操作,就像操作普通内存一
样,但实际上是对文件或设备进行读写,从而实现高效的 I/O 操作
* 
* void *addr: 指向期望映射的内存起始地址的指针,通常设为 NULL,让系统选择合适
的地址
* size_t length: 要映射的内存区域的长度,以字节为单位
* int prot: 内存映射区域的保护标志,可以是以下标志的组合
* (1) PROT_READ: 允许读取映射区域
* (2) PROT_WRITE: 允许写入映射区域
* (3) PROT_EXEC: 允许执行映射区域
* (4) PROT_NONE: 页面不可访问
* int flags:映射选项标志
* (1) MAP_SHARED: 映射区域是共享的,对映射区域的修改会影响文件和其
他映射到同一区域的进程(一般使用共享)
* (2) MAP_PRIVATE: 映射区域是私有的,对映射区域的修改不会影响原始文
件,对文件的修改会被暂时保存在一个私有副本中
* (3) MAP_ANONYMOUS: 创建一个匿名映射,不与任何文件关联
* (4) MAP_FIXED: 强制映射到指定的地址,如果不允许映射,将返回错误
* int fd: 文件描述符,用于指定要映射的文件或设备,如果是匿名映射,则传入无效的
文件描述符(例如-1)
* off_t offset: 从文件开头的偏移量,映射开始的位置
* return void*: (1) 成功时,返回映射区域的起始地址,可以像操作普通内存那样使
用这个地址进行读写
* (2) 如果出错,返回 (void *) -1,并且设置 errno 变量来表示错
误原因
*/
void *mmap(void *addr, size_t length, int prot, int flags, 
 int fd, off_t offset);
/**
* 用于取消之前通过 mmap() 函数建立的内存映射关系
* 
* void *addr: 这是指向之前通过 mmap() 映射的内存区域的起始地址的指针,这个地
址必须是有效的,并且必须是 mmap() 返回的有效映射地址
* size_t length: 这是要解除映射的内存区域的大小(以字节为单位),它必须与之前通
过 mmap() 映射的大小一致
* return: int 成功 0
* 失败 -1
*/
int munmap(void *addr, size_t length);

5.测试

5.1Makefile

CC :=gcc
shard_memory: shard_memory.cpp
	-$(CC) -o $@ $^
	-./$@ "This is the way"
	-rm ./$@

5.2代码实现

//
// Created by root on 2024/9/11.
//
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc,char *argv[]){
    char shm_name[100];
    sprintf(shm_name,"/letter%d",getpid());
    int fd=shm_open(shm_name,O_RDWR | O_CREAT,0664);
    if(fd<0){
        perror("shm_open error");
        exit(EXIT_FAILURE);
    }

    //设置共享内存大小
    ftruncate(fd,1024);
    //内存映射
    void *share=mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(share==MAP_FAILED){
        perror("mmap error");
        exit(EXIT_FAILURE);
    }
    //关闭文件描述符
    close(fd);

    //通讯
    pid_t pid=fork();
    if(pid<0){
        perror("fork error");
        exit(EXIT_FAILURE);
    }else if(pid==0){
        strcpy((char *)share,"This is the way");
    }else{
        waitpid(pid,NULL,0);
        printf("%s\n",(char*)share);
        //释放内存映射区
        int res=munmap(share,1024);
        if (res==-1){
            perror("munmap error");
            exit(EXIT_FAILURE);
        }
    }

    //释放共享内存对象
    shm_unlink(shm_name);
    return 0;
}

6.临时文件系统

​ Linux 的临时文件系统(tmpfs)是一种基于内存的文件系统,它将数据存储在 RAM 或者在需要时部分使用交换空间(swap)。tmpfs 访问速度快,但因为存储在内存,重启 后数据清空,通常用于存储一些临时文件。

​ 我们可以通过 df -h 查看当前操作系统已挂载的文件系统。

7.内存共享对象在临时文件系统中的表示

内存共享对象在临时文件系统中的表示位于/dev/shm 目录下。 为看到共享对象在临时文件系统中的表示,我们修改程序,在创建后不销毁

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
int main()
{ 
 char *share;
 pid_t pid;
 char shmName[100]={0};
 sprintf(shmName,"/letter%d",getpid());
 printf("shmName: %s\n", shmName);
 // 共享内存对象的文件标识符
 int fd;
 fd = shm_open(shmName, O_CREAT | O_RDWR, 0644);
 if (fd < 0) 
 { 
 perror("共享内存对象开启失败!\n");
 exit(EXIT_FAILURE);
 } 
 while(1);
 return 0; 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值