proc文件系统:2.seq_file接口


一、seq_file接口

1.目的

  • 从内核中导出信息到用户空间有很多方法,可以自己去实现file_operations的read函数或者mmap函数,但是这种方法不够简单,而且也会有一些限制,比如一次read
    读取大于1页时,驱动里就不得不去进行复杂的缓冲区管理,容易出现Bug。

  • 内核黑客们对一些/proc代码做了研究,抽象出共性,最终形成了seq_file(Sequence file:序列文件)接口。

  • 为了更简单和方便,内核提供了single_xxx系列接口,它是对seq_file的进一步封装

  • 在这里插入图片描述

2.函数

Seq_file必须实现四个操作函数:start(), next(), show(), stop()。

struct seq_operations
{
	// 主要实现初始化工作,在遍历一个链接对象开始时调用。
    void *(*start)(struct seq_file *m, loff_t *pos);
    // 当所有链接对象遍历结束时调用。主要完成一些清理工作。
    void (*stop)(struct seq_file *m, void *v);
    // 用来在遍历中寻找下一个链接对象。返回下一个链接对象或者NULL(遍历结束)。
    void *(*next)(struct seq_file *m, void *v, loff_t *pos);
    // 对遍历对象进行操作的函数。主要是调用seq_printf()之类的函数,打印出这个对象节点的信息。
    int (*show)(struct seq_file *m, void *v);
};

二、实验

1.内容

Proc文件系统实践

2.过程

(1)编写内核模块.c文件

mkdir project && cd project
gedit catkinModule.c
#include <linux/kernel.h>   // 内核
#include <linux/module.h>   // 模块
#include <linux/mutex.h>    // 使用mutex
#include <linux/proc_fs.h>  // proc_fs定义
#include <linux/seq_file.h> // seq_file接口
#include <linux/uaccess.h>  // copy_to_user() & copy_from_user

static struct mutex lock;
// 链表的头结点
static struct list_head head;
struct my_data
{
    struct list_head list;
    // 存放字符串数组的长度
    int value;
    // 存放字符串
    char str[128];
};

// 在链表上新建一个结点
static void add_one(void)
{
    // 结点指针
    struct my_data *data;
    // 加锁mutex
    mutex_lock(&lock);
    // 分配内存空间用kmalloc
    data = kmalloc(sizeof(*data), GFP_KERNEL);
    memset(data->str, 0, sizeof(data));
    // 用list_add()
    if (data != NULL)
        list_add(&data->list, &head);
    mutex_unlock(&lock);
}

// 开始
static void *_seq_start(struct seq_file *m, loff_t *pos)
{
    // 加锁
    mutex_lock(&lock);
    return seq_list_start(&head, *pos);
}

// 下一个
static void *_seq_next(struct seq_file *m, void *p, loff_t *pos)
{
    return seq_list_next(p, &head, pos);
}

// 停止
static void _seq_stop(struct seq_file *m, void *p)
{
    // 释放锁
    mutex_unlock(&lock);
}

// 如何打印
static int _seq_show(struct seq_file *m, void *p)
{
    struct my_data *data = list_entry(p, struct my_data, list);
    seq_printf(m, "len:%d,str:%s", data->value, data->str);
    printk("[read]<len:%d>,<str:%s>\n", data->value, data->str);
    return 0;
}

// seq_operations结构体函数
static struct seq_operations _seq_ops = {
    .start = _seq_start,
    .next = _seq_next,
    .stop = _seq_stop,
    .show = _seq_show,
};

// 打开
static int _seq_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &_seq_ops);
}

// 写入新的结点数据
static ssize_t _seq_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
    // 新建一个结点
    add_one();
    /* 此处使用copy_from_user可以从用户态传递数据到内核态 */
    struct my_data *data;
    data = list_entry((&head)->next, struct my_data, list);

    printk("[write]<len:%d>,<str:%s>\n", count, buffer);

    // 因为字符串数组str的限制,最大截取127个字符
    if (count < 128)
    {
        // 将用户态的buffer的值赋值给内核态的data->str
        copy_from_user(data->str, buffer, count);
        data->value = count;
        return count;
    }
    else
    {
        copy_from_user(data->str, buffer, 127);
        data->value = 127;
        return 127;
    }
}

// file_operations结构体函数
static struct file_operations _seq_fops = {
    .open = _seq_open,
    .read = seq_read,
    .write = _seq_write,
    .llseek = seq_lseek,
    .release = seq_release,
};

// module_init()内的初始化函数。
static int __init init(void)
{
    struct proc_dir_entry *entry;
    struct my_data *data;

    mutex_init(&lock);
    INIT_LIST_HEAD(&head);

    // 在/proc下创建一个文件my_data
    entry = proc_create("my_data", S_IWUSR | S_IRUGO, NULL, &_seq_fops);
    if (entry == NULL)
    {
        // 释放链表
        while (!list_empty(&head))
        {
            data = list_entry((&head)->next, struct my_data, list);
            list_del(&data->list);
            kfree(data);
        }
        printk(KERN_ALERT "creating error!The catkinModule has not installed\n");
        return -ENOMEM;
    }
    printk(KERN_ALERT "Hello!The catkinModule has installed!\n");
    return 0;
}

// module_exit()内的退出函数。
static void __exit fini(void)
{
    struct my_data *data;
    // 删除/proc下的节点my_data
    remove_proc_entry("my_data", NULL);
    // 释放链表
    while (!list_empty(&head))
    {
        data = list_entry((&head)->next, struct my_data, list);
        list_del(&data->list);
        kfree(data);
    }
    printk(KERN_ALERT "Bye!The catkinModule has uninstalled!\n");
}

module_init(init);
module_exit(fini);

// 内核模块描述
MODULE_DESCRIPTION("a simple driver module");
// GPL协议证书
MODULE_LICENSE("GPL");

(2)编写Makefile文件

gedit Makefile
obj-m := catkinModule.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers

(3)编译

make

PS:
如果重新编译的话,要把之前残留的垃圾删除(试过不管这些垃圾也能编译成功,但编辑结果还是原来的就很气)

# 清理垃圾
make clean
# 编译
make

(4)安装模块:

先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西

sudo dmesg -c

安装

sudo insmod catkinModule.ko

查看printk的输出在缓冲区的信息:

sudo dmesg

(5)交互proc文件

cd /proc
echo "just" > my_data
echo "do it" > my_data
cat my_data

在这里插入图片描述

(6)卸载模块:

sudo rmmod catkinModule

(7)清除printk输出在缓存区的信息:

sudo dmesg -c

三、问题

1.字符溢出现象

(1)正常的

在这里插入图片描述
可以看到write写入部分的字符"just""do it"后面有很多乱码,而read部分没有。
这是因为static ssize_t _seq_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)buffer是用户的全部缓冲区,所有后面有其他的东西。

(2)异常现象

就是不仅是write部分有,而且read部分也有。

经过多次尝试,发现read部分也有的原因是因为static void add_one(void)中缺少了memset(data->str, 0, sizeof(data));,分配给新的结点的空间都是乱码的,所以如果不清零的话,字符串中就有乱码。

发布了461 篇原创文章 · 获赞 183 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/sandalphon4869/article/details/104781331