先看下面這張圖,這是Linux 中虛擬文件系統(tǒng)、一般的設(shè)備文件與設(shè)備驅(qū)動(dòng)程序值間的函數(shù)調(diào)用關(guān)系;
上面這張圖展現(xiàn)了一個(gè)應(yīng)用程序調(diào)用字符設(shè)備驅(qū)動(dòng)的過程, 在設(shè)備驅(qū)動(dòng)程序的設(shè)計(jì)中,一般而言,會(huì)關(guān)心 file 和 inode 這兩個(gè)結(jié)構(gòu)體
用戶空間使用 open() 函數(shù)打開一個(gè)字符設(shè)備 fd = open("/dev/hello",O_RDWR) , 這一函數(shù)會(huì)調(diào)用兩個(gè)數(shù)據(jù)結(jié)構(gòu) struct inode{...}與struct file{...} ,二者均在虛擬文件系統(tǒng)VFS處,下面對兩個(gè)數(shù)據(jù)結(jié)構(gòu)進(jìn)行解析:
一、file 文件結(jié)構(gòu)體
在設(shè)備驅(qū)動(dòng)中,這也是個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu),必須要注意一點(diǎn),這里的file與用戶空間程序中的FILE指針是不同的,用戶空間FILE是定義在C庫中,從來不會(huì)出現(xiàn)在內(nèi)核中。而struct file,卻是內(nèi)核當(dāng)中的數(shù)據(jù)結(jié)構(gòu),因此,它也不會(huì)出現(xiàn)在用戶層程序中。
file結(jié)構(gòu)體指示一個(gè)已經(jīng)打開的文件(設(shè)備對應(yīng)于設(shè)備文件),其實(shí)系統(tǒng)中的每個(gè)打開的文件在內(nèi)核空間都有一個(gè)相應(yīng)的struct file結(jié)構(gòu)體,它由內(nèi)核在打開文件時(shí)創(chuàng)建,并傳遞給在文件上進(jìn)行操作的任何函數(shù),直至文件被關(guān)閉。如果文件被關(guān)閉,內(nèi)核就會(huì)釋放相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
在內(nèi)核源碼中,struct file要么表示為file,或者為filp(意指“file pointer”), 注意區(qū)分一點(diǎn),file指的是struct file本身,而filp是指向這個(gè)結(jié)構(gòu)體的指針。
下面是幾個(gè)重要成員:
a -- fmode_t f_mode;
此文件模式通過FMODE_READ, FMODE_WRITE識別了文件為可讀的,可寫的,或者是二者。在open或ioctl函數(shù)中可能需要檢查此域以確認(rèn)文件的讀/寫權(quán)限,你不必直接去檢測讀或?qū)憴?quán)限,因?yàn)樵谶M(jìn)行octl等操作時(shí)內(nèi)核本身就需要對其權(quán)限進(jìn)行檢測。
b -- loff_t f_pos;
當(dāng)前讀寫文件的位置。為64位。如果想知道當(dāng)前文件當(dāng)前位置在哪,驅(qū)動(dòng)可以讀取這個(gè)值而不會(huì)改變其位置。對read,write來說,當(dāng)其接收到一個(gè)loff_t型指針作為其最后一個(gè)參數(shù)時(shí),他們的讀寫操作便作更新文件的位置,而不需要直接執(zhí)行filp ->f_pos操作。而llseek方法的目的就是用于改變文件的位置。
c -- unsigned int f_flags;
文件標(biāo)志,如O_RDONLY, O_NONBLOCK以及O_SYNC。在驅(qū)動(dòng)中還可以檢查O_NONBLOCK標(biāo)志查看是否有非阻塞請求。其它的標(biāo)志較少使用。特別地注意的是,讀寫權(quán)限的檢查是使用f_mode而不是f_flog。所有的標(biāo)量定義在頭文件中
d -- struct file_Operations *f_op;
與文件相關(guān)的各種操作。當(dāng)文件需要迅速進(jìn)行各種操作時(shí),內(nèi)核分配這個(gè)指針作為它實(shí)現(xiàn)文件打開,讀,寫等功能的一部分。filp->f_op 其值從未被內(nèi)核保存作為下次的引用,即你可以改變與文件相關(guān)的各種操作,這種方式效率非常高。
file_operation 結(jié)構(gòu)體解析如下:Linux 字符設(shè)備驅(qū)動(dòng)結(jié)構(gòu)(四)—— file_operations 結(jié)構(gòu)體知識解析
e -- void *PRivate_data;
在驅(qū)動(dòng)調(diào)用open方法之前,open系統(tǒng)調(diào)用設(shè)置此指針為NULL值。你可以很自由的將其做為你自己需要的一些數(shù)據(jù)域或者不管它,如,你可以將其指向一個(gè)分配好的數(shù)據(jù),但是你必須記得在file struct被內(nèi)核銷毀之前在release方法中釋放這些數(shù)據(jù)的內(nèi)存空間。private_data用于在系統(tǒng)調(diào)用期間保存各種狀態(tài)信息是非常有用的。
二、 inode結(jié)構(gòu)體
VFS inode 包含文件訪問權(quán)限、屬主、組、大小、生成時(shí)間、訪問時(shí)間、最后修改時(shí)間等信息。它是Linux 管理文件系統(tǒng)的最基本單位,也是文件系統(tǒng)連接任何子目錄、文件的橋梁。
內(nèi)核使用inode結(jié)構(gòu)體在內(nèi)核內(nèi)部表示一個(gè)文件。因此,它與表示一個(gè)已經(jīng)打開的文件描述符的結(jié)構(gòu)體(即file 文件結(jié)構(gòu))是不同的,我們可以使用多個(gè)file 文件結(jié)構(gòu)表示同一個(gè)文件的多個(gè)文件描述符,但此時(shí),所有的這些file文件結(jié)構(gòu)全部都必須只能指向一個(gè)inode結(jié)構(gòu)體。
inode結(jié)構(gòu)體包含了一大堆文件相關(guān)的信息,但是就針對驅(qū)動(dòng)代碼來說,我們只要關(guān)心其中的兩個(gè)域即可:
(1) dev_t i_rdev;
表示設(shè)備文件的結(jié)點(diǎn),這個(gè)域?qū)嶋H上包含了設(shè)備號。
(2) struct cdev *i_cdev;
struct cdev是內(nèi)核的一個(gè)內(nèi)部結(jié)構(gòu),它是用來表示字符設(shè)備的,當(dāng)inode結(jié)點(diǎn)指向一個(gè)字符設(shè)備文件時(shí),此域?yàn)橐粋€(gè)指向inode結(jié)構(gòu)的指針。
下面是源代碼:
[cpp] view plain copy%20 %20 %20 可以看到全局?jǐn)?shù)組%20chrdevs%20包含了255(CHRDEV_MAJOR_HASH_SIZE%20的值)個(gè)%20struct%20char_device_struct的元素,每一個(gè)對應(yīng)一個(gè)相應(yīng)的主設(shè)備號。
%20 %20 %20 如果分配了一個(gè)設(shè)備號,就會(huì)創(chuàng)建一個(gè)%20struct%20char_device_struct%20的對象,并將其添加到%20chrdevs%20中;這樣,通過chrdevs數(shù)組,我們就可以知道分配了哪些設(shè)備號。
相關(guān)函數(shù),(這些函數(shù)在上篇已經(jīng)介紹過,現(xiàn)在回顧一下:
register_chrdev_region( ) 分配指定的設(shè)備號范圍
alloc_chrdev_region( ) 動(dòng)態(tài)分配設(shè)備范圍
他們都主要是通過調(diào)用函數(shù) __register_chrdev_region() 來實(shí)現(xiàn)的;要注意,這兩個(gè)函數(shù)僅僅是注冊設(shè)備號!如果要和cdev關(guān)聯(lián)起來,還要調(diào)用cdev_add()。
register_chrdev( )申請指定的設(shè)備號,并且將其注冊到字符設(shè)備驅(qū)動(dòng)模型中.
它所做的事情為:
a -- 注冊設(shè)備號, 通過調(diào)用 __register_chrdev_region() 來實(shí)現(xiàn)
b -- 分配一個(gè)cdev, 通過調(diào)用 cdev_alloc() 來實(shí)現(xiàn)
c -- 將cdev添加到驅(qū)動(dòng)模型中, 這一步將設(shè)備號和驅(qū)動(dòng)關(guān)聯(lián)了起來. 通過調(diào)用 cdev_add() 來實(shí)現(xiàn)
d -- 將第一步中創(chuàng)建的 struct char_device_struct 對象的 cdev 指向第二步中分配的cdev. 由于register_chrdev()是老的接口,這一步在新的接口中并不需要。
四、cdev 結(jié)構(gòu)體
在 Linux 字符設(shè)備驅(qū)動(dòng)開發(fā) (一)—— 字符設(shè)備驅(qū)動(dòng)結(jié)構(gòu)(上) 有解析。
五、文件系統(tǒng)中對字符設(shè)備文件的訪問
下面看一下上層應(yīng)用open() 調(diào)用系統(tǒng)調(diào)用函數(shù)的過程
對于一個(gè)字符設(shè)備文件, 其inode->i_cdev 指向字符驅(qū)動(dòng)對象cdev, 如果i_cdev為 NULL ,則說明該設(shè)備文件沒有被打開.
由于多個(gè)設(shè)備可以共用同一個(gè)驅(qū)動(dòng)程序.所以,通過字符設(shè)備的inode 中的i_devices 和 cdev中的list組成一個(gè)鏈表
首先,系統(tǒng)調(diào)用open打開一個(gè)字符設(shè)備的時(shí)候, 通過一系列調(diào)用,最終會(huì)執(zhí)行到 chrdev_open
(最終是通過調(diào)用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 這一系列的調(diào)用過程,本文暫不討論)
int chrdev_open(struct inode * inode, struct file * filp)
chrdev_open()所做的事情可以概括如下:
1. 根據(jù)設(shè)備號(inode->i_rdev), 在字符設(shè)備驅(qū)動(dòng)模型中查找對應(yīng)的驅(qū)動(dòng)程序, 這通過kobj_lookup() 來實(shí)現(xiàn), kobj_lookup()會(huì)返回對應(yīng)驅(qū)動(dòng)程序cdev的kobject.
2. 設(shè)置inode->i_cdev , 指向找到的cdev.
3. 將inode添加到cdev->list 的鏈表中.
4. 使用cdev的ops 設(shè)置file對象的f_op
5. 如果ops中定義了open方法,則調(diào)用該open方法
6. 返回
執(zhí)行完 chrdev_open()之后,file對象的f_op指向cdev的ops,因而之后對設(shè)備進(jìn)行的read, write等操作,就會(huì)執(zhí)行cdev的相應(yīng)操作。新聞熱點(diǎn)
疑難解答