“tcp丟包分析”實驗解析(一)--proc文件系統
“tcp丟包分析”系列文章代碼來自謝寶友老師,由西郵陳莉君教授研一學生進行解析,本文由戴君毅整理,梁金榮編輯,賀東昇校對。
最初開發 /proc
文件系統是爲了提供有關係統中進程的信息。但是這個文件系統非常有用, /proc
文件系統包含了一些目錄(用作組織信息的方式)和虛擬文件。虛擬文件可以向用戶呈現內核中的一些信息,也可以用作一種從用戶空間向內核發送信息的手段。
/proc
文件系統可以爲用戶提供很多信息, 在左邊是一系列數字編號,每個實際上都是一個目錄,表示系統中的一個進程。由於在Linux中創建的第一個進程是 init
進程,因此它的 process-id
爲 1。
右邊的目錄包含特定信息,比如 cpuinfo
包含了CPU的信息, modules
包含了內核模塊的信息。
爲了解決一些實際問題,我們需要在 /proc
下創建條目捕獲信息,使用文件系統通用方法肯定是不行的,需要使用相關API編寫內核模塊來實現。
在做謝寶友老師寫的“TCP丟包分析”實驗裏,首先就會在 /proc
下創建條目,較爲簡單,先來看 init
和 exit
:
框架還是比較清晰的,需要深入源碼來感受一下,第一部分代碼:
struct proc dir entry *pe;
結構 proc_dir_entry
定義在 <fs/proc/internal.h>
下,可以稱爲一個 pde
,在創建一個文件或目錄時就會創建一個 pde
來管理它們。而在打開它們的時候,則會創建一個 proc_inode
結構:
可以使用 PROC_I
宏,也就是我們熟悉的 container_of
,從虛擬文件系統的 inode
得到 proc_inode
,進而得到 pde
。
回到 proc_dir_entry
結構,很多信息從字段名字就可以看出, pde
需要指向創建自己的父 pde
結構, subdir
的組織方式是紅黑樹,還需要我們實現操作集以及一些引用計數和命名規則等等。
有意思的是,除了操作集之外還有一個 proc_write_t
,對於一些功能比較簡單的 proc
文件,我們只要實現這個函數即可,而不用設置 inode_operations
結構,在註冊 proc
文件的時候,會自動爲 proc_fops
設置一個缺省的 file_operations
結構。
此時,我們可以想象以下模型:
第二部分代碼是:
proc_mkdir("mooc", NULL);
proc_mkdir("mooc/net", NULL);
remove proc entry("mooc/net", NULL);
remove proc entry("mooc", NULL);
易知其功能是在 /proc
下創建和刪除條目 mooc/net
,以創建操作爲例,看下內核代碼如何實現的:
這裏邏輯很簡單, proc_mkdir
實際上是 proc_mkdir_data
默認了權限爲 S_IRUGO|S_IXUGO
,再調用 __proc_create
初始化一個局部 pde
,如果成功則初始化操作集,並調用 proc_register
註冊這個 pde
到父 pde
下並返回。
__proc_create
調用 kmem_cache_zalloc
從 cache
中獲取空間給 pde
,並且對條目名稱進行檢查。如果成功,則對名稱、模式等屬性賦值,設置引用計數並初始化鎖。
__proc_register
接收兩個參數,一個父親 pde
,一個當前 pde
,目的是把當前 pde
掛到父親名下,前面提到 subdir
的組織形式是紅黑樹,那麼肯定涉及相關代碼,來看:
首先判斷當前 pde
的 id
是否越界,如果沒有打開子目錄鎖,把當前 pde
的 parent
字段指向父親 pde
,並嘗試在紅黑樹中插入子目錄,成功後重新上鎖並返回當前 pde
。
紅黑樹的插入操作篇幅所限不再敘述。
下面看第三部分代碼:
proc_create
內部也是調用 proc_create_data
,但還需自行指定權限以及操作集回調,用於創建一個 proc
文件,在3.10內核中取代 create_proc_entry
這個舊的接口。
回到實驗代碼,我們爲加入的條目編寫操作集接口。
一般地,內核通過在 procfs
文件系統下建立文件來向用戶空間提供輸出信息,用戶空間可以通過任何文本閱讀應用查看該文件信息,但是 procfs
有一個缺陷,如果輸出內容大於1個內存頁,需要多次讀,因此處理起來很難,另外,如果輸出太大,速度比較慢,有時會出現一些意想不到的情況, AlexanderViro
實現了一套新的功能,使得內核輸出大文件信息更容易,它們叫做 seq_file
,所以在使用它們的操作集時需要包含 seq_file.h
頭文件。
Drop_packet_open
實際上是調用了 single_open
:
爲什麼這麼做?內核文檔給出了相關描述:
https://www.kernel.org/doc/Documentation/filesystems/seq_file.txt
你可能發現,內核文檔裏顯示的是 seq_open
,而實驗裏是 single_open
,它們有什麼區別呢?實際上內核文檔的最後給出了答案:
謝寶友老師的實驗中運用 seq_file
的極簡版本(extra-simple version),只需定義一個 show()
函數。完整的情況我們還需要實現 start()
, next()
等迭代器來對 seq_file
進行操作。極簡版本中, open
方法需要調用 single_open
,對應的, release
方法調用 single_release
。
推薦閱讀https://www.ibm.com/developerworks/cn/linux/l-proc.html