【并发程序设计】12.内存映射

12.内存映射

使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write,更加高效。

用到的函数

mmap函数

  1. 原型

    c 复制代码
    #include <sys/mman.h>
    void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
  2. 功能:创建共享内存映射

  3. 参数

    • addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。

    • length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。

    • prot:指定共享内存的访问权限。可取如下几个值的可选:

      • PROT_READ(可读)

      • PROT_WRITE(可写)

      • PROT_EXEC(可执行)

      • PROT_NONE(不可访问)

    • flags:由以下几个常值指定:

      1. MAP_SHARED(共享映射)

      2. MAP_PRIVATE(私有映射)

      3. MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正)

      4. MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)

        其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。

    • fd:表示要映射的文件句柄。如果匿名映射写-1。

    • offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。

  4. 返回值

    • 成功返回创建的映射区首地址
    • 失败返回MAP_FAILED,设置errno值

lseek函数

  1. 原型

    c 复制代码
    #include <sys/types.h>
    #include <unistd.h>
    off_t lseek(int fd, off_t offset, int whence);
  2. 功能:用于在文件中定位文件指针的位置

  3. 参数

    • fd:文件描述符,通常是通过openfopen函数获得的。

    • offset:相对于whence的偏移量,以字节为单位。

    • whence:表示参考点的位置,可以是以下值之一:

      :表示参考点的位置,可以是以下值之一:

      • SEEK_SET:文件开头
      • SEEK_CUR:文件当前位置
      • SEEK_END:文件结尾
  4. 返回值

    • 成功时,返回新的文件指针位置。
    • 失败时,返回-1,并设置errno

示例1

利用内存映射实现两个无亲缘关系的线程间的通信

write.c

c 复制代码
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
	void *addr;
	int fd;
	fd = open("test",O_RDWR);
	if(fd<0)
	{
		perror("open");
		return 0;
	}

	//获取文件大小
	int fileSize = lseek(fd, 0, SEEK_END);
	lseek(fd, 0, SEEK_SET); // 将文件指针重置到文件开头

	//映射文件到内存
	addr = mmap(NULL,2000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(addr == MAP_FAILED)
	{
		perror("mmap");
		close(fd);
		return 0;
	}

	//写入文件
	int i = 0;
	printf("%d\n",fileSize);
	while(i < 2000)
	{
		printf("%d\n",i);
		memcpy(addr+i,"5",1);
		sleep(1);
		i++;
	}
	
	return 0;
}

read.c

c 复制代码
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
	void *addr;
	int fd;
	fd = open("test",O_RDWR);
	if(fd<0)
	{
		perror("open");
		return 0;
	}

	//获取文件大小
	int fileSize = lseek(fd, 0, SEEK_END);
	lseek(fd, 0, SEEK_SET); // 将文件指针重置到文件开头

	//映射文件到内存
	addr = mmap(NULL,2000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(addr == MAP_FAILED)
	{
		perror("mmap");
		close(fd);
		return 0;
	}

	//读取
	int i = 0;
	printf("%d\n",fileSize);
	while(i < 2000)
	{
		printf("%s\n",(char *)addr);
		sleep(1);
	}
	
	return 0;
}

示例2匿名映射

利用内存映射匿名映射实现两个亲缘线程间的通信

c 复制代码
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(){

    void *addr;
    
    //匿名映射
    addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if(addr == MAP_FAILED)
    {
        perror("mmap");
        return 0;
    }

    //创建子进程
    pid_t pid;
    pid = fork();
    if(pid<0)
    {
        perror("fork");
        return 0;
    }

    //父进程
    else if(pid>0)
    {
        memcpy(addr,"1234567890",10);
        wait(NULL);//回收子进程
    }

    //子进程
    else 
    {
        sleep(1);
        printf("read father val=%s\n",(char *)addr);
    }
    munmap(addr,2048);//释放内存
}

注意事项

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。

  2. 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。

    当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。

  3. 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。

  4. 用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)

  5. 文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误)

  6. 映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误 ,超出映射的内存大小报段错误

  7. 映射内存大小为4K的整数倍,若申请映射大小不为4K的整数倍,则会自动向上补齐。例如申请2k,实际可以操作的内存大小为4K。

System V共享内存

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
  • 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
  • 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用

共享内存使用步骤:

  1. 生成key
  2. 创建/打开共享内存
  3. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
  4. 读写共享内存
  5. 撤销共享内存映射
  6. 删除共享内存对象

ftok函数

  1. 原型

    c 复制代码
    #include <sys/types.h>
    #include <sys/ipc.h>
    key_t **ftok**(const char *pathname, int proj_id);
  2. 功能 :生成一个唯一的与文件相关的键值(key),这个键值通常用于IPC(进程间通信)中,比如创建共享内存或信号量等。

  3. 参数

    • pathname:必须是存在的且可访问的文件路径。它可以是一个普通文件或者目录,但该路径下的某个文件需要具有相应的权限,以便其他进程可以通过该路径访问到相同的键值。
    • proj_id:是一个8位的整数(即范围在0到255之间)。它与文件的索引节点号结合生成最终的键值。
  4. 返回值

    • 如果函数执行成功,会返回一个key_t类型的键值
    • 如果执行失败,则返回-1
  5. 注意

    • 确保pathname指定的文件存在且可访问。

    • proj_id的值虽然作为int类型传递,但实际上只有8个比特被使用,因此其取值范围是0到255。

    • 生成的键值是由proj_id的后8个比特、文件所在设备的最后两位和文件的索引节点号的最后四位组成的

shmget函数

  1. 原型

    c 复制代码
    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);
  2. 功能:获取或创建一个共享内存段,并返回一个共享内存标识符。

  3. 参数

    • key:共享内存的键值,可以是0(IPC_PRIVATE)以创建一个新的共享内存对象,或者是大于0的32位整数。如果shmflg中包含了IPC_CREAT标志,且提供的键值已经存在,则会返回现有的共享内存标识符。
    • size:指定需要共享的内存容量,以字节为单位。
    • shmflg:权限标志,与open函数的mode参数类似。如果希望在键值指定的共享内存不存在时创建它,可以与IPC_CREAT进行或操作。
  4. 返回值

    • 如果成功,shmget函数返回一个共享内存标识符,该标识符可以在后续的共享内存相关操作中使用,如shmatshmdtshmctl等。
    • 如果失败,返回-1。
  5. 注意

    • 不同进程可以通过shmget返回的共享内存标识符访问同一块共享内存。
    • 当写数据到共享内存时,需要注意同步问题,即在进程间访问共享内存时要加锁,以防止数据竞争。
    • 在使用共享内存时,应确保结构体中的缓冲区已经声明了足够的大小,而不是仅使用一个指针并在需要时通过malloc分配内存,因为这样分配的地址其他进程无法访问

ipcs 命令

  1. 格式ipcs [options]
  2. 功能ipcs 命令用于提供系统中进程间通信设施的信息,包括消息队列、共享内存段和信号量。
  3. 参数
    • -a--all:显示所有类型的 IPC 资源信息。
    • -b--broad:显示 IPC 资源的宽泛信息,包括最大允许的尺寸等。
    • -m--shmems:仅显示共享内存段的信息。
    • -q--queues:仅显示消息队列的信息。
    • -s--semaphores:仅显示信号量的信息。
    • -i <id>--identifier <id>:显示特定 IPC 资源 ID 的详细信息。
    • -l--limits:显示系统限制信息。
    • -u--summary:显示 IPC 资源的摘要信息。
    • -p--pid:显示创建 IPC 资源的进程 ID。
    • -t--time:显示最后操作 IPC 资源的时间。
    • -c--creator:显示 IPC 资源的创建者信息。
    • -h--human:以人类可读的格式显示大小。
    • -v--version:显示 ipcs 命令的版本信息。
    • -V--help:显示帮助信息。
  4. 示例
    • ipcs -a:显示所有类型的 IPC 资源信息。
    • ipcs -m:仅显示共享内存段的信息。
    • ipcs -q:仅显示消息队列的信息。
    • ipcs -s:仅显示信号量的信息。
    • ipcs -u:显示 IPC 资源的摘要信息。
    • ipcs -i 1234:显示 ID 为 1234 的 IPC 资源的详细信息。

shmat函数

  1. 原型

    c 复制代码
    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);
  2. 功能:将创建好的共享内存段连接到某个进程,并指定内存空间

  3. 参数

    • shmid:是通过shmget函数返回的共享内存标识符,它唯一标识了一个共享内存段。
    • shmaddr:是一个可选参数,用于指定共享内存映射到进程地址空间中的起始地址。如果传递NULL,则由系统自动选择一个地址进行映射。
    • shmflg:是一组标志位,用于控制共享内存的访问权限和其他属性。常见的标志包括SHM_RDONLY(只读访问)和SHM_WRONLY(只写访问)。
  4. 返回值

    • 成功,返回一个指向共享内存段在进程地址空间中映射的起始位置的指针。这个指针可以用于访问共享内存中的数据。
    • 失败,返回-1

shmdt函数

  1. 原型

    c 复制代码
    #include <sys/shm.h>
    int shmdt(const void *shmaddr);
  2. 功能:从进程的地址空间中分离已经附加的共享内存段

  3. 参数shmaddr是一个指向共享内存段的指针,这个指针通常是通过shmat函数返回的。

  4. 返回值

    • 成功,shmdt函数返回0
    • 失败,返回 -1,并设置 errno 以指示错误原因。

shmctl函数

  1. 原型

    c 复制代码
    #include <sys/shm.h>
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  2. 功能:控制共享内存段,包括删除和修改权限等操作

  3. 参数

    • shmid:是通过shmget函数返回的共享内存标识符,唯一标识了一个共享内存段。
    • cmd:是控制命令,用于指定要执行的操作
      • IPC_STAT(获取共享内存的状态)
      • IPC_SET(设置共享内存的状态)
      • IPC_RMID(删除共享内存段)
    • buf:是一个指向shmid_ds结构体的指针,用于存储或接收共享内存的状态信息。在某些命令下,如IPC_STAT和IPC_SET,需要提供这个参数。
  4. 返回值

    • 成功,shmctl函数返回0;
    • 出错,返回 -1,并设置 errno 以指示错误原因

示例

创建和使用共享内存

write.c

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

int main() {
    key_t key;  // 定义一个键值变量
    int shmid;  // 定义一个共享内存标识符变量
    char *buf;  // 定义一个指向共享内存的指针变量

    key = ftok("keytest", 100);  // 生成一个唯一的键值
    if (key < 0) 
    {
        perror("ftok");  
        return 0;
    }
    printf("key=%x\n", key);  // 打印生成的键值

    shmid = shmget(key, 512, IPC_CREAT | 0666);  //创建并获取共享内存段
    if (shmid < 0) 
    {
        perror("shmget");  
        return 0;
    }

    printf("shmid=%d\n", shmid);  // 打印共享内存标识符

    buf = shmat(shmid, NULL, 0);  // 将共享内存段附加到进程的地址空间中
    if (buf < 0) 
    {
        perror("shmat");  
        return 0;
    }

    strcpy(buf, "hello world");  // 将字符串"hello world"复制到共享内存中
}

read.c

c 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

int main() {
    key_t key;  // 定义一个键值变量
    int shmid;  // 定义一个共享内存标识符变量
    char *buf;  // 定义一个指向共享内存的指针变量

    key = ftok("keytest", 100);  // 生成一个唯一的键值
    if (key < 0) 
    {
        perror("ftok");  
        return 0;
    }
    printf("key=%x\n", key);  // 打印生成的键值

    shmid = shmget(key, 512, 0666);  // 获取共享内存段
    if (shmid < 0) 
    {
        perror("shmget");  
        return 0;
    }

    printf("shmid=%d\n", shmid);  // 打印共享内存标识符

    buf = shmat(shmid, NULL, 0);  // 将共享内存段附加到进程的地址空间中
    if (buf < 0) 
    {
        perror("shmat");  
        return 0;
    }

    printf("read = %s\n",buf);
      
    if(shmdt(buf) == -1)//共享内存段从进程的地址空间中分离
        perror("shmdt");
    if(shmctl(shmid, IPC_RMID, NULL) == -1)//删除共享内存段
        perror("shmctl");
    
    return 0;
}
相关推荐
一只小菜鸡..19 小时前
南京大学 操作系统 (JYY) 学习笔记:访问操作系统的对象与“一切皆文件”
笔记·学习
longerxin202020 小时前
nano编辑器插入、编辑完整操作教程(Linux日志/配置专用)
linux·运维·编辑器
茉莉玫瑰花茶1 天前
Windows 平台下的 UDP 客户端
stm32·单片机·嵌入式硬件
听风3471 天前
Arch Linux 软件安装完全指南
linux·运维·archlinux·pacman
网络安全零基础教程1 天前
网络安全春招面试避坑与复盘指南
linux·网络·安全·web安全·面试·职场和发展
摇滚侠1 天前
《RocketMQ 官网》阅读笔记 RocketMQ 消息队列 MessageQueue 消息 Messagege
笔记·rocketmq
扛枪的书生1 天前
TigerVNC 安装流程
linux
Mortalbreeze1 天前
深入 Linux Socket 编程:端口号、网络字节序与 struct sockaddr 详解
linux·服务器·网络·c++
磨十三1 天前
U-Boot
arm开发·嵌入式硬件
躺柒1 天前
读半导体简史08大型机
linux·服务器·c语言·数据库·unix·ibm