接口API
seq_file
系列函数是为了方便内核导出信息到 sysfs
、debugfs
、procfs
实现的。以往的内核也存在各种形式的实现,但是都无法避免个别实现会产生一些漏洞,这一套函数实现可以让内核导出信息更加简单和统一稳定,维护起来更加方便。
它包含了如下一些接口:
int seq_open(struct file *file, const struct seq_operations *op);
int seq_release(struct inode *inode, struct file *file);
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos);
int seq_write(struct seq_file *seq, const void *data, size_t len);
loff_t seq_lseek(struct file *file, loff_t offset, int whence);
如何实现这些函数呢?举个例子,一般我们需要创建一个 file_operations
结构体去实现不同的 debugfs
、 procfs
。而以上函数就是用于快速实现这个操作结构体的。比如:
static const struct file_operations ct_file_ops = {
.owner = THIS_MODULE,
.open = ct_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
seq_open 操作
本节将介绍打开操作,主要功能是创建并初始化 seq_file
结构体,设置 seq_operations
操作回调。如下图所示:
- 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);
};
该结构体是一系列回调函数,用于 seq_file
系列操作内部使用。
seq_read 操作
seq_read
操作是系列函数中最为重要的一个函数,它负责从内部buffer缓冲区中读取数据并 copy_to_user
返回给应用层。阅读代码会发现该函数实现如下操作:
- 创建buffer,默认大小为1个page
- 调用seq_operations中的
start()
函数 - 调用seq_operations中的
show()
函数,填充数据到buffer中 - 调用
next()
函数,检查是否需要继续写入,如果有则循环执行上面的步骤 - 判断buffer大小是否够用,如果不够,则释放buffer,重新申请一个更大的buffer(前面的2倍),用于数据写入
- 循环执行以上步骤,如果不用继续写入,并且buffer没有溢出,那么执行
copy_to_user()
拷贝buffer中的数据到用户空间
所以对于read操作,最关键的是实现那一系列的 seq_operations
回调函数,不用维护缓冲区。
seq_write 操作
seq_write
是用于写入数据到内部的buffer缓冲区使用的函数,但是千万不要被它的名字所迷惑,这个操作并不是从应用层接收写入数据的。按照 seq_read
的逆过程来理解,它应该从应用层接收数据并写入到内部buffer才对。但实际并非如此,这个函数实现极其简单,它只负责写入数据到内部buffer,该函数不涉及内存分配,buffer是由 seq_read
在读取时分配的内存,因此该函数一般在实现 show()
函数中使用,比如:在 show
函数中把驱动数据通过 seq_write
写入到buffer中。
这样数据写入操作的调用栈为: seq_read
--> show
--> seq_write
seq_printf 操作
和 seq_write
类似, seq_printf
函数主要区别在于格式化输出数据到buffer中,使用的场景也与 seq_write
一样,一般在实现 show
函数中使用。
single_open 操作
为了更进一步简化内核接口的实现,封装了该函数,这样我们不用实现整个 seq_operations
结构体,驱动开发者只需要传入 show
即可,其他回调都使用内核实现的一个简单版本,它的代码如下所示:
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
void *data)
{
struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
int res = -ENOMEM;
if (op) {
op->start = single_start;
op->next = single_next;
op->stop = single_stop;
op->show = show;
res = seq_open(file, op);
if (!res)
((struct seq_file *)file->private_data)->private = data;
else
kfree(op);
}
return res;
}
seqfile的优点
简化了文件操作的实现,内部维护缓冲区,内部维护缓冲区的当前位置,不用驱动开发者自己实现缓冲区操作,对于驱动开发人员只需要实现相关的数据操作函数回调即可。在 procfs
、 sysfs
、 debugfs
接口中被广泛的应用。
欢迎扫码关注我的公众号!