1. 程式人生 > >探索檔案描述符(fd)與FILE結構體之間的關係

探索檔案描述符(fd)與FILE結構體之間的關係

檔案描述符(fd)

對於linux而言,所有對裝置(對於linux而言,一切皆檔案)和檔案的操作都使用檔案描述符來進行的。
檔案描述符是一個非負的整數,它是一個索引值,指向核心中每個程序開啟檔案的記錄表。
當開啟一個現存檔案或建立一個新檔案時,核心就向程序返回一個檔案描述符用於後續對檔案的讀寫操作;當需要讀寫檔案時,也需要把檔案描述符作為引數傳遞給相應的函式。

通常情況下,將一個程式從硬碟載入到記憶體後,這個程式就化身為了一個程序,這時系統會預設開啟三個檔案:
標準輸入(stdin)、標準輸出(stdout)、標準錯誤(stderr)。

這三個檔案相對應的三個檔案描述符分別為0、1、2。所以後面如果建立新檔案,那麼此時這個新檔案的檔案描述符就是3啦。。。
這是因為在Linux中,檔案的描述符分配是從小到大逐個查詢檔案描述符是否已經使用,然後再分配。

相應的,如果你提前關閉了檔案描述符1,那麼新建的檔案的描述符就是1。

這裡寫圖片描述

一個程序的檔案描述符與對應的檔案的關係簡圖:

這裡寫圖片描述

FILE結構體

1.先來看看FILE結構體的定義:

FILE結構體中最重要的兩個成員變數是:
檔案描述符和緩衝區的大小

//C語言檔案指標域檔案描述符之間可以相互轉換
int fileno(FILE * stream)
FILE * fdopen(int fd, const char * mode)

struct _iobuf {
    char *_ptr;          //緩衝區當前指標
    int   _cnt;
    char *_base;         //緩衝區基址
int _flag; //檔案讀寫模式 int _file; //檔案描述符 int _charbuf; //緩衝區剩餘自己個數 int _bufsiz; //緩衝區大小 char *_tmpfname; }; typedef struct _iobuf FILE;

FILE結構體與檔案描述符之間的關係圖示:

這裡寫圖片描述

對上圖內容的簡要介紹:

1.程序開啟一個檔案的過程:

程序通過系統呼叫open()來開啟一個檔案,實質上是獲得一個檔案描述符,以便於程序通過檔案描述符來讀寫該檔案、
程序開啟檔案時,會為該檔案建立一個file物件,並將一個指向該file物件的指標存入程序描述符表(程序描述符陣列),進而確定了開啟檔案的檔案描述符(陣列下標)。

open()系統呼叫是在核心裡通過sys_open()實現的,
sys_open()將建立檔案的dentry、inode和file物件。
建立file物件時,將file物件f_op指向了所屬檔案系統的操作函式集file_operations,而該函式集又來自具體檔案的i節點,於是虛擬檔案系統就與實際檔案系統的操作就銜接起來了。

並在file_struct結構體的程序開啟檔案表fd_array[NR_OPEN_DEFAULT]中查詢一個空閒表項(也就是此時陣列中最小的未被佔用的表項),然後返回該表項的下標(檔案描述符)。

2.上述描述中提到的結構體的定義:

描述符陣列存放在程序開啟的檔案表files_struct結構中。
檔案描述符陣列中存放了一個程序所開啟的所有檔案.

files_struct結構體定義:

struct files_struct { atomic_t count; /* 共享該表的程序數 */
rwlock_t file_lock; /* 保護以下的所有域,以免在tsk->alloc_lock中的巢狀*/
int max_fds; /*當前檔案物件的最大數*/
int max_fdset;/*當前檔案描述符的最大數*/
int next_fd; /*已分配的檔案描述符加1*/
struct file ** fd; /* 指向檔案描述符陣列的指標 */
fd_set *close_on_exec; /*指向執行exec( )時需要關閉的檔案描述符*/
fd_set *open_fds; /*指向開啟檔案描述符的指標*/
fd_set close_on_exec_init;/* 執行exec( )時需要關閉的檔案描述符的初 值集合*/
fd_set open_fds_init; /*檔案描述符的初值集合*/
struct file * fd_array[32];/* 檔案物件指標的初始化陣列*/};

file結構體:

struct file
{
struct list_head f_list; /*所有開啟的檔案形成一個連結串列*/
struct dentry *f_dentry; /*指向相關目錄項的指標*/
struct vfsmount *f_vfsmnt; /*指向VFS安裝點的指標*/
struct file_operations *f_op; /*指向檔案操作表的指標*/
mode_t f_mode; /*檔案的開啟模式*/
loff_t f_pos; /*檔案的當前位置*/
unsigned short f_flags; /*開啟檔案時所指定的標誌*/
unsigned short f_count; /*使用該結構的程序數*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*預讀標誌、要預讀的最多頁面數、上次預讀後的檔案指標、預讀的位元組數以及
預讀的頁面數*/
int f_owner; /* 通過訊號進行非同步I/O資料的傳送*/
unsigned int f_uid, f_gid; /*使用者的UID和GID*/
int f_error; /*網路寫操作的錯誤碼*/

unsigned long f_version; /*版本號*/
void *private_data; /* tty驅動程式所需 */

};

file結構體的幾個重要的成員變數:
f_flags:表示開啟檔案的許可權 。
f_pos:表示當前讀寫檔案的位置。
f_count:表示開啟檔案的引用計數,如果有多個檔案指標指向它,就會增加f_count的值。
f_mode:設定對檔案的訪問模式,例如:只讀,只寫、可讀可寫等。

③file_operations結構體:
file結構體中的f_op指標指向file_operations結構體,這個結構體中的成員除了struct module* owner 其餘都是函式指標。

file_operation就是把系統呼叫和驅動程式關聯起來的關鍵資料結構。
這個結構的每一個成員都對應著一個系統呼叫。
讀取file_operation中相應的函式指標,接著把控制權轉交給函式,從而完成了Linux裝置驅動程式的工作。

struct file_operations {
    struct module *owner;               
    //指向擁有該模組的指標;
    loff_t (*llseek) (struct file *, loff_t, int);   
    //llseek 方法用作改變檔案中的當前讀/寫位置, 並且新位置作為(正的)返回值.
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 
    //用來從裝置中獲取資料. 在這個位置的一個空指標導致 read 系統呼叫以 -EINVAL("Invalid argument") 失敗. 一個非負返回值代表了成功讀取的位元組數( 返回值是一個 "signed size" 型別, 常常是目標平臺本地的整數型別).
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    //傳送資料給裝置. 如果 NULL, -EINVAL 返回給呼叫 write 系統呼叫的程式. 如果非負, 返回值代表成功寫的位元組數.
    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);
    //對於裝置檔案這個成員應當為 NULL; 它用來讀取目錄, 並且僅對**檔案系統**有用.
    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 *);
    //mmap 用來請求將裝置記憶體對映到程序的地址空間. 如果這個方法是 NULL, mmap 系統呼叫返回 -ENODEV.
    int (*open) (struct inode *, struct file *);
    //開啟一個檔案
    int (*flush) (struct file *, fl_owner_t id);
    //flush 操作在程序關閉它的裝置檔案描述符的拷貝時呼叫;
    int (*release) (struct inode *, struct file *);
    //在檔案結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.
    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 *);
    //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 **);
};
小結:

file_struct是作業系統用來管理檔案的資料結構,
當我們建立一個程序時,會建立檔案描述符表,
程序控制塊PCB中的fs指標指向檔案描述符表,
當我們建立檔案時,會為指向該檔案的指標FILE*關聯一個檔案描述符並新增在檔案描述符表中。
在檔案描述符表中fd相當於陣列的索引,FILE*相當於陣列的內容,指向一個檔案結構體。