linux系統編程之基礎必備(三):檔案描述符file descriptor與inode的相關知識

來源:互聯網
上載者:User

       每個進程在Linux核心中都有一個task_struct結構體來維護進程相關的 資訊,稱為進程描述符(Process Descriptor),而在作業系統理論中稱為進程式控制制塊 (PCB,Process Control Block)。task_struct中有一個指標(struct
files_struct *files; )指向files_struct結構體,稱為檔案 描述符表,其中每個表項包含一個指向已開啟的檔案的指標,如所示。




         使用者程式不能直接存取核心中的檔案描述符表,而只能使用檔案描述符表的索引 (即0、1、2、3這些數字),這些索引就稱為檔案描述符(File Descriptor),用int 型變數儲存。 當調用open 開啟一個檔案或建立一個新檔案時,核心分配一個檔案描述符並返回給使用者程式,該檔案描述符表項中的指標指向新開啟的檔案。當讀寫檔案時,使用者程式把檔案描述符傳給read 或write ,核心根據檔案描述符找到相應的表項,再通過表項中的指標找到相應的檔案。

       已開啟的檔案在核心中用file 結構體表示,檔案描述符表中的指標指向file 結構體。在file 結構體中維護File Status Flag(file 結構體的成員f_flags)和當前讀寫位置(file 結構體 的成員f_pos )。在中,進程1和進程2都開啟同一檔案,但是對應不同的file 結構體,因此可 以有不同的File Status Flag和讀寫位置。file 結構體中比較重要的成員還有f_count,表示引用計
數(Reference Count),如dup 、fork 等系統調用會導致多個檔案描述符指向同一 個file 結構體,例如有fd1 和fd2 都引用同一個file 結構體,那麼它的引用計數就是2, 當close(fd1) 時並不會釋放file 結構體,而只是把引用計數減到1,如果再close(fd2) ,引用計數 就會減到0同時釋放file 結構體,這才真的關閉了檔案。 每個file 結構體都指向一個file_operations 結構體,這個結構體的成員都是函數指標,指向實現
各種檔案操作的核心功能。比如在使用者程式中read 一個檔案描述符,read 通過系統調用進入核心, 然後找到這個檔案描述符所指向的file 結構體,找到file 結構體所指向的file_operations 結構 體,調用它的read 成員所指向的核心功能(如核心代碼中實現函數可能為sys_read())以完成使用者請求。在使用者程式中調 用lseek 、read 、write 、ioctl 、open 等函數,最終都由核心調用file_operations
的各成員所指向 的核心功能完成使用者請求。file_operations 結構體中的release成員用於完成使用者程式的close 請 求,之所以叫release而不叫close 是因為它不一定真的關閉檔案,而是減少引用計數,只有引用計 數減到0才關閉檔案。對於同一個檔案系統上開啟的常規檔案來說,read 、write 等檔案操作的步驟 和方法應該是一樣的,調用的函數應該是相同的,所以圖中的三個開啟檔案的file 結構體指向同一 個file_operations 結構體。如果開啟一個字元裝置檔案,那麼它的read,write
操作肯定和常規文 件不一樣,不是讀寫磁碟的資料區塊而是讀寫硬體裝置,所以file 結構體應該指向不同 的file_operations 結構體,其中的各種檔案操作函數由該裝置的驅動程式實現。 


       每個file 結構體都有一個指向dentry結構體的指標,“dentry”是directory entry(目錄項)的縮寫。 我們傳給open 、stat 等函數的參數的是一個路徑,如/home/akaedu/a ,需要根據路徑找到檔案 的inode。為了減少讀盤次數,核心緩衝了目錄的樹狀結構,稱為dentry cache,其中每個節點是一 個dentry結構體,只要沿著路徑各部分的dentry搜尋即可,從根目錄/找到home 目錄,然後找
到akaedu目錄,然後找到檔案a。dentry cache只儲存最近訪問過的目錄項,如果要找的目錄項 在cache中沒有,就要從磁碟讀到記憶體中。 
每個dentry結構體都有一個指標指向inode 結構體。inode 結構體儲存著從磁碟inode讀上來的信 息。在的例子中,有兩個dentry,分別表示/home/akaedu/a 和/home/akaedu/b ,它們都指向同 一個inode,說明這兩個檔案互為永久連結。inode 結構體中儲存著從磁碟分割的inode讀上來資訊, 例如所有者、檔案大小、檔案類型和許可權位等。每個inode 結構體都有一個指向inode_operations結 構體的指標,後者也是一組函數指標指向一些完成檔案目錄操作的核心功能。 
       和file_operations 不同,inode_operations所指向的不是針對某一個檔案進行操作的函數,而是影 響檔案和目錄布局的函數,例如添加刪除檔案和目錄、跟蹤符號連結等等,屬於同一檔案系統的 各inode 結構體可以指向同一個inode_operations結構體。 inode 結構體有一個指向super_block結構體的指標。super_block結構體儲存著從磁碟分割的超級塊 讀上來的資訊,例如檔案系統類型、塊大小等。super_block結構體的s_root成員是一個指
向dentry的指標,表示這個檔案系統的根目錄被mount 到哪裡,在的例子中這個分區 被mount 到/home 目錄下。 
        file 、dentry、inode 、super_block這幾個結構體組成了VFS的核心概念。對於ext2檔案系統來 說,在磁碟儲存布局上也有inode和超級塊的概念,所以很容易和VFS中的概念建立對應關係。而 另外一些檔案系統格式來自非UNIX系統(例如Windows的FAT32、NTFS),可能沒有inode或超 級塊這樣的概念,但為了能mount 到Linux系統,也只好在驅動程式中硬湊一下,在Linux下 看FAT32和NTFS分區會發現許可權位是錯的,所有檔案都是rwxrwxrwx
,因為它們本來就沒 有inode和許可權位的概念,這是硬湊出來的。

        在UNIX系統中,使用者通過終端登入系統後得到一個Shell進程,這個終端成為Shell進程的控制終端 (Controlling Terminal),控制終端是儲存在PCB中的資訊,而我們知 道fork 會複製PCB中的資訊,因此由Shell進程啟動的其它進程的控制終端也是這個終端。
預設情況 下(沒有重新導向),每個進程的標準輸入(stdin)、標準輸出(stdout)和標準錯誤輸出(stderr)都指向控制終端,因為在程式啟動時(在main 函數還 沒開始執行之前)會自動把控制終端開啟三次,分別賦給三個FILE *指 針stdin 、stdout和stderr,這三個檔案指標是libc 中定義的全域變數,這三個檔案的描述符分別是0、1、2,儲存在相應的FILE
結構體中。進程從標準輸入讀也就是讀使用者的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上
標頭檔unistd.h 中有如下的宏定義來表示這三個檔案描述符: 
#define STDIN_FILENO 0 
#define STDOUT_FILENO 1 
#define STDERR_FILENO 2

       每個進程都可以通過一個特殊的裝置檔案/dev/tty(字元裝置c) 訪問它的控制終端。事實上每個終端裝置都對應一個不同的裝置檔案,/dev/tty 提供了一個通用的介面,一個進程要訪問它的控制終端既可以通過/dev/tty 也可以通過該終端裝置所對應的裝置檔案來訪問。ttyname函數可以由檔案描述符查出對應的檔案名稱,該檔案描述符必須指向一個終端裝置而不 能是任意檔案。不同的終端所對應的裝置檔案名稱可以是/dev/pts/?, /dev/tty?等


simba@simba-Aspire-4752:~$ ls -l /dev/tty 

crw-rw-rw- 1 root tty 5, 0 Jan 29 09:46 /dev/tty 

       開頭的c表示檔案類型是字元裝置。中間的5, 0是它的裝置號,主裝置號5,次裝置號0,主裝置號 標識核心中的一個裝置驅動程式,次裝置號標識該裝置驅動程式管理的一個裝置。核心通過裝置號 找到相應的驅動程式,完成對該裝置的操作。我們知道常規檔案的這一列應該顯示檔案尺寸,而設 備檔案的這一列顯示裝置號,這表明裝置檔案是沒有檔案尺寸這個屬性的,因為裝置檔案在磁碟上 不儲存資料,對裝置檔案做讀寫操作並不是讀寫磁碟上的資料,而是在讀寫裝置。

       由open 返回的檔案描述符一定是該進程尚未使用的最小描述符。由於程式啟動時自動開啟檔案描述符0、1、2,因此第一次調用open開啟檔案通常會返回描述符3,再調用open 就會返回4。可以利用 這一點在標準輸入、標準輸出或標準錯誤輸出上開啟一個新檔案,實現重新導向的功能。例如,首先 調用close 關閉檔案描述符1,然後調用open 開啟一個常規檔案,則一定會返迴文件描述符1,這時候標準輸出就不再是終端,而是一個常規檔案了,再調用printf就不會列印到螢幕上,而是寫到這
個檔案中了。

      需要說明的是,當一個進程終止時,核心對該進程所有尚未關閉的 檔案描述符調用close 關閉,所以即使使用者程式不調用close ,在終止時核心也會自動關閉它開啟的 所有檔案。但是對於一個長年累月啟動並執行程式(比如網路伺服器),開啟的檔案描述符一定要記得 關閉,否則隨著開啟的檔案越來越多,會佔用大量檔案描述符和系統資源。

------------------------------------------------------------------------------------------------------------------------------------

傳統的Unix既有v節點(vnode)也有i節點(inode),vnode的資料結構中包含了inode資訊。但在Linux中沒有使用vnode,而使用了通用inode。“實現雖不同,但在概念上是一樣的。”
vnode (“virtual node”)僅在檔案開啟的時候,才出現的;而inode定位檔案在磁碟的位置,它的資訊本身是儲存在磁碟等上的,當開啟檔案的時候從磁碟上讀入記憶體。


inode結構體記錄了很多關於檔案的資訊,比如檔案長度,檔案所在的裝置,檔案的物理位置,建立、修改和更新時間等等,特別的,它不包含檔案名稱!目錄下的所有檔案名稱和目錄名都儲存在目錄的資料區塊中,即如的目錄塊。對於常規檔案,檔案的資料存放區在資料區塊中,一個檔案通常佔用一個inode,但往往要佔用多個資料區塊,資料區塊是在分區進行檔案系統格式化時所指定的“最小儲存單位”,塊的大小為扇區的2^n倍,一個扇區512B。


  • 如果多個inode指向同一個資料區塊的時候,是不是就可以實現熟悉的連結了?!這就是軟串連的原理,建立一個檔案(一個符號連結檔案,檔案的屬性中有明確說明它是一個符號連結檔案),為需要連結的檔案分配一個新的inode,然後指向同一個資料區塊。 當我們用ls
    查看某個目錄或檔案時,如果加上-i 參數,就可以看到inode節點了;比如ls -li lsfile.sh ,最前面的數值就是inode資訊。
  • 多個檔案共用一個inode,同樣可以實現連結?!這就是永久連結的原理,inode中有連結計數器,當增加一個檔案指向這個inode時,計數器增1。特別的,當計數器為0時候,檔案才真正從磁碟刪除。即ls -l 命令輸出中的第二欄。

參考:《Linux C 編程一站式學習》(開源書籍)

http://daoluan.net/blog/inode、vnode和dentry/

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.