linux -- mmap 共享内存

1、函数定义

#include <sys/mman.h>

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

1.1、mmap说明

mmap()用来将某个文件的内容映射到内存中,对该区域的存取即直接对该文件内容的读写

  • addr:
    要映射的内存起始地址 通常传NULL,让内核自己选,映射成功会返回该地址

  • length
    将文件中多大的部分映射到内存。
    对于小文件,可以直接设置为文件的大小,然后将 offset 设置为 0

  • prot: 设置映射区域的访问方式。有以下组合
    PROT_EXEC 可执行
    PROT_READ 可读
    PROT_WRITE 可写
    PROT_NONE 不可存取

  • flages:设置映射区域的特性
    MAP_SHARED :
    对映射区域写入数据会复制回文件,允许其他映射该文件的进程共享
    MAP_PRIVATE :
    设置为私有,对此区域的修改不会影响原来的文件内容
    MAP_ANONYMOUS :
    建立匿名映射,会忽略参数fd,不涉及文件,映射区域不和其他进程共享
    (还有其他参数,这里只是初步了解mmp,就不过多介绍)

  • fd
    open返回的文件描述符

  • offset :
    文件的偏移量,必须是sysconf(_SC_PAGESIZE)返回的页面大小的整数数。
    可以为 0,代表从文件最前方开始映射

  • 返回值;
    成功 返回一个映射区域的内存起始地址。
    失败 返回值MAP_FAILED(即(void *) -1)
  • 错误代码:
    EBADF:参数fd无效。
    EACCES:存取权限有误:
    MAP_PRIVATE情况下文件必须可读;MAP_SHARED则要用PROT_WRITE,并且文件能写。
    EINVAL:参数addr、length、offset、有一个不合法
    EAGAIN:文件被锁住,或者太多内存被锁住 ENOMEM:内存不足

1.2、mumap说明

  • 删除指定地址的范围映射,当进程终止时,也会自动取消映射
  • 关闭文件描述符不会取消该区域的映射
  • 成功返回0。失败返回-1

1.3、简单使用

int fd = open("./a.txt",O_CREAT | O_RDWR|O_TRUNC, 0666);
write(fd,"12345678\n",10);
char* p = (char*)mmap(NULL,10,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);

2、利用共享内存实现进程间通信

2.1、普通文件映射(无亲缘关系的两个进程)

  • 程序1往内存中
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>

typedef struct{
    int age;
    char name[4];
    int flag;
} STU;

int main() {
    int fd;
    char tmp[] = "hh";
    STU *p;

    //生成一个5倍STU结构大小的空文件
    fd = open("a.txt",O_CREAT|O_TRUNC|O_RDWR,0777);
    lseek(fd, sizeof(STU)*5 - 1,SEEK_SET);
    write(fd," ",1);

    p = (STU *) mmap(NULL, sizeof(STU)*10,
                     PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
    sleep(4);//等程序2先读
    printf("开始映射\n");
   //写入10倍STU结构大小的内存
    for (int i = 0; i < 10 ; i++) {
        (p+i)->age = i+20;
        (p+i)->flag = 1;
        memcpy(&((p+i)->name),tmp,2);
        printf("name: %s,   age: %d   flag: %d\n",p[i].name,p[i].age,p[i].flag);
        sleep(1);
    }
    sleep(6);//等待读程序
    munmap(p, sizeof(STU)*10);
    printf("映射结束\n");
    return 0;
}
  • 程序2读
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

typedef struct{
    int age;
    char name[4];
    int flag;
} STU;
int main() {
    int fd,i = 0;
    STU *p_map,*p;

    fd = open("a.txt",O_CREAT|O_RDWR,0777);
    p_map = (STU *) mmap(NULL, sizeof(STU)*10,
                         PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);

    printf("开始读取\n");
    p = p_map;
    while (i<10){
        if(p->flag == 1){
            printf("name: %s,   age: %d\n",p->name,p->age);
            p++;
            i++;
            sleep(1);
        }
        else
        sleep(1);
    }

    munmap(p_map, sizeof(STU)*10);
    p = NULL;
    printf("读取结束\n");
    return 0;
}
  • 开两个终端分别执行程序1,程序2,下图是程序2的两次执行结果
  • 第一次是先执行程序2先读,再执行程序1写
    当读到age = 24后,程序2会等待,直到程序1写入后面的内容
  • 第二次是先执行程序1写,并等其结束后再执行程序2读
    程序2会一直等待
    在这里插入图片描述

2.2、匿名映射(父子进程)

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <stdlib.h>

typedef struct{
    int count;
    char data[10];
} Info;

int main() {
    int i = 0;
    pid_t pid;
    Info *p;

    p = (Info *) mmap(NULL, sizeof(Info)*3,
                     PROT_READ|PROT_WRITE,
                     MAP_SHARED|MAP_ANONYMOUS,0,0);

    pid = fork();
    if(pid == 0){
        sleep(2);//等待父进程写完
        while (i<3){
         printf("child read:count=%d     message=%s\n",p[i].count,p[i].data);
          i++;
        }
        //修改共享内存的内容
        i=i-1;
        memcpy(&((p+i)->data),"child ",6);

        munmap(p, sizeof(Info)*3);
        exit(0);
    }
    else if (pid > 0){
        for (i = 0; i < 3 ; i++) {
            (p+i)->count = i+1;
            memcpy(&((p+i)->data),"parent",6);
        }
        sleep(3);//等待子进程读完

        i=i-1;
        printf("parent read:count=%d      message=%s \n\n",p[i].count,p[i].data);
        wait(NULL);
        munmap(p, sizeof(Info)*3);
    }
    return 0;
}

在这里插入图片描述

2.3、 结论

1、映射的内存可以同时读写
2、文件一旦被映射,调用mmap的进程对返回地址的操作时作用在内存上
3、只有调用munmmap时才会写回文件,且所写的内容不会超过文件的大小
4、mmap()返回的地址可用于父子进程共享

3、地址越界问题

映射时可用的内存空间大小受限于被映射文件的大小,但不完全受限于文件大小

在这里插入图片描述即使被映射的文件只有一个字节,内核也会为其分配一个页面大小的内存,对这个页面的任意位置访问不会出错,但对该页面以外的地址访问就会出错。

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

typedef struct{
    int count;
    char data[10];
} Info;
int main(int argc,char **argv) {
    int fd,pagesize,offset = 0;
    Info *p;
		
	//设置偏移量
    pagesize = (int)sysconf(_SC_PAGESIZE);//页面大小
    printf("页面大小 = %d\n",pagesize);
    if(argc == 1){
        printf("偏移量 = %d\n",offset);
    }
    else {
        offset = pagesize;
        printf("偏移量 = %d\n",offset);
    }

	//文件大小介于两个页面之间
    fd = open("a.txt",O_CREAT|O_TRUNC|O_RDWR,0644);
    lseek(fd,pagesize*2-100,SEEK_SET);
    write(fd," ",1);

    p = (Info *) mmap(NULL, pagesize*4,
                     PROT_READ|PROT_WRITE,
                     MAP_SHARED,fd,offset);
    close(fd);
    
    printf("开始访问\n");
    for(int i = 1;i < 5;i ++){
        //p + x 的实际大小是 p + x*sizeof(Info)
        (p+pagesize/ sizeof(Info)*i-2)->count = 100;
        printf("访问页面 %d \n",i);
        (p+pagesize/ sizeof(Info)*i-1)->count = 100;
        printf("访问页面 %d 的边界\n",i);
    }
    munmap(p,pagesize*3);
    return 0;
}

在这里插入图片描述

参考:《高质量嵌入式LinuC编程》 梁康 陈明 马小陆 编著 第281页–289页

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值