# Device Driver 重點整理 ![](https://i.imgur.com/XXv7AuE.png) ## Introduction Kernel 的工作 * 處理 System Call,將硬體回應回傳給 user process * System Call : OS interface, call to the OS service Device Driver 扮演的角色 * 一種軟體,能讓 OS 認識某種硬體,並讓應用程式能利用這個硬體 Device Driver 授權形式會受連結方式所影響 * Static link : GPU * Dynamic link : driver 開發者自訂 以 kernel module 形式提供的 device driver,開發者可透過 `MODULE_LICENSE` 這個巨集,將授權方式設定為以下七種之一: GPL, GPL v2, GPL and additional rights, Dual BSD/GPL, Dual MIT/GPL, Dual MPL/GPL, Proprietary ![](https://i.imgur.com/Z4JnRxB.png) ## Linux 驅動程式三類型 Linux device driver 可分成 3 種類型 1. character device driver 2. block device driver 3. network device driver 驅動程式本身可分成 2 個層面來討論 1. virtual device driver * 往上層支援 Linux kernel 所提供的 Virtual File System 層,並藉此實作 system calls * Virtual device driver 的目的在於善用 Linux 的 APIs 來設計機制 (mechanism) 與行為 (behavior) 良好的驅動程式 2. physical device driver * 往下層使用 Linux kernel 所提供的 device interface 來存取並控制實體硬體裝置 * Physical device driver 則是討論「如何透過 I/O port 或 I/O memory」來控制裝置,也就是與晶片組的溝通 ## System Call 與驅動程式的關係 System call 是 user application 與 Linux device driver 的溝通介面 User application 透過呼叫 system call 來「叫起」driver 的 task,user application 要呼叫 system call 必須呼叫 GNU C 所提供的「wrapper function」,每個 system call 都會對應到 driver 內的一個 task,此 task 即是 file_operation 函數指標所指的函數 Linux 驅動程式與 user application 間的溝通方式是透過 system call,實際上 user application 是以 device file 與裝置驅動程式溝通。要達成此目的,驅動程式必須建構在此「file」之上,因此 Linux 驅動程式必須透過 VFS(virtual file system)層來實作 system call /dev 目錄下的檔案稱為 device file,是 user application 用來與硬體裝置溝通的介面 ## Device File 我們把外部的周邊裝置均視為一個檔案,並透過此檔案與實體硬體溝通,這樣的檔案就叫做 device files 或 special files 檔案屬性的第一個位元如果顯示為 “c” 表示這是一個字元型裝置的 device file,若為 “b” 表示這是一個區塊型裝置的 device file ```shell $ ls -ls /dev/sda? /dev/ttyS? 0 brw-rw---- 1 root disk 8, 1 三 9 15:28 /dev/sda1 0 crw-rw---- 1 root dialout 4, 64 三 9 15:28 /dev/ttyS0 0 crw-rw---- 1 root dialout 4, 65 三 9 15:28 /dev/ttyS1 0 crw-rw---- 1 root dialout 4, 66 三 9 15:28 /dev/ttyS2 0 crw-rw---- 1 root dialout 4, 67 三 9 15:28 /dev/ttyS3 0 crw-rw---- 1 root dialout 4, 68 三 9 15:28 /dev/ttyS4 0 crw-rw---- 1 root dialout 4, 69 三 9 15:28 /dev/ttyS5 0 crw-rw---- 1 root dialout 4, 70 三 9 15:28 /dev/ttyS6 0 crw-rw---- 1 root dialout 4, 71 三 9 15:28 /dev/ttyS7 0 crw-rw---- 1 root dialout 4, 72 三 9 15:28 /dev/ttyS8 0 crw-rw---- 1 root dialout 4, 73 三 9 15:28 /dev/ttyS9 ``` Device file 的 major number 代表一個特定的裝置,例如 major number 為 1 是 null 虛擬裝置,major number 定義於 kernel 文件目錄 `Documentation/devices.txt` > Minor number 代表裝置上的子裝置,例如,同一個硬碟上的分割區就用不同的 minor number 來代表,但其 major number 相同 ### Device File 與 Driver 的關係 我們在設計 device driver 時,會先透過一個 **註冊** (register) 的動作將自己註冊到 kernel 裡,註冊時,我們會指定一個 major number 參數,以指定此驅動程式所要實作的週邊裝置。當 user 開啟 device file 時,kernel 便會根據 device file 的 major number 找到對應的驅動程式來回應使用者。Minor number 則是 device driver 內部所使用,kernel 並不會處理不同的 minor number 設計 device driver 的第一個步驟就是要定義 driver 所要提供的功能(capabilities),當 user application 呼叫 open() system call 時,kernel 就會連繫相對應的 driver 來回應使用者 file_operations 是學習 device driver 最重要的一個資料結構,file_operations 內的成員為函數指標,指向 ==system call 的實作函數==,file_operations 即是圖中的 VFS 層 > 換句話說,Linux 驅動程式是透過 file_operations 來建構 VFS 層的支援。而 file_operation 裡的函數指標,即是指向每一個 system call 的實作函數 ## Linux 驅動程式一般化設計流程 Virtual device driver 往上是為了連結 Linux kernel 的 VFS 層,physical device drvier 往下是為了存取實體硬體 ### Virtual Device Driver Virtual device driver 的目的在於設計一個「機制」良好的 kernel mode 驅動程式,virtual device driver 也必須考慮與 user application 的互動。實作上,則是需要善用 kernel 所提供的介面(interface),即 kernel APIs 1. 定義 file_operations 2. 實作 system calls 3. 註冊 driver (VFS) fops 是指向 file_operations 結構的指標,驅動程式呼叫 `register_chrdev()` 將 fops 註冊到 kernel 裡後,fops 便成為該 device driver 所實作的 system call 進入點。實作 system call 的函數便是透過 file_operations 結構來定義,我們稱實作 system call 的函數為 driver method。 kernel 會在需要時 callback 我們所註冊的 driver method,因此,當 driver 裡的 method 被呼叫時,kernel 便將傳遞 argument 給 driver method,driver method 可由 kernel 所傳遞進來的 argument 取得驅動程式資訊 註冊 driver 的動作呼叫 register_chrdev()函數完成,此函數接受 3 個參數如下 1. major : 要註冊的裝置 major number 2. name : device 名稱 3. fops : driver 的 file operation **「註冊」這個動作觀念上是將 fops 加到 kernel 的 VFS 層**,因此 user application 必須透過「device file」才能呼叫到 driver method。註冊這個動作的另一層涵意則是將 driver method 與不同的 system call 做「正確的對應」,當 user application 呼叫 system call 時,才能執行正確的 driver method ## 重點整理 將 driver 自己「註冊」到 kernel 的 VFS 層,註冊時所要呼叫的函數根據裝置類型的不同而不同 將驅動程式「註冊」(registration)至 kernel 的動作必須在 init_module()函數裡實作,根據裝置類型的不同,所呼叫的函數也不同,以下是幾個基本的置註冊函數 * 註冊字元型驅動程式 ```c int register_chrdev(unsigned int major, const char * name, struct file_operations *fops) ``` > 注意 major : 為 device file 的 major number該 device file,應在 Linux 系統底下以 root 身份手動建立 * 註冊區塊型驅動程式 ```c int register_blkdev(unsigned int major, const char *name, struct file_operations *fops) ``` * 註冊 USB 驅動程式 ```c int usb_register(struct usb_driver *new_driver) ``` ```c int pci_register_driver(struct pci_driver *) ``` 註冊的動作是寫在 `init_module()` 裡,因此當使用者執行 `insmod` 載入驅動程式時,`register_chrdev()`便會執行。由此可知,註冊驅動程式的時機為 `insmod` 時。相對的,在 `rmmod` 時,必須執行解除註冊的動作,此動作必須實作在 `cleanup_module()`函數裡 * 解除註冊字元型驅動程式 ```c void unregister_chrdev(unsigned int major, const char * name) ``` * 解除註冊區塊型驅動程式 ```c void unregister_blkdev(unsigned int major, const char *name) ``` * 解除註冊 USB 驅動程式 ```c void usb_unregister(struct usb_driver *new_driver) ``` * 解除註冊 PCI 驅動程式 ```c void pci_unregister_driver(struct pci_driver *) ``` Linux 驅動程式的「註冊」是一個非常重要的動作,這個動作代表 Linux 驅動程式是一個嚴謹的分層式架構,換句話說,Linux 驅動程式的分層 (layered) 關係可透過「註冊」的程序來分析 ![](https://i.imgur.com/4Q7wlDf.png) ## 參考資料 * [Linux Character Drivers](https://sysplay.in/blog/linux-device-drivers/2013/05/linux-character-drivers/) * [Introduction to Linux kernel driver programming](https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Introduction-to-Linux-Kernel-Driver-Programming-Michael-Opdenacker-Bootlin-.pdf)