內容簡介
本文主要講述序列文件(seq_file)接口的內核實現,如何使用它將Linux內核裏面常用的數據結構通過文件(主要關注proc文件)導出到用戶空間,最後定義了一些宏以便於編程,減少重複代碼。在分析序列文件接口實現的過程中,還連帶涉及到一些應用陷阱和避免手段。序列文件接口
UNIX的世界裏,文件是最普通的概念,所以用文件來作爲內核和用戶空間傳遞數據的接口也是再普通不過的事情,並且這樣的接口對於shell也是相當友好的,方便管理員通過shell直接管理系統。由於僞文件系統proc文件系統在處理大數據結構(大於一頁的數據)方面有比較大的侷限性,使得在那種情況下進行編程特別彆扭,很容易導致bug,所以序列文件接口被髮明出來,它提供了更加友好的接口,以方便程序員。之所以選擇序列文件接口這個名字,應該是因爲它主要用來導出一條條的記錄數據。爲了能給大家一個具體形象的認識,我們首先來看一段用序列文件接口通過proc文件導出內核雙向循環鏈接表的實例代碼:
|
注:以上代碼需要內核版本不小於2.6.23,小版本內核可以從2.6.23內核代碼中抽取函數seq_list_start和seq_list_next。
上面的這段程序創建了一個結構體my_data的雙向循環鏈表(head),並且通過my_data proc文件將其導出到用戶空間,用戶空間的程序除了可以通過對my_data的讀操作來獲取my_data鏈表中各個結構體中的value域的值之外,還能通過寫(write)系統調用添加一個具有隨機value值的my_data結構體到雙向循環鏈表中(從鏈表的頭部添加)。
gentux kernel # cat /proc/my_data value: 474615376 value: 474615632 value: 474615568 gentux kernel # echo 0 > /proc/my_data gentux kernel # cat /proc/my_data value: 758528120 value: 474615376 value: 474615632 value: 474615568 |
雖然,上面的那段代碼行數不少,但是和輸出相關的部分除了函數_seq_show外都是慣例代碼,這種簡便性是很直觀的。相信對於雙向循環鏈表數據結構的導出,讀者們都能夠照貓畫虎寫出自己的代碼而毋須我再多言。
序列文件接口相關的兩個重要數據結構爲seq_file結構體和seq_operations表(表是延續ULK3的叫法,實際上就是隻包含函數指針的結構體)。首先介紹seq_file結構體:
- buf: 序列文件對應的數據緩衝區,要導出的數據都是首先打印到這個緩衝區,然後才被拷貝到用戶指定的用戶空間緩衝區。
- size: 緩衝區的大小,默認爲1個內存頁面大小,隨着需求會動態以2的級數倍擴張,比如:4k,8k,16k...
- from: 沒有拷貝到用戶空間的數據在buf中的起始偏移量。
- count: buf中沒有被拷貝到用戶空間的數據的字節數。
- index: 數據項的索引,和稍後提到的seq_operations表中的start, next操作中的pos項一致。
- version: 文件的版本。
- lock: 序列化對這個文件的並行操作
- op:指向稍後提到的seq_operations表。
- private: 指向文件的私有數據,是特例化一個序列文件的方法。
- start: 開始讀數據項,通常需要在這個函數裏面加鎖,以防止並行訪問數據。
- stop: 停止讀數據項,和start相對,通常需要解鎖。
- next:找到下一個要處理的數據項。
- show:打印數據項到臨時緩衝區。
一些有用的全局函數:
- seq_open:通常會在打開文件的時候調用,以第二個參數爲seq_operations表創建seq_file結構體。
- seq_read, seq_lseek和seq_release:他們通常都直接對應着文件操作表中的read, llseek和release。
- seq_escape:將一個字符串中的需要轉義的字符(字節長)以8進制的方式打印到seq_file。
- seq_putc, seq_puts, seq_printf:他們分別和C語言中的putc,puts和printf相對應。
- seq_path:用於輸出文件名。
- single_open, single_release: 打開和釋放只有一條記錄的文件。
- seq_open_private, __seq_open_private, seq_release_private:和seq_open類似,不過打開seq_file的時候創建一小塊文件私有數據。
- seq_list_start:返回鏈表中的特定項。
- seq_list_start_head:在需要輸出表格頭的時候使用。
- seq_list_next: 返回鏈表中的下一項。
序列文件接口的內核實現
序列文件接口的實現比較簡單易懂,出於完整性考慮,我姑且把核心函數seq_read拿來和讀者一起解讀:- 如果是第一次對序列文件調用讀操作,那麼內核數據緩衝區指針(buf)爲空,這個時候申請一整頁內存作爲緩衝區。
- 如果內核緩衝區中尚且有數據,那麼先用它們填補用戶緩衝區。
- 如果用戶緩衝區仍有空間,則按序調用start和show,填補內核緩衝區。如果當前的內核緩衝區不足以容納一條記錄,那麼將緩衝區大小加倍後再次嘗試,直到可以容納至少一條記錄。然後持續調用next和show,直到內核緩衝區無空間容納新的整條記錄後調用stop終止。
因爲序列文件的緩衝區有自動擴張的功能,所以它更便於導出大於一頁的數據結構,這也正是single_open/release函數的用武之地。
內核常用數據結構的導出
應用序列文件接口導出常用的面向記錄的數據結構通常是非常簡單的,下面我將給出內核常用的數據結構通過序列文件接口導出的思路。數組
數組通過其索引就能夠隨機地訪問其中的任一元素,所以我們只要將start和next參數中的pos當成索引,返回對應元素的地址即可。雙向循環鏈表
- start:跳過前pos個元素,返回即可。
- next:增加pos,返回下一個元素。
哈希表
哈希表有兩種導出方法:- 以每個哈希桶爲元素,這個時候哈希表退化爲數組。
- 以每個哈希節點爲元素,這個時候它退化爲按桶連接起來的鏈表。
紅黑樹
因爲Linux內核提供了按照類似鏈表的方式遍歷紅黑樹的方法,所以導出它的方法和雙向鏈接表類似。序列文件接口的幫助宏
實際上,應用序列文件接口進行編程,存在大量的重複代碼,爲了減少可惡的重複代碼,也爲了減少複製粘貼可能引入的bug,我寫了這一套宏以簡化大多數情況下的序列文件接口調用。具體宏代碼如下:
|
應用起來比較簡單,下面是用其重寫過的上面的雙向連接表導出代碼:
|
是不是簡單了不少了?其它數據結構的實例代碼可以參考文後的附件。
參考資料:
文章出自:http://www.4ucode.com/Study/Topic/1554981