LINUX内核中编写procfs

procfs

注意:适用于2.7-3.7,3.10之后的版本procfs又做了改变,因此编译最后的源码会出现错误。如果编译出现”dereferencing pointer to incomplete type“类似的错误,那就是版本变更了。新版本里用的是file_operation。

说明文档

首先要明白,procfs终究还是fs,也就是属于filesystem的一种。因此可以在内核文档中找到相关资料。如果你手头上有一份内核源码,那么可以根据以下相对路径找到这份说明:
./Documentation/filesystems/proc.txt

直接在网上也可以看到这份文档:
http://lxr.oss.org.cn/source/Documentation/filesystems/proc.txt

API

比较“官方”的procfs的API文档:
http://kernelnewbies.org/Documents/Kernel-Docbooks?action=AttachFile&do=get&target=procfs-guide_2.6.29.pdf

从url中可以看出,这是一份2.6版本内核所写的文档。目前而言,算是最新的了。毕竟2.6和2.4这两个版本之间的差距有点大。http://kernelnewbies.org这个网站里有很多适合Linux新手可以使用的东西,有兴趣可以收藏一下,以后查资料什么的也有个地方。

下面开始对一些关键API的解释,源码可以在以下相对路径中找到:
./kernel/fs/proc/generic.c

1.struct proc_dir_entry* create_proc_entry(const char* name, mode_t mode,struct proc_dir_entry* parent);
@name:创建出的procfs文件的名字
@mode:文件的权限,跟linux文件权限一样,默认为755
@parent:文件的父母节点,为NULL则会直接创建在’/proc‘目录下。可以不为空,详情参考下面一条API

创建一个procfs文件。表现是会在‘/proc’目录下多出一个名为@name的文件

2.struct proc_dir_entry* proc_mkdir(const char* name, struct proc_dir_entry*parent);
@name:文件夹的名字
@parent:父节点,与上面的API一样,可以为空

这个函数创建了一个文件夹,因此如果想在‘/proc’下创建一个文件夹,用于存放自定义的proc文件,则可以调用该函数创建一个文件夹。并将该函数的返回值作为‘create_proc_entry’函数的@parent参数。

3.void remove_proc_entry(const char* name, struct proc_dir_entry* parent);
@name:文件名称
@parent:父节点,指的是proc_mkdir创建出来的文件夹,或者为空

根据@name删除已创建的procfs文件,如果有调用上面提到的proc_mkdir,则后面的parent不能为NULL

4.数据结构:struct proc_dir_entry;直接上源码

struct proc_dir_entry {
    unsigned int low_ino;
    umode_t mode;
    nlink_t nlink;
    kuid_t uid;
    kgid_t gid;
    loff_t size;
    const struct inode_operations *proc_iops;
    /*
     * NULL ->proc_fops means "PDE is going away RSN" or
     * "PDE is just created". In either case, e.g. ->read_proc won't be
     * called because it's too late or too early, respectively.
     *
     * If you're allocating ->proc_fops dynamically, save a pointer
     * somewhere.
     */
    const struct file_operations *proc_fops;
    struct proc_dir_entry *next, *parent, *subdir;
    void *data;
    read_proc_t *read_proc;
    write_proc_t *write_proc;
    atomic_t count;     /* use count */
    int pde_users;  /* number of callers into module in progress */
    struct completion *pde_unload_completion;
    struct list_head pde_openers;   /* who did ->open, but not ->release */
    spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
    u8 namelen;
    char name[];
};

在使用的过程中,比较重要的成员有两个:
@read_proc
@write_proc
这两个成员需要直接调用赋值函数指针。代表的意思很明显,就是进行读和写创建的procfs文件时会调用到。另外,由于在系统内部处理过程中,真正读的过程中会调用到另外一个函数’__proc_file_read‘其中有一行:

n = dp->read_proc(page, &start, *ppos,count, &eof, dp->data);

因此赋值的函数需要注意格式,参数应该类似如下:

func(char *page, char **start,off_t off, int count,int *eof, void *data)

写的函数参数与读一致。简单说一下这些参数代表的意义:
@page:指向的是文件内容,也就是打开文件时可以在屏幕上看到的。如名字所示,空
间大小根据page_size决定,一般X86机器上均为4K页。有修改过页大小的则例外

@start:如果是用file_operation进行读写操作时候有用,这里可以忽略
@count:同上,忽略
@eof:需要赋值为’\0’,标明读写结束
@data:procfs的私有地址数据

源代码

最后,附上一份删减过的源代码
注意:内核版本3.10之后的版本procfs又做了改变,因此不能最后的源码会出现错误。如果编译出现”dereferencing pointer to incomplete type“类似的错误,那就是版本变更了。新版本里用的是file_operation。

#include 
#include 
#include 
#include 

#define MODULE_NAME"procfs_example"
#define MODULE_VERS"1.0"
#define FOOBAR_LEN 8
struct fb_data_t {
char name[FOOBAR_LEN + 1];
char value[FOOBAR_LEN + 1];
};

static struct proc_dir_entry *example_dir, *foo_file;
struct fb_data_t foo_data, bar_data;

static int proc_read_foobar(char *page, char **start,off_t off, int count,int *eof, void *data)
{
    int len;
    struct fb_data_t *fb_data = (struct fb_data_t *)data;
    /* DON’T DO THAT - buffer overruns are bad */
    len = sprintf(page, "%s = ’%s’\n",fb_data->name, fb_data->value);
    return len;
}
static int proc_write_foobar(struct file *file,const char *buffer,
unsigned long count,void *data)
{
    int len;
    struct fb_data_t *fb_data = (struct fb_data_t *)data;
    if(count > FOOBAR_LEN)
        len = FOOBAR_LEN;
    else
        len = count;
    if(copy_from_user(fb_data->value, buffer, len))
    return -EFAULT;
    fb_data->value[len] = '\0';
    return len;
}
static int __init init_procfs_example(void)
{
    int rv = 0;
    /* create directory */
    example_dir = proc_mkdir(MODULE_NAME, NULL);
    if(example_dir == NULL) {
        rv = -ENOMEM;
        goto out;
    }
    example_dir->owner = THIS_MODULE;
    foo_file = create_proc_entry("foo", 0644, example_dir);
    if(foo_file == NULL) {
        rv = -ENOMEM;
        goto no_foo;
    }
    strcpy(foo_data.name, "foo");
    strcpy(foo_data.value, "foo");
    foo_file->data = &foo_data;
    foo_file->read_proc = proc_read_foobar;
    foo_file->write_proc = proc_write_foobar;
    foo_file->owner = THIS_MODULE;
    return rv;
no_foo:
    remove_proc_entry("foo", example_dir);
    remove_proc_entry(MODULE_NAME, NULL);
out:
    return rv;
}
static void __exit cleanup_procfs_example(void)
{
    remove_proc_entry("foo", example_dir);
    remove_proc_entry(MODULE_NAME, NULL);
    printk(KERN_INFO "%s %s removed\n",
    MODULE_NAME, MODULE_VERS);
}
module_init(init_procfs_example);
module_exit(cleanup_procfs_example);
MODULE_LICENSE("GPL");

你可能感兴趣的:(linux内核)