Linux 設備驅動開發實例

編譯和運行

驅動編譯要用到kernel的Makefile文件 — — 也就是源碼樹的編譯系統。因此,源碼需要被配置和編譯,以ubuntu自帶的源碼爲例:

0.png

編譯外部模塊(.ko)的編譯命令是:

$ make -C <path_to_kernel_src> M=$PWD

也就是進入到kernel目錄,利用kbuild系統來編譯驅動文件。obj-m 告訴編譯系統需要編譯成一個module(.ko),foo.o表明需要源文件是foo.c或者foo.S,如果驅動模塊包含多個文件(如: foo_main.c, foo_common.c),寫法如下:

0.png

kbuild將編譯$(foo-y)列出的所有文件,合併產生 foo.ko

在編譯期間,模塊的Makefile會被kbuild多次讀取,因此建議使用$(KERNELRELEASE)來區分Makefile的使用階段,優化後的Makefile如下:

0.png

第一次運行make的時侯,&dollar;(KERNELRELEASE) 爲空,因此,Makefile的 'else' 內容首先被讀取,然後,執行 ‘make -C .....’, 執行過程中,會回讀Makefile文件,這次, 'ifneq' 條件滿足,兩次走不同的路徑,編譯系統配置不同的變量參數。

如果,不使用 &dollar;(KERNELRELEASE) 區分的話,每次編譯系統都會設置所有的變量和規則,可能會與kernel的Makefile變量或者規則衝突,因此,建議在$(KERNELRELEASE)爲空的情況下,配置driver專用的變量和規則,除了使用 $(KERNELRELEASE)外,kernel還提供了一些其它的做法,
更多的kernel 編譯系統信息,請參考kernel源碼下的 “Documentation/kbuild/”

驅動模塊運行相關命令

  • insmod foo.ko —— 加載driver 到kernel去運行。
  • rmmod foo —— 從kernel 移除driver.
  • lsmod —— 查看當前kernel 運行的模塊。

字符設備

字符設備驅動實際上就是實現一個文件接口,讓設備文件可以像一個普通文件那樣來訪問,這樣應用程序就可以使用libc庫的'文件IO API(open/write/read/close 系列函數)' 來訪問驅動程序,與驅動交換數據,因此,它的核心就是實現文件系統的接口 -- 文件操作。

程序入口

宏內核與微內核的一個最大區別就是驅動程序的運行空間。微內核系統,驅動程序作爲一個應用程序,運行在用戶空間,它的入口就是應用程序的‘main’函數。 Linux作爲一個宏內核系統,它的驅動程序與內核是一體的,運行在內核空間,它的入口是 ‘module_init’,‘module_exit’則是對應的退出函數,它們一定是成對出現的。

0.png

foo_init 執行了最基本的字符設備操作:使用 cdev_add 添加一個 'cdev'到字符設備列表(其實是一個map結構), 這樣就把foo這個字符設備託付給kernel進行管理了,當應用程序操作相應的設備文件時,kernel能調度到foo驅動程序。

foo_exit 一定要使用 cdev_del 從列表裏面刪除設備,不然,當kernel從列表裏面查找到 cdev時,返回的將是“過時”的指針,使用它來 callback相應操作時,就會出現空指針異常,導致kernel會掛掉。切記!foo_initfoo_exit 一定要成對使用,執行相反的操作。

bug 實例:

  1. foo_exit 不執行 cdev_del 函數。
  2. insmod foo.ko -- OK。
  3. 應用程序對設備文件讀寫 -- OK。
  4. rmmod foo -- OK。
  5. insmod foo.ko -- OK。
  6. 應用程序對設備文件讀寫 -- core dump。

rmmod foo’時,會調用 foo_exit,但是,程序員忘了執行 cdev_del 函數,導致 foo.cdev 的指針沒有被刪除而變成了一個空指針,它仍然在字符設備列表裏面。 當第二次插入foo.ko後, 讀寫該設備時,Kernel找的是舊的 foo.cdev 空指針,用它調用相應的文件操作時,就發生了空指針的 core dump 錯誤。

文件操作

0.png

setup_dev: 註冊當前的設備的文件操作函數,當應用程序操作設備文件時,調用到對應的驅動函數。與用戶空間交換數據,copy_from/to_user,這兩個函數返回0表示函數執行成功。

  • copy_from_user: 把用戶寫入的數據copy驅動數據buf保存起來。
  • copy_to_user: copy驅動數據buf到 用戶讀取數據的buf。

應用程序與字符驅動的交互流程

  1. 創建設備文件 -- sudo mknod /dev/foodev c 500 0
  2. 修改設備文件權限 -- sudo chmod 766 /dev/foodev
  3. 應用程序使用open函數打開設備文件。
  4. kernel根據文件類型(字符設備文件)找到字符設備列表,並根據設備號(Major, Minor),找到對應的設備驅動模塊。
  5. 調用設備驅動的open函數 foo_open 。
  6. 應用程序調用 read/write函數來讀寫設備文件。
  7. 驅動調用 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操作。

註冊

註冊塊設備(主設備號)

0.png

註冊設備(MAJOR,MINOR)

0.png

添加磁盤

0.png

這個磁盤會出現在 /dev 目錄下面, 本例是 _/dev/frd0_,用戶可以對設備文件進行格式化,分區等磁盤相關的操作。如: ‘_mkfs.ext2 /dev/frd0_’, ‘_mount /dev/frd0 /mnt_’。

初始化請求隊列

0.png

處理設備請求

kernel 提供了一些宏來幫助遍歷請求列表。對請求的處理策略,就是Block驅動最核心最精華的部分,開發者得根據設備的物理特性來提高訪問效率,解決併發擁堵等問題。
fr_queue_rq() -- ‘請求隊列’處理函數,在初始化請求隊列時設置,Loop處理每個請求:

0.png

fr_transfer() -- 物理設備讀寫數據,根據請求的上下文內容(context),進行底層數據傳輸,這裏就是最底層的IO通訊了,驅動根據物理設備的接口協議來進行數據的讀寫。

0.png

塊設備驅動測試

0.png

執行上面命令後,frd_data_r 和 frd_data_w的內容應該是一樣的。

附錄:

參考資料

  1. Linux 3.13源碼。
  2. Linux Device Drivers。

歡迎大家來我的網站交流:般若程序蟬
qrcode_258.jpg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章