1. 程式人生 > >使用 lsof 查詢開啟的檔案

使用 lsof 查詢開啟的檔案

通過檢視開啟的檔案,瞭解更多關於系統的資訊。瞭解應用程式打開了哪些檔案或者哪個應用程式打開了特定的檔案,作為系統管理員,這將使得您能夠作出更好的決策。例如,您不應該解除安裝具有開啟檔案的檔案系統。使用 lsof,您可以檢查開啟的檔案,並根據需要在解除安裝之前中止相應的程序。同樣地,如果您發現了一個未知的檔案,那麼可以找出到底是哪個應用程式打開了這個檔案。

在 UNIX® 環境中,檔案無處不在,這便產生了一句格言:“任何事物都是檔案”。通過檔案不僅僅可以訪問常規資料,通常還可以訪問網路連線和硬體。在有些情況下,當您使用 ls 請求目錄清單時,將出現相應的條目。在其他情況下,如傳輸控制協議 (TCP) 和使用者資料報協議 (UDP) 套接字,不存在相應的目錄清單。但是在後臺為該應用程式分配了一個檔案描述符

,無論這個檔案的本質如何,該檔案描述符為應用程式與基礎作業系統之間的互動提供了通用介面。

因為應用程式開啟檔案的描述符列表提供了大量關於這個應用程式本身的資訊,所以能夠檢視這個列表將是很有幫助的。完成這項任務的實用程式稱為 lsof,它對應於“list open files”(列出開啟的檔案)。幾乎在每個 UNIX 版本中都有這個實用程式,但奇怪的是,大多數供應商並沒有將其包含在作業系統的初始安裝中。要獲取更多關於 lsof 的資訊,請參見參考資料部分。

只需輸入 lsof 就可以生成大量的資訊,如清單 1 所示。因為 lsof 需要訪問核心記憶體和各種檔案,所以必須以 root 使用者的身份執行它才能夠充分地發揮其功能。

bash-3.00# lsof 
COMMAND    PID   USER   FD   TYPE        DEVICE SIZE/OFF      NODE NAME
sched        0   root  cwd   VDIR         136,8     1024         2 /
init         1   root  cwd   VDIR         136,8     1024         2 /
init         1   root  txt   VREG         136,8    49016      1655 /sbin/init
init         1   root  txt   VREG         136,8    51084      3185 /lib/libuutil.so.1
vi        2013   root    3u  VREG         136,8        0      8501 /var/tmp/ExXDaO7d
...

每行顯示一個開啟的檔案,除非另外指定,否則將顯示所有程序開啟的所有檔案。CommandPIDUser 列分別表示程序的名稱、程序識別符號 (PID) 和所有者名稱。DeviceSIZE/OFFNodeName 列涉及到檔案本身的資訊,分別表示指定磁碟的名稱、檔案的大小、索引節點(檔案在磁碟上的標識)和該檔案的確切名稱。根據 UNIX 版本的不同,可能將檔案的大小報告為應用程式在檔案中進行讀取的當前位置(偏移量)。清單 1 來自一臺可以報告該資訊的 Sun Solaris 10 計算機,而 Linux® 沒有這個功能。

FDType 列的含義最為模糊,它們提供了關於檔案如何使用的更多資訊。FD 列表示檔案描述符,應用程式通過檔案描述符識別該檔案。Type 列提供了關於檔案格式的更多描述。我們來具體研究一下檔案描述符列,清單 1 中出現了三種不同的值。cwd 值表示應用程式的當前工作目錄,這是該應用程式啟動的目錄,除非它本身對這個目錄進行更改。txt 型別的檔案是程式程式碼,如應用程式二進位制檔案本身或共享庫,再比如本示例的列表中顯示的 init 程式。最後,數值表示應用程式的檔案描述符,這是開啟該檔案時返回的一個整數。在清單 1 輸出的最後一行中,您可以看到使用者正在使用 vi 編輯 /var/tmp/ExXDaO7d,其檔案描述符為 3。u 表示該檔案被開啟並處於讀取/寫入模式,而不是隻讀 (r) 或只寫 (w) 模式。有一點不是很重要但卻很有幫助,初始開啟每個應用程式時,都具有三個檔案描述符,從 0 到 2,分別表示標準輸入、輸出和錯誤流。正因為如此,大多數應用程式所開啟的檔案的 FD 都是從 3 開始。

FD 列相比,Type 列則比較直觀。根據具體作業系統的不同,您會發現將檔案和目錄稱為 REGDIR(在 Solaris 中,稱為 VREGVDIR)。其他可能的取值為 CHRBLK,分別表示字元和塊裝置;或者 UNIXFIFOIPv4,分別表示 UNIX 域套接字、先進先出 (FIFO) 佇列和網際協議 (IP) 套接字。

儘管與使用 lsof 沒有什麼直接的關係,但對 /proc 目錄進行簡要的介紹是有必要的。/proc 是一個目錄,其中包含了反映核心和程序樹的各種檔案。這些檔案和目錄並不存在於磁碟中,因此當您對這些檔案進行讀取和寫入時,實際上是在從作業系統本身獲取相關資訊。大多數與 lsof 相關的資訊都儲存於以程序的 PID 命名的目錄中,所以 /proc/1234 中包含的是 PID 為 1234 的程序的資訊。

在 /proc 目錄的每個程序目錄中存在著各種檔案,它們可以使得應用程式簡單地瞭解程序的記憶體空間、檔案描述符列表、指向磁碟上的檔案的符號連結和其他系統資訊。lsof 實用程式使用該資訊和其他關於核心內部狀態的資訊來產生其輸出。稍後我將把 lsof 的輸出與 /proc 目錄中的資訊聯絡起來。

常見用法

前面,我向您介紹瞭如何簡單地執行不帶任何引數的 lsof,以便顯示關於每個程序所開啟的檔案的資訊。本文餘下的部分將重點關注如何使用 lsof 來顯示所需的資訊以及如何正確地對其進行解釋。

lsof 常見的用法是查詢應用程式開啟的檔案的名稱和數目。您可能想嘗試找出某個特定應用程式將日誌資料記錄到何處,或者正在跟蹤某個問題。例如,UNIX 限制了程序能夠開啟檔案的數目。通常這個數值很大,所以不會產生問題,並且在需要時,應用程式可以請求更大的值(直到某個上限)。如果您懷疑應用程式耗盡了檔案描述符,那麼可以使用 lsof 統計開啟的檔案數目,以進行驗證。

要指定單個程序,可以使用 -p 引數,後面加上該程序的 PID。因為這樣做不僅會返回該應用程式所開啟的檔案,還會返回共享庫和程式碼,所以通常需要對輸出進行篩選。要完成此任務,可以使用 -d 標誌根據 FD 列進行篩選,使用 -a 標誌表示兩個引數都必須滿足 (AND)。如果沒有 -a 標誌,預設的情況是顯示匹配任何一個引數 (OR) 的檔案。清單 2 顯示了 sendmail 程序開啟的檔案,並使用 txt 對這些檔案進行篩選。

sh-3.00# lsof -a -p 605 -d ^txt
COMMAND  PID USER   FD   TYPE  DEVICE SIZE/OFF     NODE NAME
sendmail 605 root  cwd   VDIR  136,8     1024    23554 /var/spool/mqueue
sendmail 605 root    0r  VCHR  13,2            6815752 /devices/pseudo/[email protected]:null
sendmail 605 root    1w  VCHR  13,2            6815752 /devices/pseudo/[email protected]:null
sendmail 605 root    2w  VCHR  13,2            6815752 /devices/pseudo/[email protected]:null
sendmail 605 root    3r  DOOR             0t0       58
		/var/run/name_service_door(door to nscd[81]) (FA:->0x30002b156c0)
sendmail 605 root    4w  VCHR  21,0           11010052 
						/devices/pseudo/[email protected]:conslog->LOG
sendmail 605 root    5u  IPv4 0x300010ea640      0t0      TCP *:smtp (LISTEN)
sendmail 605 root    6u  IPv6 0x3000431c180      0t0      TCP *:smtp (LISTEN)
sendmail 605 root    7u  IPv4 0x300046d39c0      0t0      TCP *:submission (LISTEN)
sendmail 605 root    8wW VREG         281,3       32  8778600 /var/run/sendmail.pid

清單 2 為 lsof 指定了三個引數。第一個是 -a,它表示當所有的引數都為真時,才顯示這個檔案。第二個引數是 -p 605,它限制僅輸出 PID 為 605 的程序,可以通過 ps 命令獲取這個資訊。最後一個引數 -d ^txt,它表示篩選出其中 txt 型別的記錄(脫字元號 [^] 表示排除)。

清單 2 的輸出提供了關於程序行為的資訊。如 cwd 行所示,該應用程式的工作目錄為 /var/spool/mqueue。檔案描述符 0、1 和 2 分配給了 /dev/null(Solaris 大量使用符號連結,所以這裡顯示了相應的偽裝置)。FD 3 是一個 Solaris 門(高速遠端過程呼叫 (RPC) 介面),以只讀模式開啟。FD 4 中的內容比較有趣,因為它是一個字元裝置的只讀控制代碼,實質上是 /dev/log。從這個檔案中,您可以收集該應用程式向 UNIX syslog 守護程序進行的記錄,所以 /etc/syslog.conf 規定了日誌檔案的位置。

作為一個網路應用程式,sendmail 對網路埠進行監聽。檔案描述符 5、6 和 7 可以告訴您,該應用程式正以 IPv4 和 IPv6 模式監聽簡單郵件傳輸協議 (SMTP) 埠,並以 IPv4 模式監聽提交埠。最後一個檔案描述符是隻寫的,並且指向 /var/run/sendmail.pid。FD 列中的大寫 W 表示該應用程式具有對整個檔案的寫鎖。該檔案用於確保每次只能開啟一個應用程式例項。

在其他情況下,您有一個檔案或目錄,並且需要知道哪個應用程式控制了該檔案(打開了該檔案)。清單 2 顯示了由 sendmail 程序打開了 /var/run/sendmail.pid。如果您不知道這個資訊,那麼在給定檔名的情況下,lsof 可以提供該資訊。清單 3 顯示了相應的輸出。

bash-3.00# lsof /var/run/sendmail.pid
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
sendmail 605 root    8wW VREG  281,3       32 8778600 /var/run/sendmail.pid

正如輸出所示,程序 sendmail(PID 為 605)控制了檔案 /var/run/sendmail.pid,並且通過排它鎖開啟該檔案以便進行寫入。如果出於某種原因,您需要刪除這個檔案,那麼正確的做法是中止該程序,而不是直接刪除這個檔案。否則,這個守護程序下次可能無法正常啟動,或者可能稍後會啟動另一個例項,從而導致爭用。

有時您只知道在檔案系統的某處打開了檔案。在解除安裝檔案系統時,如果該檔案系統中有任何開啟的檔案,那麼操作將會失敗。通過指定裝入點的名稱,您可以使用 lsof 顯示一個檔案系統中所有開啟的檔案。清單 4 顯示瞭如何嘗試解除安裝 /export/home,然後使用 lsof 找出誰在使用該檔案系統。

bash-3.00# umount /export/home
umount: /export/home busy
bash-3.00# lsof /export/home
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash    1943 root  cwd   VDIR  136,7     1024    4 /export/home/sean
bash    2970 sean  cwd   VDIR  136,7     1024    4 /export/home/sean
ct      3030 sean  cwd   VDIR  136,7     1024    4 /export/home/sean
ct      3030 sean    1w  VREG  136,7        0   25 /export/home/sean/output

在這個示例中,使用者 sean 正在其 home 目錄中進行一些操作。有兩個 bash(一種 Shell)例項正在執行,並且當前目錄設定為 sean 的 home 目錄。還有一個名為 ct 的應用程式正運行於相同的目錄,並且其標準輸出(檔案描述符 1)重定向到一個名為 output 的檔案。要成功地解除安裝 /export/home,應該在通知使用者以確保情況正常之後,中止這些程序。

這個示例說明了應用程式的當前工作目錄非常重要,因為它仍保持著檔案資源,並且可以防止檔案系統被解除安裝。這就是為什麼大部分守護程序(後臺程序)將它們的目錄更改為根目錄、或服務特定的目錄(如 sendmail 示例中的 /var/spool/mqueue)的原因,以避免該守護程序阻止解除安裝不相關的檔案系統。如果 sendmail 從 /export/home/sean 目錄啟動,並且沒有將其目錄更改為 /var/spool/mqueue,那麼在解除安裝 /export/home 前必須中止它。

如果您對非裝入點目錄中開啟的檔案感興趣,那麼必須通過 +d+D 指定該目錄的名稱,具體使用其中的哪一個標誌取決於您需要遞迴到子目錄(+D)或者不需要遞迴到子目錄(+d)。例如,要檢視 /export/home/sean 中所有開啟的檔案,可以使用 lsof +D /export/home/sean。在前面的示例中,相關的目錄是一個裝入點,而這裡與前面的示例存在細微的差別,並且限制了 lsof 和核心之間的互動。這還會引起潛在的問題,即 lsof /export/homelsof /export/home/(請注意尾部的斜槓)有所區別。第一種方式可以正常工作,因為它指向了裝入點。第二種方式不會生成任何輸出,因為它指向了目錄。如果您在 Shell 中使用 Tab 鍵自動完成命令,那麼可能碰到這個問題,其中會幫助您新增結尾的斜槓。在這種情況下,您可以刪除這個斜槓或者使用 +D 指定目錄。前者是首選的方法,因為與指定任意的目錄相比,其執行速度更快。

在前面的部分中,我們研究了 lsof 的基本用法,即顯示開啟的檔案和控制它們的程序之間的關係。當您想對系統進行一些煩瑣的操作,而又不希望破壞別人重要的文件時,這種方法很有幫助。您還可以使用相同的方法執行一些高難度的 UNIX 操作。

當 UNIX 計算機受到入侵時,常見的情況是日誌檔案被刪除,以掩蓋攻擊者的蹤跡。管理錯誤也可能導致意外刪除重要的檔案,比如在清理舊日誌時,意外地刪除了資料庫的活動事務日誌。有時可以恢復這些檔案,並且 lsof 可以為您提供幫助。

當程序打開了某個檔案時,只要該程序保持開啟該檔案,即使將其刪除,它依然存在於磁碟中。這意味著,程序並不知道檔案已經被刪除,它仍然可以向開啟該檔案時提供給它的檔案描述符進行讀取和寫入。除了該程序之外,這個檔案是不可見的,因為已經刪除了其相應的目錄條目。

前面曾在轉到 /proc 目錄部分中說過,通過在適當的目錄中進行查詢,您可以訪問程序的檔案描述符。在隨後的內容中,您看到了 lsof 可以顯示程序的檔案描述符和相關的檔名。您能明白我的意思嗎?

但願它真的這麼簡單!當您向 lsof 傳遞檔名時,比如在 lsof /file/I/deleted 中,它首先使用 stat() 系統呼叫獲得有關該檔案的資訊,不幸的是,這個檔案已經被刪除。在不同的作業系統中,lsof 可能可以從核心記憶體中捕獲該檔案的名稱。清單 5 顯示了一個 Linux 系統,其中意外地刪除了 Apache 日誌,我正使用 grep 工具查詢是否有人打開了該檔案。

# lsof | grep error_log
httpd      2452     root    2w      REG       33,2      499    3090660
					/var/log/httpd/error_log (deleted)
httpd      2452     root    7w      REG       33,2      499    3090660
					/var/log/httpd/error_log (deleted)
... more httpd processes ...

在這個示例中,您可以看到 PID 2452 開啟檔案的檔案描述符為 2(標準錯誤)和 7。因此,可以在 /proc/2452/fd/7 中檢視相應的資訊,如清單 6 所示。

# cat /proc/2452/fd/7
[Sun Apr 30 04:02:48 2006] [notice] Digest: generating secret for digest authentication
[Sun Apr 30 04:02:48 2006] [notice] Digest: done
[Sun Apr 30 04:02:48 2006] [notice] LDAP: Built with OpenLDAP LDAP SDK

Linux 的優點在於,它儲存了檔案的名稱,甚至可以告訴我們它已經被刪除。在遭到破壞的系統中查詢相關內容時,這是非常有用的內容,因為攻擊者通常會刪除日誌以隱藏他們的蹤跡。Solaris 並不提供這些資訊。然而,我們知道 httpd 守護程序使用了 error_log 檔案,所以可以使用 ps 命令找到這個 PID,然後可以檢視這個守護程序開啟的所有檔案。

# lsof -a -p 8663 -d ^txt
COMMAND  PID   USER   FD   TYPE        DEVICE SIZE/OFF    NODE NAME
httpd   8663 nobody  cwd   VDIR         136,8     1024       2 /
httpd   8663 nobody    0r  VCHR          13,2          6815752 /devices/pseudo/[email protected]:null
httpd   8663 nobody    1w  VCHR          13,2          6815752 /devices/pseudo/[email protected]:null
httpd   8663 nobody    2w  VREG         136,8      185  145465 / (/dev/dsk/c0t0d0s0)
httpd   8663 nobody    4r  DOOR                    0t0      58 /var/run/name_service_door
						(door to nscd[81]) (FA:->0x30002b156c0)
httpd   8663 nobody   15w  VREG         136,8      185  145465 / (/dev/dsk/c0t0d0s0)
httpd   8663 nobody   16u  IPv4 0x300046d27c0      0t0     TCP *:80 (LISTEN)
httpd   8663 nobody   17w  VREG         136,8        0  145466 
                                                          /var/apache/logs/access_log
httpd   8663 nobody   18w  VREG         281,3        0 9518013 /var/run (swap)

我使用 -a-d 引數對輸出進行篩選,以排除程式碼程式段,因為我知道需要查詢的是哪些檔案。Name 列顯示出,其中的兩個檔案(FD 2 和 15)使用磁碟名代替了檔名,並且它們的型別為 VREG(常規檔案)。在 Solaris 中,刪除的檔案將顯示檔案所在的磁碟的名稱。通過這個線索,就可以知道該 FD 指向一個刪除的檔案。實際上,檢視 /proc/8663/fd/15 就可以得到所要查詢的資料。

如果可以通過檔案描述符檢視相應的資料,那麼您就可以使用 I/O 重定向將其複製到檔案中,如 cat /proc/8663/fd/15 > /tmp/error_log 。此時,您可以中止該守護程序(這將刪除 FD,從而刪除相應的檔案),將這個臨時檔案複製到所需的位置,然後重新啟動該守護程序。

對於許多應用程式,尤其是日誌檔案和資料庫,這種恢復刪除檔案的方法非常有用。正如您所看到的,有些作業系統(以及不同版本的 lsof)比其他的系統更容易查詢相應的資料。

網路連線也是檔案,這意味著可以使用 lsof 獲得關於它們的資訊。您曾在清單 2 中看到過這樣的示例。該示例假設您已經知道 PID,但是有時候並非如此。如果您只知道相應的埠,那麼可以使用 -i 引數利用套接字資訊進行搜尋。清單 8 顯示了對 TCP 埠 25 的搜尋。

# lsof -i :25
COMMAND  PID USER   FD   TYPE        DEVICE SIZE/OFF NODE NAME
sendmail 605 root    5u  IPv4 0x300010ea640      0t0  TCP *:smtp (LISTEN)
sendmail 605 root    6u  IPv6 0x3000431c180      0t0  TCP *:smtp (LISTEN)

需要以 protocol:@ip:port 的形式向 lsof 實用程式傳遞相關資訊,其中的 protocol 為 TCP 或 UDP(可以使用 4 或 6 作為字首,表示 IP 的版本),IP 為可解析的名稱或 IP 地址,而 port 為數字或表示該服務的名稱(來自 /etc/services)。需要一個或多個元素(埠、IP、協議)。在清單 8 中,:25 表示埠 25。輸出顯示,程序 605 正在使用 IPv6 和 IPv4 監聽埠 25。如果您對 IPv4 不感興趣,那麼可以將篩選器改為 6:25,以表示監聽埠 25 的 IPv6 套接字,或者直接使用 6 表示所有的 IPv6 連線。

除了顯示出這些守護程序正在監聽的物件,lsof 還可以發現發生的連線,同樣是使用 -i 引數。清單 9 顯示了搜尋與 192.168.1.10 之間的所有連線。

# lsof -i @192.168.1.10
COMMAND  PID USER   FD   TYPE        DEVICE  SIZE/OFF NODE NAME
sshd    1934 root    6u  IPv6 0x300046d21c0 0t1303608  TCP sun:ssh->linux:40379
							 (ESTABLISHED)
sshd    1937 root    4u  IPv6 0x300046d21c0 0t1303608  TCP sun:ssh->linux:40379
							 (ESTABLISHED)

在這個示例中,sunlinux 之間有兩個 IPv6 連線。對其進行更仔細的研究可以看出,這些連線來自於兩個不同的程序,但它們卻是相同的,這是因為兩臺主機是相同的,並且埠也是相同的(ssh 和 40379)。這是由於進入主程序的連線分叉出一個處理程式,並將該套接字傳遞給它。您還可以看到,名為 sun 的計算機正在使用埠 22 (ssh),而 linux 具有埠 40379。這表示,sun 是該連線的接收者,因為它關聯於該服務的已知埠。40379 是源或臨時埠,並且僅對這個連線有意義。

因為,至少在 UNIX 中,套接字是另一類檔案,所以 lsof 可以獲得關於這些連線的詳細資訊,並找出誰對它們負責。

結束語

UNIX 大量使用了檔案。作為系統管理員,lsof 允許您對核心記憶體進行檢視,以找出系統當前如何使用這些檔案。lsof 最簡單的用法可以告訴您哪些程序打開了哪些檔案,以及哪些檔案由哪些程序開啟。在收集關於應用程式工作情況的資訊時,或在進行某些可能損壞資料的操作前確保檔案未被使用時,這一點特別重要lsof 更高階的用法可以幫助您查詢刪除的檔案,並獲得關於網路連線的資訊。這是一個功能強大的工具,它幾乎可以用於任何地方。

相關推薦

no