您的位置:首页 > 运维架构 > Linux

linux 读取proc文件之seq_file浅析1

2014-04-26 23:52 609 查看
在linux中通过proc文件系统进行用户态与内核态的交互或内核微调都一种比较简单有效的手段。最近由于项目需要,需要通过proc把一个内核模块的数据输出到用户态,借此机会学习了一下seq_file。在此做一个简单的记录,不对的地方欢迎批评指正啊。要用seq_file,需要#include<linux/seq_file.h>

好啦,废话不多说,开始吧,^_^。假设,在我们的一个内核模块中,有一组数据存放在可迭代的数据结构(如,链表、数组等)中,想把它们通过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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: