1. 程式人生 > >Linux——檔案描述符與檔案管理結構

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個檔案,以實現對其訪問。