您的位置:首页 > 运维架构 > Linux

linux proc文件系统-属性文件使用之读写

2016-08-20 16:14 507 查看

1.记录目的

之前在看其他驱动的时候,经常见到创建proc文件系统文件的代码。虽然经常见到,但是我对proc还不会用,所以今天就特意写了个测试程序,来记录下proc代码如何使用。当然调试例程的时候,也遇到了一些错误,后面会贴出来,和大家分享。测试代码在PC机上实现,并验证过。如果博文中有不对的地方,欢迎网友指出,我们共同进步。

2.认识一些重要的结构和函数

proc文件系统属性文件和驱动中创建的设备属性文件很相识,它们都可以达到同样的目的。我们这里只讨论使用,如果想分析原理,还是建议read the fucking code。在开始介绍代码之前,大家有必要认识一下seq_operations这个结构,如下所示。

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内部机制可能会使用show方法获取start返回值指向的结构体中的数据到内部缓存,并适时送往用户空间。

m:指向的是本seq_file的结构体,在正常情况下无需处理。

pos:是一个整型位置值,指示开始读取的位置。对于这个位置的意义完全取决于底层实现,不一定是字节为单位的位置,可能是一个元素的序列号。

返回值如果非NULL,则是一个指向迭代器实现的私有数据结构体指针。如果访问出错则返回NULL

stop:在stop实现中,参数m指向本seq_file的结构体,在正常情况下无需处理。而v是指向上一个nextstart返回的元素指针。在需要做退出处理的时候才需要实现具体的功能。但是许多情况下可以直接返回。

next:在next实现中应当递增pos指向的值,但是具体递增的数量和迭代器的实现有关,不一定是1。而next的返回值如果非NULL,则是下一个需要输出到缓存的元素指针,否则表明已经输出结束,将会调用stop方法做清理。

v:是之前调用start或next返回的元素指针,可能是上一个show已经完成输出所指向的元素。

pos:需要移动到的元素索引值。

show:负责将v指向元素中的数据输出到seq_file的内部缓存,具体请看代码中所示。其中的参数v就是next返回值。

3.read过程及层次结构

read

//代码段位于kernel/seq_file.c  ------seq_read()
pos = m->index;
p = m->op->start(m, &pos); //这里调了一次start
while (1) {
err = PTR_ERR(p);
if (!p || IS_ERR(p))
break;
err = m->op->show(m, p); //show time
if (err < 0)
break;
if (unlikely(err))
m->count = 0;
if (unlikely(!m->count)) {[ 7122.899602] pos_start=0
p = m->op->next(m, p, &pos);   //next指针下移动
m->index = pos;
continue;
}
if (m->count < m->size)
goto Fill;
m->op->stop(m, p);
kvfree(m->buf);
m->buf = seq_buf_alloc(m->size <<= 1);
if (!m->buf)
goto Enomem;
m->count = 0;
m->version = 0;
pos = m->index;
p = m->op->start(m, &pos); //又调了一次start。
}
m->op->stop(m, p);
m->count = 0;
goto Done;
经过上面的折腾,我们打印顺序确实如上面一样(log代码看下方代码)

[ 7122.899602] pos_start=0

[ 7122.899613] show time!

[ 7122.899618] pos_next=0

[ 7122.899621] show time!

[ 7122.899624] pos_next=1

[ 7122.899712] pos_start=2

层次结构:

下面这个层次图引用自博文,大家可以看看。



4.代码分析

/*************************************************************************
> File Name: proc.c
> Author: armwind
> Mail: armwind@163.com
> Created Time: Tue 16 Aug 2016 19:30:38 CST
************************************************************************/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>

static DEFINE_MUTEX(registration_lock);
static struct list_head head;
struct test_data {
struct list_head list;
char *value;
};

static void *test_seq_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(®istration_lock);
printk(KERN_ALERT "pos_start=%d\n",*pos); //打印标记
return seq_list_start(&head,*pos);
}

static void *test_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
printk(KERN_ALERT "pos_next=%d\n",*pos);
return seq_list_next(v,&head, pos); //返回的地址就是给show函数第二个参数使用
}

static void test_seq_stop(struct seq_file *m, void *v)
{
mutex_unlock(®istration_lock); //这里一般什么都不做。
}

static int test_seq_show(struct seq_file *m, void *v)
{
struct test_data *data = list_entry(v,struct test_data,list); //v就是next函数返回过来的,它就是当前要查看的节点。
printk(KERN_ALERT "show time!\n");
seq_printf(m,"value: %s\n", data->value);
return 0;
}

static ssize_t add_data(const char __user* buffer,
size_t count)
{
struct test_data *data;
if (NULL == buffer){   //注意指针在使用之前要判断一下,保护。
printk(KERN_ALERT "kzalloc mem failed\n");
return -1;
}
mutex_lock(®istration_lock);
char *tmp = kzalloc((count+1), GFP_KERNEL);
if (!tmp){
printk(KERN_ALERT "kzalloc mem failed\n");
return -ENOMEM;
}
if (copy_from_user(tmp, buffer, count)){
kfree(tmp);
return EFAULT;
}
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (data == NULL)
printk(KERN_ALERT "kmalloc mem failed\n");

list_add(&data->list,&head);
data->value = tmp;
mutex_unlock(®istration_lock);
}

static ssize_t my_write(struct file*file,const char __user* buffer,
size_t count, loff_t *ppos)
{
add_data(buffer,count);
return count;
}

static const struct seq_operations proc_test_seq_ops = {//标准的seq_operation操作集合
.start	= test_seq_start,
.next	= test_seq_next,
.stop	= test_seq_stop,
.show	= test_seq_show,
};

static int proc_test_open(struct inode *inode, struct file *file)
{
return seq_open(file, &proc_test_seq_ops); //prco属性文件和proc_test_seq_ops绑定
}

static const struct file_operations test_proc_fops = {
.owner		= THIS_MODULE,
.open		= proc_test_open,
.read		= seq_read,
.write      = my_write,
.llseek		= seq_lseek,
.release	= seq_release,
};

struct proc_dir_entry *entry = NULL;

int hello_init(void)
{
struct proc_dir_entry *son = NULL;
struct proc_dir_entry *root= NULL;

mutex_init(®istration_lock); //这里变量在初始化时,一定要初始化。
INIT_LIST_HEAD(&head); //记得初始化。
root = proc_mkdir("father",NULL); //在proc目录下,建立一个father目录。
entry = proc_create("test",0666, root, &test_proc_fops); //test属性文件和test_proc_fops操作集合绑定。
if (!entry){
printk(KERN_ALERT "create proc dir failed\n");
return -1;
}
son = proc_mkdir("son",root);//这里仅仅是为了测试,没具体意义。
printk(KERN_ALERT "hello init ok\n");
return 0;
}

void hello_exit(void)
{
proc_remove(entry);
printk(KERN_ALERT "Hello Exit!\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
效果:上面的模块编译运行后就会在proc目录创建一个father和son目录,加上一个test属性文件。



测试结果:
第一去cat test文件时,可以发现什么都没有。后面相继写入,打印,结果大家看看吧。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: