二、
Linux驅動程式架構Linux將所有外部裝置看成是一類特殊檔案,稱之為“裝置檔案”,如果說系統調用是Linux核心和應用程式之間的介面,那麼裝置驅動程式則可以看成是Linux核心與外部裝置之間的介面。裝置驅動程式嚮應用程式屏蔽了硬體在實現上的細節,使得應用程式可以像操作普通檔案一樣來操作外部裝置。
1. 字元裝置和塊裝置
Linux抽象了對硬體的處理,所有的硬體裝置都可以像普通檔案一樣來看待:它們可以使用和操作檔案相同的、標準的系統調用介面來完成開啟、關閉、讀寫和I/O控制操作,而驅動程式的主要任務也就是要實現這些系統調用函數。Linux系統中的所有硬體裝置都使用一個特殊的裝置檔案來表示,例如,系統中的第一個IDE硬碟使用/dev/hda表示。每個裝置檔案對應有兩個裝置號:一個是主裝置號,標識該裝置的種類,也標識了該裝置所使用的驅動程式;另一個是次裝置號,標識使用同一裝置驅動程式的不同硬體裝置。裝置檔案的主裝置號必須與裝置驅動程式在登入該裝置時申請的主裝置號一致,否則使用者進程將無法訪問到裝置驅動程式。
在Linux作業系統下有兩類主要的裝置檔案:一類是字元裝置,另一類則是塊裝置。字元裝置是以位元組為單位逐個進行I/O操作的裝置,在對字元裝置發出讀寫請求時,實際的硬體I/O緊接著就發生了,一般來說字元裝置中的緩衝是可有可無的,而且也不支援隨機訪問。塊裝置則是利用一塊系統記憶體作為緩衝區,當使用者進程對裝置進行讀寫請求時,驅動程式先查看緩衝區中的內容,如果緩衝區中的資料能滿足使用者的要求就返回相應的資料,否則就調用相應的請求函數來進行實際的I/O操作。塊裝置主要是針對磁碟等慢速裝置設計的,其目的是避免耗費過多的CPU時間來等待操作的完成。一般說來,PCI卡通常都屬於字元裝置。
所有已經註冊(即已經載入了驅動程式)的硬體裝置的主裝置號可以從/proc/devices檔案中得到。使用mknod命令可以建立指定類型的裝置檔案,同時為其分配相應的主裝置號和次裝置號。例如,下面的命令:
[root@gary root]# mknod /dev/lp0 c 6 0 |
將建立一個主裝置號為6,次裝置號為0的字元裝置檔案/dev/lp0。當應用程式對某個裝置檔案進行系統調用時,Linux核心會根據該裝置檔案的裝置類型和主裝置號調用相應的驅動程式,並從使用者態進入到核心態,再由驅動程式判斷該裝置的次裝置號,最終完成對相應硬體的操作。
2. 裝置驅動程式介面
Linux中的I/O子系統向核心中的其他部分提供了一個統一的標準裝置介面,這是通過include/linux/fs.h中的資料結構file_operations來完成的:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);}; |
當應用程式對裝置檔案進行諸如open、close、read、write等操作時,Linux核心將通過file_operations結構訪問驅動程式提供的函數。例如,當應用程式對裝置檔案執行讀操作時,核心將調用file_operations結構中的read函數。
2. 裝置驅動程式模組
Linux下的裝置驅動程式可以按照兩種方式進行編譯,一種是直接靜態編譯成核心的一部分,另一種則是編譯成可以動態載入的模組。如果編譯進核心的話,會增加核心的大小,還要改動核心的源檔案,而且不能動態地卸載,不利於調試,所有推薦使用模組方式。
從本質上來講,模組也是核心的一部分,它不同於普通的應用程式,不能調用位於使用者態下的C或者C++庫函數,而只能調用Linux核心提供的函數,在/proc/ksyms中可以查看到核心提供的所有函數。
在以模組方式編寫驅動程式時,要實現兩個必不可少的函數init_module( )和cleanup_module( ),而且至少要包含<linux/krernel.h>和<linux/module.h>兩個標頭檔。在用gcc編譯核心模組時,需要加上-DMODULE -D__KERNEL__ -DLINUX這幾個參數,編譯產生的模組(一般為.o檔案)可以使用命令insmod載入Linux核心,從而成為核心的一個組成部分,此時核心會調用模組中的函數init_module( )。當不需要該模組時,可以使用rmmod命令進行卸載,此進核心會調用模組中的函數cleanup_module( )。任何時候都可以使用命令來lsmod查看目前已經載入的模組以及正在使用該模組的使用者數。
3. 裝置驅動程式結構
瞭解裝置驅動程式的基本結構(或者稱為架構),對開發人員而言是非常重要的,Linux的裝置驅動程式大致可以分為如下幾個部分:驅動程式的註冊與登出、裝置的開啟與釋放、裝置的讀寫操作、裝置的控制操作、裝置的中斷和輪詢處理。
向系統增加一個驅動程式意味著要賦予它一個主裝置號,這可以通過在驅動程式的初始化過程中調用register_chrdev( )或者register_blkdev( )來完成。而在關閉字元裝置或者塊裝置時,則需要通過調用unregister_chrdev( )或unregister_blkdev( )從核心中登出裝置,同時釋放佔用的主裝置號。
開啟裝置是通過調用file_operations結構中的函數open( )來完成的,它是驅動程式用來為今後的操作完成初始化準備工作的。在大部分驅動程式中,open( )通常需要完成下列工作:1. 檢查裝置相關錯誤,如裝置尚未準備好等。2. 如果是第一次開啟,則初始化硬體裝置。3. 識別次裝置號,如果有必要則更新讀寫操作的當前位置指標f_ops。4. 分配和填寫要放在file->private_data裡的資料結構。5. 使用計數增1。釋放裝置是通過調用file_operations結構中的函數release( )來完成的,這個裝置方法有時也被稱為close( ),它的作用正好與open( )相反,通常要完成下列工作:6. 使用計數減1。7. 釋放在file->private_data中分配的記憶體。8. 如果使用計算為0,則關閉裝置。
字元裝置的讀寫操作相對比較簡單,直接使用函數read( )和write( )就可以了。但如果是塊裝置的話,則需要調用函數block_read( )和block_write( )來進行資料讀寫,這兩個函數將向裝置請求表中增加讀寫請求,以便Linux核心可以對請求順序進行最佳化。由於是對記憶體緩衝區而不是直接對裝置進行操作的,因此能很大程度上加快讀寫速度。如果記憶體緩衝區中沒有所要讀入的資料,或者需要執行寫操作將資料寫入裝置,那麼就要執行真正的資料轉送,這是通過調用資料結構blk_dev_struct中的函數request_fn( )來完成的。
除了讀寫操作外,應用程式有時還需要對裝置進行控制,這可以通過裝置驅動程式中的函數ioctl( )來完成。ioctl( )的用法與具體裝置密切關聯,因此需要根據裝置的實際情況進行具體分析。
對於不支援中斷的硬體裝置,讀寫時需要輪流查詢裝置狀態,以便決定是否繼續進行資料轉送。如果裝置支援中斷,則可以按中斷方式進行操作。
上一篇:《Linux下PCI裝置驅動程式開發 --- PCI 體繫結構(一)》
下一篇:《Linux下PCI裝置驅動程式開發 --- PCI驅動的實現(三)》