Kernel之seq_file接口

背景

最近在看/proc/cpuinfo的内核实现时,发现了一个以前没注意的东东: seq_file接口

seq_file(Sequence file:序列文件)接口
内核文档: Documentation/filesystems/seq_file.txt

There are numerous ways for a device driver (or other kernel component) to
provide information to the user or system administrator. One useful
technique is the creation of virtual files, in debugfs, /proc or elsewhere.
Virtual files can provide human-readable output that is easy to get at
without any special utility programs; they can also make life easier for
script writers. It is not surprising that the use of virtual files has
grown over the years.

Creating those files correctly has always been a bit of a challenge,
however. It is not that hard to make a virtual file which returns a
string. But life gets trickier if the output is long - anything greater
than an application is likely to read in a single operation. Handling
multiple reads (and seeks) requires careful attention to the reader’s
position within the virtual file - that position is, likely as not, in the
middle of a line of output. The kernel has traditionally had a number of
implementations that got this wrong.

主要是由于proc等虚拟文件系统在处理大一点的数据时有比较大的局限性,很容易出错导致bug,所以seq_file接口就出来了,它提供了更加友好的接口,来方便程序员操作

简单例子

#include <linux/init.h>  
#include <linux/kernel.h>  
#include <linux/module.h>  

#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

#define VERSION "0.1"
#define MODULE_NAME ""

//#define USE_SEQ_OPEN
#define TEST_NODE_NAME "seq_test"

static int c_show(struct seq_file *m, void *v)
{
	seq_puts(m, "test start...\n");
	seq_printf(m, "test: %d\n", 0);
	seq_puts(m, "\n");
	return 0;
}
#ifdef USE_SEQ_OPEN
static void *c_start(struct seq_file *m, loff_t *pos)
{
	return *pos < 1 ? (void *)1 : NULL;
}

static void *c_next(struct seq_file *m, void *v, loff_t *pos)
{
	++*pos;
	return NULL;
}


static void c_stop(struct seq_file *m, void *v)
{
}

const struct seq_operations seq_ops = {
	.start	= c_start,
	.next	= c_next,
	.stop	= c_stop,
	.show	= c_show
};

static int test_open(struct inode *inode, struct file *file)
{
	printk("test_open:  seq_open()\n");
	return seq_open(file, &seq_ops);
}

#else
static int test_open(struct inode *inode, struct file *file)
{
	printk("test_open:  single_open()\n");
	return single_open(file, c_show, inode->i_private);
}
#endif

static ssize_t test_write(struct file *file, const char __user *buffer,
                size_t count, loff_t *pos)
{
	printk("test_write: count=%d\n",count);
	return count;
}

static const struct file_operations proc_seq_test_ops = {
	.open	= test_open,
	.read	= seq_read,
    .write = test_write,
	.llseek	= seq_lseek,
#ifdef USE_SEQ_OPEN
	.release = seq_release, 
#else
	.release = single_release, 
#endif
};

/*模块加载函数,通过insmod命令加载模块时,被自动执行*/
static int __init seq_test_init(void)   
{  
	printk("init module...  \n");
  
	proc_create(TEST_NODE_NAME, 0, NULL, &proc_seq_test_ops);

	return 0;  
}  

/*模块卸载函数,当通过rmmod命令卸载时,会被自动执行*/
static void __exit seq_test_exit(void)   
{  
	printk("exit modules... \n");  
	remove_proc_entry(TEST_NODE_NAME, NULL); 
}  

module_init(seq_test_init);   /*声明初始化函数*/
module_exit(seq_test_exit);    /*声明卸载函数*/

MODULE_AUTHOR("zdd <xxx@xxx.cn>");           /*模块作者,可选*/
MODULE_LICENSE("GPL");     /*模块许可,描述内核模块的许可权限,必须*/
MODULE_VERSION(VERSION);   /*模块版本,可选*/

参考