使用procfs

在《使用sysctl》一文中说到sysctl也是基于procfs设计的,那这次我们来对procfs进行一番了解吧。 在源码的include/linux/proc_fs.h文件里有procfs相关API的声明,其实现在fs/proc/generic.c文件中,由于从linux-3.10版本内核起,create_proc_entry()和create_proc_read_entry()函数被去除了,需使用proc_create()来代替了,先了解下本次学习涉及的内容:
1.proc_create()
在include/linux/proc_fs.h文件里有如下定义:
static inline struct proc_dir_entry *proc_create(
        const char *name, umode_t mode, struct proc_dir_entry *parent,
        const struct file_operations *proc_fops)
{
        return proc_create_data(name, mode, parent, proc_fops, NULL);
}
其中,name是创建在/proc目录下的节点(文件、目录)名称,mode是创建的节点的权限,其在include/uapi/linux/stat.h有相关宏(如S_IRWXU),parent指定当前节点的父目录节点,如果没有就设为NULL,proc_fops是设置对该节点的操作回调函数。 该函数默认创建文件节点,如果要创建目录节点呢?那么修改mode参数,把S_IFDIR宏“|”上即可。
2.proc_remove
有了创建函数,那么对应的就是移除函数了,在fs/proc/generic.c文件里有该函数的定义:
void proc_remove(struct proc_dir_entry *de)
{
        if (de)
                remove_proc_subtree(de->name, de->parent);
}
其在删除创建的节点时会把挂在该节点上相应的子节点一并删除。
3.proc_mkdir和proc_mkdir_mode
在上面proc_create函数里说到创建目录节点要修改mode,其实在fs/proc/generic.c文件里有如下定义:
struct proc_dir_entry *proc_mkdir_mode(const char *name, umode_t mode,
        struct proc_dir_entry *parent)
{
        return proc_mkdir_data(name, mode, parent, NULL);
}
struct proc_dir_entry *proc_mkdir(const char *name,
        struct proc_dir_entry *parent)
{
        return proc_mkdir_data(name, 0, parent, NULL);
}
其最终调用的proc_mkdir_data函数在同一文件里定义如下:
struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
        struct proc_dir_entry *parent, void *data)
{
        struct proc_dir_entry *ent;

        if (mode == 0)
                mode = S_IRUGO | S_IXUGO;

        ent = __proc_create(&parent, name, S_IFDIR | mode, 2);
        if (ent) {
                ent->data = data;
                if (proc_register(parent, ent) < 0) {
                        kfree(ent);
                        ent = NULL;
                }
        }
        return ent;
}
看到了吧,就在mode这里的S_IFDIR,该函数不用多说,也是可以看得懂了。
4.proc_symlink
在fs/proc/generic.c文件里有相应的定义,在include/linux/proc_fs.h文件里有如下声明:
extern struct proc_dir_entry *proc_symlink(const char *,
        struct proc_dir_entry *, const char *);
其中,第一个参数name,第二个参数parent,第三个参数dest,在generic.c文件里是这样定义的,该函数就是在parent目录下创建name节点,该节点为指向dest的软链接,name与dest都以parent作为同级目录处理(如果parent为空则以/proc目录)。
5.struct proc_dir_entry
在fs/proc/internal.h文件里有如下定义:
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;
        const struct file_operations *proc_fops;
        struct proc_dir_entry *next, *parent, *subdir;
        void *data;
        atomic_t count; /* use count */
        atomic_t in_use; /* number of callers into module in progress; */
        /* negative -> it's going away RSN */
        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[];
};
该结构体便于我们对照,在这里不详细说明,下面的结构体也一样,我们本着实践的宗旨,在这里先学会用,后期有专栏来分析这些结构体。
6.struct file_operations
在include/linux/fs.h文件里有如下的结构体:
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                loff_t len);
        int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
下面还是我们的主要环节,上代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>

struct proc_dir_entry * slam_dir = NULL;
struct proc_dir_entry * slam_entry = NULL;

static char * dir_name = "slam";
static char * entry_name = "xinu";
static char * symlink_name = "stxinu";

static ssize_t xinu_proc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
        char *str = "My name is xinu.\n";
        int len = strlen(str);
        copy_to_user(buf, str, len);
        if (*ppos == 0)
                *ppos += len;
        else
                len = 0;
        return len;
}

static ssize_t xinu_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
        char str[128];
        if (count > PAGE_SIZE) //procfs write and read has PAGE_SIZE limit
        count = 128;

        if (copy_from_user(str, buf, count))
        {
                printk("copy_from_user failed!\n");
                return -EFAULT;
        }

        str[count-1] = '\0';
        printk("Your enter :\n%s\n", str);
        return count;
}

static const struct file_operations slam_fops =
{
        .owner = THIS_MODULE,
        .read = xinu_proc_read,
        .write = xinu_proc_write,
};

static __init int procfs_example_init(void)
{
        #ifdef CONFIG_PROC_FS
                slam_dir = proc_mkdir(dir_name, NULL);
                if (!slam_dir)
                {
                        printk("Create directory \"%s\" failed.\n", dir_name);
                        return -1;
                }

                slam_entry = proc_create(entry_name, 0666, slam_dir, &slam_fops);
                if (!slam_entry)
                {
                        printk("Create file \"%s\" and symlink \"%s\" failed.\n", entry_name, symlink_name);
                        return -1;
                }

                proc_symlink(symlink_name, slam_dir, entry_name);
        #else
                printk("This module requests the kernel to support procfs,need set CONFIG_PROC_FS configure Y\n");
        #endif
        return 0;
}

static __exit void procfs_example_exit(void)
{
        #ifdef CONFIG_PROC_FS
                proc_remove(slam_entry);
                proc_remove(slam_dir);
        #endif
}

module_init(procfs_example_init);
module_exit(procfs_example_exit);
对应的Makefile文件内容如下:
obj-m += procfs_example.o

CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6

all:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
对应的源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/procfs_example/
├── Makefile
└── procfs_example.c
当编译加载驱动后,我们可以看到在/proc目录下有slam目录,slam目录下有xinu和stxinu两个文件节点,而stxinu则是指向xinu的软链接,即/proc/slam/stxinu->/proc/slam/xinu。
当我们对相应文件节点操作时,有如下输出:
xinu@slam:~$ ls -l /proc/slam/stxinu
lrwxrwxrwx 1 root root 4 May 4 17:30 /proc/slam/stxinu -> xinu
xinu@slam:~$ ls -l /proc/slam/xinu
-rw-rw-rw- 1 root root 0 May 4 17:30 /proc/slam/xinu
xinu@slam:~$ cat /proc/slam/stxinu
My name is xinu.
xinu@slam:~$ cat /proc/slam/xinu
My name is xinu.
xinu@slam:~$ echo “Hello” > /proc/slam/xinu
xinu@slam:~$ echo “Hello” > /proc/slam/stxinu
当我们执行最后两句后,可通过dmesg看到如下输出:
[31451.840871] Your enter :
[31451.840871] Hello
[31462.441119] Your enter :
[31462.441119] Hello
至此,我们再次体验了另一种内核态与用户态交换方式了,继续加油!
参考网址:
https://www.ibm.com/developerworks/cn/linux/l-kerns-usrs2/
http://www.embeddedlinux.org.cn/html/yingjianqudong/201304/17-2552.html
http://brightconan.com/?p=14
http://nano-chicken.blogspot.com/2009/11/linux-modulesiii.html
http://nano-chicken.blogspot.com/2009/12/linux-modules31-procfsvector.html
http://nano-chicken.blogspot.com/2010/04/linux-modules32-procfssymlinkmkdir_16.html
https://lwn.net/Articles/549737/
http://reverland.org/linux-tech/2013/12/24/writing-drivers-in-linuxa-brief-tutorial/

评论

此博客中的热门博文

I/O映射之I/O端口

通过Netlink检测网线插拔

使用seq_file