总结自linux下的seq_file.txt的document。
虚拟文件系统如procfs,debugfs等在建立虚拟文件的时候,都要设计到虚拟文件的操作。
使用虚拟文件的时候比较挑战的一点在于,当虚拟文件相当大的时候,操作虚拟文件需要一些技巧,在多次读写的时候要小心的操作文件的position,当虚拟文件的使用越来越广泛的时候,这样的操作代码也越来越多,因此在2.6内核中加入了一组函数来方便实现虚拟文件的操作,这就是seq_file。
seq_file声明在
希望使用seq_file接口的模块都必须要要实现iterator对象来遍历虚拟文件的数据。
iterator接口包含4个函数需要实现用于遍历:
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接口接受一个pos作为起始位置,并且返回一个iterator指针。对于一个顺序访问的虚拟文件来说,最简单的start函数可能如下:
static void *ct_seq_start(struct seq_file *s, loff_t *pos)
{
loff_t *spos = kmalloc(sizeof(loff_t), GFP_KERNEL);
if (! spos)
return NULL;
*spos = *pos;
return spos;
}
对于顺序访问的文件来说,只需要一个loff_t来记录虚拟文件的当前访问位置即可,这个loff_t就是iterator对象。
对于复杂的应用场景,seq_file中的private可以用来记录信息。
当start的pos为0的时候,start函数可以有一个特殊的返回值SEQ_START_TOKEN,这个值从来让show接口了解到当前正在起始位置从而输出header信息。
next接口用于将虚拟文件位置移动pos个位置,参数v就是start接口中返回的iterator。
next接口会返回更新后的iterator,或者在没有数据的时候返回NULL。
static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
loff_t *spos = v;
*pos = ++*spos;
return spos;
}
stop接口是做iterator对象的cleanup动作,主要就是释放在start中申请的iterator对象。
static void ct_seq_stop(struct seq_file *s, void *v)
{
kfree(v);
}
show函数,将iterator指向的位置的对象进行格式化输出。
static int ct_seq_show(struct seq_file *s, void *v)
{
loff_t *spos = v;
seq_printf(s, "%lld\n", (long long)*spos);
return 0;
}
show函数正常都应该返回0,负数是表明发生了某些错误。
show函数可以返回SEQ_SKIP,表明当前的item会被skip,如果之前show已经产生过输出,那么这整个输出都会被丢弃掉。
seq_file在设计的时候,start()和stop()之间是不会sleep的,所以在start和stop之间持有一个锁是很合理的,除此之外,不会持有其他的锁。
seq_file的主要功能就是管理虚拟文件的位置,最终将输出传递给用户用户。内核中提供了一组函数,用来帮助输出内容。
int seq_printf(struct seq_file *m, const char *f, ...)
最常用的输出函数是seq_printf,函数和printk类似,输出内容保存在seq_file的buffer中。
int seq_putc(struct seq_file *m, char c);
int seq_puts(struct seq_file *m, const char *s);
int seq_escape(struct seq_file *m, const char *s, const char *esc);
seq_putc和seq_puts很简单,就是将一个字符/字符串输入到seq_file中去。
seq_escape是在seq_puts的基础上,将esc字符串中所包含的字符都转为用8进制的显示。
在虚拟文件中使用seq_file,需要实现一组file_operation,seq_file也提供给了一组可用的函数。
int seq_open(struct file *file, const struct seq_operations *op);
seq_open中会使用seq_operations建立seq_file对象,并将其赋值给到file->private_data。seq_open常用来实现open操作,下面就是一个最简单的示例:
static int ct_open(struct inode *inode, struct file *file)
{
return seq_open(file, &ct_seq_ops);
}
对于read,lseek,release操作,seq_file中已经提供直接可用的函数:
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos);
loff_t seq_lseek(struct file *file, loff_t offset, int whence);
int seq_release(struct inode *inode, struct file *file);
简单的例子如下:
static const struct file_operations ct_file_ops = {
.owner = THIS_MODULE,
.open = ct_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
对于极端简单的例子,可以使用一个早期的接口single_open,
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
void *data)
single_open完成的工作和seq_open类似,只是不需要编写完整的seq_operation,只需要实现其中的show接口即可,其他接口会有默认的实现
和single_open配套的release函数是single_release
int single_release(struct inode *inode, struct file *file);
如果虚拟文件被组织成list的形式,那么下面的seq_list的函数会比较有用,方便实现对应的seq_operation操作。
struct list_head *seq_list_start(struct list_head *head,
loff_t pos);
struct list_head *seq_list_start_head(struct list_head *head,
loff_t pos);
struct list_head *seq_list_next(void *v, struct list_head *head,
loff_t *ppos);