Linux——檔案描述符與檔案管理結構
Linux中的檔案
檔案:
Linux核心將一切視為檔案,那麼Linux中檔案是什麼呢?其既可以是事實上的真正的物理檔案,也可以是裝置,管道,甚至可以是一塊記憶體,狹義的檔案是指檔案系統中的物理檔案,廣義上的檔案可以是Linux管理的所有物件。這些廣義的檔案利用VFS機制,以檔案系統的形式掛載在Linux核心中,對外提供一系列的檔案操作介面。
檔案描述符:
對於linux而言,所有對裝置和檔案的操作都使用檔案描述符來進行的。檔案描述符是一個非負的整數,它是一個索引值,指向核心中每個程序開啟檔案的記錄表。當開啟一個現存檔案或建立一個新檔案時,核心就向程序返回一個檔案描述符;當需要讀寫檔案時,也需要把檔案描述符作為引數傳遞給相應的函式。
通常,一個程序啟動時,都會開啟3個檔案:標準輸入、標準輸出和標準出錯處理。這3個檔案分別對應檔案描述符為0、1 和 2
從數值上看,檔案描述符是一個非負整數,其實質就是 一個控制代碼,所以可以認為檔案描述符就是一個檔案控制代碼,那何為控制代碼呢?一切對於使用者透明的返回值,即可視為控制代碼。使用者空間利用檔案描述符與核心進行互動,而核心拿到檔案 描述符之後,可以通過它得到用於管理檔案的真正的資料結構。
使用檔案描述符的好處:
1、為了增加安全性, 控制代碼型別對使用者完全透明,使用者無法通過任何hacking的方式,更改控制代碼對應內部結果,Linux核心的檔案描述符,只有核心才能通過該值得到對應的檔案結構; 2、增加了可擴充套件性,使用者的程式碼只依賴於控制代碼的值,這樣實際結構的型別就可以隨時改變,與控制代碼的對映關係也可以隨時改變,這些變化都不會影響任何現有的使用者程式碼。
Linux的每個程序都會維護一個檔案表,以便維護該程序開啟檔案的資訊,包括開啟的檔案個數,每個開啟檔案的偏移量等資訊。
檔案表的實現:
核心中程序對應的結構是task_struct,程序的檔案表儲存在task_struct—>files中,結構程式碼:
struct files_struct
{
atomic_t count; //引用計數
struct fdtable _rcu *fdt;
struct fdtable fdtab;
/* 為什麼會有兩個 fdtable? 這是核心的一種優化策略,fdt為指標,fdtab為普通變數,一般情況下,fdt是指向fdtab的,當需要它時,才會動態的開闢記憶體空間,因為預設大小的檔案表足以滿足大多數情況,因此這樣就避免了頻繁的申請空> 間,在建立時,使用普通的變數或陣列,然後讓指標指向他,作為預設情況,當程序使用量超過預設值時,才會動態申請記憶體 */
int next_fd;
//用於查詢下一個空閒的fd
struct embedded_fd_set open_fds_init;
//儲存開啟檔案描述符的點陣圖
struct embedded_fd_set close_on_exec_init;
//儲存執行exec需要關閉的檔案描述符的點陣圖
struct files_struct __rcu* fd_array[NR_OPEN_DEFAULT];
//fd_array是一個固定大小的file的結構陣列,struct file是核心用於檔案管理的結構,這裡使用了預設大小的陣列,就是為了涵蓋大多數情況,避免動態分配
};
在初始狀態下,files_struct 與 fdtable、files的關係為:
檔案表,檔案描述符表以及檔案結構的關係圖如上所示
我們通常這樣描述,”開啟一個檔案“,那麼這個所謂的開啟究竟打開了什麼呢?核心在這個過程中,又做了什麼事情?
我們來跟蹤核心open原始碼open–>do_sys_open 來剖析
long do_sys_open(int dfd,const char __user *filename,int flags,int mode)
{
struct open_flags op; //flags為使用者層傳遞的引數,核心會對flags進行合法性檢查,並根據mode生成新的flags值賦給lookup
int lookup = build_open_flags(flags,mode,&op);
//將使用者空間的檔名引數複製到核心空間
char* tmp = getname(filename);
int fd = PTR_ERR(tmp);
if(!IS_ERR(tmp))
{
//為出錯則申請新的檔案描述符
fd = get_unused_fd_flags(flags);
if(fd>=0)
{
//申請新的檔案管理結構file
struct file* f= do_filp_open(dfd,tmp,&op,lookup);
if(IS_ERR(f))
{
put_unused_fd(fd);
fd = PTR_ERR(f);
}
else
{
fsnotity_open(f);// 產生檔案開啟的通知事件
fd_install(fd,f);//將檔案描述符fd與檔案管理結構file對應起來,即安裝
}
}
putname(tmp);
}
return fd;
}
//從do_sys_open 可以看出,開啟檔案時,核心主要消耗了兩種資源,檔案描述符與核心管理檔案結構file
檔案描述符的選擇:
根據POSIX標準,當獲取一個新的檔案描述符時,要返回最低的未使用的檔案描述符。Linux是如何實現這一標準的呢?在Linux中,通過do_sys_open->get_unused_fd_flags->alloc_fd(0, (flags))來選擇檔案描述符,程式碼如下:
int alloc_fd(unsigned start, unsigned flags)
{
struct files_struct *files = current->files;
unsigned int fd;
int error;
struct fdtable *fdt;
/* files為程序的檔案表,下面需要更改檔案表,所以需要先鎖檔案表 */
spin_lock(&files->file_lock);
repeat:
/* 得到檔案描述符表 */
fdt = files_fdtable(files);
/* 從start開始,查詢未用的檔案描述符。在開啟檔案時,start為0 */
fd = start;
/* files->next_fd為上一次成功找到的fd的下一個描述符。使用next_fd,可以快速找到未用的文
件描述符;*/
if (fd < files->next_fd)
fd = files->next_fd;
/*
當小於當前檔案表支援的最大檔案描述符個數時,利用點陣圖找到未用的檔案描述符。
如果大於max_fds怎麼辦呢?如果大於當前支援的最大檔案描述符,那它肯定是未
用的,就不需要用點陣圖來確認了。
*/
if (fd < fdt->max_fds)
fd = find_next_zero_bit(fdt->open_fds->fds_bits,
fdt->max_fds, fd);
/* expand_files用於在必要時擴充套件檔案表。何時是必要的時候呢?比如當前檔案描述符已經超過了當
前檔案表支援的最大值的時候。 */
error = expand_files(files, fd);
if (error < 0)
goto out;
/*
* If we needed to expand the fs array we
* might have blocked - try again.
*/
if (error)
goto repeat;
/* 只有在start小於next_fd時,才需要更新next_fd,以儘量保證檔案描述符的連續性。*/
if (start <= files->next_fd)
files->next_fd = fd + 1;
/* 將開啟檔案點陣圖open_fds對應fd的位置置位 */
FD_SET(fd, fdt->open_fds);
/* 根據flags是否設定了O_CLOEXEC,設定或清除fdt->close_on_exec */
if (flags & O_CLOEXEC)
FD_SET(fd, fdt->close_on_exec);
else
FD_CLR(fd, fdt->close_on_exec);
error = fd;
#if 1
/* Sanity check */
if (rcu_dereference_raw(fdt->fd[fd]) != NULL) {
printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
rcu_assign_pointer(fdt->fd[fd], NULL);
}
#endif
out:
spin_unlock(&files->file_lock);
return error;
}
檔案描述符fd與檔案管理結構file
核心使用fd_install 將檔案管理結構file與fd組合起來,具體操作如下:
void fd_install(unsignet int fd,struct file* file)
{
struct files_struct *files = current->files;
struct fdtable *fdt;
spin_lock(&files->file_lock);
// 得到檔案描述符表
fdt = files_fdtable(files);
BUG_ON(fdt->fd[fd] != NULL);
//將檔案描述符表中的file型別的指標陣列中 對應fd的項 指向 file,這樣檔案描述符fd就與file建立了對應關係
rcu_assign_pointer(fdt->fd[fd],file);
spin_unlock(&files->file_lock);
}
// 得到:當用戶使用fd與核心交時,核心可以用fd從 fdt->fd[fd]中得到內部管理檔案的結構struct file
當新建一個程式,因為程序啟動時,打開了標準輸入、標準輸出和標準出錯處理三個檔案,因此返回的檔案描述符fd 為3,程式測試:
c語言中,檔案型別指標:
在緩衝檔案系統中,關鍵概念是“”檔案指標“”,每個被使用的檔案都在記憶體中開闢一個區,用來存放檔案的資訊(如檔案的名字,檔案的狀態,檔案當前的位置等),這些資訊是儲存在一個結構體變數中,該結構體型別是由系統定義的,為 FILE ,檔案型別宣告為
typedef struct
{
short level; //緩衝區“滿”或“空”的程度
unsigned flags; //檔案狀態標誌
char fd; //檔案描述符
unsigned char hold; //如無緩衝區不讀取字元
short bsize; //緩衝區的大小
unsigned char* buffer //資料緩衝區位置
unsigned char* curp //指標,當前的指向
unsigned istemp //臨時檔案,指示器
short token //用於有效性檢測
}FILE;
FILE f[5]
//定義了一個結構體陣列,他有5個元素,可以用來存放5個檔案資訊
FILE * fp
//fp是一個指向FILE 型別結構體的指標變數,可以使fp指向某一個檔案的結構體變數,從而通過該結構體變數中的檔案資訊能夠訪問該檔案,也就是說,通過檔案指標能找到與他相關的檔案,如果有n個檔案,一般對應n個指標變數,使他們分別指向n個檔案,以實現對其訪問。