zoukankan      html  css  js  c++  java
  • 【转载】Linux内核实践之序列文件

    作者:bullbat

         

            seq_file机制提供了标准的例程,使得顺序文件的处理好不费力。小的文件系统中的文件,通常用户层是从头到尾读取的,其内容可能是遍历一些数据项创建的。Seq_file机制容许用最小代价实现此类文件,无论名称如何,但顺序文件是可以进行定为操作的,但其实现不怎么高效。顺序访问,即逐个访问读取数据项,显然是首选的访问模式。某个方面具有优势,通常会在其他方面付出代价。

           下面我们一步一步来看看怎么编写序列文件的处理程序。对于文件、设备相关驱动程序(其实设备也是文件)的操作,我们都知道需要提供一个struct file_operations的实例。对于这里序列文件的操作,内核中附加提供了一个struct seq_operations结构,该结构很简单:

    1. struct seq_operations {  
    2.     void * (*start) (struct seq_file *m, loff_t *pos);  
    3.     void (*stop) (struct seq_file *m, void *v);  
    4.     void * (*next) (struct seq_file *m, void *v, loff_t *pos);  
    5.     int (*show) (struct seq_file *m, void *v);  
    6. };  

    start():          主要实现初始化工作,在遍历一个链接对象开始时,调用。返回一个链接对象的偏移或SEQ_START_TOKEN(表征这是所有循环的开始)。出错返回ERR_PTR。 stop():         当所有链接对象遍历结束时调用。主要完成一些清理工作。 next():        用来在遍历中寻找下一个链接对象。返回下一个链接对象或者NULL(遍历结束)。 show():         对遍历对象进行操作的函数。主要是调用seq_printf(), seq_puts()之类的函数,打印出这个对象节点的信息。

            由于c语言中任何数据类型的数据块都可以转化为数据块的内存基址(指针)+数据块大小来传递,不难想到基于我们上面提供的函数,将我们操作的数据用于序列文件的读写、定为、释放等操作完全可以通用话。内核也为我们提供了这些用于读写、定位、释放等操作的通用函数。当然这些操作需要数据结构的支持(比如读取当前位置、数据大小等等),这就是在后面我们会看到的struct seq_file结构。由于我们读写的是文件,在内核中必须提供一个struct file_operations结构的实例,我们可以直接用内核为我们提供的上述函数,并且重写file_operatios结构的open方法,用该方法将虚拟文件系统关联到我们处理的序列文件,那么那些通用的读写函数就可以正常工作了。原理基本上是这样的,下面我们看怎么用file_operatios结构的open方法将我们的序列文件关联到虚拟文件系统。在此之前,我们看看序列文件的表示结构struct seq_file:

    1. struct seq_file {  
    2.     char *buf;  
    3.     size_t size;  
    4.     size_t from;  
    5.     size_t count;  
    6.     loff_t index;  
    7.     loff_t read_pos;  
    8.     u64 version;  
    9.     struct mutex lock;  
    10.     const struct seq_operations *op;  
    11.     void *private;  
    12. };  

            Buf指向一个内存缓冲区,用于构建传输给用户层的数据。Count指定了需要传输到用户层的剩余的字节数。复制操作的起始位置由from指定,而size给出了缓冲区总的字节数。Index是缓冲区的另一个索引。他标记了内核向缓冲区写入下一个新纪录的起始位置。要注意的是,index和from的演变过程是不同的,因为从内核向缓冲区写入数据,与将这些数据复制到用户空间,这两种操作是不同的。

    一般情况,对于序列文件,我们的文件操作实例如下:

    1. static struct file_operations my_operations={  
    2.     .open   =my_open,  
    3.     .read   =seq_read,  
    4.     .llseek =seq_lseek,  
    5.     .release    =seq_release,  
    6. };  

            其中,my_open函数需要我们重写的,也是我们将其用于关联我们的序列文件。其他都是内核为我们实现好的,在后面我们会详细介绍。

    1. static int my_open(struct inode *inode,struct file *filp)  
    2. {  
    3.     return seq_open(filp,&my_seq_operations);  
    4. }  

    我们这里调用seq_open函数建立这种关联。

    1. int seq_open(struct file *file, const struct seq_operations *op)  
    2. {  
    3.     struct seq_file *p = file->private_data;/*p为seq_file结构实例*/  
    4.   
    5.     if (!p) {  
    6.         p = kmalloc(sizeof(*p), GFP_KERNEL);  
    7.         if (!p)  
    8.             return -ENOMEM;  
    9.         file->private_data = p;/*放到file的private_data中*/  
    10.     }  
    11.     memset(p, 0, sizeof(*p));  
    12.     mutex_init(&p->lock);  
    13.     p->op = op;/*设置seq_file的operation为op*/  
    14.   
    15.     /* 
    16.      * Wrappers around seq_open(e.g. swaps_open) need to be 
    17.      * aware of this. If they set f_version themselves, they 
    18.      * should call seq_open first and then set f_version. 
    19.      */  
    20.     file->f_version = 0;  
    21.   
    22.     /* 
    23.      * seq_files support lseek() and pread().  They do not implement 
    24.      * write() at all, but we clear FMODE_PWRITE here for historical 
    25.      * reasons. 
    26.      * 
    27.      * If a client of seq_files a) implements file.write() and b) wishes to 
    28.      * support pwrite() then that client will need to implement its own 
    29.      * file.open() which calls seq_open() and then sets FMODE_PWRITE. 
    30.      */  
    31.     file->f_mode &= ~FMODE_PWRITE;  
    32.     return 0;  
    33. }  

          可以看到,我们的seq_file结构以file的私有数据字段传入虚拟文件系统,同时在open函数中设置了seq_file的操作实例。

    我们看下面这个简单的例子:

    1. #include <linux/init.h>  
    2. #include <linux/module.h>  
    3. #include <linux/kernel.h>  
    4. #include <linux/proc_fs.h>  
    5. #include <linux/seq_file.h>  
    6.   
    7.   
    8. #define MAX_SIZE 10  
    9.   
    10.   
    11. MODULE_LICENSE("GPL");  
    12. MODULE_AUTHOR("Mike Feng");  
    13.   
    14. /*用于操作的数据*/  
    15. struct my_data  
    16. {  
    17.     int data;  
    18. };  
    19.   
    20. /*全局变量*/  
    21. struct my_data *md;  
    22.   
    23. /*数据的申请*/  
    24. struct my_data* my_data_init(void)  
    25. {  
    26.     int i;  
    27.     md=(struct my_data*)kmalloc(MAX_SIZE*sizeof(struct my_data),GFP_KERNEL);  
    28.   
    29.     for(i=0;i<MAX_SIZE;i++)  
    30.         (md+i)->data=i;  
    31.   
    32.     return md;  
    33. }  
    34.   
    35. /*seq的start函数,仅仅做越界判断然后返回pos*/  
    36. void *my_seq_start(struct seq_file *file,loff_t *pos)  
    37. {  
    38.     return (*pos<MAX_SIZE)? pos :NULL;  
    39. }  
    40.   
    41. /*seq的next函数,仅仅做越界判断然后pos递增*/  
    42. void *my_seq_next(struct seq_file *p,void *v,loff_t *pos)  
    43. {  
    44.     (*pos)++;  
    45.     if(*pos>=MAX_SIZE)  
    46.         return NULL;  
    47.   
    48.     return pos;  
    49. }  
    50.   
    51. /*seq的show函数,读数据的显示*/  
    52. int my_seq_show(struct seq_file *file,void *v)  
    53. {  
    54.     unsigned int i=*(loff_t*)v;  
    55.     seq_printf(file,"The %d data is:%d ",i,(md+i)->data);  
    56.       
    57.     return 0;  
    58. }  
    59.   
    60. /*seq的stop函数,什么也不做*/  
    61. void my_seq_stop(struct seq_file *file,void *v)  
    62. {  
    63.   
    64. }  
    65.   
    66.   
    67. /*operations of seq_file */  
    68. static const struct seq_operations my_seq_ops={  
    69.     .start  =my_seq_start,  
    70.     .next   =my_seq_next,  
    71.     .stop   =my_seq_stop,  
    72.     .show   =my_seq_show,  
    73. };  
    74.   
    75. /*file的open函数,用于seq文件与虚拟文件联系*/  
    76. static int my_open(struct inode *inode,struct file *filp)  
    77. {  
    78.     return seq_open(filp,&my_seq_ops);  
    79. }  
    80.   
    81. /*file操作*/  
    82. static const struct file_operations my_file_ops={  
    83.     .open   =my_open,  
    84.     .read   =seq_read,  
    85.     .llseek =seq_lseek,  
    86.     .release=seq_release,  
    87.     .owner  =THIS_MODULE,  
    88. };  
    89.   
    90. static __init int my_seq_init(void)  
    91. {  
    92.     struct proc_dir_entry *p;  
    93.     my_data_init();  
    94.     p=create_proc_entry("my_seq",0,NULL);  
    95.     if(p)  
    96.     {  
    97.         p->proc_fops=&my_file_ops;  
    98.     }  
    99.   
    100.     return 0;  
    101.       
    102. }  
    103.   
    104. static void my_seq_exit(void)  
    105. {  
    106.     remove_proc_entry("my_seq",NULL);  
    107. }  
    108.   
    109. module_init(my_seq_init);  
    110. module_exit(my_seq_exit);  

    实验与结果:

           你可能会好奇,上面的结果是怎么得到的。当我们用命令cat /proc/my_seq时,即是读取文件/proc/my_seq,而在我们的程序中,my_seq文件绑定到了我们给定的文件操作(p->proc_fops=&my_file_ops;)。那么很自然想到,他是调用my_file_ops中的.read函数,即seq_read函数,我们看看这个函数在内核中是怎么实现的(<fs/seq_file.c>)。

    ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)

    {

           struct seq_file *m = (struct seq_file *)file->private_data;

           ……

           /* we need at least one record in buffer */

           pos = m->index;

           p = m->op->start(m, &pos);

           while (1) {

                  err = PTR_ERR(p);

                  if (!p || IS_ERR(p))

                         break;

                  err = m->op->show(m, p);

                  if (err < 0)

                         break;

                  if (unlikely(err))

                         m->count = 0;

                  if (unlikely(!m->count)) {

                         p = m->op->next(m, p, &pos);

                         m->index = pos;

                         continue;

                  }

                  if (m->count < m->size)

                         goto Fill;

                  m->op->stop(m, p);

                  kfree(m->buf);

                  m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);

                  if (!m->buf)

                         goto Enomem;

                  m->count = 0;

                  m->version = 0;

                  pos = m->index;

                  p = m->op->start(m, &pos);

           }

           m->op->stop(m, p);

           m->count = 0;

           goto Done;

    ……

    }

           该函数代码比较长,我们只看while循环部分,也即循环打印的过程,我们从红色代码部分可以看出程序循环调用seq_file操作的start、show、next、stop函数,直到读完数据。而start返回的值传入了next和stop函数(就是我们的序列文件读指针索引,在next中为void*类型)。

           除了上面的描述,内核还为我们提供了一系列辅助函数,比如single_open函数只需要我们重写show函数即可,需要用的话可以查看相关的代码,了解其定义。这里,我们看看对于内核链表组织的数据seq_file是怎么使用的。

    程序文件(list_seq.c):

    1. #include <linux/init.h>  
    2. #include <linux/module.h>  
    3. #include <linux/kernel.h>  
    4. #include <linux/mutex.h>  
    5. #include <linux/proc_fs.h>  
    6. #include <linux/seq_file.h>  
    7.   
    8. #define N 10  
    9.   
    10. MODULE_LICENSE("GPL");  
    11. MODULE_AUTHOR("Mike Feng");  
    12.   
    13. /*对内核链表操作需要加锁*/  
    14. static struct mutex lock;  
    15.   
    16. static struct list_head head;  
    17.   
    18. struct my_data  
    19. {  
    20.     struct list_head list;  
    21.     int value;  
    22. };  
    23.   
    24. /*链表的插入元素*/  
    25. struct list_head* insert_list(struct list_head *head,int value)  
    26. {  
    27.     struct my_data *md=NULL;  
    28.     mutex_lock(&lock);  
    29.     md=(struct my_data*)kmalloc(sizeof(struct my_data),GFP_KERNEL);  
    30.     if(md)  
    31.     {  
    32.         md->value=value;  
    33.         list_add(&md->list,head);  
    34.           
    35.     }  
    36.     mutex_unlock(&lock);  
    37.   
    38.     return head;  
    39. }  
    40. /*打印,传入参数v为open函数返回的,链表需要操作的节点*/  
    41. static int list_seq_show(struct seq_file *file,void *v)  
    42. {  
    43.     struct list_head *list=(struct list_head*)v;  
    44.   
    45.     struct my_data *md=list_entry(list,struct my_data,list);  
    46.   
    47.     seq_printf(file,"The value of my data is:%d ",md->value);  
    48.   
    49.     return 0;  
    50. }  
    51. static void *list_seq_start(struct seq_file *file,loff_t *pos)  
    52. {  
    53.     /*加锁*/  
    54.     mutex_lock(&lock);  
    55.     return seq_list_start(&head,*pos);  
    56. }  
    57.   
    58. static void *list_seq_next(struct seq_file *file,void *v,loff_t *pos)  
    59. {  
    60.     return seq_list_next(v,&head,pos);  
    61. }  
    62. static void list_seq_stop(struct seq_file *file,void *v)  
    63. {  
    64.     /*解锁*/  
    65.     mutex_unlock(&lock);  
    66. }  
    67. static struct seq_operations list_seq_ops=  
    68. {  
    69.     .start  =list_seq_start,  
    70.     .next   =list_seq_next,  
    71.     .stop   =list_seq_stop,  
    72.     .show   =list_seq_show,  
    73. };  
    74.   
    75. static int list_seq_open(struct inode *inode,struct file *file)  
    76. {  
    77.     return seq_open(file,&list_seq_ops);  
    78. }  
    79.   
    80. static struct file_operations my_file_ops=  
    81. {  
    82.     .open   =list_seq_open,  
    83.     .read   =seq_read,  
    84.     .write  =seq_write,  
    85.     .llseek =seq_lseek,  
    86.     .release=seq_release,  
    87.     .owner  =THIS_MODULE,  
    88. };  
    89.   
    90. static __init int list_seq_init(void)  
    91. {  
    92.     struct proc_dir_entry *entry;  
    93.     int i;  
    94.     mutex_init(&lock);  
    95.     INIT_LIST_HEAD(&head);  
    96.   
    97.     for(i=0;i<N;i++)  
    98.         head=*(insert_list(&head,i));  
    99.   
    100.     entry=create_proc_entry("list_seq",0,NULL);  
    101.     if(entry)  
    102.         entry->proc_fops=&my_file_ops;  
    103.     return 0;  
    104. }  
    105.   
    106. static void list_seq_exit(void)  
    107. {  
    108.     struct my_data *md=NULL;  
    109.     remove_proc_entry("list_seq",NULL);  
    110.   
    111.     while(!list_empty(&head))  
    112.     {  
    113.         md=list_entry((&head)->next,struct my_data,list);  
    114.         list_del(&md->list);  
    115.         kfree(md);  
    116.     }  
    117.       
    118. }  
    119.   
    120. module_init(list_seq_init);  
    121. module_exit(list_seq_exit);  

    测试试验结果:

    由于内核函数list_add为前插,所以打出的数据为倒序的。

    序列文件的实现基于proc文件系统,下一步将对其进行分析学习。

     
    1
  • 相关阅读:
    对象属性编辑器中实现像Size,Rectangle等可以展开的属性
    远程办公产品风口会不会把SOHO自由职业吹起来
    项目加
    推荐几款免费又好用的项目管理工具
    Sprint Retrospective
    敏捷管理的大概背景和Scrum的特性
    推荐几款最好用的项目管理系统,即好用又免费
    项目管理的需求变更问题
    敏捷管理有一个原则就是:拥抱变化
    推荐5款体验最好的项目管理工具
  • 原文地址:https://www.cnblogs.com/cxt-janson/p/7094327.html
Copyright © 2011-2022 走看看