Linux驱动编程 - seq_open、single_open使用方法

目录

 前言:

一、seq_xxx

1、seq_xxx 函数介绍

1.1 seq_open

1.2 seq_read

1.3 seq_lseek

1.4 seq_release

1.5 格式化输出函数

2、seq_open 实例

二、single_xxx 函数

1、single_xxx 函数介绍

1.1 single_open

1.2 single_start

1.3 single_next

1.4 single_stop

1.5 single_release

2、single_open 实例

三、更精简的方法 DEFINE_SHOW_ATTRIBUTE


 前言:


        内核态和用户态数据交互有很多方法,比如驱动中的 copy_to_user、copy_from_user。再比如驱动中添加属性文件,cat、echo调用节点属性文件的xx_show函数或xx_store函数。

        本文介绍 seq_file 的用法,为了更简单和方便,内核提供了single_xxx系列接口,它是对seq_file的进一步封装。

        相比较于之前的方法其优势可以向应用层导出比较多的数据,例如大于1个PAGE_SIZE,同时驱动文件中我们不用关注复杂的缓冲区管理,这些内核自己处理,属于比较成熟的内核接口。

        seq_file机制提供了一种标准化的例程,使得顺序文件的处理变得简单高效。顺序文件通常用于遍历一些数据项并创建文件内容,其访问模式是逐个访问读取数据项。seq_file机制通过struct seq_operations结构定义了一系列操作函数,包括开始、停止、显示和获取下一个操作,从而简化了文件处理过程‌

测试示例依赖debugfs,内核配置打开debugfs:

CONFIG_DEBUG_FS=y

如果/sys/kernel/debug是空的,执行以下命令挂载debugfs文件系统:

$ mount -t debugfs none /sys/kernel/debug

一、seq_xxx


seq_file只是在普通的文件read中加入了内核缓冲的功能,从而实现顺序多次遍历,读取大数据量的简单接口。

/* 路径:include/linux/seq_file.h */
struct seq_file {
	char *buf;			//在seq_open中分配,大小为4KB
	size_t size;		//4096
	size_t from;		//struct file从seq_file中向用户态缓冲区拷贝时相对于buf的偏移地址
	size_t count;		//可以拷贝到用户态的字符数目
	size_t pad_until;
	loff_t index;		//从内核态向seq_file的内核态缓冲区buf中拷贝时start、next的处理的下标pos数值,即用户自定义遍历iter
	loff_t read_pos;	//当前已拷贝到用户态的数据量大小,即struct file中拷贝到用户态的数据量
	u64 version;
	struct mutex lock;
	const struct seq_operations *op;	//操作集,包含seq_start,seq_next,set_show,seq_stop函数
	int poll_event;
	const struct file *file;
	void *private;
};

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:用于指定seq_file文件的读开始位置,返回实际读开始位置,如果指定的位置超过文件末尾,应当返回NULL;
  • stop:用于在读完seq_file文件后调用,它类似于文件操作close,用于做一些必要的清理,如释放内存等;
  • next:用于把seq_file文件的当前读位置移动到下一个读位置,返回实际的下一个读位置,如果已经到达文件末尾,返回NULL;
  • show:用于格式化输出,如果成功返回0,否则返回出错码;

1、seq_xxx 函数介绍


内核自带函数 seq_open、seq_read、seq_lseek、seq_release等函数

1.1 seq_open

功能:用于把struct seq_operations结构与seq_file文件关联起来

/*
参数:
    *file:指向要操作的文件指针
    *op:指向一个seq_operations结构体,包含start、show等
返回值:0:成功, 非0:失败
*/
int seq_open(struct file *file, const struct seq_operations *op)
{
	struct seq_file *p;

	WARN_ON(file->private_data);

	p = kmem_cache_zalloc(seq_file_cache, GFP_KERNEL);
	if (!p)
		return -ENOMEM;

	file->private_data = p;

	mutex_init(&p->lock);
	p->op = op;

	// No refcounting: the lifetime of 'p' is constrained
	// to the lifetime of the file.
	p->file = file;

	/*
	 * Wrappers around seq_open(e.g. swaps_open) need to be
	 * aware of this. If they set f_version themselves, they
	 * should call seq_open first and then set f_version.
	 */
	file->f_version = 0;

	/*
	 * seq_files support lseek() and pread().  They do not implement
	 * write() at all, but we clear FMODE_PWRITE here for historical
	 * reasons.
	 *
	 * If a client of seq_files a) implements file.write() and b) wishes to
	 * support pwrite() then that client will need to implement its own
	 * file.open() which calls seq_open() and then sets FMODE_PWRITE.
	 */
	file->f_mode &= ~FMODE_PWRITE;
	return 0;
}

1.2 seq_read

功能:用于从内核缓冲区中读取数据并将其输出到用户空间

/*
参数:
    *file:指向要操作的文件指针
    *buf:用户空间的缓冲区地址,用于存放读取的数据
    size:用户请求读取的字节数
    *ppos:文件描述符的当前位置,指示读取的起始位置
返回值:读取的字节数,失败返回负数错误码
*/
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 = 0;
	size_t n;
	void *p;
	int err = 0;

	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;

	/*
	 * if request is to read from zero offset, reset iterator to first
	 * record as it might have been already advanced by previous requests
	 */
	if (*ppos == 0) {
		m->index = 0;
		m->version = 0;
		m->count = 0;
	}

	/* Don't assume *ppos is where we left it */
	if (unlikely(*ppos != m->read_pos)) {
		while ((err = traverse(m, *ppos)) == -EAGAIN)
			;
		if (err) {
			/* With prejudice... */
			m->read_pos = 0;
			m->version = 0;
			m->index = 0;
			m->count = 0;
			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) {
		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 (!size)
			goto Done;
	}
	/* we need at least one record in buffer */
	m->from = 0;
	p = m->op->start(m, &m->index);
	while (1) {
		err = PTR_ERR(p);
		if (!p || IS_ERR(p))
			break;
		err = m->op->show(m, p);
		if (err < 0)
			break;
		if (unlikely(err))
			m->count = 0;
		if (unlikely(!m->count)) {
			p = m->op->next(m, p, &m->index);
			continue;
		}
		if (m->count < m->size)
			goto Fill;
		m->op->stop(m, p);
		kvfree(m->buf);
		m->count = 0;
		m->buf = seq_buf_alloc(m->size <<= 1);
		if (!m->buf)
			goto Enomem;
		m->version = 0;
		p = m->op->start(m, &m->index);
	}
	m->op->stop(m, p);
	m->count = 0;
	goto Done;
Fill:
	/* they want more? let's try to get some more */
	while (1) {
		size_t offs = m->count;
		loff_t pos = m->index;

		p = m->op->next(m, p, &m->index);
		if (pos == m->index)
			/* Buggy ->next function */
			m->index++;
		if (!p || IS_ERR(p)) {
			err = PTR_ERR(p);
			break;
		}
		if (m->count >= size)
			break;
		err = m->op->show(m, p);
		if (seq_has_overflowed(m) || err) {
			m->count = offs;
			if (likely(err <= 0))
				break;
		}
	}
	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;
	m->from = n;
Done:
	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;
}

1.3 seq_lseek

功能:重新定位文件内的读/写文件偏移量

/*
参数:
    *file:指向要操作的文件指针
    offset:偏移量,表示从文件开头向后移动的字节数
    whence:起始位置,可以是SEEK_SET(文件开头)、SEEK_CUR(当前位置)或SEEK_END(文件末尾)
返回值:文件位置,失败返回-1
*/
loff_t seq_lseek(struct file *file, loff_t offset, int whence)
{
	struct seq_file *m = file->private_data;
	loff_t retval = -EINVAL;

	mutex_lock(&m->lock);
	m->version = file->f_version;
	switch (whence) {
	case SEEK_CUR:
		offset += file->f_pos;
		/* fall through */
	case SEEK_SET:
		if (offset < 0)
			break;
		retval = offset;
		if (offset != m->read_pos) {
			while ((retval = traverse(m, offset)) == -EAGAIN)
				;
			if (retval) {
				/* with extreme prejudice... */
				file->f_pos = 0;
				m->read_pos = 0;
				m->version = 0;
				m->index = 0;
				m->count = 0;
			} else {
				m->read_pos = offset;
				retval = file->f_pos = offset;
			}
		} else {
			file->f_pos = offset;
		}
	}
	file->f_version = m->version;
	mutex_unlock(&m->lock);
	return retval;
}

1.4 seq_release

功能:释放seq_file结构体资源

int seq_release(struct inode *inode, struct file *file)
{
	struct seq_file *m = file->private_data;
	kvfree(m->buf);
	kmem_cache_free(seq_file_cache, m);
	return 0;
}

1.5 格式化输出函数

/*函数seq_putc用于把一个字符输出到seq_file文件*/
int seq_putc(struct seq_file *m, char c);
/*函数seq_puts则用于把一个字符串输出到seq_file文件*/
int seq_puts(struct seq_file *m, const char *s);
/*函数seq_escape类似于seq_puts,只是,它将把第一个字符串参数中出现的包含在第二个字符串参数中的字符按照八进制形式输出,也即对这些字符进行转义处理*/
int seq_escape(struct seq_file *, const char *, const char *);
/*函数seq_printf是最常用的输出函数,它用于把给定参数按照给定的格式输出到seq_file文件*/
int seq_printf(struct seq_file *, const char *, ...)__attribute__ ((format(printf,2,3)));
/*函数seq_path则用于输出文件名,字符串参数提供需要转义的文件名字符,它主要供文件系统使用*/
int seq_path(struct seq_file *, struct vfsmount *, struct dentry *, char *);

2、seq_open 实例


#include <linux/init.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/fs.h>

static void *my_seq_start(struct seq_file *p, loff_t *pos)
{
    if (*pos >= 1)
        return NULL;
    return 1;
}

static void *my_seq_next(struct seq_file *p, void *v, loff_t *pos)
{
    ++(*pos);
    return NULL;
}

static void my_seq_stop(struct seq_file *p, void *v)
{
    return;
}

static int my_seq_show(struct seq_file *p, void *v)
{
    seq_printf(p, "hello word\n");
    return 0;
}

static const struct seq_operations my_seq_ops= {
    .start = my_seq_start,
    .next  = my_seq_next,
    .stop  = my_seq_stop,
    .show  = my_seq_show,
};

static int my_seq_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &my_seq_ops);	//内核自带的 seq_open 设置 seq_operations
}

static const struct file_operations my_debug_fops = {
    .owner = THIS_MODULE,
    .open = my_seq_open,
    .read = seq_read,		//内核自带的 seq_read
    .llseek = seq_lseek,	//内核自带的 seq_lseek
    .release = seq_release,	//内核自带的 seq_release
};

static struct dentry *seq_file_demo_dir = NULL;
int demo_seq_init(void)
{
    /* 生成 /sys/kernel/debug/demo_seq 文件 */
    seq_file_demo_dir = debugfs_create_file("demo_seq", 0444, NULL,
                NULL, &my_debug_fops);
    return 0;
}

static void __exit demo_seq_exit(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    if (seq_file_demo_dir)
            debugfs_remove(seq_file_demo_dir);
}

module_init(demo_seq_init);
module_exit(demo_seq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dongao");

运行结果:

$ insmod ./demo_seq_driver.ko 
$ cat /sys/kernel/debug/demo_seq 
hello word

也可以应用程序 open 打开 /sys/kernel/debug/demo_seq ,然后read,如下:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
 
int main(int argc, char *argv[])
{
	int fd = -1;
	int ret = -1;
	char buf[1024] = {0};
	
	fd = open("/sys/kernel/debug/demo_seq", O_RDONLY);
	if(fd < 0){
		printf("open failed!\r\n");
		return -1;
	}
	
	lseek(fd, 0, SEEK_SET);
	ret = read(fd, buf, sizeof(buf) - 1);
	if(ret < 0){
		printf("read Failed!\r\n");
		close(fd);
		return -1;
	}

	printf("%s", buf);		//打印输出 "hello word"
	
	ret = close(fd); /* 关闭文件 */
	if(ret < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	
	return 0;
}

二、single_xxx 函数


相比于seq_open()函数,内核还提供了一个更加精简的single_open()函数,只需要实现xx_show函数就行。

1、single_xxx 函数介绍

1.1 single_open

single_start、single_next、single_stop是内核提供的函数。single_open中实现了设置struct seq_operations,并调用了 seq_open。

/* fs/seq_file.c */
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;
}

1.2 single_start

static void *single_start(struct seq_file *p, loff_t *pos)
{
	return NULL + (*pos == 0);
}

1.3 single_next

static void *single_next(struct seq_file *p, void *v, loff_t *pos)
{
	++*pos;
	return NULL;
}

1.4 single_stop

static void single_stop(struct seq_file *p, void *v)
{
}

1.5 single_release

int single_release(struct inode *inode, struct file *file)
{
	const struct seq_operations *op = ((struct seq_file *)file->private_data)->op;
	int res = seq_release(inode, file);
	kfree(op);
	return res;
}

2、single_open 实例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/fs.h>

static int my_seq_show(struct seq_file *m, void *v)
{
    seq_printf(m, "test single_open\n");
    return 0;
}

static int my_seq_open(struct inode *inode, struct file *file)
{
    return single_open(file, &my_seq_show, NULL);		//设置 xxx_show
}

static const struct file_operations my_debug_fops = {
    .owner = THIS_MODULE,
    .open = my_seq_open,
    .read = seq_read,		//内核自带的 seq_read
    .llseek = seq_lseek,	//内核自带的 seq_lseek
    .release = seq_release,	//内核自带的 seq_release
};

static struct dentry *seq_file_demo_dir = NULL;
int demo_seq_init(void)
{
    /* 生成 /sys/kernel/debug/demo_seq 文件 */
    seq_file_demo_dir = debugfs_create_file("demo_seq", 0444, NULL,
                NULL, &my_debug_fops);
    return 0;
}

static void __exit demo_seq_exit(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
    if (seq_file_demo_dir)
            debugfs_remove(seq_file_demo_dir);
}

module_init(demo_seq_init);
module_exit(demo_seq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dongao");

生成 /sys/kernel/debug/demo_seq 文件,测试方法同上。

三、更精简的方法 DEFINE_SHOW_ATTRIBUTE


#define DEFINE_SHOW_ATTRIBUTE(__name)                   \
static int __name ## _open(struct inode *inode, struct file *file)  \
{                                   \
    return single_open(file, __name ## _show, inode->i_private);    \
}                                   \
                                    \
static const struct file_operations __name ## _fops = {         \
    .owner      = THIS_MODULE,                  \
    .open       = __name ## _open,              \
    .read       = seq_read,                 \
    .llseek     = seq_lseek,                    \
    .release    = single_release,               \
}

实例如下

#include <linux/init.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/fs.h>

static struct dentry *seq_file_demo_dir;

static int my_seq_show(struct seq_file *seq, void *v)
{
        seq_printf(seq, "Hello World\n");
        return 0;
}
DEFINE_SHOW_ATTRIBUTE(my_seq);

static int __init demo_seq_init(void)
{
        /* 生成 /sys/kernel/debug/demo_seq 文件 */
        seq_file_demo_dir = debugfs_create_file("demo_seq", 0444, NULL,
                NULL, &my_seq_fops);
        return 0;
}

static void __exit demo_seq_exit(void)
{
        if (seq_file_demo_dir)
                debugfs_remove(seq_file_demo_dir);
}

module_init(demo_seq_init);
module_exit(demo_seq_exit);
MODULE_LICENSE("GPL");

测试方法同上,不再赘述。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值