seq_file适用于内核需要向应用层输出信息时使用,最常见的用法是遍历内核中的一个list
数据结构输出list
的内容到应用层;当然也可以输出任意的数据,并且输出到应用层的数据大小没有限制,默认缓冲区是一个PAGE_SIZE
,当输出的数据大于PAGE_SIZE
时seq_file会把缓冲区大小翻倍,直到超过要输出的数据大小,或者把内存耗尽。
seq_file不能单独使用,需要配合procfs
或者sysfs
等文件系统使用,利用文件系统提供的file_operations
接口和应用层交互;seq_file本身也无法接收来自应用层的数据,同样需要使用file_operations
提供的接口接收应用层写入的数据。
下面以procfs为例介绍seq_file的用法
注: seq_开头的函数是seq_file
提供的接口, my_开头的函数是我们自己实现的函数
procfs接口
首先, 我们需要创建一个proc文件, 以便用户可以通过该文件read
内核的数据, 这里我们需要使用seq_file
提供的接口填充file_operations
结构, 以使用seq_file
的能力, 不过open
函数需要我们自己实现, 下文将提供示例
static const struct file_operations my_proc_ops = {
.open = my_seq_open, // 我们自己实现
.read = seq_read, // seq_file提供
.llseek = seq_lseek, // seq_file提供
.release = seq_release, // seq_file提供
};
// 调用proc_create在procfs中创建文件,以便应用层可以`read`内核数据
proc_create("/proc/my_seq", 0644, NULL, &my_proc_ops)
seq_file接口
这里我们来实现procfs
接口中file_operations
用到的my_seq_open
函数, 并在该函数中初始化seq_file系统. my_seq_open
的实现需要用到一个seq_operations
结构, 此结构用于对seq_file
的操作, 在应用层调用read
函数读取/proc/my_seq
时, seq_file
会调用seq_operations
结构中的相关函数指针从内核读取数据, 这些函数指针正是使用seq_file
的关键
static const struct seq_operations my_seq_ops = {
.start = my_seq_ops_start,
.next = my_seq_ops_next,
.stop = my_seq_ops_stop,
.show = my_seq_ops_show,
};
int my_seq_open(struct inode *inode, struct file *file)
{
seq_open(file, my_seq_ops)
}
下面我们以遍历输出内核中的task
为例, 来实现seq_operations
中的函数指针
// 开始输出任务列表
// my_seq_start()的返回值,会传递给my_seq_next()的v参数
static void *my_seq_start(struct seq_file *m, loff_t *pos)
{
loff_t index = *pos;
struct task_struct *task;
klogi(log_tag "my_seq_start m=%pK, pos=%d, index=%lld", m, (int)(*pos), m->index);
// 如果缓冲区不足, seq_file可能会重新调用start()函数,
// 并且传入的pos是之前已经遍历到的位置,
// 这里需要根据pos重新计算开始的位置
for_each_process(task){
if (index-- == 0){
return task;
}
}
return NULL;
}
// 继续遍历, 直到my_seq_next()放回NULL或者错误
static void *my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
struct task_struct *task = next_task((struct task_struct *)v);
klogi(log_tag "my_seq_next m=%pK, v=%pK, pos=%d, index=%lld, comm=[%s]", m, v, (int)*pos, m->index, task->comm);
// 这里加不加好像都没有作用
++*pos;
// 返回NULL, 遍历结束
if(task == &init_task){
return NULL;
}
return task;
}
// 遍历完成/出错时seq_file会调用stop()函数
static void my_seq_stop(struct seq_file *m, void *v)
{
klogi(log_tag "my_seq_stop m=%pK, v=%pK, index=%lld", m, v, m->index);
}
// 此函数将数据写入`seq_file`内部的缓冲区
// `seq_file`会在合适的时候把缓冲区的数据拷贝到应用层
// 参数@V是start/next函数的返回值
static int my_seq_show(struct seq_file *m, void *v)
{
struct task_struct * task = (struct task_struct *)v;
klogi(log_tag "my_seq_show m=%pK, v=%pK, index=%lld comm=[%s]", m, v, m->index, task->comm);
seq_printf(m, "PID=%u, task: %s, index=%lld, read_pos=%lld\n", task->tgid, task->comm, m->index, m->read_pos);
return 0;
}