struct file(file結構體):
struct file結構體定義在include/linux/fs.h中定義。文件結構體代表一個打開的文件,系統中的每個打開的文件在內核空間都有一個關聯的 struct file。
它由內核在打開文件時創建,並傳遞給在文件上進行操作的任何函數。在文件的所有實例都關閉后,內核釋放這個數據結構。在內核創建和驅動源碼中,
struct file的指針通常被命名為file或filp。其有兩個非常重要的字段:文件描述符和緩沖區。
文件描述符fd:
fd只是一個小整數,在open時產生。起到一個索引的作用,進程通過PCB中的文件描述符表找到該fd所指向的文件指針filp。
文件描述符的操作(如: open)返回的是一個文件描述符,內核會在每個進程空間中維護一個文件描述符表, 所有打開的文件都將通過此表中的文件描述符來引用;
而流(如: fopen)返回的是一個FILE結構指針, FILE結構是包含有文件描述符的,FILE結構函數可以看作是對fd直接操作的系統調用的封裝, 它的優點是帶有I/O
緩存。
每個進程在PCB(Process Control Block)即進程控制塊中都保存着一份文件描述符表,文件描述符就是這個表的索引,文件描述表中每個表項都有一個指
向已打開文件的指針,現在我們明確一下:已打開的文件在內核中用file結構體表示,文件描述符表中的指針指向file結構體。
緩沖區:
A)緩沖區機制
根據應用程序對文件的訪問方式,即是否存在緩沖區,對文件的訪問可以分為帶緩沖區的操作和非緩沖區的文件操作:
a) 帶緩沖區文件操作:高級標准文件I/O操作,將會在用戶空間中自動為正在使用的文件開辟內存緩沖區。
b) 非緩沖區文件操作:低級文件I/O操作,讀寫文件時,不會開辟對文件操作的緩沖區,直接通過系統調用對磁盤進行操作(讀、寫等),當然用於可以在自己
的程序中為每個文件設定緩沖區。
兩種文件操作的解釋和比較:
1、非緩沖的文件操作訪問方式,每次對文件進行一次讀寫操作時,都需要使用讀寫系統調用來處理此操作,即需要執行一次系統調用,執行一次系統調用將涉
及到CPU狀態的切換,即從用戶空間切換到內核空間,實現進程上下文的切換,這將損耗一定的CPU時間,頻繁的磁盤訪問對程序的執行效率造成很大的影響。
2、ANSI標准C庫函數 是建立在底層的系統調用之上,即C函數庫文件訪問函數的實現中使用了低級文件I/O系統調用,ANSI標准C庫中的文件處理函數為了減
少使用系統調用的次數,提高效率,采用緩沖機制,這樣,可以在磁盤文件進行操作時,可以一次從文件中讀出大量的數據到緩沖區中,以后對這部分的訪問就不需
要再使用系統調用了,即需要少量的CPU狀態切換,提高了效率。
B)緩沖類型
標准I/O提供了3種類型的緩沖區。
1、全緩沖區:這種緩沖方式要求填滿整個緩沖區后才進行I/O系統調用操作。對於磁盤文件的操作通常使用全緩沖的方式訪問。第一次執行I/O操作時,ANSI標
准的文件管理函數通過調用malloc函數獲得需要使用的緩沖區,默認大小為8192。
2、行緩沖區:在行緩沖情況下,當在輸入和輸出中遇到換行符時,標准I/O庫函數將會執行系統調用操作。當所操作的流涉及一個終端時(例如標准輸入和標准
輸出),使用行緩沖方式。因為標准I/O庫每行的緩沖區長度是固定的,所以只要填滿了緩沖區,即使還沒有遇到換行符,也會執行I/O系統調用操作,默認行緩沖區
的大小為1024。
3、無緩沖區:
無緩沖區是指標准I/O庫不對字符進行緩存,直接調用系統調用。標准出錯流stderr通常是不帶緩沖區的,這使得出錯信息能夠盡快地顯示出來。
注:
1、標准輸入和標准輸出設備:當且僅當不涉及交互作用設備時,標准輸入流和標准輸出流才是全緩沖的。
2、標准錯誤輸出設備:標准出錯絕不會是全緩沖方式的。
3、對於任何一個給定的流,可以調用setbuf()和setvbuf()函數更改其緩沖區類型。
下面我們通過如下程序來進一步了解緩沖區:
當打印到屏幕(標准輸出):
當寫到文件中:
那么為什么輸出到屏幕只有5條輸出命令而輸出到文件有7條輸出命令呢?
根據輸出結果我們可以看出printf和fwrite重復寫了兩次,沒有重復打印的是write.。
printf和fwrite都是庫函數:結合已有知識,我們了解到當使用庫函數命令時,打印消息並沒有直接寫到輸出位置上,而是 先把數據寫到輸出緩沖區,在刷新至輸
出位置。
1、當輸出目標位置為輸出到顯示器時,則刷新方式是行刷新;
2、當輸出目標位置為輸出到文件中時,刷新方式由行緩沖變為全緩沖,全緩沖是指當把緩沖區寫滿后才能刷新。(或者強制刷新)
代碼中printf和fwrite第一次打印,在fork操作之前,第二次在fork操作之后,原因是因為在fork操作前,printf和fwrite的輸出命令將數據先寫到緩沖區中,此時
只執行了這兩條命令,由於是全緩沖的刷新方式,所以這兩條命令並不足以將緩存寫滿,所以數據暫存在緩沖區中;然后進行fork創建子進程,由於fork創建出的父子
進程代碼共享,而數據不共享,各自私有一份,緩沖區中的數據都屬於數據,因為父進程的殘留數據還在緩沖區中,所以fork完畢后,父子進程將緩存中的數據各自存
一份有父進程殘留數據的數據,所以當父子進程各自刷新時,父子進程會各自執行一次printf和fwrite的輸出命令,所以命令就從原來的兩條變為四條。
struct file 的其他重要成員有:.
1.mode_t f_mode;
文件模式確定文件是可讀的或者是可寫的(或者都是), 通過位 FMODE_READ 和FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函數中檢查這個成員的讀寫
許可, 但是不需要檢查讀寫許可, 因為內核在調用你的方法之前檢查. 當文件還沒有為那種存取而打開時讀或寫的企圖被拒絕, 驅動甚至不知道這個情況.
2.loff_t f_pos;
當前讀寫位置. loff_t 在所有平台都是 64 位( 在 gcc 術語里是 long long ). 驅動可以讀這個值,如果它需要知道文件中的當前位置, 但是正常地不應該改變它; 讀
和寫應當使用它們作為最后參數而收到的指針來更新一個位置, 代替直接作用於 filp->f_pos. 這個規則的一個例外是在 llseek 方法中, 它的目的就是改變文件位置.
3.unsigned int f_flags;
這些是文件標志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驅動應當檢查O_NONBLOCK 標志來看是否是請求非阻塞操作; 其他標志很少使用. 特別地,
應當檢查讀/寫許可, 使用 f_mode 而不是f_flags. 所有的標志在頭文件<linux/fcntl.h> 中定義.
4.struct file_operations *f_op;
和文件關聯的操作. 內核安排指針作為它的open 實現的一部分, 接着讀取它當它需要分派任何的操作時. filp->f_op 中的值從不由內核保存為后面的引用; 這意味
着你可改變你的文件關聯的文件操作, 在你返回調用者之后新方法會起作用. 例如, 關聯到主編號 1 (/dev/null, /dev/zero, 等等)的 open 代碼根據打開的次編號來替
代 filp->f_op 中的操作. 這個做法允許實現幾種行為, 在同一個主編號下而不必在每個系統調用中引入開銷. 替換文件操作的能力是面向對象編程的"方法重載"的內核對
等體.
5.void *private_data;
open 系統調用設置這個指針為 NULL, 在為驅動調用 open 方法之前. 你可自由使用這個成員或者忽略它; 你可以使用這個成員來指向分配的數據, 但是接着你必須
記住在內核銷毀文件結構之前, 在 release 方法中釋放那個內存. private_data 是一個有用的資源, 在系統調用間保留狀態信息, 我們大部分例子模塊都使用它.
6.struct dentry *f_dentry;
關聯到文件的目錄入口( dentry )結構. 設備驅動編寫者正常地不需要關心 dentry 結構, 除了作為 filp->f_dentry->d_inode 存取 inode 結構.