Linux設備驅動學習(4)-字符設備驅動
時間:2018-12-26 00:00:00
來源:信盈達
作者:信盈達
本篇文章記錄的是我閱讀《Essential Linux Device Drivers》-字符設備驅動的閱讀筆記和思考紀錄。
順序存取設備數(shù)據(jù)。字符設備驅動驅動程序能從打印機、鼠標、看門狗、磁帶、內存、實時時鐘等幾類設備獲取原始數(shù)據(jù),但它不適合管理硬盤、軟盤和光盤等可隨機訪問的塊設備中的數(shù)據(jù)。
從程序結構的角度看,字符設備驅動程序包括如下內容:
(2)入口函數(shù)集,如open()、read(),這些函數(shù)對應相應的I/O系統(tǒng)調用,由用戶程序通過對應的/dev、節(jié)點調用。
(3)中斷例程、底半部例程、定時器處理例程、內核輔助線程以及其他組成部分。
從數(shù)據(jù)流的角度看,包括如下關鍵的數(shù)據(jù)結構:
(3)struct file_operations
驅動程序初始化,init()函數(shù)是注冊機制的基礎。它負責完成如下工作:
(1)申請分配主設備號,alloc_chrdev_region();
(2)為特定設備相關的數(shù)據(jù)結構分配內存,file_operation
(3)將入口函數(shù)(open()、read()等)與字符驅動程序的cdev抽象相關聯(lián)
(4)將主設備號與驅動程序的cdev相關聯(lián),cdev_init(),cdev_add()
(5)在/dev和/sys下創(chuàng)建節(jié)點,class_create(),device_create(),(這兩個函數(shù)用于自動創(chuàng)建設備結點)
打開與釋放,當應用程序打開設備節(jié)點時,內核調用相應驅動程序的open()函數(shù),關閉時,內核調用release()函數(shù)。
數(shù)據(jù)交換,read()和write()負責在用戶空間和設備之間交換數(shù)據(jù)的主要驅動函數(shù)。但是,不能從內核中直接訪問用戶空間的緩沖區(qū),反之亦然。將數(shù)據(jù)復制到用戶空間,調用copy_to_user()。調用copy_from_user()完成相反的工作。由于這兩個函數(shù)可能會睡眠,所以在調用這兩個函數(shù)的時候不能持有自旋鎖。
如果一個字符驅動程序的write()成功返回,就表示驅動程序已經(jīng)完成了將數(shù)據(jù)傳送下去的任務。但這并不能保證數(shù)據(jù)已經(jīng)成功地寫到了設備中。可以調用fsync()函數(shù),確保數(shù)據(jù)從驅動程序緩沖區(qū)中排出,并且寫到設備。
如果用戶程序有數(shù)據(jù)存儲在多個緩沖區(qū)中并需要發(fā)送至設備,可以使用向量驅動函數(shù)aio_read()/aio_write()。
另一個數(shù)據(jù)訪問函數(shù)是mmap(),他將設備內存和用戶的虛擬內存關聯(lián)在一起。
宏likely()和unlikely()負責將相關條件為真/假的可能性報告給GCC。GCC根據(jù)這一信息決定要執(zhí)行的代碼分支。
查找,內核使用內部指針跟蹤當前文件訪問的位置。應用程序使用lseek()系統(tǒng)調用去申請內部文件指針的重定位。字符驅動程序相對應的是llseek()函數(shù)。
控制,常見的字符驅動程序函數(shù)被稱作I/O控制(ioctl)。
兩個能夠感知數(shù)據(jù)是否可獲得的字符驅動程序方法:poll()和fasync()。前者是同步的,后者是異步的。
輪詢,poll()驅動程序方法是select()系統(tǒng)調用的支柱。
fasync,fcntl(F_SETFL)調用導致fasync()驅動程序方法的調用。fasync()負責從接收SIGIO信號的進程列表里添加或刪除條目。最后,fasync()利用內核庫函數(shù)提供的服務調用了fasync_helper()。
字符驅動程序調用kill_fasync()發(fā)送SIGIO給注冊的進程。為了通知一個讀事件,將POLLIN作為kill_fasync()的參數(shù)。相應的寫事件傳遞的參數(shù)是POLLOUT。
drivers/parport/目錄包括IEEE1284并行端口通信的具體實現(xiàn)代碼(稱為parport)。parport有一個架構無關的模塊和一個架構相關的模塊。這兩個模塊為以并行端口為接口的設備驅動程序提供可編程接口。
新的設備模型將驅動程序和設備區(qū)分開來。調用parport_register_device()注冊設備。
還可以使用sysfs控制并行端口。它使用了kobject,用于代表“控制”抽象。
內核中對RTC的支持分為兩層:(1)硬件無關的頂層字符設備驅動程序,用于實現(xiàn)內核的RTC API;(2)硬件相關的底層驅動程序,用于和底層的總線通信。底層的RTC驅動程序由總線決定。
內核有一個專門的RTC子系統(tǒng),提供了頂層的字符設備驅動程序,并給出了用于頂層和底層RTC驅動程序進行捆綁的核心基礎結構。分散在不同的總線有關的目錄下的底層RTC驅動程序通過此子系統(tǒng)統(tǒng)一在drivers/rtc/下。
RTC子系統(tǒng)使系統(tǒng)可以擁有不只一個RTC。
為了使能RTC子系統(tǒng),在內核配置過程中需要選中CONFIG_RTC_CLASS配置選項。
有幾個常用的內核工具沒有和任何物理硬件相連接,它們被靈巧地實現(xiàn)偽字符設備。null設備、zero設備和內核隨機數(shù)產(chǎn)生器被當作虛擬設備,并使用偽字符設備驅動程序來訪問。
/dev/null字符設備接收你不想顯示在屏幕上的數(shù)據(jù)。
/dev/zero驅動程序的read()方法中獲取一串0。
/dev/random和/dev/urandom用于產(chǎn)生隨機數(shù),從/dev/random讀取的隨機數(shù)隨機性高。
/dev/mem和/dev/kmem是典型的偽字符設備,它們提供了查看系統(tǒng)內存的工具。
上述幾種字符設備擁有不同的設備號,但擁有靜態(tài)分配的相同的主設備號1。還有其他的偽驅動程序屬于同一個主設備號系列:其中/dev/full模擬一個總是處于滿的設備,/dev/port查看系統(tǒng)的I/O端口。
混雜驅動程序是那些簡單的字符設備驅動程序,它們擁有一些相同的特性。內核將這些共同性抽象至一個API中(具體實現(xiàn)見代碼drivers/char/misc.c),這簡化了這些驅動程序初始化的方式。
所有的混雜設備被分配一個主設備號10,但每個設備可選擇一個單獨的次設備號。
混雜驅動程序只需要調用misc_register()即可。每個混雜驅動程序自動出現(xiàn)在/sys/class/misc/文件中,而不必驅動程序編寫者再編寫了。
在drivers/char/目錄下運行grep misc_register()命令可找到內核中其他的混雜設備。
(1)open()調用可能由于幾個原因而失敗。
(2)成功運行的read()和write()返回的字節(jié)數(shù)可能是1至請求的字節(jié)數(shù)之間的任意值,因此應用程序必須能處理這些情況。
(3)即使1字節(jié)的數(shù)據(jù)讀或寫就緒,select()也會返回成功。
(4)很多字符驅動程序方法是可選的,并不是所有的方法都提供。
另外,字符驅動程序不僅在drivers/char/目錄下。下面是一些“超級”字符驅動程序:
(1)串行驅動程序,放在drivers/serial/目錄下。
(2)輸入驅動程序,放在drivers/input/目錄下。
(3)幀緩存區(qū)(/dev/fb/*)提供對顯存的訪問,/dev/mem提供對系統(tǒng)內存的訪問途徑。
(4)一些設備類支持少量采用字符接口的硬件。
(5)一些子系統(tǒng)提供額外的字符接口,以向用戶空間提供原始的設備模型。例如MTD子系統(tǒng)
(6)一些內核層提供鉤子,通過導出相應的字符接口實現(xiàn)用戶空間的設備驅動程序。
在drivers/目錄下的register_chrdev上運行grep-r可了解內核中字符驅動程序的大致情況。