Linux 設備驅動開發實例


編譯和運行

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

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

make -C <path_to_kernel_src> M=mak**eC<pathtokernelsrc>M=PWD

也就是進入到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如下:

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

如果,不使用 $(KERNELRELEASE) 區分的話,每次編譯系統都會設置所有的變量和規則,可能會與kernel的Makefile變量或者規則沖突,因此,建議在(KERNELRELEASE)為空的情況下,配置driver專用的變量和規則,除了使用(KERNELRELEASE)為空的情況下,配置drive**r專用的變量和規則,除了使用(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’則是對應的退出函數,它們一定是成對出現的。

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 錯誤。

文件操作

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操作。

注冊

注冊塊設備(主設備號)

注冊設備(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 設備驅動開發實例。

本文由博客一文多發平台 OpenWrite 發布!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM