file_operation就是把系統調用和驅動程序關聯起來的關鍵數據結構。這個結構的每個成員都對應着一個系統調用。讀取file_operation中相應的函數指針,接着把控制權轉交給函數,從而完成了Linux設備驅動程序的工做。在系統內部,I/O設備的存取操做經過特定的入口點來進行,而這組特定的入口點偏偏是由設備驅動程序提供的。一般這組設備驅動程序接口是由結構file_operations結構體向系統說明的,它定義在include/linux/fs.h中。node
傳統上, 一個 file_operation 結構或者其一個指針稱爲 fops( 或者它的一些變體). 結構中的每一個成員必須指向驅動中的函數, 這些函數實現一個特別的操做, 或者對於不支持的操做留置爲 NULL. 當指定爲 NULL 指針時內核的確切的行爲是每一個函數不一樣的。在你通讀 file_operations 方法的列表時, 你會注意到很多參數包含字串 __user. 這種註解是一種文檔形式, 注意, 一個指針是一個不能被直接解引用的用戶空間地址. 對於正常的編譯, __user 沒有效果, 可是它可被外部檢查軟件使用來找出對用戶空間地址的錯誤使用。linux
註冊設備編號僅僅是驅動代碼必須進行的諸多任務中的第一個。首先須要涉及一個別的,大部分的基礎性的驅動操做包括3 個重要的內核數據結構,稱爲 file_operations,file,和 inode。須要對這些結構的基本瞭解纔可以作大量感興趣的事情。web
struct file_operations是一個字符設備把驅動的操做和設備號聯繫在一塊兒的紐帶,是一系列指針的集合,每一個被打開的文件都對應於一系列的操做,這就是file_operations,用來執行一系列的系統調用。
struct file表明一個打開的文件,在執行file_operation中的open操做時被建立,這裏須要注意的是與用戶空間inode指針的區別,一個在內核,而file指針在用戶空間,由c庫來定義。
struct inode被內核用來表明一個文件,注意和struct file的區別,struct inode一個是表明文件,struct file一個是表明打開的文件,struct inode包括很重要的二個成員:
dev_t i_rdev設備文件的設備號
struct cdev *i_cdev 表明字符設備的數據結構
struct inode結構是用來在內核內部表示文件的.同一個文件能夠被打開好屢次,因此能夠對應不少struct file,可是隻對應一個struct inode.後端
File_operations的數據結構以下:服務器
struct module *owner網絡
第一個 file_operations 成員根本不是一個操做; 它是一個指向擁有這個結構的模塊的指針. 這個成員用來在它的操做還在被使用時阻止模塊被卸載. 幾乎全部時間中, 它被簡單初始化爲 THIS_MODULE, 一個在 <linux/module.h> 中定義的宏.數據結構
loff_t (*llseek) (struct file *, loff_t, int);app
llseek 方法用做改變文件中的當前讀/寫位置, 而且新位置做爲(正的)返回值. loff_t 參數是一個"long offset", 而且就算在 32位平臺上也至少 64 位寬. 錯誤由一個負返回值指示. 若是這個函數指針是 NULL, seek 調用會以潛在地沒法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述).異步
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);async
用來從設備中獲取數據. 在這個位置的一個空指針致使 read 系統調用以 -EINVAL("Invalid argument") 失敗. 一個非負返回值表明了成功讀取的字節數( 返回值是一個 "signed size" 類型, 經常是目標平臺本地的整數類型).
ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一個異步讀 -- 可能在函數返回前不結束的讀操做. 若是這個方法是 NULL, 全部的操做會由 read 代替進行(同步地).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
發送數據給設備. 若是 NULL, -EINVAL 返回給調用 write 系統調用的程序. 若是非負, 返回值表明成功寫的字節數.
ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化設備上的一個異步寫.
int (*readdir) (struct file *, void *, filldir_t);
對於設備文件這個成員應當爲 NULL; 它用來讀取目錄, 而且僅對文件系統有用.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 個系統調用的後端: poll, epoll, 和 select, 都用做查詢對一個或多個文件描述符的讀或寫是否會阻塞. poll 方法應當返回一個位掩碼指示是否非阻塞的讀或寫是可能的, 而且, 可能地, 提供給內核信息用來使調用進程睡眠直到 I/O 變爲可能. 若是一個驅動的 poll 方法爲 NULL, 設備假定爲不阻塞地可讀可寫.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系統調用提供了發出設備特定命令的方法(例如格式化軟盤的一個磁道, 這不是讀也不是寫). 另外, 幾個 ioctl 命令被內核識別而沒必要引用 fops 表. 若是設備不提供 ioctl 方法, 對於任何未事先定義的請求(-ENOTTY, "設備無這樣的 ioctl"), 系統調用返回一個錯誤.
int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用來請求將設備內存映射到進程的地址空間. 若是這個方法是 NULL, mmap 系統調用返回 -ENODEV.
int (*open) (struct inode *, struct file *);
儘管這經常是對設備文件進行的第一個操做, 不要求驅動聲明一個對應的方法. 若是這個項是 NULL, 設備打開一直成功, 可是你的驅動不會獲得通知.
int (*flush) (struct file *);
flush 操做在進程關閉它的設備文件描述符的拷貝時調用; 它應當執行(而且等待)設備的任何未完成的操做. 這個必須不要和用戶查詢請求的 fsync 操做混淆了. 當前, flush 在不多驅動中使用; SCSI 磁帶驅動使用它, 例如, 爲確保全部寫的數據在設備關閉前寫到磁帶上. 若是 flush 爲 NULL, 內核簡單地忽略用戶應用程序的請求.
int (*release) (struct inode *, struct file *);
在文件結構被釋放時引用這個操做. 如同 open, release 能夠爲 NULL.
int (*fsync) (struct file *, struct dentry *, int);
這個方法是 fsync 系統調用的後端, 用戶調用來刷新任何掛着的數據. 若是這個指針是 NULL, 系統調用返回 -EINVAL.
int (*aio_fsync)(struct kiocb *, int);
這是 fsync 方法的異步版本.
int (*fasync) (int, struct file *, int);
這個操做用來通知設備它的 FASYNC 標誌的改變. 異步通知是一個高級的主題, 在第 6 章中描述. 這個成員能夠是NULL 若是驅動不支持異步通知.
int (*lock) (struct file *, int, struct file_lock *);
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 *);
這些方法實現發散/匯聚讀和寫操做. 應用程序偶爾須要作一個包含多個內存區的單個讀或寫操做; 這些系統調用容許它們這樣作而沒必要對數據進行額外拷貝. 若是這些函數指針爲 NULL, read 和 write 方法被調用( 可能多於一次 ).
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
這個方法實現 sendfile 系統調用的讀, 使用最少的拷貝從一個文件描述符搬移數據到另外一個. 例如, 它被一個須要發送文件內容到一個網絡鏈接的 web 服務器使用. 設備驅動經常使 sendfile 爲 NULL.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另外一半; 它由內核調用來發送數據, 一次一頁, 到對應的文件. 設備驅動實際上不實現 sendpage.
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
這個方法的目的是在進程的地址空間找一個合適的位置來映射在底層設備上的內存段中. 這個任務一般由內存管理代碼進行; 這個方法存在爲了使驅動能強制特殊設備可能有的任何的對齊請求. 大部分驅動能夠置這個方法爲 NULL.
int (*check_flags)(int)
這個方法容許模塊檢查傳遞給 fnctl(F_SETFL...) 調用的標誌.
int (*dir_notify)(struct file *, unsigned long);
這個方法在應用程序使用 fcntl 來請求目錄改變通知時調用. 只對文件系統有用; 驅動不須要實現 dir_notify.
scull 設備驅動只實現最重要的設備方法. 它的 file_operations 結構是以下初始化的:
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
這個聲明使用標準的C標記式結構初始化語法. 這個語法是首選的, 由於它使驅動在結構定義的改變之間更加可移植, 而且, 有爭議地, 使代碼更加緊湊和可讀. 標記式初始化容許結構成員從新排序; 在某種狀況下, 真實的性能提升已經實現, 經過安放常用的成員的指針在相同硬件高速存儲行中.
結構體file_operations在頭文件linux/fs.h中定義,用來存儲驅動內核模塊提供的對設備進行各類操做的函數的指針。該結構體的每一個域都對應着驅動內核模塊用來處理某個被請求的事務的函數的地址。 舉個例子,每一個字符設備須要定義一個用來讀取設備數據的函數。結構體file_operations中存儲着內核模塊中執行這項操做的函數的地址。一下是該結構體在內核2.6.5中看起來的樣子: 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(*aio_read) (struct kiocb *, char__user *, size_t, loff_t); ssize_t(*write) (struct file *, const char__user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, constchar __user *, size_t,loff_t); int (*readdir) (struct file *, void *,filldir_t); unsigned int (*poll) (struct file *, structpoll_table_struct *); int (*ioctl) (struct inode *, struct file*, unsigned int,unsigned long); int (*mmap) (struct file *, structvm_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 (*aio_fsync) (struct kiocb *, intdatasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, structfile_lock *); ssize_t(*readv) (struct file *, conststruct iovec *, unsigned long,loff_t *); ssize_t(*writev) (struct file *, conststruct iovec *, unsigned long,loff_t *); ssize_t(*sendfile) (struct file *, loff_t*, size_t, read_actor_t,void __user *); ssize_t(*sendpage) (struct file *, structpage *, int, size_t,loff_t *, int); unsigned long (*get_unmapped_area) (structfile *, unsigned long,unsigned long, unsigned long,unsigned long); }; 驅動內核模塊是不須要實現每一個函數的。像視頻卡的驅動就不須要從目錄的結構中讀取數據。那麼,相對應的file_operations重的項就爲NULL。 gcc還有一個方便使用這種結構體的擴展。你會在較現代的驅動內核模塊中見到。新的使用這種結構體的方式以下: struct file_operations fops = { read: device_read, write: device_write, open: device_open, release: device_release }; 一樣也有C99語法的使用該結構體的方法,而且它比GNU擴展更受推薦。我使用的版本爲2.95爲了方便那些想移植你的代碼的人,你最好使用這種語法。它將提升代碼的兼容性: struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release }; 這種語法很清晰,你也必須清楚的意識到沒有顯示聲明的結構體成員都被gcc初始化爲NULL。 指向結構體struct file_operations的指針一般命名爲fops。 |