当前位置: 首页 > news >正文

【Linux】mmap分析

简介

平时我们写Linux驱动和用户空间交互时,都是使用read或者write函数接口,并且在函数内使用copy_to_user或者copy_from_user。因为用户空间是不能直接访问内核空间数据的,只能先将数据拷贝过来,然后再操作。如果用户空间需要传几MB的数据给内核,那么原来的拷贝方式显然效率比较低,这个时候就需要使用mmap操作,简单来说就是让一块物理内存拥有两份映射,即拥有两个虚拟地址,一个在内核空间,一个在用户空间,这样用户空间和内核空间就可以访问同一块内存了。关系如下

在这里插入图片描述

mmap系统调用并不是完全为了用于共享内存而设计的,它本身提供了不同于一般对普通文件的访问方式,普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用readwrite操作。

函数接口

// include<sys/mman.h>
//成功执行时,mmap()返回被映射区的指针,munmap()返回0。
//失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
  • start:要映射到的内存区域的起始地址,通常都是用NULL(NULL即为0)。NULL表示由内核来指定该内存地址。

  • length:要映射的内存区域的大小

  • prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起。

    PROT_EXEC //页内容可以被执行

    PROT_READ //页内容可以被读取

    PROT_WRITE //页可以被写入

    PROT_NONE //页不可访问

  • flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体。

    MAP_FIXED :使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

    MAP_SHARED :对映射区域的写入数据会复制回文件内, 而且允许其他映射该文件的进程共享。

    MAP_PRIVATE :建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

    MAP_DENYWRITE :这个标志被忽略。

    MAP_EXECUTABLE :同上 。

    MAP_NORESERVE :不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。

    MAP_LOCKED :锁定映射区的页面,从而防止页面被交换出内存。

    MAP_GROWSDOWN :用于堆栈,告诉内核VM系统,映射区可以向下扩展。

    MAP_ANONYMOUS :匿名映射,映射区不与任何文件关联。

    MAP_ANONMAP_ANONYMOUS的别称,不再被使用。

    MAP_FILE :兼容标志,被忽略。

    MAP_32BIT :将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。 MAP_POPULATE :为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。

    MAP_NONBLOCK :仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

  • fd:文件描述符(由open函数返回),匿名映射时设为-1。

  • offset:表示被映射对象(即文件)从那里开始对映,通常都是用0。 该值应该为大小为PAGE_SIZE的整数倍。

start:要进行同步的映射的内存区域的起始地址。
length:要同步的内存区域的大小
flag:flags可以为以下三个值之一: 
MS_ASYNC : 请Kernel快将资料写入。 
MS_SYNC : 在msync结束返回前,将资料写入。 
MS_INVALIDATE : 让核心自行决定是否写入,仅在特殊状况下使用int msync(const void *start, size_t length, int flags); 

对映射内存的内容的更改并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你内存的更新就能立即保存到文件里。

vma:用户虚拟内存区域,由内核自动填充。
addr:用户空间中内存映射的起始地址。
pfn:要映射的内核内存的起始页帧号,是物理地址右移PAGE_SHIFT位得到的。
size:映射的内存大小。
prot:页表项的保护属性int remap_pfn_range(struct vm_area_struct *vma,unsigned long addr,unsigned long pfn,unsigned long size,pgprot_t prot);

示例

驱动层代码示例

#include "haptic_misc.h"
#include <linux/init.h>  
#include <linux/module.h>   
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>#define HAPTICS_MISC_DEV_NAME  "haptics"#define BUFF_SIZE (10) 
static unsigned char * buffer=NULL;//打开设备
static int haptics_open(struct inode* inode,struct file * filp)
{pr_info("%s\n",__FUNCTION__);return 0;
}//关闭设备
static int haptics_release(struct inode* inode ,struct file* filp)
{pr_info("%s\n",__FUNCTION__);return 0;
}//读设备
static ssize_t haptics_read(struct file* filp, char __user *buf,size_t count,loff_t* ppos)
{int ret;	int tmp = count ;if (tmp > BUFF_SIZE)tmp = BUFF_SIZE;ret = copy_to_user(buf, buffer, tmp);return tmp;
}//写设备
static ssize_t haptics_write(struct file* filp,const char __user *buf,size_t count,loff_t* ppos)
{int ret;int tmp = count ;if (tmp > BUFF_SIZE )tmp = BUFF_SIZE ;memset(buffer,0,BUFF_SIZE);ret = copy_from_user(buffer, buf, tmp);return tmp;
}static int haptics_mmap(struct file *file, struct vm_area_struct *vma)
{unsigned long page;unsigned long start = (unsigned long)vma->vm_start;unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);//得到物理地址page = virt_to_phys(buffer);    //将用户空间的一个vma虚拟内存区映射到以page开始的一段连续物理页面上if(remap_pfn_range(vma,start,page>>PAGE_SHIFT,size,PAGE_SHARED))//第三个参数是页帧号,由物理地址右移PAGE_SHIFT得到return -1;return 0;
}static struct file_operations haptics_fops=
{.owner = THIS_MODULE,.open = haptics_open,.release = haptics_release,.read = haptics_read,.write = haptics_write,.mmap = haptics_mmap,
};static struct miscdevice mdev =
{.minor = MISC_DYNAMIC_MINOR,.name = HAPTICS_MISC_DEV_NAME,.fops = &haptics_fops,
};//定义一个杂项设备结构体int register_misc_dev(void)
{int ret = 0;ret = misc_register(&mdev);if(0 == ret){pr_info("misc_register ok minor=%d\n",mdev.minor);}buffer = kzalloc(PAGE_SIZE,GFP_KERNEL);memcpy(buffer,"i am .....",BUFF_SIZE);return ret;
}int deregister_misc_dev(void)
{misc_deregister(&mdev);kfree(buffer);pr_info("%s\n",__FUNCTION__);return 0;
}

应用层代码示例

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include "haptics_ioctl.h"
/*
argc:应用程序参数个数,包括应用程序本身
argv[]:具体的参数内容,字符串形式
./main <filename> <r:w> r表示读 w表示写
*/
#define PAGE_SIZE 4096
static char msg[10]="abcdefghjk";
int main(int argc,char * argv[])
{char* filename;int fd=0;if(argc!=3){printf("error usage\n");return -1;}filename=argv[1];fd = open(filename,O_RDWR);if(fd<0){printf("can not open file %s\n",filename);return -2;}char* addr = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);if(0 == strcmp(argv[2],"r"))//设备文件测试读接口{char buf[10]={0};read(fd,buf,10);printf("read %s\n", buf);}else if(0 == strcmp(argv[2],"w"))//设备文件测试写接口{char buf[10]={0};memcpy(buf,msg,10);write(fd,buf,10);printf("read %s\n", addr);}else//普通文件测试接口{printf("read %s\n", addr);char msg[128] = "hello i am new kernel driver......hahaha";memcpy(addr,msg,128);}close(fd);
}
mmap设备文件示例
root@RK3588:/bin# ./main /dev/haptics r
[  101.150745] haptics_open
read i am .....!
[  101.151100] haptics_release
root@RK3588:/bin# ./main /dev/haptics w
[  104.609699] haptics_open
read abcdefghjk
[  104.610043] haptics_release
mmap普通文件示例
root@RK3588:/bin# vi test
root@RK3588:/bin# ./main test 1
read i am kernel driver.....hahaharoot@RK3588:/bin# cat test
hello i am new kernel driver..root@RK3588:/bin#

总结

  1. 映射内存大小的基本单位的是页,也就是PAGE_SIZE的整数倍。
  2. 我们一般会先申请一块连续的、大小是PAGE_SIZE的整数倍的虚拟内存,但是有的文档上写着需要SetPageReserved,这个地方还需要继续深入。。。。。。
  3. 对于普通文件映射时,修改内存内容后,是否不会立即更新到文件中,而是有一段延时,需要调用msync函数来显示同步。这个地方也还没有验证。。。。。。
  4. 对于设备文件映射时,如果应用层修改了映射内存的内容,驱动层好像是没有通知之类的东西去知道的。这里一般的做法是,在映射内存中定义一个flag,驱动层可以去轮询检查flag的值,就知道映射内存是否有更新了。
http://www.lqws.cn/news/558901.html

相关文章:

  • Excel限制编辑:保护表格的实用功能
  • 嵌入式网络通信与物联网协议全解析:Wi-Fi、BLE、LoRa、ZigBee 实战指南
  • Linux环境安装Redis的多种方式分析
  • Flutter基础(Isolate)
  • cocos creator 3.8 - 精品源码 - 六边形消消乐(六边形叠叠乐、六边形堆叠战士)
  • docker解析
  • Netty 揭秘CompositeByteBuf:零拷贝优化核心技术
  • Flutter基础(路由页面跳转)
  • Neo4j无法建立到 localhost:7474 服务器的连接出现404错误
  • Nacos源码之服务拉取(RestTemplate)
  • 访问不了/druid/index.html (sql.html 或 login.html)
  • CPU内部总线方式对比
  • 领域驱动设计(DDD)【20】之值对象(Value Object):入门
  • Spring Cloud 微服务(负载均衡策略深度解析)
  • nt!IoSynchronousPageWrite函数分析之atapi!IdeReadWrite----非常重要
  • 23种设计模式——策略模式:像换口红一样切换你的算法
  • Learning to Prompt for Continual Learning
  • 数据结构与算法 --- 双向链表
  • 问卷标记语言(QML):简化调查问卷设计与部署的XML解决方案
  • 【YOLOv13保姆级教程#03】自建数据集训练与验证(Train Val)全流程 | 手把手教你构建数据集、标签格式转换与yaml配置
  • Go开发工程师-Golang基础知识篇
  • Vue工程化实现约定式路由自动注册
  • 使用vue3构建一套网站
  • TCP 和 UDP 是什么?
  • 【Python基础】06 实战:视频压缩迷你脚本设计
  • 深入理解C#委托操作:添加、移除与调用全解析
  • 港澳地区,海外服务器ping通可能是地区运营商问题
  • MySQL为什么要使用b+树
  • 1 Studying《Computer Architecture A Quantitative Approach》1-4
  • 鸿蒙HarmonyOS 5小游戏实践:数字记忆挑战(附:源代码)