【并发程序设计】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;
}
相关推荐
Wanliang Li11 分钟前
Linux电源管理——CPU Hotplug 流程
linux·嵌入式硬件·嵌入式·armv8·电源管理·cpuhotplug
m0_6896182812 分钟前
数学建模助力干细胞研究,配体纳米簇如何影响干细胞命运
笔记·数学建模
fnd_LN19 分钟前
Linux文件目录 --- mkdir命令,创建目录,多级目录,设置目录权限
linux·运维·服务器
析木不会编程35 分钟前
【C语言】动态内存管理:详解malloc和free函数
c语言·开发语言
达帮主36 分钟前
7.C语言 宏(Macro) 宏定义,宏函数
linux·c语言·算法
行思理1 小时前
Linux 下SVN新手操作手册
linux·运维·svn
羊村懒哥1 小时前
tomcat-安装笔记(包含虚拟主机配置)
java·笔记·tomcat
茶猫_1 小时前
力扣面试题 39 - 三步问题 C语言解法
c语言·数据结构·算法·leetcode·职场和发展
初学者丶一起加油1 小时前
C语言基础:指针(数组指针与指针数组)
linux·c语言·开发语言·数据结构·c++·算法·visual studio
紫阡星影1 小时前
【模块系列】STM32&1.69TFT屏幕
stm32·单片机·嵌入式硬件