文章目录
一、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));
,分配给新的结点的空间都是乱码的,所以如果不清零的话,字符串中就有乱码。