Try   HackMD

Device Driver 重點整理

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

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) 良好的驅動程式
  1. 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

$ 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()函數裡實作,根據裝置類型的不同,所呼叫的函數也不同,以下是幾個基本的置註冊函數

  • 註冊字元型驅動程式
int register_chrdev(unsigned int major,
		     const char * name, 
		     struct file_operations *fops)

注意 major : 為 device file 的 major number該 device file,應在 Linux 系統底下以 root 身份手動建立

  • 註冊區塊型驅動程式
int register_blkdev(unsigned int major,
		     const char *name, 
		     struct file_operations *fops)
  • 註冊 USB 驅動程式
int usb_register(struct usb_driver *new_driver)
int pci_register_driver(struct pci_driver *)

註冊的動作是寫在 init_module() 裡,因此當使用者執行 insmod 載入驅動程式時,register_chrdev()便會執行。由此可知,註冊驅動程式的時機為 insmod 時。相對的,在 rmmod 時,必須執行解除註冊的動作,此動作必須實作在 cleanup_module()函數裡

  • 解除註冊字元型驅動程式
void unregister_chrdev(unsigned int major,
		        const char * name)
  • 解除註冊區塊型驅動程式
void unregister_blkdev(unsigned int major,
		        const char *name)
  • 解除註冊 USB 驅動程式
void usb_unregister(struct usb_driver *new_driver)
  • 解除註冊 PCI 驅動程式
void pci_unregister_driver(struct pci_driver *)

Linux 驅動程式的「註冊」是一個非常重要的動作,這個動作代表 Linux 驅動程式是一個嚴謹的分層式架構,換句話說,Linux 驅動程式的分層 (layered) 關係可透過「註冊」的程序來分析

參考資料