/proc 文件系統是一個虛擬文件系統,通過它可以使用一種新的方法在 Linux® 內核空間和用戶空間之間進行通信。在 /proc 文件系統中,我們可以將對虛擬文件的讀寫作爲與內核中實體進行通信的一種手段,但是與普通文件不同的是,這些虛擬文件的內容都是動態創建的。本文對 /proc 虛擬文件系統進行了介紹,並展示了它的用法。
最初開發 /proc 文件系統是爲了提供有關係統中進程的信息。但是由於這個文件系統非常有用,因此內核中的很多元素也開始使用它來報告信息,或啓用動態運行時配置。
/proc 文件系統包含了一些目錄(用作組織信息的方式)和虛擬文件。虛擬文件可以向用戶呈現內核中的一些信息,也可以用作一種從用戶空間向內核發送信息的手段。實際上我們並不會同時需要實現這兩點,但是本文將向您展示如何配置這個文件系統進行輸入和輸出。
儘管像本文這樣短小的一篇文章無法詳細介紹 /proc 的所有用法,但是它依然對這兩種用法進行了展示,從而可以讓我們體會一下 /proc 是多麼強大。清單
1 是對 /proc 中部分元素進行一次交互查詢的結果。它顯示的是 /proc
文件系統的根目錄中的內容。注意,在左邊是一系列數字編號的文件。每個實際上都是一個目錄,表示系統中的一個進程。由於在 GNU/Linux 中創建的第一個進程是
init
進程,因此它的 process-id
爲 1。然後對這個目錄執行一個
ls
命令,這會顯示很多文件。每個文件都提供了有關這個特殊進程的詳細信息。例如,要查看 init
的
command-line 項的內容,只需對 cmdline
文件執行 cat
命令。
/proc 中另外一些有趣的文件有:cpuinfo
,它標識了處理器的類型和速度;pci
,顯示在 PCI
總線上找到的設備;modules
,標識了當前加載到內核中的模塊。
清單 1. 對 /proc 的交互過程
|
清單 2 展示了對 /proc 中的一個虛擬文件進行讀寫的過程。這個例子首先檢查內核的 TCP/IP 棧中的 IP 轉發的目前設置,然後再啓用這種功能。
清單 2. 對 /proc 進行讀寫(配置內核)
|
另外,我們還可以使用 sysctl
來配置這些內核條目。有關這個問題的更多信息,請參閱 參考資料 一節的內容。
順便說一下,/proc 文件系統並不是 GNU/Linux 系統中的惟一一個虛擬文件系統。在這種系統上,sysfs 是一個與 /proc 類似的文件系統,但是它的組織更好(從 /proc 中學習了很多教訓)。不過 /proc 已經確立了自己的地位,因此即使 sysfs 與 /proc 相比有一些優點,/proc 也依然會存在。還有一個 debugfs 文件系統,不過(顧名思義)它提供的更多是調試接口。debugfs 的一個優點是它將一個值導出給用戶空間非常簡單(實際上這不過是一個調用而已)。
可加載內核模塊(LKM)是用來展示 /proc 文件系統的一種簡單方法,這是因爲這是一種用來動態地向 Linux 內核添加或刪除代碼的新方法。LKM 也是 Linux 內核中爲設備驅動程序和文件系統使用的一種流行機制。
如果您曾經重新編譯過 Linux 內核,就可能會發現在內核的配置過程中,有很多設備驅動程序和其他內核元素都被編譯成了模塊。如果一個驅動程序被直接編譯到了內核中,那麼即使這個驅動程序沒有運行,它的代碼和靜態數據也會佔據一部分空間。但是如果這個驅動程序被編譯成一個模塊,就只有在需要內存並將其加載到內核時纔會真正佔用內存空間。有趣的是,對於 LKM 來說,我們不會注意到有什麼性能方面的差異,因此這對於創建一個適應於自己環境的內核來說是一種功能強大的手段,這樣可以根據可用硬件和連接的設備來加載對應的模塊。
下面是一個簡單的 LKM,可以幫助您理解它與在 Linux 內核中看到的標準(非動態可加載的)代碼之間的區別。清單 3 給出了一個最簡單的 LKM。(可以從本文的 下載 一節中下載這個代碼)。
清單 3 包括了必須的模塊頭(它定義了模塊的 API、類型和宏)。然後使用 MODULE_LICENSE
定義了這個模塊使用的許可證。此處,我們定義的是 GPL,從而防止會污染到內核。
清單 3 然後又定義了這個模塊的 init
和 cleanup
函數。my_module_init
函數是在加載這個模塊時被調用的,它用來進行一些初始化方面的工作。my_module_cleanup
函數是在卸載這個模塊時被調用的,它用來釋放內存並清除這個模塊的蹤跡。注意此處 printk
的用法:這是內核的
printf
函數。KERN_INFO
符號是一個字符串,可以用來對進入內核迴環緩衝區的信息進行過濾(非常類似於 syslog
)。
最後,清單 3 使用 module_init
和 module_exit
宏聲明瞭入口函數和出口函數。這樣我們就可以按照自己的意願來對這個模塊的 init
和 cleanup
函數進行命名了,不過我們最終要告訴內核維護函數就是這些函數。
清單 3. 一個簡單的但可以正常工作的 LKM(simple-lkm.c)
|
清單 3 儘管非常簡單,但它卻是一個真正的 LKM。現在讓我們對其進行編譯並在一個 2.6 版本的內核上進行測試。2.6
版本的內核爲內核模塊的編譯引入了一種新方法,我發現這種方法比原來的方法簡單了很多。對於文件 simple-lkm.c
,我們可以創建一個
makefile,其惟一內容如下:
obj-m += simple-lkm.o |
要編譯 LKM,請使用 make
命令,如清單 4 所示。
清單 4. 編譯 LKM
|
結果會生成一個 simple-lkm.ko
文件。這個新的命名約定可以幫助將這些內核對象(LKM)與標準對象區分開來。現在可以加載或卸載這個模塊了,然後可以查看它的輸出。要加載這個模塊,請使用
insmod
命令;反之,要卸載這個模塊,請使用 rmmod
命令。lsmod
可以顯示當前加載的 LKM(參見清單 5)。
清單 5. 插入、檢查和刪除 LKM
|
注意,內核的輸出進到了內核迴環緩衝區中,而不是打印到 stdout
上,這是因爲 stdout
是進程特有的環境。要查看內核迴環緩衝區中的消息,可以使用 dmesg
工具(或者通過 /proc 本身使用 cat
/proc/kmsg
命令)。清單 6 給出了 dmesg
顯示的最後幾條消息。
清單 6. 查看來自 LKM 的內核輸出
|
可以在內核輸出中看到這個模塊的消息。現在讓我們暫時離開這個簡單的例子,來看幾個可以用來開發有用 LKM 的內核 API。
|
內核程序員可以使用的標準 API,LKM 程序員也可以使用。LKM 甚至可以導出內核使用的新變量和函數。有關 API 的完整介紹已經超出了本文的範圍,因此我們在這裏只是簡單地介紹後面在展示一個更有用的 LKM 時所使用的幾個元素。
要在 /proc 文件系統中創建一個虛擬文件,請使用 create_proc_entry
函數。這個函數可以接收一個文件名、一組權限和這個文件在 /proc 文件系統中出現的位置。create_proc_entry
的返回值是一個 proc_dir_entry
指針(或者爲 NULL,說明在 create
時發生了錯誤)。然後就可以使用這個返回的指針來配置這個虛擬文件的其他參數,例如在對該文件執行讀操作時應該調用的函數。create_proc_entry
的原型和 proc_dir_entry
結構中的一部分如清單 7 所示。
清單 7. 用來管理 /proc 文件系統項的元素
|
稍後我們就可以看到如何使用 read_proc
和 write_proc
命令來插入對這個虛擬文件進行讀寫的函數。
要從 /proc 中刪除一個文件,可以使用 remove_proc_entry
函數。要使用這個函數,我們需要提供文件名字符串,以及這個文件在 /proc 文件系統中的位置(parent)。這個函數原型如清單 7 所示。
parent 參數可以爲 NULL(表示 /proc 根目錄),也可以是很多其他值,這取決於我們希望將這個文件放到什麼地方。表 1
列出了可以使用的其他一些父 proc_dir_entry
,以及它們在這個文件系統中的位置。
表 1. proc_dir_entry 快捷變量
proc_dir_entry | 在文件系統中的位置 |
---|---|
proc_root_fs |
/proc |
proc_net |
/proc/net |
proc_bus |
/proc/bus |
proc_root_driver |
/proc/driver |
我們可以使用 write_proc
函數向 /proc 中寫入一項。這個函數的原型如下:
int mod_write( struct file *filp, const char __user *buff, |
filp
參數實際上是一個打開文件結構(我們可以忽略這個參數)。buff
參數是傳遞給您的字符串數據。緩衝區地址實際上是一個用戶空間的緩衝區,因此我們不能直接讀取它。len
參數定義了在
buff
中有多少數據要被寫入。data
參數是一個指向私有數據的指針(參見 清單
7)。在這個模塊中,我們聲明瞭一個這種類型的函數來處理到達的數據。
Linux 提供了一組 API 來在用戶空間和內核空間之間移動數據。對於 write_proc
的情況來說,我們使用了
copy_from_user
函數來維護用戶空間的數據。
我們可以使用 read_proc
函數從一個 /proc 項中讀取數據(從內核空間到用戶空間)。這個函數的原型如下:
int mod_read( char *page, char **start, off_t off, |
page
參數是這些數據寫入到的位置,其中 count
定義了可以寫入的最大字符數。在返回多頁數據(通常一頁是 4KB)時,我們需要使用 start
和 off
參數。當所有數據全部寫入之後,就需要設置 eof
(文件結束參數)。與 write
類似,data
表示的也是私有數據。此處提供的 page
緩衝區在內核空間中。因此,我們可以直接寫入,而不用調用 copy_to_user
。
我們還可以使用 proc_mkdir
、symlinks
以及
proc_symlink
在 /proc 文件系統中創建目錄。對於只需要一個 read
函數的簡單
/proc 項來說,可以使用 create_proc_read_entry
,這會創建一個 /proc 項,並在一個調用中對
read_proc
函數進行初始化。這些函數的原型如清單 8 所示。
清單 8. 其他有用的 /proc 函數
|
|
下面是一個可以支持讀寫的 LKM。這個簡單的程序提供了一個財富甜點分發。在加載這個模塊之後,用戶就可以使用 echo
命令向其中導入文本財富,然後再使用 cat
命令逐一讀出。
清單 9 給出了基本的模塊函數和變量。init
函數(init_fortune_module
)負責使用
vmalloc
來爲這個點心罐分配空間,然後使用 memset
將其全部清零。使用所分配並已經清空的
cookie_pot
內存,我們在 /proc 中創建了一個 proc_dir_entry
項,並將其稱爲
fortune。當 proc_entry
成功創建之後,對自己的本地變量和
proc_entry
結構進行了初始化。我們加載了 /proc read
和
write
函數(如清單 9 和清單 10 所示),並確定這個模塊的所有者。cleanup
函數簡單地從
/proc 文件系統中刪除這一項,然後釋放 cookie_pot
所佔據的內存。
cookie_pot
是一個固定大小(4KB)的頁,它使用兩個索引進行管理。第一個是
cookie_index
,標識了要將下一個 cookie 寫到哪裏去。變量 next_fortune
標識了下一個 cookie 應該從哪裏讀取以便進行輸出。在所有的 fortune 項都讀取之後,我們簡單地回到了
next_fortune
。
清單 9. 模塊的 init/cleanup 和變量
|
向這個罐中新寫入一個 cookie 非常簡單(如清單 10 所示)。使用這個寫入 cookie 的長度,我們可以檢查是否有這麼多空間可用。如果沒有,就返回
-ENOSPC
,它會返回給用戶空間。否則,就說明空間存在,我們使用 copy_from_user
將用戶緩衝區中的數據直接拷貝到 cookie_pot
中。然後增大
cookie_index
(基於用戶緩衝區的長度)並使用 NULL 來結束這個字符串。最後,返回實際寫入
cookie_pot
的字符的個數,它會返回到用戶進程。
清單 10. 對 fortune 進行寫入操作所使用的函數
|
對 fortune 進行讀取也非常簡單,如清單 11
所示。由於我們剛纔寫入數據的緩衝區(page
)已經在內核空間中了,因此可以直接對其進行操作,並使用
sprintf
來寫入下一個 fortune。如果 next_fortune
索引大於
cookie_index
(要寫入的下一個位置),那麼我們就將 next_fortune
返回爲
0,這是第一個 fortune 的索引。在將這個 fortune 寫入用戶緩衝區之後,在 next_fortune
索引上增加剛纔寫入的 fortune 的長度。這樣就變成了下一個可用 fortune 的索引。這個 fortune
的長度會被返回並傳遞給用戶。
清單 11. 對 fortune 進行讀取操作所使用的函數
|
從這個簡單的例子中,我們可以看出通過 /proc 文件系統與內核進行通信實際上是件非常簡單的事情。現在讓我們來看一下這個 fortune 模塊的用法(參見清單 12)。
清單 12. 展示 fortune cookie LKM 的用法
|
/proc 虛擬文件系統可以廣泛地用來報告內核的信息,也可以用來進行動態配置。我們會發現它對於驅動程序和模塊編程來說都是非常完整的。在下面的 參考資料 中,我們可以學習到更多相關知識。
|
描述 | 名字 | 大小 | 下載方法 |
---|---|---|---|
Linux kernel module source | l-proc-lkm.zip | 2KB | HTTP |
關於下載方法的信息 |
學習
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文。
- “實時管理 Linux”(developerWorks,2003 年 5 月)詳細介紹了 /proc
的基礎知識,包括如何管理操作系統的衆多細節,而不用關閉或重新啓動機器。
- 探索 /proc 文件系統中的 文件和子目錄。
- 有關 Linux 內核 2.6 版本的 driver porting 的文章詳細討論了內核模塊的問題。
- LinuxHQ
是有關 Linux 內核信息的一個很好站點。
- debugfs
文件系統是除 /proc 之外另外一個調試選擇。
- “內核比較: 從 2.4 到 2.6 內核開發中的改進” (developerWorks,2004 年 2
月)對構成 2.6 版本內核的工具、測試以及技術進行了深入介紹。
- “使用 Kprobes 調試內核” (developerWorks,2004 年 8 月)介紹了 Kprobes
如何與 2.6 版本的內核結合使用,提供一種輕量級的非破壞性的強大機制來動態插入
printk
函數。 printk
函數和dmesg
方法都是用來進行內核調試的常用方法。Allessando Rubini 撰寫的 Linux Device Drivers 一書提供了一章有關內核調試技術的 在線內容。- sysctl 命令是另外一種實現動態內核配置的方法。
- 在 developerWorks Linux 專區 中可以找到爲 Linux 開發人員準備的更多參考資料。
- 隨時關注 developerWorks 技術活動和網絡廣播。
獲得產品和技術
- kernel.org 上提供最新的
Linux 內核。
- GNU make 工具文檔 在 gnu.org 站點 上可以找到。
- Modutils 包提供了很多有關內核模塊的工具。
- 索取免費的 SEK for Linux,這有兩張 DVD,包括最新的 IBM for Linux
試用版軟件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。
- 用可直接從 developerWorks 下載的 IBM 試用軟件
構建您的下一個開發項目。
討論
- 通過參與 developerWorks blogs 加入 developerWorks
社區。
M. Tim Jones 是一名嵌入式軟件工程師,他是 GNU/Linux Application Programming、AI Application Programming 和 BSD Sockets Programming from a Multilanguage Perspective 等書的作者。他的工程背景非常廣泛,從對地同步宇宙飛船的內核開發到嵌入式架構設計,再到網絡協議的開發。Tim 是 Emulex Corp. 的一名資深軟件工程師。 |