linux 内核mmap,Linux内核mmap机制

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

52f9aeb70312cab615c988eb3c3986f6.png

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内存映射区,存映射区的虚拟地址上,

将来用户访问这个虚拟地址就是在访问硬件(文件);不再经过内核空间

e38cff1dc2f7d3048ccc487f1a640f52.png

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 切记:对于GPIO操作的设备,利用mmap访问操作的时候,记得要把cache功能屏蔽掉!

案例

利用mmap实现开关灯

#include #include #include #include #include //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 #include #include #include #include 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 \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;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值