探索檔案描述符(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*相當於陣列的內容,指向一個檔案結構體。