在 UNIX? 環境中,文件無處不在,這便產生了一句格言:“任何事物都是文件”。通過文件不僅僅可以訪問常規數據,通常還可以訪問網絡連接和硬件。在有些情況下,當您使用?ls
?請求目錄清單時,將出現相應的條目。在其他情況下,如傳輸控制協議 (TCP) 和用戶數據報協議 (UDP) 套接字,不存在相應的目錄清單。但是在后臺為該應用程序分配了一個文件描述符,無論這個文件的本質如何,該文件描述符為應用程序與基礎操作系統之間的交互提供了通用接口。
因為應用程序打開文件的描述符列表提供了大量關于這個應用程序本身的信息,所以能夠查看這個列表將是很有幫助的。完成這項任務的實用程序稱為?lsof
,它對應于“list open files”(列出打開的文件)。幾乎在每個 UNIX 版本中都有這個實用程序,但奇怪的是,大多數供應商并沒有將其包含在操作系統的初始安裝中。要獲取更多關于?lsof
?的信息,請參見參考資料部分。
lsof 簡介
只需輸入?lsof
?就可以生成大量的信息,如清單 1?所示。因為?lsof
?需要訪問核心內存和各種文件,所以必須以 root 用戶的身份運行它才能夠充分地發揮其功能。
清單 1. lsof 的示例輸出
1 2 3 4 5 6 7 8 | 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 ... |
每行顯示一個打開的文件,除非另外指定,否則將顯示所有進程打開的所有文件。Command
、PID
?和?User
?列分別表示進程的名稱、進程標識符 (PID) 和所有者名稱。Device
、SIZE/OFF
、Node
?和?Name
?列涉及到文件本身的信息,分別表示指定磁盤的名稱、文件的大小、索引節點(文件在磁盤上的標識)和該文件的確切名稱。根據 UNIX 版本的不同,可能將文件的大小報告為應用程序在文件中進行讀取的當前位置(偏移量)。清單 1?來自一臺可以報告該信息的 Sun Solaris 10 計算機,而 Linux? 沒有這個功能。
FD
?和?Type
?列的含義最為模糊,它們提供了關于文件如何使用的更多信息。FD
?列表示文件描述符,應用程序通過文件描述符識別該文件。Type
?列提供了關于文件格式的更多描述。我們來具體研究一下文件描述符列,清單 1?中出現了三種不同的值。cwd
值表示應用程序的當前工作目錄,這是該應用程序啟動的目錄,除非它本身對這個目錄進行更改。txt
?類型的文件是程序代碼,如應用程序二進制文件本身或共享庫,再比如本示例的列表中顯示的?init
?程序。最后,數值表示應用程序的文件描述符,這是打開該文件時返回的一個整數。在清單 1?輸出的最后一行中,您可以看到用戶正在使用?vi
?編輯 /var/tmp/ExXDaO7d,其文件描述符為 3。u
?表示該文件被打開并處于讀取/寫入模式,而不是只讀 (r
) 或只寫 (w
) 模式。有一點不是很重要但卻很有幫助,初始打開每個應用程序時,都具有三個文件描述符,從 0 到 2,分別表示標準輸入、輸出和錯誤流。正因為如此,大多數應用程序所打開的文件的 FD 都是從 3 開始。
與?FD
?列相比,Type
?列則比較直觀。根據具體操作系統的不同,您會發現將文件和目錄稱為?REG
?和?DIR
(在 Solaris 中,稱為?VREG
?和?VDIR
)。其他可能的取值為?CHR
?和?BLK
,分別表示字符和塊設備;或者?UNIX
、FIFO
?和?IPv4
,分別表示 UNIX 域套接字、先進先出 (FIFO) 隊列和網際協議 (IP) 套接字。
轉到 /proc 目錄
盡管與使用?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 對這些文件進行篩選。
清單 2. 帶有 PID 篩選器并進行 txt 文件描述符篩選的 lsof 輸出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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/mm@0:null sendmail 605 root??? 1w? VCHR? 13,2??????????? 6815752 /devices/pseudo/mm@0:null sendmail 605 root??? 2w? VCHR? 13,2??????????? 6815752 /devices/pseudo/mm@0: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/log@0: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?顯示了相應的輸出。
清單 3. 要求 lsof 顯示關于某個文件的信息
1 2 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
?找出誰在使用該文件系統。
清單 4. 使用 lsof 找出誰在使用文件系統
1 2 3 4 5 6 7 8 | 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/home
?與?lsof /export/home/
(請注意尾部的斜杠)有所區別。第一種方式可以正常工作,因為它指向了裝入點。第二種方式不會生成任何輸出,因為它指向了目錄。如果您在 Shell 中使用 Tab 鍵自動完成命令,那么可能碰到這個問題,其中會幫助您添加結尾的斜杠。在這種情況下,您可以刪除這個斜杠或者使用?+D
?指定目錄。前者是首選的方法,因為與指定任意的目錄相比,其執行速度更快。
不常見的用法
在前面的部分中,我們研究了?lsof
?的基本用法,即顯示打開的文件和控制它們的進程之間的關系。當您想對系統進行一些煩瑣的操作,而又不希望破壞別人重要的文檔時,這種方法很有幫助。您還可以使用相同的方法執行一些高難度的 UNIX 操作。
恢復刪除的文件
當 UNIX 計算機受到入侵時,常見的情況是日志文件被刪除,以掩蓋攻擊者的蹤跡。管理錯誤也可能導致意外刪除重要的文件,比如在清理舊日志時,意外地刪除了數據庫的活動事務日志。有時可以恢復這些文件,并且?lsof
?可以為您提供幫助。
當進程打開了某個文件時,只要該進程保持打開該文件,即使將其刪除,它依然存在于磁盤中。這意味著,進程并不知道文件已經被刪除,它仍然可以向打開該文件時提供給它的文件描述符進行讀取和寫入。除了該進程之外,這個文件是不可見的,因為已經刪除了其相應的目錄條目。
前面曾在轉到 /proc 目錄部分中說過,通過在適當的目錄中進行查找,您可以訪問進程的文件描述符。在隨后的內容中,您看到了?lsof
?可以顯示進程的文件描述符和相關的文件名。您能明白我的意思嗎?
但愿它真的這么簡單!當您向?lsof
?傳遞文件名時,比如在?lsof /file/I/deleted
?中,它首先使用?stat()
?系統調用獲得有關該文件的信息,不幸的是,這個文件已經被刪除。在不同的操作系統中,lsof
?可能可以從核心內存中捕獲該文件的名稱。清單 5?顯示了一個 Linux 系統,其中意外地刪除了 Apache 日志,我正使用?grep
?工具查找是否有人打開了該文件。
清單 5. 在 Linux 中使用 lsof 查找刪除的文件
1 2 3 4 5 6 | # 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?所示。
清單 6. 通過 /proc 查找刪除的文件
1 2 3 4 | # 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,然后可以查看這個守護進程打開的所有文件。
清單 7. 在 Solaris 中查找刪除的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 | # 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/mm@0:null httpd?? 8663 nobody??? 1w? VCHR????????? 13,2????????? 6815752 /devices/pseudo/mm@0: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 的搜索。
清單 8. 查找監聽端口 25 的進程
1 2 3 4 | # 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 之間的所有連接。
清單 9. 搜索活動的連接
1 2 3 4 5 6 | # 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) |
在這個示例中,sun
?和?linux
?之間有兩個 IPv6 連接。對其進行更仔細的研究可以看出,這些連接來自于兩個不同的進程,但它們卻是相同的,這是因為兩臺主機是相同的,并且端口也是相同的(ssh 和 40379)。這是由于進入主進程的連接分叉出一個處理程序,并將該套接字傳遞給它。您還可以看到,名為?sun
?的計算機正在使用端口 22 (ssh),而?linux
?具有端口 40379。這表示,sun
?是該連接的接收者,因為它關聯于該服務的已知端口。40379 是源或臨時端口,并且僅對這個連接有意義。
因為,至少在 UNIX 中,套接字是另一類文件,所以?lsof
?可以獲得關于這些連接的詳細信息,并找出誰對它們負責。
結束語
UNIX 大量使用了文件。作為系統管理員,lsof
?允許您對核心內存進行查看,以找出系統當前如何使用這些文件。lsof
?最簡單的用法可以告訴您哪些進程打開了哪些文件,以及哪些文件由哪些進程打開。在收集關于應用程序工作情況的信息時,或在進行某些可能損壞數據的操作前確保文件未被使用時,這一點特別重要lsof
?更高級的用法可以幫助您查找刪除的文件,并獲得關于網絡連接的信息。這是一個功能強大的工具,它幾乎可以用于任何地方。
?
Reference:
從「https://www.ibm.com/developerworks/cn/aix/library/au-lsof.html??使用 lsof 查找打開的文件」轉載
?