分类: LINUX
2010-12-25 15:01:43
最近写一个屏驱动的时候,需要把一块内核中用kmalloc分配的内存映射到应用层中使用。这是一个很simple的需求,很轻松的就可以通过mmap搞定:
驱动部分代码:
1698 if((mmap_addr = kmalloc(PAGE_SIZE, GFP_KERNEL)) == NULL){
1699 ret = -ENOMEM;
1700 goto out;
1701 }
1702 SetPageReserved(virt_to_page((mmap_addr)));
....
899 long length = vma->vm_end - vma->vm_start;
900
901 if (length > PAGE_SIZE)
902 return -EIO;
903
904 if ((ret = remap_pfn_range(vma,vma->vm_start,virt_to_phys((void *)mmap_addr) >> PAGE_SHIFT,length,vma->vm_page_prot)) < 0) {
905 return ret;
....
应用层通过
401 if( ( addr = mmap(NULL, 4096, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0 )) == (void *)-1)
402 {
403 perror("mmap error\n");
404 return -1;
405 }
获得地址, 然后往内写入
这是一种通过普通内存设为保留,当成io内存映射并map出去的一种方式。经过测试,发现大部分时候数据是对的,但偶尔数据会出错。随后尝试不采用remap_pfn_range建立页表,而是通过缺页时返回分配页的动态方式来处理,结果仍然一样。
我们猜测是由于cache的原因导致的,于是在mmap的时候使其nocache,仍然无法解决。在实在没有办法的情况下,我们尝试采用在内核读取共享内存时执行flush_all_cache,果然,解决了问题,证明了确实是cache导致的。
那么为什么一开始我们种种尝试未能成功呢?因为我们弄反了方向。我们以为是应用层在读cache,内核读内存,实际上,由于采用remap_pfn_range,或者我们在mmap的时候指定了nocache的方式,应用层读取这片vma的时候是根据其指定的nocache属性去读内存,而内核访问kmalloc的时候却是读内存。如何让内核也读内存呢,很简单,通过ioremap_nocache把kmalloc得到的地址再做一次映射,然后给内核用就可以了。
这就是传说中的Cache Coherence ---缓存一致性。由于cache和内存在某些时候的不一致而导致的不同地址空间分别读写导致的问题。并不是所有体系结构都存在这种问题,比如x86.我曾经在x86上用过mmap并且使用良好,因为x86的体系结构确保了缓存一致性:其监听技术使当某片被cache的内存被其他请求操作时,会被立刻回写,确保cache与内存的一致性。但这种监听技术会带来性能上的损耗,所以arm是由软件来确保这个一致性的:一些时候,比如进程切换,必须通过flush整个cache获得正确的内存访问。
在这里例子里,我们是通过内核和应用都nocache的方式来进行内存共享的。那么双方能不能通过cache的方式来访问呢?我们必须知道,有两种cache:物理cache和逻辑cache,对于armv6以下的arm芯片,采用的是逻辑cache的方式,即cache在mmu之前,而armv6及以上的cpu,cache在mmu之后,cpu送出的要访问的地址先通过mmu进行虚拟/物理的转换,再送到cache。这意味着什么呢?意味着对于我们的cpu(v5),当应用层用其地址空间的地址把共享区从内存加到cache后,内核同样访问这片区域的时候,也可以按照其kmalloc分配的地址将同一片内存中的数据加载到cache的另一行中,这就意味着一个内存块会有2个cache拷贝,也就是传说中的别名。简单地说,如果你用arm11,由于使用物理cache,所以不会有问题,而arm9的话,就很麻烦了。
那为什么有时候访问正确,有时候访问错误呢?这就和cache的替换策略有关了。不像x86由于监视的原因,可以在相关内存被touch的时候回写,arm的cache只有当是dirty,并且被cache轮转策略选中需要换出的时候,才会被回写。所以有时候,某些加载共享内存的cache块没有被替换,而相应的内存块又被内核加载到cache形成别名,错误就自然产生了。
这就是cache导致的问题。如果双方采用一样的cache策略,自然cache就是透明的,但是如果是不一样的方式,那么可能就会有问题。具体会有哪些问题,如前所述,就和cpu,体系结构有着密切的关系了。
补记:
用remap_pfn_range 做mmap映射到应用层的时候,要先调用pgprot_noncached防止缓存。其实质就是建立页表项的时候把cache位给清掉。