seq_file机制提供了标准的例程,使得顺序文件的处理好不费力。小的文件系统中的文件,通常用户层是从头到尾读取的,其内容可能是遍历一些数据项创建的。Seq_file机制容许用最小代价实现此类文件,无论名称如何,但顺序文件是可以进行定为操作的,但其实现不怎么高效。顺序访问,即逐个访问读取数据项,显然是首选的访问模式。某个方面具有优势,通常会在其他方面付出代价。
下面我们一步一步来看看怎么编写序列文件的处理程序。对于文件、设备相关驱动程序(其实设备也是文件)的操作,我们都知道需要提供一个struct
file_operations的实例。对于这里序列文件的操作,内核中附加提供了一个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():
主要实现初始化工作,在遍历一个链接对象开始时,调用。返回一个链接对象的偏移或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. };
指向一个内存缓冲区,用于构建传输给用户层的数据。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的操作实例。
我们看下面这个简单的例子:
[cpp] view plaincopyprint? 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\n",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\n",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文件系统,下一步将对其进行分析学习。