Chinaunix首页 | 论坛 | 博客
  • 博客访问: 378009
  • 博文数量: 160
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 250
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-18 01:16
文章分类

全部博文(160)

文章存档

2016年(4)

2015年(13)

2014年(29)

2013年(114)

我的朋友

分类: LINUX

2013-05-15 01:36:33

原文地址:seq_file 作者:sillyboytao

 seq_file 讲得很明白
转自:http://blog.csdn.net/unbutun/article/details/4620541

kernel module编程(八):读取proc文件之seq_file

 

  在上次我们使用了read_proc的方式通过/proc文件读取kernel module的信息。作者给的例子他自己说是ugly。而我们在读取大量数据时发现,受到用户buffer大小的限制(page的大小),可能需要读取多次,不仅需要记录上次读取的位置,而且由于每次读取我们申请了信号量,读取完释放,那么如果多次读取的间隔中,如果信号量被写所获取就好出现混乱。 linux kernel提供seq_file更好的方式来解决这个问题,除非我们确定读取的信息量非常少,能够在page中返回,我们应使用seq_file的方式而不是read_proc 。

  LDD3中介绍的方式,我觉得是典型的西方人和中国人思维方式的不同。seq_file的介绍中,LDD3先从每个操作具体将其,然后到如何和proc文件联系,最后到如何创建proc文件,我喜欢反过来的方式,先创建proc,在一步步细化。老外是日月年,我们是年月日,嘿嘿。 seq_file的处理方式开看有点发展,步骤有些多,但是安全,是规范的处理方式。

步骤一:建立proc文件。

  通过一个struct proc_dir_entry的元素,在/proc中建立文件,如下:

struct proc_dir_entry * entry = create_proc_entry(scullseq,0,NULL)。参数的内容和read_proc,第一个参数表示文件名,第二个参数表示文件属性,对于只读方式为0,第三个参数表示文件路径,NULL表示缺省路径,即/proc

步骤二:关联proc的操作。

   需要对文件进行操作,见过文件和struct file_operations相关联,我们注意到这个数据结构也用于模块操作关联中。具体操作如下:

#ifdef SCULL_SEQ_FILE 
/* 步骤二:2、定义proc文件所关联的文件操作数据 */ 
static struct file_operations scull_proc_ops = {
 
 
        .owner  = THIS_MODULE,  
 
        .open    = scull_proc_open, 
 //open通常这是我们唯一需要重新定义的函数,需要和特定的seq_file关联起来。 
        .read     = seq_read,               //采用系统的处理方式 
        .llseek   = seq_lseek,              //采用系统的处理方式 
        .release = seq_release,         //采用系统的处理方式 
};

/* 步骤二:4、在前面的步骤二1~3中我们创建了proc文件,关联了proc文件和file_operations,并进一步关联了seq_file,这里我们具体定义被关联的seq_file */ 
static struct seq_operations scull_seq_ops = { 
        .start         
 = scull_seq_start, 
        .next         
 = scull_seq_next, 
        .stop         
 = scull_seq_stop, 
        .show        
 = scull_seq_show, 
};

static struct proc_dir_entry * entry; 
#endif 
… 
 
static int __init scull_init(void) 

… 
 
#ifdef SCULL_SEQ_FILE 
       
 /* 步骤一:创建proc文件*/ 
       
 entry = create_proc_entry ("scullseq", 0 ,NULL); 
       
 /* 步骤二:1、将proc文件和对应的文件操作关联起来*/ 
       
 if(entry) 
               
 entry -> proc_fops = & scull_proc_ops ; 
#endif 

... ...
 

static void __exit scull_exit(void) 

#ifdef SCULL_SEQ_FILE
 
/* 我们在模块中创建的proc文件,都应该模块cleanup模块的时候删除,以防影响系统,另外,我们应该在删除模块函数的开始执行这个操作,防止相关联的数据已经删除或者注销后再来处理,避免异常出现。 */ 
       
 remove_proc_entry("scullseq",NULL ); 
#endif 
        if(is_get_dev < 0){ 
               
 return ; 
       
 }else{ 
               
 int i = 0; 
               
 for(i = 0; i < SCULL_DEV_NUM; i ++){ 
                       
 scull_trim(&mydev[i]); 
                       
 cdev_del( & mydev[i].cdev ); 
               
 } 
               
 unregister_chrdev_region(dev,SCULL_DEV_NUM); 
               
 WDEBUG(WEI_KERN_NOTICE,"Scull module exit/n"); 
       
 } 
}

/* 步骤二:3、具体实现proc文件的open操作,目的与seq_file相关联。*/ 
int scull_proc_open(struct inode * inode , struct file * file) 

       
 return seq_open(file, & scull_seq_ops ); 
}

步骤三:处理seq_file操作过程。

  seq_file操作定义了四个操作,格式如下:

void * start(struct seq_file * s, loff_t * v); 
void * next (struct seq_file * s, void * v, loff_t * pos); 
void
  
 stop (struct seq_file * s, void * v); 
int
    show (struct seq_file * s, void * v);

  其中loff_t表示位置,这是由我们自己程序控制的,初始为0,在scull中我们依次读取scull0-3,因此使用该偏移量来表示我们所读取的设备的序号。

  我们利用void * v来记录设备的入口位置。start根据编译量,即我们的设备的序号,返回scullx的入口位置,无论下一操作是nextstop,还是show,这个返回值会作为参数void *v输入。next表示下一查询,和start相似,只是多了void * v的输入,同样它的返回值也作为下一操作的参数void *v输入。show用于通过/proc文件输出。stop表示一次读取的结束。虽然在seq_file中和read_proc不一样,不需要考虑每次可以输出的buff的大小,但是实际读取不会连续一片很大的数据输出,在例子后面,我们将讨论这些操作的执行。

  输出方式非常简单,一般可以使用seq_printf,另外还有seq_putcseq_putsseq_escape。例子如下:

#ifdef SCULL_SEQ_FILE 
void * scull_seq_start (struct seq_file * s, loff_t * pos) 

       
 printk("==scull_seq_start() enter %p %p %lli/n", s , pos , * pos); 
       
 if( * pos >= SCULL_DEV_NUM) 
               
 return NULL; 
       
 else 
               
 return mydev + * pos; 
}

void * scull_seq_next (struct seq_file * s, void * v, loff_t * pos) 

        printk("==scull_seq_next() enter %p %p %p %lli/n", s , v, pos , * pos); 

        (* pos ) ++; 
       
 return scull_seq_start(s, pos); 
}

void scull_seq_stop (struct seq_file * s, void * v) 

       printk("==scull_seq_stop() enter/n");
 
       
 return ; 
}

int scull_seq_show (struct seq_file * s, void * v) 

       
 struct scull_dev * dev = (struct scull_dev *) v; 
       
 struct scull_qset * qs = NULL; 
       
 int j = 0; 
       printk("==scull_seq_show() enter/n");

        if(down_interruptible(&dev->sem)) 
               
 return -ERESTARTSYS; 

       
 seq_printf (s, "/n Device Scull%d: qset %i, q %i sz %li/n", (int) (dev - mydev),dev->qset, dev->quantum, dev->size); 
        printk("/n Device Scull%d: qset %i, q %i sz %li/n", (int) (dev - mydev),dev->qset, dev->quantum, dev->size);

        for(qs = dev->data ; qs ; qs=qs->next){ 
                seq_printf ( s,"  item at %p, qset at %p/n", qs, 
 qs->data); 
                printk("  item at %p, qset at %p/n", qs,  qs->data);
 
               
 if(qs->data ){ 
                       
 for(; j < dev->qset /* && qs->data[ j ] */ ;j++){ 
                               seq_printf (s ,"/t%4i:%8p/n", 
 j,qs->data[ j ]); 
                                printk("/t%4i:%8p/n",  j,qs->data[ j ]);
 
                       
 } 
               
 } 
       
 } 

       
 up(&dev->sem); 
       
 return 0; 
}

  我们描述一下处理的过程:当我们读取proc文件,例如cat /proc/scullseq时,我们假设scull0和scull1都有较多信息输入。

  一开始调用start,偏移量为0,返回scull0的入口,接着调用show,scull0的入口作为参数输入,在show中,我们可以遍历 scull0的数据结构,通过seq_printf输出。完成show后,由于输出信息多,进入stop,在例子中stop没有实际操作,我们只是用来跟踪处理的流程。

   再次调用start,偏移量步进1,即1,返回scull0的入口,接着调用show,scull1的入口作为参数输入,在show中,我们可以遍历scull1的数据结构,通过seq_printf输出。完成show后,由于输出信息多,进入stop。

  再次调用start,偏移量步进1,即2,返回scull2的入口,接着调用show,scull2的入口作为参数输入,在show中,我们可以遍历scull2的数据结构,通过seq_printf输出。完成show后,由于输出信息非常少,kernel认为可以继续进行操作,而不需要 stop,调用next(),在next参数中输入的参数loff_t2next将其加一,为3,返回scull3的入口。接着调用 showscull3的入口作为参数输入,在show中,我们可以遍历scull3的数据结构,通过seq_printf输出。完成show后,由于输出信息非常少,kernel认为可以继续进行操作,而不需要stop,调用next()。由于已经全部信息返回,在next中发现没有数据,返回 NULL。系统再次调用start,返回NULL,系统调用stop,结束这次输出。

  再次调用start,返回NULL,表示已经没有数据输出,调用stop,结束所有的输出。

  值得注意 seq_file 代码在调用 start  stop 之间不睡眠或者进行其他非原子性任务你也肯定会看到在调用 start 后马上有一个 stop 调用因此对你的 start 方法来说请求信号量或自旋锁是安全的只要你的其他 seq_file 方法是原子的,调用的整个序列是原子的.

  本文也即《Linux Device Drivers》,LDD3的第四章Debuging Techniques的读书笔记之三,但我们不限于此内容。
阅读(281) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~