Linux下的系统编程——共享存储映射(十)

前言:

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

目录

一.文件间进程通信:

二、存储映射I/O:

三、mmap函数:

1.创建共享内存映射:

2.释放映射区:

*3.使用注意事项:

*4.mmap函数的保险调用方式:

5.mmap父子进程通信:

[6.无血缘关系进程间 mmap 通信:](#6.无血缘关系进程间 mmap 通信:)


一.文件间进程通信:

打开的文件是内核中的一块缓冲区。多个无血缘关系的进程,可以同时访问该文件。

两个完全独立没有血缘关系的进程文件之间也可以完成进程间的通信

test1.c 先执行,将数据写入文件test.txt

cpp 复制代码
/*
 * 先执行,将数据写入文件test.txt
 */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#define N 5

int main(void)
{
    char buf[1024];
    char *str = "--------------secesuss-------------\n";
    int ret;

    int fd = open("test.txt", O_RDWR|O_TRUNC|O_CREAT, 0664);

    //直接打开文件写入数据
    write(fd, str, strlen(str));
    printf("test1 write into test.txt finish\n");

    sleep(N);

    lseek(fd, 0, SEEK_SET);
    ret = read(fd, buf, sizeof(buf));
    ret = write(STDOUT_FILENO, buf, ret);

    if (ret == -1) {
        perror("write second error");
        exit(1);
    }

    close(fd);

    return 0;
}

test2.c后执行,尝试读取另外一个进程写入文件的内容

cpp 复制代码
/*
 * 后执行,尝试读取另外一个进程写入文件的内容
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(void)
{
    char buf[1024];
    char *str = "----------test2 write secesuss--------\n";
    int ret;

    sleep(2);   //睡眠2秒,保证test1将数据写入test.txt文件

    int fd = open("test.txt", O_RDWR);

    //尝试读取test.txt文件中test1写入的数据
    ret = read(fd, buf, sizeof(buf));   

    //将读到的数据打印至屏幕
    write(STDOUT_FILENO, buf, ret);

    //写入数据到文件test.txt中, 未修改读写位置
    write(fd, str, strlen(str));

    printf("test2 read/write finish\n");

    close(fd);

    return 0;
}

二、存储映射I/O:

存储映射I/o(Memory-mapped l/o)使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成l/o操作。

使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过 mmap函数来实现。

三、***mmap函数:

1.创建共享内存映射:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数:

addr: 指定映射区的首地址。通常传NULL,表示让系统自动分配

length: 共享内存映射区的大小。(<= 文件的实际大小)

prot: 共享内存映射区的读写属性。

PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE

flags: 标注共享内存的共享属性。

MAP_SHARED、MAP_PRIVATE

fd: 用于创建共享内存映射区的那个文件的文件描述符。

offset: 默认0,表示映射文件全部。偏移位置。需是 4k 的整数倍。

返回值:

成功:映射区的首地址。

失败:MAP_FAILED (void*(-1)), errno

2.释放映射区:

int munmap(void *addr, size_t length);

addr:mmap 的返回值

length:大小

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc,char *argv[])
{

	char *p = NULL;
	int fd;

    //打开或者创建tetsmap文件
	fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);
	if(fd == -1){
		sys_err("open error");
	}

	/*两个函数等价于ftruncate()函数
	 	lseek(fd,10,SEEK_END);
		write(fd,"\0",1);
	*/

	ftruncate(fd,20);
	int len = lseek(fd,0,SEEK_END);

	p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(p == MAP_FAILED){
		sys_err("mmap error");
	}

    //使用p对文件进行读写操作

	strcpy(p,"hello mmap\n");

	printf("-------%s\n",p);//写操作

	int ret = munmap(p,len);
	if(ret == -1){
		sys_err("munmap error");
	}
	return 0;
}

*3.使用注意事项:

cpp 复制代码
语法
open()方法语法格式如下:

os.open(file, flags[, mode]);

参数
file -- 要打开的文件

flags -- 该参数可以是以下选项,多个使用 "|" 隔开:

O_RDONLY: 以只读的方式打开
O_WRONLY: 以只写的方式打开
O_RDWR : 以读写的方式打开
O_NONBLOCK: 打开时不阻塞
O_APPEND: 以追加的方式打开
O_CREAT: 创建并打开一个新文件
O_TRUNC: 打开一个文件并截断它的长度为零(必须有写权限)
O_EXCL: 如果指定的文件存在,返回错误
O_SHLOCK: 自动获取共享锁
O_EXLOCK: 自动获取独立锁
O_DIRECT: 消除或减少缓存效果
O_FSYNC : 同步写入
O_NOFOLLOW: 不追踪软链接



1)PROT_READ:表示内存段内的内容可写;

2)PROT_WRITE:表示内存段内的内容可读;

3)PROT_EXEC:表示内存段中的内容可执行;

4)PROT_NONE:表示内存段中的内容根本没法访问。

(1). 用于创建映射区 的文件大小为 0,实际指定非0大小创建映射区,出 "总线错误"。

cpp 复制代码
	fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);  //O_RDWR : 以读写的方式打开
	if(fd == -1){
		sys_err("open error");
	}

    //ftruncate(fd,20);
	//int len = lseek(fd,0,SEEK_END);

	int len = 20;

    //PROT_READ:表示内存段内的内容可写;   PROT_WRITE:表示内存段内的内容可读

    p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);      //可读可写

(2) 用于创建映射区 的文件大小为 0,实际制定0大小创建映射区, 出**"无效参数"**。

cpp 复制代码
	fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);    //O_RDWR : 以读写的方式打开
	if(fd == -1){
		sys_err("open error");
	}
    
    //ftruncate(fd,20);
	//int len = lseek(fd,0,SEEK_END);

	int len = 0;

    //PROT_READ:表示内存段内的内容可写;   PROT_WRITE:表示内存段内的内容可读

    p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);      //可读可写

(3). 用于创建映射区的文件读写属性为,只读 。映射区属性为 读、写。 出 "无效参数"。

cpp 复制代码
	fd = open("testmap",O_RDONLY|O_CREAT|O_TRUNC,0644);    //O_RDONLY: 以只读的方式打开
	if(fd == -1){
		sys_err("open error");
	}

	ftruncate(fd,20);
	int len = lseek(fd,0,SEEK_END);

	//int len = 0;

    //PROT_READ:表示内存段内的内容可写;   PROT_WRITE:表示内存段内的内容可读

    p = mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);      //可读可写

(4). 创建映射区 ,需要read权限。当访问权限指定为 "共享**"MAP_SHARED**是, mmap的读写权限,应该 <=文件的open权限。只写不行。

cpp 复制代码
	//fd = open("testmap",O_RDONLY|O_CREAT|O_TRUNC,0644);
	fd = open("testmap",O_WRONLY);    //文件的只写
	if(fd == -1){
		sys_err("open error");
	}
	ftruncate(fd,20);
	int len = lseek(fd,0,SEEK_END);

	//int len = 0;

	p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);  //文件只写
cpp 复制代码
	fd = open("testmap",O_RDWR);    //文件的读写

注意:当文件是读写的时候可以写,当文件是只读的时候可以是只读。当文件是只写的时候不能写

(5). 文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用地址访问。

cpp 复制代码
	//fd = open("testmap",O_RDONLY|O_CREAT|O_TRUNC,0644);
	fd = open("testmap",O_RDWR);
	if(fd == -1){
		sys_err("open error");
	}
	ftruncate(fd,20);
	int len = lseek(fd,0,SEEK_END);

	//int len = 0;

	p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);//文件只写
	if(p == MAP_FAILED){
		sys_err("mmap error");
	}
	
	close(fd);    //创建映射区完成即可关闭

(6). offset 必须是 4096的整数倍 。(MMU 映射的最小单位 4k )

cpp 复制代码
p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,1000);
cpp 复制代码
p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,4096);

(7). 对申请的映射区内存,不能越界访问。

cpp 复制代码
p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);

               .......

strcpy(p+len+4096*4,"hello mmap\n");

(8). munmap用于释放的地址,必须是mmap申请返回的地址。

cpp 复制代码
p = mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);

               .......

strcpy(p++,"hello mmap\n");

解决方法:

cpp 复制代码
 char *temp = p;
 strcpy(temp++,"hello mmap\n");

(9). 映射区访问权限为 "私有"MAP_PRIVATE, 对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。

cpp 复制代码
	int fdfd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);
	//fd = open("testmap",O_RDWR);
	
	if(fd == -1){
		sys_err("open error");
	}
	ftruncate(fd,20);
	int len = lseek(fd,0,SEEK_END);
	

	//int len = 0;
	p = mmap(NULL,len,PROT_WRITE|PROT_READ,MAP_PRIVATE,fd,0);//可读可写

(10).映射区访问权限为 "私有"MAP_PRIVATE, 需要open文件时,有读权限用于创建映射区即可。

cpp 复制代码
    int fd;
	//int fd = open("testmap",O_RDWR|O_CREAT|O_TRUNC,0644);
	fd = open("testmap",O_RDONLY);    //O_RDONLY: 以只读的方式打开
	
	if(fd == -1){
		sys_err("open error");
	}
	//ftruncate(fd,20);
	int len = lseek(fd,0,SEEK_END);
	

	//int len = 0;
	p = mmap(NULL,len,PROT_WRITE|PROT_READ,MAP_PRIVATE,fd,0);//可读可写

	if(p == MAP_FAILED){
		sys_err("mmap error");
	}

*4.mmap函数的保险调用方式:

  1. fd = open("文件名", O_RDWR);

  2. mmap(NULL , 有效文件大小 , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

*5.mmap父子进程通信:

父子等有血缘关系的进程之间也可以通过 mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:

MAP_PRIVATE:(私有映射)父子进程各自独占映射区;

MAP_SHARED:(共享映射 )父子进程共享映射区;

父进程创建映射区,然后fork子进程,子进程修改映射区内容,而后,父进程读取映射区内容,查验是否共享。

思路:

父进程先创建映射区。 open( O_RDWR) mmap( MAP_SHARED );

指定 MAP_SHARED 权限

fork() 创建子进程。

一个进程读 , 另外一个进程写。

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

//全局变量
int var = 100;

int main(void)
{
	int *p;
	pid_t pid;

	int fd;
	fd = open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
	if(fd < 0){
		perror("open error");
		exit(1);
	}
	unlink("temp");
	ftruncate(fd,4);

    p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);    //MAP_SHARED共享映射
  //p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);   //MAP_PRIVATE私有映射

	if(p == MAP_FAILED){        //注意:不是p == NULL
		perror("mapp error");
		exit(1);
	}

	close(fd);           //映射区建立完毕,即可关闭文件

	pid = fork();        //创建子进程

	if(pid == 0){        //子进程
		*p = 2000;       //写共享内存
		var = 1000;
		printf("child *p = %d,var = %d\n",*p,var);
	}else if(pid > 0){    //父进程
		sleep(1);
		printf("parent *p = %d,var = %d\n",*p,var);    //读共享内存
		wait(NULL);

		int ret = munmap(p,4);    //释放映射区
		if(ret == -1){
			perror("munmap error");
			exit(1);
		}
	}

	return 0;
}
cpp 复制代码
//p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);    //MAP_SHARED共享映射
  p = (int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);   //MAP_PRIVATE私有映射

6.无血缘关系进程间 mmap 通信:

两个进程 打开同一个文件,创建映射区。

指定flags 为 MAP_SHARED。

一个进程写入,另外一个进程读出。

【注意】:无血缘关系进程间通信。

mmap:数据可以重复读取。

fifo:数据只能一次读取。

匿名映射:只能用于血缘关系进程间通信

p = (int *)mmap(NULL, 40,PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

写端:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>

struct student {
    int id;
    char name[256];
    int age;
};

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc, char *argv[])
{
    struct student stu = {1, "xiaoming", 18};
    struct student *p;
    int fd; 

    fd = open("test_map", O_RDWR|O_CREAT|O_TRUNC, 0664);
//    fd = open("test_map", O_RDWR);
    if (fd == -1)
        sys_err("open error");

    ftruncate(fd, sizeof(stu));

    p = mmap(NULL, sizeof(stu), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        sys_err("mmap error");

    close(fd);

    while (1) {
        memcpy(p, &stu, sizeof(stu));
        stu.id++;
        sleep(2);
    }
    
    munmap(p, sizeof(stu));

	return 0;
}

读端:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>

struct student {
    int id;
    char name[256];
    int age;
};

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc, char *argv[])
{
    struct student stu;
    struct student *p;
    int fd; 

    fd = open("test_map", O_RDONLY);
    if (fd == -1)
        sys_err("open error");

    p = mmap(NULL, sizeof(stu), PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED)
        sys_err("mmap error");

    close(fd);

    while (1) {
        printf("id= %d, name=%s, age=%d\n", p->id, p->name, p->age);
        sleep(1);
    }
    
    munmap(p, sizeof(stu));

	return 0;
}

7.mmap匿名映射区:

通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open 一个temp文件,创建好了再unlink、close掉,比较麻烦。可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。

匿名映射:只能用于血缘关系进程间通信。

p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;

int main(void)
{
    int *p;
    pid_t pid;

	/*
    int fd,ret;
	fd = open("temp", O_RDWR|O_CREAT|O_TRUNC,0644);
	if(fd == -1){
		perror("open error");
		exit(1);
	}
	
	ret = unlink("temp");
	if(ret == -1){
		perror("unlink error");
		exit(1);
	}
	
	ftruncate(fd,4);
	*/


    p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(p == MAP_FAILED){		//注意:不是p == NULL
        perror("mmap error");
        exit(1);
    }
    //close(fd);

    pid = fork();				//创建子进程
    if(pid == 0){
       *p = 7000;               // 写共享内存
        var = 1000;
        printf("child, *p = %d, var = %d\n", *p, var);
    } else {
        sleep(1);
        printf("parent, *p = %d, var = %d\n", *p, var);     // 读共享内存
        wait(NULL);

        int ret = munmap(p, 4);				//释放映射区
        if (ret == -1) {
            perror("munmap error");
            exit(1);
        }
    }

    return 0;
}
cpp 复制代码
	int fd = open("/dev/zero",O_RDWR);    ///dev/zero ---》 '\0'

    p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    // p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(p == MAP_FAILED){		//注意:不是p == NULL
        perror("mmap error");
        exit(1);
    }
    close(fd);
相关推荐
夜听莺儿鸣1 天前
502-003_Linux 中断与异常(一):从Linux角度理解中断与异常
linux·中断与异常·linux中断
杨云龙UP1 天前
TDengine 3.4.2.8 Community 三节点三副本生产集群部署实战(DNode/MNode/taosAdapter/Explorer)
大数据·linux·运维·数据库·tdengine·时序库
向成科技1 天前
XC3576H工控主板|深度适配Ubuntu 26.04 LTS,释放边缘AI与工业开发新潜能
linux·人工智能·ubuntu·机器人·硬件·主板·边缘ai
Fcy6481 天前
Linux下 进程间关系与守护进程
linux·运维·服务器·守护进程
码农小韩1 天前
Linux驱动理论(二)——Linux字符设备驱动
linux·嵌入式软件开发·linux操作系统·linux应用开发·linux驱动理论
well06121 天前
Linux粘滞位与Makefile机制深度解析
linux·运维·服务器
再写一行代码就下班1 天前
linux sh脚本在windows修改导致无法使用解决方式
java·linux·centos
额额额对了1 天前
SPI通信
linux·c语言·汇编·单片机·嵌入式硬件·arm
子木HAPPY阳VIP1 天前
Ubuntu 关闭防火墙操作步骤
linux·运维·ubuntu
王振超wzc1 天前
嵌入式开发环境搭建--WM软件安装,Ubuntu操作系统安装
linux·运维·ubuntu