大多數(shù)基本的驅(qū)動(dòng)操作涉及到內(nèi)核的 3 個(gè)重要數(shù)據(jù)結(jié)構(gòu): file_operations, file 和 inode 。
我們已經(jīng)擁有一些設(shè)備號,但是如何將其與驅(qū)動(dòng)操作連在一起呢? file_operations 結(jié)構(gòu)就是這個(gè)橋梁,這個(gè)結(jié)構(gòu)體定義在 <linux/fs.h> 中,它是一群函數(shù)的指針集合,每個(gè)所打開的文件都存在一個(gè) f_op 指針指向file_operations 結(jié)構(gòu)體,里面的操作大部分主要完成系統(tǒng)調(diào)用,如 open,read 等。我們可以將 file 看成對象 , 對它操作的操作看成是方法 , 使用面向?qū)ο蟪绦蛟O(shè)計(jì)( object-oriented programming )這個(gè)術(shù)語表征某一對象的行為聲明會(huì)作用于它自身。后面將會(huì)看到更多這種情況。
一般來說,一個(gè) 指向file_operations 結(jié)構(gòu)的指針稱為 fops 。這個(gè)結(jié)構(gòu)體里面的每個(gè)域必須指向驅(qū)動(dòng)中的某些函數(shù)以完成一些特定的操作,或者賦予 NULL 值表示沒有支持的操作。當(dāng)被賦予 NULL 時(shí),內(nèi)核的具體行為對每個(gè)函數(shù)來說都不盡相同。
1 首先我們來看看 file_operations 結(jié)構(gòu)吧
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
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);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
看到上面一大坨是不是有點(diǎn)暈?zāi)??其?shí)我們只關(guān)注一些在寫驅(qū)動(dòng)程序時(shí)需要用到的相關(guān)項(xiàng)就行了。
當(dāng)你瀏覽 file_operations 結(jié)構(gòu)中的各種操作方法時(shí),我們可以發(fā)現(xiàn)有許多的參數(shù)含有 ”__user ” ,可以說這是?種規(guī)范吧,用以表明這是用戶層空間的指針,在內(nèi)核中我們不能直接對它進(jìn)行引用。
先來看第一個(gè)域吧: struct module *owner;
這個(gè)域并非某種操作方式,它只是一個(gè)指向擁有此結(jié)構(gòu)體的模塊的指針,它主要是用來防止模塊在使用的過程中被卸載。在大多數(shù)情況下,這個(gè)域一般都被初始化成 THIS_MODULE, 即 .owner = THIS_MODULE;
loff_t (*llseek) (struct file *, loff_t, int);
此方法是用來改變文件中的讀寫位置,函數(shù)正確返回的是一個(gè)新的位置。第二個(gè)參數(shù): loff_t 是一個(gè)“ long” 型的偏移量,一般是 64 位。函數(shù)出錯(cuò)時(shí)返回的是負(fù)值。
ssize_t (*read) (struct file *, char _ _user *, size_t, loff_t *);
此方法用于存儲來自設(shè)備的數(shù)據(jù),當(dāng)返回值為非負(fù)值時(shí)表示成功讀取的字節(jié)數(shù),返回值一般是有符號整型的數(shù)據(jù)類型。
ssize_t (*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);
默認(rèn)情況下為異步讀取 – 在函數(shù)返回之前,可能讀操作并未全部完成。如果此方法為 NULL 型,所有的操作方式都由 read (同步)代替。
ssize_t (*write) (struct file *, const char _ _user *, size_t, loff_t *);
主要是給設(shè)備發(fā)數(shù)據(jù),如果函數(shù)返回值為非負(fù)整數(shù),表示成功寫入的字節(jié)數(shù)。
ssize_t (*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *);
對設(shè)備進(jìn)行異常的寫操作
int (*readdir) (struct file *, void *, filldir_t);
對設(shè)備文件而言,此域應(yīng)為 NULL ,因?yàn)樗怯糜谧x目錄,在文件系統(tǒng)中使用。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
這三個(gè)系統(tǒng)調(diào)用 (poll, epoll , select)最終都用調(diào)用底層的poll方法 ,換句話說,這三個(gè)系統(tǒng)調(diào)用最終在驅(qū)動(dòng)里都是執(zhí)行 poll 方法。他們是用于查詢是否對阻塞的文件描述符進(jìn)行讀寫操作。 Poll 方法將返回一位隱碼標(biāo)志位( mask )來指明是否有可用的非阻塞讀或?qū)懖僮?,如果有,則給內(nèi)核提供一些信息,這些信息用于使相應(yīng)的進(jìn)程進(jìn)入休眠直到 IO 可用。如果驅(qū)動(dòng)中給此域賦 NULL, 此時(shí)設(shè)備無阻塞發(fā)生時(shí)依舊都是可讀、可寫的。
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系統(tǒng)調(diào)用提供了一種解決設(shè)備特殊命令的方法,如格式化磁盤操作,這些既不是讀也不是寫操作。此外,許多 ioctl 命令是由內(nèi)核組織的而非從 ftops 表中引用,如果設(shè)備沒有提供 ioctl 方法,此系統(tǒng)調(diào)用將返回錯(cuò)誤。
int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用于將設(shè)備存儲空間與進(jìn)程的地址空間進(jìn)行映射,如果此域?yàn)?/span> NULL,mmap 系統(tǒng)調(diào)用將返回 -ENODEV.
int (*open) (struct inode *, struct file *);
open 總是在設(shè)備文件上執(zhí)行的第一步操作。這個(gè)比較簡單,不多說啦。。
int (*flush) (struct file *);
當(dāng)進(jìn)程關(guān)閉設(shè)備的文件描述時(shí), flush 操作就被觸發(fā),它將對設(shè)備執(zhí)行一些非常重要的操作。不能將其與應(yīng)用層上的 fsync 操作混淆。在目前, flush 用在非常少的驅(qū)動(dòng)上,如 SCSI 磁道驅(qū)動(dòng)就使用它來確保在設(shè)備關(guān)閉之前所有的數(shù)據(jù)都被寫入硬盤,如果 flush 為 NULL, 那么內(nèi)核會(huì)忽略用戶應(yīng)用層的請求。
int (*release) (struct inode *, struct file *);
當(dāng) file 結(jié)構(gòu)體被釋放時(shí),此操作被觸發(fā)。與 open 一樣,它也可以為 NULL.
int (*fsync) (struct file *, struct dentry *, int);
此操作用于通知設(shè)備其 FASYNC 標(biāo)志位發(fā)生變化了,此方法在異步通知得到了應(yīng)用,如果此域?yàn)?/span> NULL, 驅(qū)動(dòng)就不支持異步通知操作了。
int (*lock) (struct file *, int, struct file_lock *);
用于完成文件的加鎖操作。鎖對一般文件而言是?不可少的特性,但是在設(shè)備驅(qū)動(dòng)中不會(huì)實(shí)現(xiàn)它。
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
此方法主要完成分散或聚集式的 read/write 操作,應(yīng)用程序偶爾需要在多個(gè)內(nèi)存區(qū)域中做一些簡單的讀或?qū)懖僮鳎@些系統(tǒng)調(diào)用可以使應(yīng)用程序不需要在數(shù)據(jù)上做一些額外的工作就可以實(shí)現(xiàn)在多個(gè)內(nèi)存中做簡單的讀寫。如果為 NULL, 那么調(diào)用的就是 read/write 了。
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t * , int);
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
設(shè)備驅(qū)動(dòng)一般不涉及此項(xiàng)
int (*check_flags)(int)
此方法允許模塊檢查傳遞給 fcntl(F_SETFL…) 的標(biāo)志量。
----------------------------------------------------------------------------
2file 文件結(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í)系統(tǒng)中的每個(gè)打開的文件在內(nèi)核中都有一個(gè)相應(yīng)的 struct file 結(jié)構(gòu)體,直至文件被關(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)體的指針。
fmode_t f_mode;
此文件模式通過 FMODE_READ, FMODE_WRITE 識別了文件為可讀的,可寫的,或者是二者。在 open 或ioctl 函數(shù)中可能需要檢查此域以確認(rèn)文件的讀 / 寫權(quán)限,你不必直接去檢測讀或?qū)憴?quán)限,因?yàn)樵谶M(jìn)行 open/ioctl等操作時(shí)內(nèi)核本身就需要對其權(quán)限進(jìn)行檢測。
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 方法的目的就是用于改變文件的位置。
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)量定義在頭文件 <linux/fcntl.h> 中
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)的各種操作,這種方式效率非常高。
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)信息是非常有用的。
3 inode 結(jié)構(gòu)
內(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)的指針。
此外,內(nèi)核也提供了兩個(gè)宏可以從inode結(jié)點(diǎn)中獲取主次設(shè)備號,宏的原型如下:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);