- 論壇徽章:
- 0
|
這周碰到的一個問題,在解決的過程中得到不少高手的熱心幫助,把一些總結(jié)貼出來,歡迎大家指點。
寫一個屏驅(qū)動的時候,需要把一塊內(nèi)核中用kmalloc分配的內(nèi)存映射到應(yīng)用層中使用。這是一個很simple的需求,很輕松的就可以通過mmap搞定:
驅(qū)動部分代碼:
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;
....
應(yīng)用層通過
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 }
獲得地址, 然后往內(nèi)寫入
這是一種通過普通內(nèi)存設(shè)為保留,當(dāng)成io內(nèi)存映射并map出去的一種方式。經(jīng)過測試,發(fā)現(xiàn)大部分時候數(shù)據(jù)是對的,但偶爾數(shù)據(jù)會出錯。隨后嘗試不采用remap_pfn_range建立頁表,而是通過缺頁時返回分配頁的動態(tài)方式來處理,結(jié)果仍然一樣。
我們猜測是由于cache的原因?qū)е碌,于是在mmap的時候使其nocache,仍然無法解決。在實在沒有辦法的情況下,我們嘗試采用在內(nèi)核讀取共享內(nèi)存時執(zhí)行flush_all_cache,果然,解決了問題,證明了確實是cache導(dǎo)致的。
那么為什么一開始我們種種嘗試未能成功呢?因為我們弄反了方向。我們以為是應(yīng)用層在讀cache,內(nèi)核讀內(nèi)存,實際上,由于采用remap_pfn_range,或者我們在mmap的時候指定了nocache的方式,應(yīng)用層讀取這片vma的時候是根據(jù)其指定的nocache屬性去讀內(nèi)存,而內(nèi)核訪問kmalloc的時候卻是讀內(nèi)存。如何讓內(nèi)核也讀內(nèi)存呢,很簡單,通過ioremap_nocache把kmalloc得到的地址再做一次映射,然后給內(nèi)核用就可以了。
這就是傳說中的Cache Coherence ---緩存一致性。由于cache和內(nèi)存在某些時候的不一致而導(dǎo)致的不同地址空間分別讀寫導(dǎo)致的問題。并不是所有體系結(jié)構(gòu)都存在這種問題,比如x86.我曾經(jīng)在x86上用過mmap并且使用良好,因為x86的體系結(jié)構(gòu)確保了緩存一致性:其總線監(jiān)聽技術(shù)使當(dāng)某片被cache的內(nèi)存被其他請求操作時,會被立刻回寫,確保cache與內(nèi)存的一致性。但這種監(jiān)聽技術(shù)會帶來性能上的損耗,所以arm是由軟件來確保這個一致性的:一些時候,比如進(jìn)程切換,必須通過flush整個cache獲得正確的內(nèi)存訪問。
在這里例子里,我們是通過內(nèi)核和應(yīng)用都nocache的方式來進(jìn)行內(nèi)存共享的。那么雙方能不能通過cache的方式來訪問呢?我們必須知道,有兩種cache:物理cache和邏輯cache,對于armv6以下的arm芯片,采用的是邏輯cache的方式,即cache在mmu之前,而armv6及以上的cpu,cache在mmu之后,cpu送出的要訪問的地址先通過mmu進(jìn)行虛擬/物理的轉(zhuǎn)換,再送到cache。這意味著什么呢?意味著對于我們的cpu(v5),當(dāng)應(yīng)用層用其地址空間的地址把共享區(qū)從內(nèi)存加到cache后,內(nèi)核同樣訪問這片區(qū)域的時候,也可以按照其kmalloc分配的地址將同一片內(nèi)存中的數(shù)據(jù)加載到cache的另一行中,這就意味著一個內(nèi)存塊會有2個cache拷貝,也就是傳說中的別名。簡單地說,如果你用arm11,由于使用物理cache,所以不會有問題,而arm9的話,就很麻煩了。
那為什么有時候訪問正確,有時候訪問錯誤呢?這就和cache的替換策略有關(guān)了。不像x86由于總線監(jiān)視的原因,可以在相關(guān)內(nèi)存被touch的時候回寫,arm的cache只有當(dāng)是dirty,并且被cache輪轉(zhuǎn)策略選中需要換出的時候,才會被回寫。所以有時候,某些加載共享內(nèi)存的cache塊沒有被替換,而相應(yīng)的內(nèi)存塊又被內(nèi)核加載到cache形成別名,錯誤就自然產(chǎn)生了。
這就是cache導(dǎo)致的問題。如果雙方采用一樣的cache策略,自然cache就是透明的,但是如果是不一樣的方式,那么可能就會有問題。具體會有哪些問題,如前所述,就和cpu,體系結(jié)構(gòu)有著密切的關(guān)系了。 |
|