從 lsof 開始,深刻理解 Linux 虛擬文件系統

背景node


有時會出現這樣的狀況,磁盤空間顯示已經被佔滿,可是在查看磁盤的具體文件佔用狀況時,發現磁盤仍然有很大的空餘空間。mysql

1.執行df命令查看磁盤使用狀況,發現磁盤已經滿了。sql

-bash-4.2$ df -Th
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/vda1      ext4       30G    30G 0      100% /
devtmpfs       devtmpfs  489M     0  489M   0% /dev
tmpfs          tmpfs     497M     0  497M   0% /dev/shm
tmpfs          tmpfs     497M   50M  447M  11% /run
tmpfs          tmpfs     497M     0  497M   0% /sys/fs/cgroup

2.執行 du 命令查看各個目錄的磁盤佔用狀況,把各個目錄文件的大小相加,發現並無佔滿磁盤,有10多G空間莫名失蹤。緩存

-bash-4.2$ du -h --max-depth=1 /home16M /home/logs11G /home/serverdog11G /home

3.爲什麼會出現這樣的狀況呢?bash

由於雖然文件已被刪除,可是一些進程仍然打開這些文件,所以其佔用的磁盤空間並無被釋放。執行lsof 命令顯示打開已刪除的文件。將有問題的進程重啓(或,清空),磁盤空間就會獲得釋放。app

-bash-4.2# lsof | grep deletemysqld     2470         mysql    4u      REG              253,1           0     523577 /var/tmp/ibfTeQFn (deleted)
mysqld     2470         mysql    5u      REG              253,1           0     523579 /var/tmp/ibaHcIdW (deleted)
mysqld     2470         mysql    6u      REG              253,1           0     523581 /var/tmp/ibLjiALu (deleted)
mysqld     2470         mysql    7u      REG              253,1           0     523585 /var/tmp/ibCFnzTB (deleted)
mysqld     2470         mysql   11u      REG              253,1

那麼,Linux 的文件系統,到底爲何這麼設計呢?要了解這些,就要先弄清楚並不容易,下面將從一些基本概念入手,一步步將這些梳理清楚:less

  • 什麼是虛擬文件系統(VFS:virtual filesystem)?
  • 什麼是通用文件模型?
  • 超級塊對象(superblock object)
  • 索引節點對象(inode object)
  • 文件對象(file object)
  • 目錄項對象(dentry object)
  • 文件的概念
  • 文件的表達
  • 內存表達
  • 磁盤表達
  • 目錄樹的構建
  • 軟連接 vs 硬連接
  • 文件 & 磁盤管理
  • 索引節點狀態
  • 文件 & 進程管理
  • 操做:
  • 打開&刪除

虛擬文件系統(virtual filesystem)

下圖顯示了 Linux 操做系統中負責文件管理的基本組件。上半區域爲用戶模式,下半區域爲內核模式。應用程序使用標準庫libc來訪問文件,庫將請求映射到系統調用,以便進入內核模式。全部與文件相關的操做的入口都是虛擬文件系統(VFS),而非特定的額文件系統(如Ext三、ReiserFS和NFS)。VFS 提供了系統庫和特定文件系統之間的接口。所以,VFS 不只充當抽象層,並且實際上它提供了一個文件系統的基本實現,能夠由不一樣的實現來使用和擴展。所以,要了解文件系統是如何工做的,就要先了解VFS 。post

通用文件模型

VFS 的主要思想在於引入了一個通用文件模型(common file model)。通用文件模型由如下對象類型組成:ui

超級塊對象(superblock object)spa

  • 內存:文件系統安裝時建立,存放文件系統的有關信息
  • 磁盤:對應於存放在磁盤上的文件系統控制塊(filesystem control block)

索引節點對象(inode object)

  • 內存:訪問時建立,存放關於具體文件的通常信息(inode 結構)
  • 磁盤:對應於存放在磁盤上的文件控制塊(file control block)每一個索引節點對象都有一個索引節點號,惟一地標識文件系統的文件

文件對象(file object) -內存:打開文件時建立,存放 打開文件 與進程之間進行交互的有關信息(file 結構) 打開文件信息,僅當進程訪問文件期間存在於內核內存中。

目錄項對象(dentry object)

  • 內存:目錄項一旦被讀入內存,VFS就會將其轉換成dentry 結構的目錄項對象
  • 磁盤:特定文件系統以特定的方式存儲在磁盤上
  • 存放目錄項(即,文件名稱)與對應文件進行連接的有關信息

目錄樹

綜合來講,Linux 的 根文件系統(system’s root filessystem) 是內核啓動mount的第一個文件系統。內核代碼映像文件保存在根文件系統中,而系統引導啓動程序會在根文件系統掛載以後,從中把一些基本的初始化腳本和服務等加載到內存中去運行(文件系統和內核是徹底獨立的兩個部分)。其餘文件系統,則後續經過腳本或命令做爲子文件系統安裝在已安裝文件系統的目錄上,最終造成整個目錄樹。

start_kernel 
  vfs_caches_init 
    mnt_init 
      init_rootfs     // 註冊rootfs文件系統
      init_mount_tree // 掛載rootfs文件系統 
  … 
  rest_init 
  kernel_thread(kernel_init, NULL, CLONE_FS);

就單個文件系統而言,在文件系統安裝時,建立超級塊對象;沿樹查找文件時,老是首先從初識目錄的中查找匹配的目錄項,以便獲取相應的索引節點,而後讀取索引節點的目錄文件,轉化爲dentry對象,再檢查匹配的目錄項,反覆執行以上過程,直至找到對應的文件的索引節點,並建立索引節點對象。

軟連接 vs 硬連接

軟連接是一個普通的文件,其中存放的是另一個文件的路徑名。硬連接則指向同一個索引節點,硬連接數記錄在索引節點對象的 i_nlink 字段。當i_nlink字段爲零時,說明沒有硬連接指向該文件。

文件 & 進程管理

下圖是一個簡單示例,說明進程是怎樣與文件進行交互。三個不一樣進程打開同一個文件,每一個進程都有本身的文件對象,其中兩個進程使用同一個硬連接(每一個硬連接對應一個目錄對象),兩個目錄項對象都指向同一個 索引節點對象。

索引節點的數據又由兩部分組成:內存數據和磁盤數據。Linux 使用 Write back 做爲索引節點的數據一致性策略。對於索引節點的數據,當文件被打開時,纔會加載索引節點到內存;當再也不被進程使用,則從內存踢出;若是中間有更新,則須要把數據寫回磁盤。

*  "in_use" - valid inode, i_count > 0, i_nlink > 0
*  "dirty"  - as "in_use" but also dirty
*  "unused" - valid inode, i_count = 0

索引節點是否仍在使用,是經過 open() 和 close() 操做創建和銷燬文件對象,文件對象經過索引節點提供的 iget 和 iput 更新索引節點的i_count字段,以完成使用計數。open 操做使得 i_count 加一, close 操做使得 i_count 減一。在 close 操做時判斷索引節點是否釋放,若是 i_count = 0,則意味着再也不有進程引用,將會從內存釋放。

文件 & 磁盤管理

文件與磁盤管理聯繫最緊密的操做,莫過於touch和rm操做,而尤之後者最爲關鍵。經過strace(或 dtruss),查看 rm 的實際的系統調用

# dtruss rm tmp
...
geteuid(0x0, 0x0, 0x0)       = 0 0
ioctl(0x0, 0x4004667A, 0x7FFEE06F09C4)       = 0 0
lstat64("tmp0", 0x7FFEE06F0968, 0x0)        = 0 0
access("tmp0", 0x2, 0x0)        = 0 0
unlink("tmp0", 0x0, 0x0)        = 0 0

能夠發現 rm 實際是經過 unlink 完成的。unlink表明刪除目錄項,以及減小其索引節點的計數。由通用文件模型可知,父目錄自己一樣是一個文件,也就意味着目錄項是其文件數據的一部分。刪除目錄項等價於從父目錄的文件中刪除數據,也就意味着首先要打開父目錄的文件。那麼,刪除操做便可理解爲:

  • 刪除命令(一個進程)使用 open 操做得到父目錄文件對象
  • 經過 iget 增長 目錄文件的索引節點對象計數
  • 讀取目錄文件數據
  • 將目錄文件數據轉化爲目錄項對象
  • 因爲目錄項包含文件的索引節點,相似的,須要經過 iget 增長文件的索引節點對象計數
  • 刪除目錄的目錄項
  • 減小文件索引節點對象的硬連接計數i_nlink
  • 經過 iput 結束對文件索引節點對象的操做,使用計數 i_count 減一
  • 判斷i_count是否爲零,若是爲零,則釋放內存
  • 而後,判斷i_nlink是否爲零,若是爲零,則釋放磁盤空間
  • 經過 iput 結束對目錄索引節點對象的操做。

總結

回頭來看遇到的問題,其實能夠從兩個角度來理解:

索引與數據

文件系統與文件、磁盤管理與文件、進程管理與文件,最核心的都是文件的索引,而不是文件的數據。把數據和索引分開是理解文件系統的關鍵。

緩存策略

因爲操做系統使用 Write back 的策略,意味着只有先釋放內存,纔有可能釋放磁盤。

Why lsof ?

從上面的模型能夠很清楚的理解,由於目錄已經沒有索引到文件了,可是打開文件還有索引到文件,因此不能馬上釋放磁盤空間。

爲何 lsof 能夠找到已刪除未釋放的文件呢?

lsof,顧名思義:list open files,該命令的原理就是查找打開文件的列表,所以能夠找到已刪除未釋放的文件。

做者:cyningsun
連接:https://juejin.im/post/687511...

image

相關文章
相關標籤/搜索