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