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 ...
随机推荐
- WSAAsyncSelect模型中,FD_WRITE事件什么时候触发?
当一个套接字连接被建立上时(包括客户端的connect(),connectex()等和服务器端的accept接收到后创建的新套接字),这时会触发FD_WRITE,以后就可以用send(),WSASen ...
- Spring Boot修改内置Tomcat端口号
spring Boot 内置Tomcat默认端口号为8080,在开发多个应用调试时很不方便,本文介绍了修改 Spring Boot内置Tomcat端口号的方法. 一.EmbeddedServletCo ...
- df -h和du -sh显示结果不一样的原因及解决
一.背景:一台2T硬盘的mysql服务器,保存电话的CDR信息.按照历史数据的水平,一个月能生成20+GB的文件.然而短短的半年时间,满了?! 登录服务器看谁占了这么大的空间?好吧,slow-quer ...
- Android Developers:向其它应用发送用户
Android的一个非常重要的功能是,应用程序基于它要执行的一个“动作”想其它应用程序发送用户的能力.例如,如果你的应用程序要显示一个地图,你没有在你的应用程序中创建显示地图的Activity.相反, ...
- 查看mysql状态的常用命令
在mysql客户端输入"show status"之后将会看到如下输出: 如果想要查看某个具体的值,可以使用如下命令: show status LIKE "%具体变量%&q ...
- JDK1.7新特性,语言篇
1. 可以用二进制表达数字 可以用二进制表达数字(加前缀0b/0B),包括:byte, short, int, long // 可以用二进制表达数字(加前缀0b/0B),包括:byte, short, ...
- atitit.html编辑器的设计要点与框架选型 attilax总结
atitit.html编辑器的设计要点与框架选型 attilax总结 1. html编辑器的设计要求1 1.1. 障碍訪问 1 1.2. 强大Ajax上传 1 1.3. Word完美支持 2 1.4. ...
- EMIT 动态创建类型
https://www.cnblogs.com/xiaodlll/p/4029051.html 值得一读
- flexb布局图解
- Unix/Linux系统中僵尸进程是如何产生的?有什么危害?如何避免?
如题 Unix/Linux系统中僵尸进程是如何产生的?有什么危害?如何避免? 一个进程在调用exit命令结束自己的生命的时候,其实他并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结 ...