Linux内核mmap机制

1. 问:如何将物理地址映射到用户空间的虚拟地址上?


2.linux内核mmap机制

2.1.回顾LED驱动数据流的操作过程

通过分析LED驱动,得出以下结论:
如果利用read,write,ioctl三个系统调用函数实现对LED硬件进行操作,这三个系统调用函数操作数据最终要经过两次数据拷贝,
分别是用户空间到内核空间,内核空间到硬件或者硬件到内核,内核到用户;

如果操作访问的数据量比较小,对系统性能的影响几乎可以忽略不计,如果数据量比较大,这种影响不能忽略,例如显卡,摄像头,声卡等;

明确:数据的最终走向要不是用户到硬件,或者硬件到用户;

2.2.linux对于这类设备,在数据访问的时候,为了提供性能,
利用mmap机制将硬件设备的物理地址直接映射到用户空间的虚拟地址上,
将来用户在用户空间访问这个虚拟地址就是在访问对应的物理地址,不再经过内核空间,
将两次数据拷贝转换成一次数据拷贝!

3.mmap实现机制

3.1 本质目的:就是将设备物理地址(物理内存)映射到用户空间的虚拟地址(虚拟内存)上,
将来用户在用户空间访问映射的虚拟地址就是在访问物理地址;不再经过内核空间,将原先的两次数据拷贝(read,write,ioctl)转换成一 次

3.2.回忆linux的mmap应用编程:
void *addr;
int fd = open("a.txt", O_RDWR);
addr = mmap(0, 文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 文件偏移量);
memcpy(addr, "hello,world", 12);

参数说明:
0:让内核帮你在MMAP内存映射区找一块空闲的内存区域用来映射文件;
addr:内核将找到的空闲的虚拟内存区域的首地址告诉给用户,那么即可通过这个地址来访问硬件

说明:“文件”:太抽象,“文件”最终对应的就是一个硬件设备(内存,闪存,硬盘),对应的一个硬件设备的物理地址;
利用mmap将文件映射到用户3G虚拟内存的MMAP内存映射区,存映射区的虚拟地址上,
将来用户访问这个虚拟地址就是在访问硬件(文件);不再经过内核空间

3.3.mmap系统调用过程:
1.当应用程序调用mmap,首先调用到C库的mmap函数
2.C库的mmap函数将会做以下事情:
2.1.保存mmap系统调用号到R7寄存器
2.2.调用svc触发软中断异常
3.跳转到内核准备的异常向量表软中断的处理入口
3.1.从R7寄存器取出mmap的系统调用号
3.2.在以系统调用号为索引,在系统调用表中找到对应的内核函数sys_mmap(由内核实现)
4.内核的sys_mmap将会做以下事情:
4.1.内核在用户3G虚拟内存的MMAP内存映射区找一块空闲的内存区域,将来映射某个硬件设备;
4.2.一旦找到,内核用struct vm_area_struct创建一个对象来描述这块空闲的内存区域;
struct vm_area_struct {
unsigned long vm_start; //空闲内存区域的首地址
unsigned long vm_end;//结束地址
pgprot_t vm_page_prot; //访问权限
unsigned long vm_pgoff; //偏移量
...
};

4.3.最后内核调用底层驱动的mmap函数,
并且内核将描述这块空闲内存区域的信息传递给底层驱动的mmap(对象的首地址传递给底层驱动的mmap),
就等价于内核将空闲内存区域的所有属性告知给底层驱动的mmap

此时此刻,物理地址和用户虚拟地址做好映射了吗?内核没有做映射,直接去调用底层驱动的mmap了

5.底层驱动的mmap函数
struct file_operations {
int (*mmap)(struct file *file, struct vm_area_struct *vma)
}

mmap接口:
功能:底层驱动的mmap永远只做一件事:将物理地址映射到用户虚拟地址上;
参数:
file:文件指针;
vma:此指针指向内核创建的描述内核找的空闲内存区域的对象,底层驱动可以通过此指针获取空闲内区域的属性(起始地址,结束地址...)
目的:
通过芯片手册和原理图能够获取设备的物理地址
通过vma指针能够获取用户虚拟内存区域的信息

问:如何将物理地址最终映射到用户虚拟地址上呢?
答:
明确:此事由底层驱动的mmap来做!

通过调用remap_pfn_range函数来进行;

int remap_pfn_range(struct vm_area_struct *vma,
unsigned long addr,
unsigned long pfn,
unsigned long size,
pgprot_t prot)
函数功能:
1.将已知的物理地址映射到已知的用户虚拟地址上;
参数:
vma:指向内核创建的描述空闲内存区域的对象
addr:空闲内存区域的首地址;vm_start
pfn:将物理地址右移12位
size:空闲虚拟内存的大小,vm_end - vm_start
prot:访问权限,bm_prot
切记:映射时指针的虚拟地址和物理地址必须是页面大小的整数倍;
0xe0200060:配置寄存器物理地址
0xe0200064:数据寄存器物理地址
通过阅读芯片手册,发现GPIO所有寄存器的基地址:0xE0200000,并且寄存器存储空间都是连续的;
其他寄存器的地址只需基地址加一个对应的偏移量即可;所以用mmap做地址映射时,物理地址可以采用0xe0200000,对应的虚拟地址将来也要加上对应的偏移量

物理地址 用户虚拟地址
0xe0200000 A (vm_start)
0xe0200060 A + 0x60
0xe0200064 A + 0x64

#include <linux/mm.h>

切记:对于GPIO操作的设备,利用mmap访问操作的时候,记得要把cache功能屏蔽掉!

案例

利用mmap实现开关灯 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>

//vma:指向空闲虚拟内存区域
static int led_mmap(struct file *file,
                        struct vm_area_struct *vma)
{
    //对于GPIO操作的设备,禁止cache功能
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
    
    //只做一件事:将物理地址映射到用户虚拟地址上
    remap_pfn_range(vma, vma->vm_start,
                    0xe0200000 >> 12,
                    vma->vm_end - vma->vm_start,
                        vma->vm_page_prot);
    return 0;
}

//定义初始化硬件操作方法
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .mmap = led_mmap
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};
static int led_init(void)
{
    //注册混杂设备对象
    misc_register(&led_misc);
    return 0;
}

static void led_exit(void)
{
    //卸载混杂设备对象
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char *argv[])
{
    int fd;
    unsigned char *gpio_vir_base; //0xe0200000对应的用户虚拟地址
    unsigned long *gpiocon, *gpiodata;//配置和数据的用户虚拟地址
    
    if (argc != 2) {
        printf("Usage:\n%s <on|off>\n", argv[0]);
        return -1;
    }
    
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0)
        return -1;
    
    //将0xe0200000物理地址,物理内存大小为0x1000映射到用户虚拟内存上
    gpio_vir_base = mmap(0, 0x1000, PROT_READ|PROT_WRITE,
                            MAP_SHARED, fd, 0);
    
    //获取0xe0200060,0xe0200064的用户虚拟地址
    gpiocon = (unsigned long *)(gpio_vir_base + 0x60);
    gpiodata = (unsigned long *)(gpio_vir_base + 0x64);
   
    //配置GPIO为输出口
    *gpiocon &= ~((0xf << 12) | (0xf << 16));
    *gpiocon |= ((1 << 12) | (1 << 16));
    
    if (!strcmp(argv[1], "on")) 
        *gpiodata |= ((1 << 3) | (1 << 4));
    else
        *gpiodata &= ~((1 << 3) | (1 << 4));

    //解除地址映射
    munmap(gpio_vir_base, 0x1000);
    close(fd);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值