linux内核seq_file接口
seq相关头文件linux/seq_file.h,seq相关函数的实现在fs/seq_file.c。seq函数最早是在2001年就引入了,但以前内核中一直用得不多,而到了2.6内核后,许多/proc的只读文件中大量使用了seq函数处理。
由于procfs的默认操作函数只使用一页的缓存,在处理较大的proc文件时就有点麻烦,并且在输出一系列结构体中的数据时也比较不灵活,需要自己在read_proc函数中实现迭代,容易出现Bug。所以内核黑客们对一些/proc代码做了研究,抽象出共性,最终形成了seq_file(Sequence file:序列文件)接口。 这个接口提供了一套简单的函数来解决以上proc接口编程时存在的问题,使得编程更加容易,降低了Bug出现的机会。
在需要创建一个由一系列数据顺序组合而成的虚拟文件或一个较大的虚拟文件时,推荐使用seq_file接口。但是我个人认为,并不是只有procfs才可以使用这个seq_file接口,因为其实seq_file是实现的是一个操作函数集,这个函数集并不是与proc绑定的,同样可以用在其他的地方。
实现
seq_file结构体定义于linux/seq_file.h
struct seq_file {
char *buf; //序列文件对应的数据缓冲区,要导出的数据是首先打印到这个缓冲区,然后才被拷贝到指定的用户缓冲区。
size_t size; //缓冲区大小,默认为1个页面大小,随着需求会动态以2的级数倍扩张,4k,8k,16k...
size_t from; //没有拷贝到用户空间的数据在buf中的起始偏移量
size_t count; //buf中没有拷贝到用户空间的数据的字节数,调用seq_printf()等函数向buf写数据的同时相应增加m->count
size_t pad_until;
loff_t index; //正在或即将读取的数据项索引,和seq_operations中的start、next操作中的pos项一致,一条记录为一个索引
loff_t read_pos; //当前读取数据(file)的偏移量,字节为单位
u64 version; //文件的版本
struct mutex lock; //序列化对这个文件的并行操作
const struct seq_operations *op; //指向seq_operations
int poll_event;
const struct file *file; // seq_file相关的proc或其他文件
void *private; //指向文件的私有数据
};
seq操作函数:
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos); //开始读数据项,通常需要在这个函数中加锁,以防止并行访问数据
void (*stop) (struct seq_file *m, void *v); //停止数据项,和start相对,通常需要解锁
void * (*next) (struct seq_file *m, void *v, loff_t *pos); //下一个要处理的数据项
int (*show) (struct seq_file *m, void *v); //打印数据项到临时缓冲区
};
start在*pos为0时可以返回SEQ_START_TOKEN,通过这个值传递给show的时候,show会打印表格头。
start和next返回一条数据记录,stop停止打印,show显示一条记录。
注意:要在next中对pos递增处理,但递增的单位与迭代器有关,可能不是1。
一些有用的全局函数:
- seq_open:通常会在打开文件的时候调用,以第二个参数为seq_operations表创建seq_file结构体。
- seq_read, seq_lseek和seq_release:他们通常都直接对应着文件操作表中的read, llseek和release。
- seq_escape:将一个字符串中的需要转义的字符(字节长)以8进制的方式打印到seq_file。
- seq_putc, seq_puts, seq_printf:他们分别和C语言中的putc,puts和printf相对应。
- seq_path:用于输出文件名。
- single_open, single_release: 打开和释放只有一条记录的文件。
- seq_open_private, __seq_open_private, seq_release_private:和seq_open类似,不过打开seq_file的时候创建一小块文件私有数据。
特别提取了双向链表和hash链表处理函数:
struct list_head *seq_list_start(struct list_head *head, loff_t pos)
{
struct list_head *lh; list_for_each(lh, head)
if (pos-- == )
return lh; return NULL;
}
EXPORT_SYMBOL(seq_list_start); struct list_head *seq_list_start_head(struct list_head *head, loff_t pos)
{
if (!pos)
return head; return seq_list_start(head, pos - );
}
EXPORT_SYMBOL(seq_list_start_head); 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;
}
EXPORT_SYMBOL(seq_list_next);
seq_file实现中关键函数为seq_read(),将记录逐条读取到用户空间:
/**
* seq_read - ->read() method for sequential files.
* @file: the file to read from
* @buf: the buffer to read to
* @size: the maximum number of bytes to read
* @ppos: the current position in the file
*
* Ready-made ->f_op->read()
*/
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
struct seq_file *m = file->private_data;
size_t copied = ;
loff_t pos;
size_t n;
void *p;
int err = ; mutex_lock(&m->lock);
/*
* seq_file->op->..m_start/m_stop/m_next may do special actions
* or optimisations based on the file->f_version, so we want to
* pass the file->f_version to those methods.
*
* seq_file->version is just copy of f_version, and seq_file
* methods can treat it simply as file version.
* It is copied in first and copied out after all operations.
* It is convenient to have it as part of structure to avoid the
* need of passing another argument to all the seq_file methods.
*/
m->version = file->f_version; /* Don't assume *ppos is where we left it */
if (unlikely(*ppos != m->read_pos)) { // 读取位置与当前buf位置不同,traverse遍历
while ((err = traverse(m, *ppos)) == -EAGAIN)
;
if (err) {
/* With prejudice... */
m->read_pos = ;
m->version = ;
m->index = ;
m->count = ;
goto Done;
} else {
m->read_pos = *ppos;
}
} /* grab buffer if we didn't have one */
if (!m->buf) {
m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
if (!m->buf)
goto Enomem;
}
/* if not empty - flush it first */
if (m->count) { //操作完后两情况:buf数据读取完,m->count=0;未读完,size=0,下次再读,本次读取结束
n = min(m->count, size);
err = copy_to_user(buf, m->buf + m->from, n);
if (err)
goto Efault;
m->count -= n;
m->from += n;
size -= n;
buf += n;
copied += n;
if (!m->count) {
m->from = ;
m->index++;
}
if (!size)
goto Done;
}
/* we need at least one record in buffer */
pos = m->index;
p = m->op->start(m, &pos);
while () { //容纳一条数据后执行Fill,不能容纳一条数据就分配更大空间,若读到最后记录就break。
err = PTR_ERR(p);
if (!p || IS_ERR(p))
break;
err = m->op->show(m, p);
if (err < )
break;
if (unlikely(err))
m->count = ;
if (unlikely(!m->count)) {
p = m->op->next(m, p, &pos);
m->index = pos;
continue;
}
if (m->count < m->size)
goto Fill;
m->op->stop(m, p);
kvfree(m->buf);
m->count = ;
m->buf = seq_buf_alloc(m->size <<= );
if (!m->buf)
goto Enomem;
m->version = ;
pos = m->index;
p = m->op->start(m, &pos);
}
m->op->stop(m, p);
m->count = ;
goto Done;
Fill: //退出条件为没有记录可读(next返回NULL)或buf缓冲区满
/* they want more? let's try to get some more */
while (m->count < size) {
size_t offs = m->count;
loff_t next = pos;
p = m->op->next(m, p, &next);
if (!p || IS_ERR(p)) {
err = PTR_ERR(p);
break;
}
err = m->op->show(m, p);
if (seq_has_overflowed(m) || err) {
m->count = offs;
if (likely(err <= ))
break;
}
pos = next;
}
m->op->stop(m, p);
n = min(m->count, size);
err = copy_to_user(buf, m->buf, n);
if (err)
goto Efault;
copied += n;
m->count -= n;
if (m->count)
m->from = n;
else
pos++;
m->index = pos;
Done: // 操作完成后,出错返回错误,成功则file位置偏移和seq读指针偏移增加拷贝字节数
if (!copied)
copied = err;
else {
*ppos += copied;
m->read_pos += copied;
}
file->f_version = m->version;
mutex_unlock(&m->lock);
return copied;
Enomem:
err = -ENOMEM;
goto Done;
Efault:
err = -EFAULT;
goto Done;
}
EXPORT_SYMBOL(seq_read);
示例
如下例用于列出进程相关信息,在ubuntu 16/18上测试通过:
//#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/percpu.h>
#include <linux/init.h>
#include <linux/sched.h>
// #include <linux/sched/signal.h> static struct proc_dir_entry *entry;
static loff_t offset = ; static void *l_start(struct seq_file *m, loff_t * pos)
{
loff_t index = *pos;
loff_t i = ;
struct task_struct * task ; if (index == ) {
seq_printf(m, "Current all the processes in system:\n"
"%-24s%-5s\n", "name", "pid");
printk(KERN_EMERG "++++++++++=========>%5d\n", );
// offset = 1;
return &init_task;
}else {
for(i = 0, task=&init_task; i < index; i++){
task = next_task(task);
}
BUG_ON(i != *pos);
if(task == &init_task){
return NULL;
} printk(KERN_EMERG "++++++++++>%5d\n", task->pid);
return task;
}
} static void *l_next(struct seq_file *m, void *p, loff_t * pos)
{
struct task_struct * task = (struct task_struct *)p; task = next_task(task);
if ((*pos != ) && (task == &init_task)) {
// if ((task == &init_task)) {
// printk(KERN_EMERG "=====>%5d\n", task->pid);
return NULL;
} printk(KERN_EMERG "=====>%5d\n", task->pid);
offset = ++(*pos); return task;
} static void l_stop(struct seq_file *m, void *p)
{
printk(KERN_EMERG "------>\n");
} static int l_show(struct seq_file *m, void *p)
{
struct task_struct * task = (struct task_struct *)p; seq_printf(m, "%-24s%-5d\t%lld\n", task->comm, task->pid, offset);
// seq_printf(m, "======>%-24s%-5d\n", task->comm, task->pid);
return ;
} static struct seq_operations exam_seq_op = {
.start = l_start,
.next = l_next,
.stop = l_stop,
.show = l_show
}; static int exam_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &exam_seq_op);
} static struct file_operations exam_seq_fops = {
.open = exam_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
}; static int __init exam_seq_init(void)
{ // entry = create_proc_entry("exam_esq_file", 0, NULL);
entry = proc_create("exam_esq_file", , NULL, &exam_seq_fops);
if (!entry)
printk(KERN_EMERG "proc_create error.\n");
//entry->proc_fops = &exam_seq_fops; printk(KERN_EMERG "exam_seq_init.\n");
return ;
} static void __exit exam_seq_exit(void)
{
remove_proc_entry("exam_esq_file", NULL);
printk(KERN_EMERG "exam_seq_exit.\n");
} module_init(exam_seq_init);
module_exit(exam_seq_exit);
MODULE_LICENSE("GPL");
makefile
obj-m := seq.o
KDIR := /lib/modules/$(shell uname -r)/build
#KDIR := ~/source_ap/build_dir/target-arm_cortex-a7_uClibc-1.0.14_eabi/linux-ipq806x/linux-3.14.
PWD := $(shell pwd) default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean:
rm -rf *.o *.ko *.mod* *.order *.sym*
参考:
1. 内核proc文件系统与seq接口(4)---seq_file接口编程浅析 tekkaman
2. 内核proc文件系统与seq接口(5)---通用proc接口与seq_file接口实验 tekkaman
linux内核seq_file接口的更多相关文章
- Linux 内核USB 接口配置
USB 接口是自己被捆绑到配置的. 一个 USB 设备可有多个配置并且可能在它们之间转换 以便改变设备的状态. 例如, 一些允许固件被下载到它们的设备包含多个配置来实现这个. 一个配置只能在一个时间点 ...
- Linux 内核class_simple 接口
class_simple 接口意图是易于使用, 以至于没人会抱怨没有暴露至少一个包含设备的被 分配的号的属性. 使用这个接口只不过是一对函数调用, 没有通常的和 Linux 设备模型 关联的样板. 第 ...
- Linux 内核 usb_control_msg 接口
usb_control_msg 函数就像 usb_bulk_msg 函数, 除了它允许一个驱动发送和结束 USB 控制信息: int usb_control_msg(struct usb_device ...
- Linux 内核usb_bulk_msg 接口
usb_bulk_msg 创建一个 USB 块 urb 并且发送它到特定的设备, 接着在返回到调用者之 前等待完成. 它定义为: int usb_bulk_msg(struct usb_device ...
- Linux内核学习笔记之seq_file接口创建可读写proc文件
转自:http://blog.csdn.net/mumufan05/article/details/45803219 学习笔记与个人理解,如有错误,欢迎指正. 温馨提示:建议跟着注释中的编号顺序阅读代 ...
- 深入理解linux网络技术内幕读书笔记(三)--用户空间与内核的接口
Table of Contents 1 概论 1.1 procfs (/proc 文件系统) 1.1.1 编程接口 1.2 sysctl (/proc/sys目录) 1.2.1 编程接口 1.3 sy ...
- linux seq_file 接口
如我们上面提到的, 在 /proc 下的大文件的实现有点麻烦. 一直以来, /proc 方法因为 当输出数量变大时的错误实现变得声名狼藉. 作为一种清理 /proc 代码以及使内核开发 者活得轻松些的 ...
- 【request_firmware】 linux内核下载模块固件接口【转】
转自:http://blog.csdn.net/magod/article/details/6049558 [-] 8 处理固件 1481 内核固件接口 1482 它如何工作 14.8. 处理固件 作 ...
- 【引用】Linux 内核驱动--多点触摸接口
本文转载自James<Linux 内核驱动--多点触摸接口> 译自:linux-2.6.31.14\Documentation\input\multi-touch-protocol.t ...
随机推荐
- CListCtrl自适应宽度
原文链接: http://blog.csdn.net/benny5609/article/details/1967084 void CListCtrlExDlg::AdjustColumnWidth( ...
- 构建高性能J2EE应用的五种核心策略
对于J2EE,我们知道当开发应用时,在架构设计阶段的决定将对应用的性能和可扩展性产生深远的影响.现在当开发一个应用项目时,我们越来越多地注意到了性能和可扩展性的问题.应用性能的问题比应用功能的不丰富问 ...
- 转 分享我在阿里工作十年接触过Java框架设计模式
转 原文: 分享我在阿里工作十年接触过Java框架设计模式 一.前言 说起来设计模式,大家应该都耳熟能详,设计模式代表了软件设计的最佳实践,是经过不断总结提炼出来的代码设计经验的分类总结,这些模式或者 ...
- swagger配置和简单使用
说明:本地环境idea + maven3.5 + springboot2.0.0 + springfox-swagger2 2.8.0 + springfox-swagger-ui 2.8.0 + ...
- 关于 NSInvocation
Invocation 调用的意思. 可想而知NSInvocation 是一个 方法调用 封装的类. 这体现了 面向对象的思想, 及一切皆对象.函数也不例外. 一般编程中,应该很少用到这个. 但是 ...
- C语言浮点数存储结构
float类型占四个字节,每个字节占8位,总共32位,其内存结构如下图: 31位为符号位:0表示正数,1表示负数 31~23位:共8位表示指数位,内存存储数据从0~2^8-1=255,由于指数可以是正 ...
- ISO GPS定位,坐标转换以及如何显示
这个写的公共类叫做:GPScombineClass类主要展示GPS位置的定位,GPS坐标的获取,然后从手机坐标转换成火星坐标,继而在需要的情况下,由火星转百度 ,百度转火星的详细算法: 在GPSc ...
- tengine 增加ngx_http_cache_purge_module 模块
wget http://labs.frickle.com/files/ngx_cache_purge-2.1.tar.gz tar zxvf ngx_cache_purge-2.1.tar.gz -- ...
- 通过show variables like ‘general_log%’可以看查询日志
mysql> show variables like 'general_log%'; +------------------+-----------------------------+ | V ...
- 生产BackPressure 的代码
public class BackPressureStatsTrackerImpl implements BackPressureStatsTracker { private static final ...