編譯和運行
驅動編譯要用到kernel的Makefile文件 — — 也就是源碼樹的編譯系統。所以,源碼須要被配置和編譯,以ubuntu自帶的源碼爲例:linux
編譯外部模塊(.ko)的編譯命令是:程序員
make -C <path_to_kernel_src> M=mak**e−C<pathtokernelsrc>M=PWDubuntu
也就是進入到kernel目錄,利用kbuild系統來編譯驅動文件。obj-m 告訴編譯系統須要編譯成一個module(.ko),foo.o代表須要源文件是foo.c或者foo.S,若是驅動模塊包含多個文件(如: foo_main.c, foo_common.c),寫法以下:併發
kbuild將編譯$(foo-y)列出的全部文件,合併產生 foo.ko函數
在編譯期間,模塊的Makefile會被kbuild屢次讀取,所以建議使用$(KERNELRELEASE)來區分Makefile的使用階段,優化後的Makefile以下:oop
第一次運行make的時侯,$(KERNELRELEASE) 爲空,所以,Makefile的 'else' 內容首先被讀取,而後,執行 *‘make -C .....’*, 執行過程當中,會回讀Makefile文件,此次, 'ifneq' 條件知足,兩次走不一樣的路徑,編譯系統配置不一樣的變量參數。測試
若是,不使用 $(KERNELRELEASE) 區分的話,每次編譯系統都會設置全部的變量和規則,可能會與kernel的Makefile變量或者規則衝突,所以,建議在(KERNELRELEASE)爲空的狀況下,配置driver專用的變量和規則,除了使用(KERNELRELEASE)爲空的狀況下,配置drive**r專用的變量和規則,除了使用(KERNELRELEASE)外,kernel還提供了一些其它的作法, 更多的kernel 編譯系統信息,請參考kernel源碼下的 「Documentation/kbuild/」優化
驅動模塊運行相關命令ui
- insmod foo.ko —— 加載driver 到kernel去運行。
- rmmod foo —— 從kernel 移除driver.
- lsmod —— 查看當前kernel 運行的模塊。
字符設備
字符設備驅動實際上就是實現一個文件接口,讓設備文件能夠像一個普通文件那樣來訪問,這樣應用程序就能夠使用libc庫的'文件IO API(open/write/read/close 系列函數)' 來訪問驅動程序,與驅動交換數據,所以,它的核心就是實現文件系統的接口 -- 文件操做。url
程序入口
宏內核與微內核的一個最大區別就是驅動程序的運行空間。微內核系統,驅動程序做爲一個應用程序,運行在用戶空間,它的入口就是應用程序的‘main’函數。 Linux做爲一個宏內核系統,它的驅動程序與內核是一體的,運行在內核空間,它的入口是 ‘module_init’,‘module_exit’則是對應的退出函數,它們必定是成對出現的。
foo_init 執行了最基本的字符設備操做:使用 cdev_add 添加一個 'cdev'到字符設備列表(實際上是一個map結構), 這樣就把foo這個字符設備託付給kernel進行管理了,當應用程序操做相應的設備文件時,kernel能調度到foo驅動程序。
foo_exit 必定要使用 cdev_del 從列表裏面刪除設備,否則,當kernel從列表裏面查找到 cdev時,返回的將是「過期」的指針,使用它來 callback相應操做時,就會出現空指針異常,致使kernel會掛掉。切記!foo_init 和 foo_exit 必定要成對使用,執行相反的操做。
bug 實例:
- foo_exit 不執行 cdev_del 函數。
- insmod foo.ko -- OK。
- 應用程序對設備文件讀寫 -- OK。
- rmmod foo -- OK。
- insmod foo.ko -- OK。
- 應用程序對設備文件讀寫 -- core dump。
‘rmmod foo’時,會調用 foo_exit,可是,程序員忘了執行 cdev_del 函數,致使 foo.cdev 的指針沒有被刪除而變成了一個空指針,它仍然在字符設備列表裏面。 當第二次插入foo.ko後, 讀寫該設備時,Kernel找的是舊的 foo.cdev 空指針,用它調用相應的文件操做時,就發生了空指針的 core dump 錯誤。
文件操做
setup_dev: 註冊當前的設備的文件操做函數,當應用程序操做設備文件時,調用到對應的驅動函數。與用戶空間交換數據,copy_from/to_user,這兩個函數返回0表示函數執行成功。
- copy_from_user: 把用戶寫入的數據copy驅動數據buf保存起來。
- copy_to_user: copy驅動數據buf到 用戶讀取數據的buf。
應用程序與字符驅動的交互流程
- 建立設備文件 -- sudo mknod /dev/foodev c 500 0
- 修改設備文件權限 -- sudo chmod 766 /dev/foodev
- 應用程序使用open函數打開設備文件。
- kernel根據文件類型(字符設備文件)找到字符設備列表,並根據設備號(Major, Minor),找到對應的設備驅動模塊。
- 調用設備驅動的open函數 foo_open 。
- 應用程序調用 read/write函數來讀寫設備文件。
- 驅動調用 foo_read/write並使用copy_from/to_user來交換數據。
常見問題
Q: 讀寫設備文件時,write或者 read函數返回0,不能讀寫數據 ? A: 這類設備文件讀寫失敗問題,頗有多是權限問題,確認下文件讀寫權限,其次是數據是否符合驅動的要求。
塊設備
塊設備指的是存儲設備,塊設備驅動就是存儲驅動如:HD,SSD。Linux 用 Block 子系統對它們進行管理,把應用層的IO讀寫請求,轉變爲Request ,傳給相應的會設備驅動。驅動流程比較簡單:
register_blkdev → alloc_disk → 處理request
Q: 文件系統與Block子系統的關係? A: Block子系統主要是提供最底層的數據讀寫,也就是raw io,文件系統使用它進行IO操做。
註冊
註冊塊設備(主設備號)
註冊設備(MAJOR,MINOR)
添加磁盤
這個磁盤會出如今 /dev 目錄下面, 本例是 /dev/frd0,用戶能夠對設備文件進行格式化,分區等磁盤相關的操做。如: ‘mkfs.ext2 /dev/frd0’, ‘mount /dev/frd0 /mnt’。
初始化請求隊列
處理設備請求
kernel 提供了一些宏來幫助遍歷請求列表。對請求的處理策略,就是Block驅動最核心最精華的部分,開發者得根據設備的物理特性來提升訪問效率,解決併發擁堵等問題。 *fr_queue_rq()* -- ‘**請求隊列’**處理函數,在初始化請求隊列時設置,Loop處理每一個請求:
*fr_transfer()* -- 物理設備讀寫數據,根據請求的上下文內容(context),進行底層數據傳輸,這裏就是最底層的IO通信了,驅動根據物理設備的接口協議來進行數據的讀寫。
塊設備驅動測試
執行上面命令後,frd_data_r 和 frd_data_w的內容應該是同樣的。
以上就是良許教程網爲各位朋友分享的Linux 設備驅動開發實例。 以上就是良許教程網爲各位朋友分享的Linux相關知識。