分类: LINUX
2013-04-19 16:09:48
用序列文件(seq_file)接口导出常用数据结构 | |
|
|
来源: ChinaUnix博客 日期: 2008.12.20 17:31 | |
/*讲的非常好,谢谢xiaosuo.*/ 内容简介本文主要讲述序列文件(seq_file)接口的内核实现,如何使用它将Linux内核里面常用的数据结构通过文件(主要关注 proc文件)导出到用户空间,最后定义了一些宏以便于编程,减少重复代码。在分析序列文件接口实现的过程中,还连带涉及到一些应用陷阱和避免手段。 序列文件接口UNIX 的世界里,文件是最普通的概念,所以用文件来作为内核和用户空间传递数据的接口也是再普通不过的事情,并且这样的接口对于shell也是相当友好的,方便 管理员通过shell直接管理系统。由于伪文件系统proc文件系统在处理大数据结构(大于一页的数据)方面有比较大的局限性,使得在那种情况下进行编程 特别别扭,很容易导致bug,所以序列文件接口被发明出来,它提供了更加友好的接口,以方便程序员。之所以选择序列文件接口这个名字,应该是因为它主要用 来导出一条条的记录数据。 为了能给大家一个具体形象的认识,我们首先来看一段用序列文件接口通过proc文件导出内核双向循环链接表的实例代码: #include linux/kernel.h> #include linux/module.h> #include linux/mutex.h> #include linux/proc_fs.h> #include linux/seq_file.h> static struct mutex lock; static struct list_head head; struct my_data { struct list_head list; int value; }; static void add_one(void) { struct my_data *data; mutex_lock(&lock); data = kmalloc(sizeof(*data), GFP_KERNEL); if (data != NULL) list_add(&data->list, &head); mutex_unlock(&lock); } static ssize_t _seq_write(struct file *file, const char __user * buffer, size_t count, loff_t *ppos) { add_one(); return count; } static int _seq_show(struct seq_file *m, void *p) { struct my_data *data = list_entry(p, struct my_data, list); seq_printf(m, "value: %d\n", data->value); return 0; } static void *_seq_start(struct seq_file *m, loff_t *pos) { mutex_lock(&lock); return seq_list_start(&head, *pos); } static void *_seq_next(struct seq_file *m, void *p, loff_t *pos) { return seq_list_next(p, &head, pos); } static void _seq_stop(struct seq_file *m, void *p) { mutex_unlock(&lock); } static struct seq_operations _seq_ops = { .start = _seq_start, .next = _seq_next, .stop = _seq_stop, .show = _seq_show }; static int _seq_open(struct inode *inode, struct file *file) { return seq_open(file, &_seq_ops); } static struct file_operations _seq_fops = { .open = _seq_open, .read = seq_read, .write = _seq_write, .llseek = seq_lseek, .release = seq_release }; static void clean_all(struct list_head *head) { struct my_data *data; while (!list_empty(head)) { data = list_entry(head->next, struct my_data, list); list_del(&data->list); kfree(data); } } static int __init init(void) { struct proc_dir_entry *entry; mutex_init(&lock); INIT_LIST_HEAD(&head); add_one(); add_one(); add_one(); entry = create_proc_entry("my_data", S_IWUSR | S_IRUGO, NULL); if (entry == NULL) { clean_all(&head); return -ENOMEM; } entry->proc_fops = &_seq_fops; return 0; } static void __exit fini(void) { remove_proc_entry("my_data", NULL); clean_all(&head); } module_init(init); module_exit(fini); 注:以上代码需要内核版本不小于2.6.23,小版本内核可以从2.6.23内核代码中抽取函数seq_list_start和seq_list_next。 上 面的这段程序创建了一个结构体my_data的双向循环链表(head),并且通过my_data proc文件将其导出到用户空间,用户空间的程序除了可以通过对my_data的读操作来获取my_data链表中各个结构体中的value域的值之外, 还能通过写(write)系统调用添加一个具有随机value值的my_data结构体到双向循环链表中(从链表的头部添加)。 gentux kernel # cat /proc/my_data value: 474615376 value: 474615632 value: 474615568 gentux kernel # echo 0 > /proc/my_data gentux kernel # cat /proc/my_data value: 758528120 value: 474615376 value: 474615632 value: 474615568 虽然,上面的那段代码行数不少,但是和输出相关的部分除了函数_seq_show外都是惯例代码,这种简便性是很直观的。相信对于双向循环链表数据结构的导出,读者们都能够照猫画虎写出自己的代码而毋须我再多言。 序列文件接口相关的两个重要数据结构为seq_file结构体和seq_operations表(表是延续ULK3的叫法,实际上就是只包含函数指针的结构体)。首先介绍seq_file结构体:
一些有用的全局函数:
序列文件接口的内核实现序列文件接口的实现比较简单易懂,出于完整性考虑,我姑且把核心函数seq_read拿来和读者一起解读: 果用户缓冲区仍有空间,则按序调用start和show,填补内核缓冲区。如果当前的内核缓冲区不足以容纳一条记录,那么将缓冲区大小加倍后再次尝试,直 到可以容纳至少一条记录。然后持续调用next和show,直到内核缓冲区无空间容纳新的整条记录后调用stop终止。可见,序列文件接口是面向整条记录的,但是它只能保证这些,无法保证导出的数据结构的整体一致性,这一点请多加留意,防止因为它导致bug。 因为序列文件的缓冲区有自动扩张的功能,所以它更便于导出大于一页的数据结构,这也正是single_open/release函数的用武之地。 内核常用数据结构的导出应用序列文件接口导出常用的面向记录的数据结构通常是非常简单的,下面我将给出内核常用的数据结构通过序列文件接口导出的思路。 数组数组通过其索引就能够随机地访问其中的任一元素,所以我们只要将start和next参数中的pos当成索引,返回对应元素的地址即可。 双向循环链表
红黑树因为Linux内核提供了按照类似链表的方式遍历红黑树的方法,所以导出它的方法和双向链接表类似。 序列文件接口的帮助宏实际上,应用序列文件接口进行编程,存在大量的重复代码,为了减少可恶的重复代码,也为了减少复制粘贴可能引入的bug,我写了这一套宏以简化大多数情况下的序列文件接口调用。具体宏代码如下: #ifndef _SEQ_FILE_HELPER_H_ #define _SEQ_FILE_HELPER_H_ #include linux/version.h> #include linux/list.h> #include linux/rbtree.h> #include linux/seq_file.h> #if LINUX_VERSION_CODE KERNEL_VERSION(2, 6, 23) static inline struct list_head *seq_list_start(struct list_head *head, loff_t pos) { struct list_head *lh; list_for_each(lh, head) if (pos-- == 0) return lh; return NULL; } static inline struct list_head *seq_list_next(void *v, struct list_head *head, loff_t *ppos) { struct list_head *lh; lh = ((struct list_head *)v)->next; ++*ppos; return lh == head ? NULL : lh; } #endif /* common macro */ #define __DEFINE_FOO_SEQ_FOPS(prefix, foo, imp_show, imp_write) \ static struct seq_operations _##foo##_seq_ops = { \ .start = _##foo##_seq_start, \ .next = _##foo##_seq_next, \ .stop = _##foo##_seq_stop, \ .show = imp_show \ }; \ \ static int _##foo##_seq_open(struct inode *inode, struct file *file) \ { \ return seq_open(file, &_##foo##_seq_ops); \ } \ \ prefix struct file_operations foo##_seq_fops = { \ .open = _##foo##_seq_open, \ .read = seq_read, \ .write = imp_write, \ .llseek = seq_lseek, \ .release = seq_release, \ .owner = THIS_MODULE }; /* helper for array */ #define _DEFINE_ARRAY_SEQ_FOPS(prefix, arr, n, lock, unlock, imp_show, \ imp_write) \ static void* _##arr##_seq_start(struct seq_file *m, loff_t *pos) \ { \ lock; \ if (*pos >= n) \ return NULL; \ return &arr[*pos]; \ } \ \ static void* _##arr##_seq_next(struct seq_file *m, void *p, loff_t *pos) \ { \ if (++*pos >= n) \ return NULL; \ return &arr[*pos]; \ } \ \ static void _##arr##_seq_stop(struct seq_file *m, void *p) \ { \ unlock; \ } \ \ __DEFINE_FOO_SEQ_FOPS(prefix, arr, imp_show, imp_write) #define DEFINE_ARRAY_SEQ_FOPS(arr, n, lock, unlock, imp_show, imp_write) \ _DEFINE_ARRAY_SEQ_FOPS(, arr, n, lock, unlock, imp_show, imp_write) #define DEFINE_STATIC_ARRAY_SEQ_FOPS(arr, n, lock, unlock, imp_show, \ imp_write) \ _DEFINE_ARRAY_SEQ_FOPS(static, arr, n, lock, unlock, imp_show, \ imp_write) /* helper for list */ #define _DEFINE_LIST_SEQ_FOPS(prefix, list, lock, unlock, imp_show, imp_write) \ static void* _##list##_seq_start(struct seq_file *m, loff_t *pos) \ { \ lock; \ return seq_list_start(&list, *pos); \ } \ \ static void* _##list##_seq_next(struct seq_file *m, void *p, loff_t *pos) \ { \ return seq_list_next(p, &list, pos); \ } \ \ static void _##list##_seq_stop(struct seq_file *m, void *p) \ { \ unlock; \ } \ \ __DEFINE_FOO_SEQ_FOPS(prefix, list, imp_show, imp_write) #define DEFINE_LIST_SEQ_FOPS(list, lock, unlock, imp_show, imp_write) \ _DEFINE_LIST_SEQ_FOPS(, list, lock, unlock, imp_show, imp_write) #define DEFINE_STATIC_LIST_SEQ_FOPS(list, lock, unlock, imp_show, imp_write) \ _DEFINE_LIST_SEQ_FOPS(static, list, lock, unlock, imp_show, imp_write) /* helper for hlist */ #ifndef hlist_for_each_continue #define hlist_for_each_continue(pos) \ for (pos = pos->next; pos && ({ prefetch(pos->next); 1; }); \ pos = pos->next) #endif #define _DEFINE_HLIST_SEQ_FOPS(prefix, head, n, lock, unlock, imp_show, \ imp_write) \ struct _##head##_seq_iter { \ unsigned int idx; \ struct hlist_node *node; \ }; \ \ static void* _##head##_seq_start(struct seq_file *m, loff_t *pos) \ { \ struct hlist_node *node; \ unsigned int idx; \ loff_t off = *pos; \ \ lock; \ for (idx = 0; idx n; idx++) { \ hlist_for_each(node, &head[idx]) { \ if (off-- == 0) { \ struct _##head##_seq_iter *iter; \ iter = kmalloc(sizeof(*iter), GFP_KERNEL); \ if (iter == NULL) \ break; \ iter->idx = idx; \ iter->node = node; \ return iter; \ } \ } \ } \ \ return NULL; \ } \ \ static void* _##head##_seq_next(struct seq_file *m, void *p, loff_t *pos) \ { \ struct _##head##_seq_iter *iter = (struct _##head##_seq_iter*)p; \ \ ++*pos; \ hlist_for_each_continue(iter->node) \ return iter; \ for (iter->idx++; iter->idx n; iter->idx++) { \ hlist_for_each(iter->node, &head[iter->idx]) \ return iter; \ } \ kfree(iter); \ \ return NULL; \ } \ \ static void _##head##_seq_stop(struct seq_file *m, void *p) \ { \ kfree(p); \ unlock; \ } \ \ static int _##head##_seq_show(struct seq_file *m, void *p) \ { \ struct _##head##_seq_iter *iter = (struct _##head##_seq_iter*)p; \ \ return imp_show(m, iter->node); \ } \ \ __DEFINE_FOO_SEQ_FOPS(prefix, head, _##head##_seq_show, imp_write) #define DEFINE_HLIST_SEQ_FOPS(head, n, lock, unlock, imp_show, imp_write) \ _DEFINE_HLIST_SEQ_FOPS(, head, n, lock, unlock, imp_show, imp_write) #define DEFINE_STATIC_HLIST_SEQ_FOPS(head, n, lock, unlock, imp_show, \ imp_write) \ _DEFINE_HLIST_SEQ_FOPS(static, head, n, lock, unlock, imp_show, \ imp_write) /* helper for rbtree */ #define _DEFINE_RBTREE_SEQ_FOPS(prefix, tree, lock, unlock, imp_show, \ imp_write) \ static void* _##tree##_seq_start(struct seq_file *m, loff_t *pos) \ { \ struct rb_node *node; \ loff_t off = *pos; \ \ lock; \ node = rb_first(&tree); \ while (off-- > 0 && node != NULL) \ node = rb_next(node); \ \ return node; \ } \ \ static void* _##tree##_seq_next(struct seq_file *m, void *p, loff_t *pos) \ { \ ++*pos; \ return rb_next((struct rb_node*)p); \ } \ \ static void _##tree##_seq_stop(struct seq_file *m, void *p) \ { \ unlock; \ } \ \ __DEFINE_FOO_SEQ_FOPS(prefix, tree, imp_show, imp_write) #define DEFINE_RBTREE_SEQ_FOPS(tree, lock, unlock, imp_show, imp_write) \ _DEFINE_RBTREE_SEQ_FOPS(, tree, lock, unlock, imp_show, imp_write) #define DEFINE_STATIC_RBTREE_SEQ_FOPS(tree, lock, unlock, imp_show, \ imp_write) \ _DEFINE_RBTREE_SEQ_FOPS(static, tree, lock, unlock, imp_show, \ imp_write) #endif /* _SEQ_FILE_HELPER_H_ */ 应用起来比较简单,下面是用其重写过的上面的双向连接表导出代码: #include linux/kernel.h> #include linux/module.h> #include linux/mutex.h> #include linux/proc_fs.h> #include "seq_file_helper.h" static struct mutex lock; static struct list_head head; struct my_data { struct list_head list; int value; }; static void add_one(void) { struct my_data *data; mutex_lock(&lock); data = kmalloc(sizeof(*data), GFP_KERNEL); if (data != NULL) list_add(&data->list, &head); mutex_unlock(&lock); } static ssize_t seq_write(struct file *file, const char __user * buffer, size_t count, loff_t *ppos) { add_one(); return count; } static int seq_show(struct seq_file *m, void *p) { struct my_data *data = list_entry(p, struct my_data, list); seq_printf(m, "value: %d\n", data->value); return 0; } DEFINE_STATIC_LIST_SEQ_FOPS(head, mutex_lock(&lock), mutex_unlock(&lock), seq_show, seq_write); static void clean_all(struct list_head *head) { struct my_data *data; while (!list_empty(head)) { data = list_entry(head->next, struct my_data, list); list_del(&data->list); kfree(data); } } static int __init init(void) { struct proc_dir_entry *entry; mutex_init(&lock); INIT_LIST_HEAD(&head); add_one(); add_one(); add_one(); entry = create_proc_entry("my_data", S_IWUSR | S_IRUGO, NULL); if (entry == NULL) { clean_all(&head); return -ENOMEM; } entry->proc_fops = &head_seq_fops; return 0; } static void __exit fini(void) { remove_proc_entry("my_data", NULL); clean_all(&head); } module_init(init); module_exit(fini); |