在linux中通過proc文件系統進行用戶態與內核態的交互或內核微調都一種比較簡單有效的手段。最近由於項目需要,需要通過proc把一個內核模塊的數據輸出到用戶態,借此機會學習了一下seq_file。在此做一個簡單的記錄,不對的地方歡迎批評指正啊。要用seq_file,需要#include
好啦,廢話不多說,開始吧,^_^。假設,在我們的一個內核模塊中,有一組數據存放在可迭代的數據結構(如,鏈表、數組等)中,想把它們通過proc輸出到用戶態中,通過cat等方式進行查看,那該怎麼做呢。為了簡單起見,假設我們有一個鏈表的數據,鏈表的頭部通過一個名為list_head的變量指向,該結構體本身用一個讀寫自旋鎖保護。鏈表節點的結構簡單如下:
struct node{
struct node *next;
int num;
};
struct node* list_head;//鏈表頭部
DEFINE_RWLOCK(list_lock);//定義並初始化一個讀寫自旋鎖。
seq_file中定義了一組用於迭代的函數指針,其功能類似於c++中的前向迭代器,但是多了一個名為show的成員,用於輸出節點的信息。這組函數指針聲明在一個名為struct seq_operations的結構體中,如下:
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
start可以做一些資源獲取和初始化工作,相當c++的構造函數的作用,它的返回值會作為第一次調用show與next的第二個參數傳給他們,之後每次會調用next,和show;start若返回NULL則直接調用stop不會再去調用show和next。若有一些文件頭要輸出可以返回SEQ_START_TOKEN內核中它被定義為:
#define SEQ_START_TOKEN ((void *)1)
next的返回值會作為show和下一次調用next的第二個參數(即v)傳給他們,當next返回NULL時,就調用stop。
stop的作用則類似c++類中的析構函數的作用,負責資源的清理和歸還操作。
show函數正確返回0,出錯返回相應的出錯碼。
注意由於輸出的信息大於內核一次分配的緩沖區等原因(具體原因等下次寫關於seq_read等函數的實現時就在說明,到時候就比較清楚了)start,show,next ,stop可能會調用多次,這就需要start和next的第三個參數pos來區別他們。所以它們的調用順序大概可以描述如下: start->show->next->show->next->show->...->next->stop。
注意也可能如下(start stop調用多次): start->show->next->show->next->show->...->next->stop->start->show->next...->next->stop。這幾個迭代函數在我們例子中可以定義如下:
static void * my_start(struct seq_file*m, l loff_t *pos)
{
int i = 1;
struct node* tmp_entry;
read_lock_bh(list_lock);//獲取讀寫自旋鎖
if(*pos==0){
return SEQ_START_TOKEN;
}
for(tmp_entry = list_head;tmp_entry != NULL; tmp_entry=tmp-entry->next){
if(i==*pos)
return tmp_entry;
++i;
}
return NULL;
}
static void my_stop (struct seq_file *m, void *v)
{
read_unlock_bh(tmp_lock);//釋放自旋鎖
seq_printf(m,"\n\n-------------data information end---------------------------");
}
static void * my_next(struct seq_file *m, void *v, loff_t *pos)
{
++*pos;
return (SEQ_START_TOKEN==v)?list_head:((struct node*)v)->next;
}
static int my_show(struct seq_file *m, void *v)
{
struct node* tmp_entry;
static int line_count= 0;
if(SEQ_START_TOKEN==v){
seq_printf(m,"\n\n-------------data informations start---------------------------\n\n");
}else{
tmp_entry = (struct node*)v;
seq_file(m," %d ",tmp_entry->num);
if(++line_count==10){//一行輸出十個數據
seq_printf(m,"\n");
line_count=0;
}
}
return 0;
}
然後,定義一個struct seq_operations,如下:
static const struct seq_operations my_seq_ops={
.start = my_start,
.next = my_next,
.stop = my_stop,
.show = my_show,
};
到這,我們還要定義一個函數把my_seq_ops的地址出給內核,如下,
static int proc_my_test_open((struct inode *inode, struct file *file)
{
return seq_open(file,&my_seq_ops);
}
最後再定義一個struct file_operations結構體,我們的proc_my_test_open傳給內核,並調用proc_create,創建一個proc目錄就大功告成啦。代碼如下:
static const struct file_operations my_file_ops={
.owner = THIS_MODULE,
.open = proc_my_test_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
my_file_opsproc_create("my_test", 0, NULL, &my_file_ops);
其實對於簡單的輸出,可以簡單定義一個show函數然後通過single_open而非我們這裡的seq_open,這個方法比較簡單,具體辦法可以自行google。