Linux驱动学习day12(mmap)
mmap
应用程序不能直读写驱动程序中的内容,如果需要应用程序读写,一般使用copy_to_user/copy_from_user这些内核提供的函数,在传输数据大的时候,使用这种方式就不太可行,比如更新LCD ,假设LCD采用1024*600*32bpp格式,那么每一帧就有1024*600*32/8 = 2.3MB。为了改进上述不足,可以通过mmap实现让应用程序直接修改驱动程序的buf,提高效率。
根据代码运行结果来看,两个值不相同,说明这两个代码运行于不同的物理内存中,打印出来的地址是CPU看见的虚拟内存,也就是说运行第一个代码的时候vaddr会映射到paddr1,运行第二个代码的时候vaddr会映射到paddr2上。MMU负责将CPU发出的虚拟地址转化为应用程序的物理地址,对于不同的程序来说,转换的方式不同。 不同的物理地址可以映射到相同的虚拟地址,同理不同的虚拟地址也可以映射到相同的物理地址,这便是应用程序和驱动程序访问同一块内存的原理。
mmap的原理
在内核中,每一个APP都有一个task_struct结构体来表示,mm_struct用来描述进程所使用的内存,既描述虚拟地址,也描述虚拟地址如何映射到物理地址。mm_struct中含有两个成员,mmap是一个链表,链表项为vm_area_struct结构体,每一个链表项代表一块虚拟内存,虚拟内存使用pgd(页目录表)将虚拟地址映射到物理地址。
页目录表如何将虚拟地址映射到物理地址
一级页表映射过程(1M vaddr->1M paddr)
假设虚拟地址是0x12345678 , 会根据高12位去页表中找到0x123页表项,取出内容,看使用一级页表。页表中存放物理地址,假设是0x81000000。这就可以知道从0x12300000开始的1M虚拟地址对应0x81000000开始1M物理地址。
所以vaddr :0x12345678对应的paddr:0x81045678
使用一级页表来映射太浪费内存了,所以可以使用二级页表来映射内存块。
二级页表映射过程
二级页表映射的内存可以是1K也可以是4K,Linux系统使用4K.
假设vaddr:0x12345678 , 首先要先一级页表的基地址发给MMU,启动MMU,CPU发送0x12345678给MMU,MMU使用高12位找到对应一级页表的0x123页表项,发现是二级页表,得到二级页表。根据45这8位,找到二级页表第0x45项,得到4K空间的基地址base。所以最终虚拟地址0x12345678-->物理地址:base + 0x678。
驱动程序需要做
1、APP得到 vaddr 内核会自动够着vm_area_struct结构体 应用程序调用mmap();
2、得到 paddr 我们需要确定物理地址
我们只需要实现驱动的mmap函数,设置属性:cache,buffer,给vm_area_struct和物理地址建立映射。
3、map : vaddr->paddr 内核函数
cache和buf
cache也是块内存,速度块,价格高。程序运行时有“局部性原理”,分为空间局部性和时间局部性。时间局部性:访问一块地址,在很小的一段时间内会多次访问这个地址;空间局部性:访问一个地址时,很小一段时间内会多次访问到该地址附近的地址。
使用mmap时,需要考虑是否使用cache和buf,因为cache里面的值和主存中的值不一致,cpu先写入cache,并不会立即从cache写入主存。
不使用cache和buf的情况
1、register 2、framebuffer(显存) 3、DMA
对于一般的变量和RAM可以使用cache。
使用buffer要注意:buffer写合并(比如,想分四次写入东西,但是使用buf之后会被合并成1个word的写操作),所以buf的功能在写寄存器的时候也不应该使用。
mmap编程
使用mmap共享内存,在驱动代码中主要添加一个.mmap的实现。该函数中获取物理地址,设置mmap属性,物理地址到虚拟地址的映射。
static int hello_drv_mmap(struct file *filp, struct vm_area_struct *vma)
{/* get phys */unsigned long phys = virt_to_phys(kernel_buf);/* set ceche and buffer */vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);/* mmap */if(remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)){printk("mmap remap_pfn_range failed\n");return -ENOBUFS;}return 0;
}
用户态程序代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
/* ./hello_test
*/int main(int argc , char* argv[])
{int fd = 0;int len =0;int ret;char *buf;char str[1024];fd = open("/dev/hello" , O_RDWR);if(fd < 0){printf("can not open file\n");return -1;}/* mmap */buf = mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);if(buf == MAP_FAILED){printf("mmap err\n");close(fd);}printf("mmap address 0x%x\n" , buf);printf("buf : %s\n" , buf);/* write */strcpy(buf, "new");/* read */ret = read(fd , str , 1024);if(strcmp(buf, str) == 0){printf("mmap compare ok\n");}else{printf("mmap compare no\n");printf("str:%s\n" , str);printf("buf:%s\n" , buf);}while(1){sleep(10);}munmap(buf, 1024*8);close(fd);return 0;
}
当用户态程序设置mmap共享内存属性为MAP_SHARED时的运行结果。
当 用户态程序设置mmap共享内存属性为MAP_PRIVATE时的运行结果。
当为私有的时候,如果用户态对共享内存进行修改,会拷贝一份相同的东西,然后在另外一个地址上进行修改,这样如果read fd的时候,读到的是老的内存中的内容,而之前的buf则是新的数据。
验证了之前Linux系统编程的时候对mmap的说法:读共享,写独占。
这是之前学习Linux系统编程对mmap的理解:
Linux系统编程 day6 进程间通信mmap_mmap进程间-CSDN博客
今天的内容比较难啃,很多关于内核的信息,加油!驱动大全