一、總體框架
deferred io機制主要用於驅動沒有實現自刷新同時應用層又不想調用FBIOPAN_DISPLAY的一個折中方案, 使用ioctrl FBIOPAN_DISPLAY好處是節能, 驅動不用盲目的刷數據(尤其是一靜態幀數據), 數據的更新是由應用程序操作的,
所以應用程序當然知道何時刷數據, 最理想的情況是應用程序一更新數據立馬調用FBIOPAN_DISPLAY, 但也有缺點, 一是要應用層顯示調用FBIOPAN_DISPLAY,二是畫面更新頻率高的話, FBIOPAN_DISPLAY帶來的系統調用開支也不小;
使用驅動自刷新當然解放應用, 應用不用關心數據顯示問題, 直接操作顯存, 所寫即所見。
二、源碼分析
代碼具體在linux/drivers/video/fb_defio.c, 如下演示刷圖穿插該框架的實現代碼:
1. fb_defio 自己實現一個mmap(), 沒有將用戶空間虛擬地址和物理幀緩存進行頁表映射, 倒是提供了缺頁異常處理函數
void fb_deferred_io_init(struct fb_info *info) { struct fb_deferred_io *fbdefio = info->fbdefio; BUG_ON(!fbdefio); mutex_init(&fbdefio->lock); info->fbops->fb_mmap = fb_deferred_io_mmap; INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); INIT_LIST_HEAD(&fbdefio->pagelist); if (fbdefio->delay == 0) /* set a default of 1 s */ fbdefio->delay = HZ; } EXPORT_SYMBOL_GPL(fb_deferred_io_init); static int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma) { vma->vm_ops = &fb_deferred_io_vm_ops; vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; if (!(info->flags & FBINFO_VIRTFB)) vma->vm_flags |= VM_IO; vma->vm_private_data = info; return 0; } static const struct vm_operations_struct fb_deferred_io_vm_ops = { .fault = fb_deferred_io_fault, .page_mkwrite = fb_deferred_io_mkwrite, };
2. 應用程序通過mmap(), 獲得一塊幀緩存的虛擬地址, 但沒有對應的實際物理內存
3. 應用程序操作內存(write), 由於頁表沒有對應物理內存導致缺頁異常
do_page_fault() -> __do_page_fault() -> handle_mm_fault() -> __handle_mm_fault() -> handle_pte_fault(): if (vma->vm_ops) { if (likely(vma->vm_ops->fault)) return do_linear_fault(mm, vma, address, pte, pmd, flags, entry); } -> __do_fault(): vma->vm_ops->fault(vma, &vmf); vma->vm_ops->page_mkwrite(vma, &vmf);
從上面流程可以看出, 當vm_ops且fault有效時, 會走自定義的fault實現, 同時如果操作時write行為, 還會調用page_mkwrite(有效的話)
4. fb_defio提供了缺頁異常的處理函數fb_deferred_io_fault(), 分配物理頁跟虛擬地址對應起來, 並把該物理頁掛到fbdefio->pagelist(即將被刷新數據), 然后啟動工作隊列延遲delay后執行這個工作項fb_deferred_io_work()
static int fb_deferred_io_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { unsigned long offset; struct page *page; struct fb_info *info = vma->vm_private_data; offset = vmf->pgoff << PAGE_SHIFT; if (offset >= info->fix.smem_len) return VM_FAULT_SIGBUS; page = fb_deferred_io_page(info, offset); if (!page) return VM_FAULT_SIGBUS; get_page(page); if (vma->vm_file) page->mapping = vma->vm_file->f_mapping; else printk(KERN_ERR "no mapping available\n"); BUG_ON(!page->mapping); page->index = vmf->pgoff; vmf->page = page; return 0; } static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs) { void *screen_base = (void __force *) info->screen_base; struct page *page; if (is_vmalloc_addr(screen_base + offs)) page = vmalloc_to_page(screen_base + offs); else page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT); return page; } /* 需要注意的是幀緩存是一開始fb驅動就應該分配好的, 同時賦值給fb_info->screen_base, fb_info->fix.smem_start * 而fb_deferred_io_fault()-> fb_deferred_io_page()分配內存只是找出並返回此次缺頁異常頁虛擬地址對應的物理地址, * 好填充頁表, 這樣應用程序就可以正常write數據到緩存, 整個過程對應用程序是透明的。 */
5. fb_deferred_io_work() 最核心就是調用函數指針fbdefio->deferred_io(驅動要實現的刷數據函數), 並調用page_mkclean()將之前虛擬地址和幀物理頁的映射清除, 使得下次操作這塊虛擬地址又能重新觸發缺頁機制
二、fb驅動示例
static void lcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist) { struct page *cur; struct fb_deferred_io *fbdefio = info->fbdefio; /* pagelist存放的都是被更新的臟頁, 由於我驅動用SPI DMA搬數據,會存在cache不一致, 所以要把cache的緩存刷回內存 */ list_for_each_entry(cur, &fbdefio->pagelist, lru) { flush_dcache_page(cur); } /* 雖然pagelist存放都是臟頁,我懶得對這些頁在幀的位置進行排布分析, 直接一整幀都刷 也即哪怕應用程序改動一個page, 驅動都會整幀刷*/ info->fbops->fb_pan_display(&info->var , info); } static struct fb_deferred_io gen_lcd_fb_defio = { .delay = HZ / 8, .deferred_io = lcd_fb_deferred_io, }; static void lcd_defio_init(struct fb_info *info, struct fb_deferred_io *fbdefio) { info->fbdefio = fbdefio; fb_deferred_io_init(info); } static void lcd_defio_cleanup(struct fb_info *info) { if (info->fbdefio != NULL) { fb_deferred_io_cleanup(info); info->fbdefio = NULL; } } ========================================== lcd_defio_init(fb_dev, gen_lcd_fb_defio) lcd_defio_cleanup(fb_dev)
三、其他
1. 要使能deferred io機制, 要打開CONFIG_FB_DEFERRED_IO配置
2. 這里沒有幀率說法, 只跟應用刷圖有關
3. 缺頁異常會被調用多次, 但fb_deferred_io_work()只會被最后頁調用一次, 比如應用要刷,2個page, 第一個page導致fb_deferred_io_mkwrite()添加到pagelist, 然后調用schedule_delayed_work()啟動延遲1/8s的工作項fb_deferred_io_work(),
接着引發第二頁缺頁異常會被繼續添加到pagelist, schedule_delayed_work再次被執行, 但只是重新更新延遲時間為1/8s
4. 我感覺page_mkclean() 這里有個bug, 它是把物理頁所有的映射都清除, 包括kernel空間的虛擬地址, 那下次缺頁異常時fb_deferred_io_page() -> vmalloc_to_page(screen_base + offs), 就會有問題, 因為頁表被清除掉了, 所以該框架目前應該只支持連續的幀緩存