linux procfs文件系统(2)

内核proc文件系统主要代码位于fs/proc目录内, 在内核称之为procsfs文件系统。看内核代码其实有一定的规律可言,尤其是这种与硬件无关的处理模块。查看内核模块代码首先需要先熟悉该模块API的使用,通过自己编写用例来使用,会更加对这个模块有深入的了解,其次看源代码,需要了解该模块的核心数据结构以及其设计原理,每个字段大概什么意思,后面需要根据使用的API 梳理代码就方便很多,经过长时间熟悉设计原则之后,就大概能够根据API 猜出里面一些大概流程然后根据问题看代码就会非常快。

struct proc_dir_entry 

procfs文件系统的核心数据结构为proc_dir_enty,内核将proc目录下的文件或者文件夹都统一抽象成proc_dir_entry结构,按照一条条entry 组织成树形结构,该数据结构定义在fs/proc/internal.h文件中,在最新5.8.11版本中结构定义主要如下:

/*
 * This is not completely implemented yet. The idea is to
 * create an in-memory tree (like the actual /proc filesystem
 * tree) of these proc_dir_entries, so that we can dynamically
 * add new files to /proc.
 *
 * parent/subdir are used for the directory structure (every /proc file has a
 * parent, but "subdir" is empty for all non-directory entries).
 * subdir_node is used to build the rb tree "subdir" of the parent.
 */
struct proc_dir_entry {
	/*
	 * number of callers into module in progress;
	 * negative -> it's going away RSN
	 */
	atomic_t in_use;
	refcount_t refcnt;
	struct list_head pde_openers;	/* who did ->open, but not ->release */
	/* protects ->pde_openers and all struct pde_opener instances */
	spinlock_t pde_unload_lock;
	struct completion *pde_unload_completion;
	const struct inode_operations *proc_iops;
	union {
		const struct proc_ops *proc_ops;
		const struct file_operations *proc_dir_ops;
	};
	const struct dentry_operations *proc_dops;
	union {
		const struct seq_operations *seq_ops;
		int (*single_show)(struct seq_file *, void *);
	};
	proc_write_t write;
	void *data;
	unsigned int state_size;
	unsigned int low_ino;
	nlink_t nlink;
	kuid_t uid;
	kgid_t gid;
	loff_t size;
	struct proc_dir_entry *parent;
	struct rb_root subdir;
	struct rb_node subdir_node;
	char *name;
	umode_t mode;
	u8 flags;
	u8 namelen;
	char inline_name[];
} __randomize_layout;

比较关键字段:

atomic_t in_use:该字段为原子操作数据,表明该entry正在被使用的数量,为防止多个进程同时使用造成冲突需要使用原子操作相关接口,对该数据的增加或删除分别封装在use_pde()和unuse_pde()函数,该函数位于fs\proc\node.c文件中,函数定义如下:

static inline int use_pde(struct proc_dir_entry *pde)
{
	return likely(atomic_inc_unless_negative(&pde->in_use));
}

static void unuse_pde(struct proc_dir_entry *pde)
{
	if (unlikely(atomic_dec_return(&pde->in_use) == BIAS))
		complete(pde->pde_unload_completion);
}

每次对proc目录下的文件进行读取,打开,修改或者map操作之后都会首先增加in_use计数,当操作完毕之后再减少in_use计数。

refcount_t refcnt:引用计数, 和in_use不同,in_user侧重于有多少个用户或者进程增在操作该文件,而refcount_t refcnt为引用计数,即通过inode_operations操作中的lookup被调用此次即被查找次数。

struct list_head pde_openers:用于记录有所有打开还没有调用close的  opener,即当前文件正在被多少opener打开。

spinlock_t pde_unload_lock用于对pde_openers数据加锁,防止多个进程或者线程同时操作pde_openers数据

struct completion *pde_unload_completion:用于等待所有的pde_openers都操作完毕之后。

const struct inode_operations *proc_iops:procfs文件系统中不同的文件类型对应的不同inode_operations操作:

文件类型 const struct inode_operations *proc_iops
软链接 proc_link_inode_operations
文件夹directory proc_dir_inode_operations
文件file proc_file_inode_operations
proc根节点root proc_root_inode_operations
/proc/sys proc_sys_dir_operations

proc_ops or proc_dir_ops: 该结构为一个联合体,主要是根据文件类型,如果是一个文件则为proc_ops,如果是一个目录directory:proc_dir_ops

const struct dentry_operations *proc_dops: proc操作封装,类似于inode_operations。

const struct seq_operations *seq_ops or int (*single_show)(struct seq_file *, void *): 该结构为一个联合体,主要是针对用户读取/proc 相应文件的内核数据时,该数据是一个单项,还是需要通过一个迭代器循环遍历。

proc_write_t write:用户空间程序 往proc文件写数据时对应的操作方法。

void *data: 对应私有数据。

unsigned int state_size:私有数据大小

unsigned int low_ino: inode numerber 每个proc文件或者目录都有唯一一个,范围从PROC_DYNAMIC_FIRST(0xF0000000U) ~ 0xffffffff,

nlink_t nlink:链接文件,如果是文件夹则说明该文件夹下有多少个文件,从2开始,因为文件夹下面默认有"."和“..”,如果是一个文件创建链接则为1.

kuid_t uid: 该文件所属user id

kgid_t gid: 该文件所属group id.

loff_t size:  文件大小 或者偏移。

struct proc_dir_entry *parent: 该文件所属的parent entry.

struct rb_root subdir: 该文件夹内所有的文件

struct rb_node subdir_node: 该文件夹内所有的子文件夹

char *name: 该entry 名称

umode_t mode: 该文件模式, 支持模式主要有如下:

#define S_IFMT  00170000
#define S_IFSOCK 0140000
#define S_IFLNK	 0120000
#define S_IFREG  0100000
#define S_IFBLK  0060000
#define S_IFDIR  0040000
#define S_IFCHR  0020000
#define S_IFIFO  0010000
#define S_ISUID  0004000
#define S_ISGID  0002000
#define S_ISVTX  0001000

u8 flags: 该entry flags

u8 namelen;: 该entry 名称长度

char inline_name[]: 该文件内部名称 一半和 name相同。

API列表

proc文件系统可以按照其文件类型:file,directory和symlink三种类型,下面按照上述三种类型进行说明

directory API

该类型文件主要是在proc文件系统内创建或者删除一个目录。

API名称 Description说明
struct proc_dir_entry *proc_mkdir(const char *name,  struct proc_dir_entry *parent) 在proc文件系统内创建一个directory, name为该directory 名称, parent为其归属的哪个parent directory,如果为NULL,则创建的directory位于/proc根目录内 
struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,  struct proc_dir_entry *parent, void *data) 在proc文件系统内创建一个directory, 与上述API相比多了一个参数data,代表的是传入到entry中的私有数据,后面处理需要用到
struct proc_dir_entry *proc_mkdir_mode(const char *name, umode_t mode,     struct proc_dir_entry *parent) 在proc文件系统内创建一个directory, 与上述API相比多了一个参数mode, 代表由驱动程序指定该enntry 权限模式 不采用默认权限
void proc_remove(struct proc_dir_entry *de) 删除传入的entry
void remove_proc_entry(const char *name, struct proc_dir_entry *parent) 根据名称删除entry, 其中paren为要产出的entry的父entry,parent一定要传对 否则就会因找不到而无法删除。如果parent为NULL,则位于/proc根目录

directory API 用例

创建一个directory最简单的一个用例:  /proc 根目录下创建一个my_proc 目录,代码如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
MODULE_LICENSE("GPL v2");

#define MY_PROC "my_proc"

static struct proc_dir_entry * my_proc_entry=NULL;

static int __init my_proc_init(void)
{
   my_proc_entry = proc_mkdir(MY_PROC,NULL);
   if (NULL == my_proc_entry)
   {
       return -ENODEV;
   }

    return 0;
}

static void __exit my_proc_exit(void)
{
   remove_proc_entry(MY_PROC, my_proc_entry);
}

module_init(my_proc_init);
module_exit(my_proc_exit);
MODULE_AUTHOR("hzk");
MODULE_DESCRIPTION("A simple moudule for my proc");
MODULE_VERSION("V1.0");

file API

file 类别API主要是用于在proc目录内创建fie,用于读取或者修改内核数据,主要API类型如下:

API名称 Description说明
struct proc_dir_entry *proc_create(const char *name, umode_t mode,    struct proc_dir_entry *parent,      const struct proc_ops *proc_ops) 创建名称为name的 proc文件,mode为文件模式包括权限等, parent为该文件所属的目录, proc_ops为对该文件操作封装,主要包括读写等功能
struct proc_dir_entry *proc_create_single_data(const char *name, umode_t mode,    struct proc_dir_entry *parent,  int (*show)(struct seq_file *, void *), void *data) 创建名称为name的 proc文件,mode为文件,parent为该文件所属的目录,一般使用该方法创建的proc文件是只读,所以仅仅提供了读show操作 , data为要读取的数据
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
        struct proc_dir_entry *parent,    const struct proc_ops *proc_ops, void *data)
创建名称为name的 proc文件,mode为文件,parent为该文件所属的目录,与proc_create函数相比较 多个data参数,指明传入的私有数据即要操作的数据
int remove_proc_subtree(const char *name, struct proc_dir_entry *parent) 删除parent目录内名称为name的子目录内的所有文件
void proc_remove(struct proc_dir_entry *de) 删除传入的entry
void remove_proc_entry(const char *name, struct proc_dir_entry *parent) 根据名称删除entry, 其中paren为要产出的entry的父entry,parent一定要传对 否则就会因找不到而无法删除。如果parent为NULL,则位于/proc根目录

file API用例一

使用proc_create_single_dataAPI创建两个文件分别为proc_1和proc_2,各种的私有数据分别为my_proc_1_data和my_proc_2_data,用例代码如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
MODULE_LICENSE("GPL v2");

#define MY_PROC "my_proc"

static struct proc_dir_entry * my_proc_entry=NULL;
static DEFINE_MUTEX(my_proc_lock);
static int my_proc_1_data=1;
static int my_proc_2_data=2;


static int my_proc_1_show(struct seq_file *s, void *v)
{
    int * value = s->private;
    seq_printf(s, "proc_1 value:%d\n", *value);
    return 0;
}

static int my_proc_2_show(struct seq_file *s, void *v)
{
    int * value = s->private;
    seq_printf(s, "proc_2 value:%d\n", *value);
    return 0;
}

static int __init my_proc_init(void)
{
   my_proc_entry = proc_mkdir(MY_PROC, NULL);
   if (NULL == my_proc_entry)
   {
       return -ENODEV;
   }

    proc_create_single_data("proc_1", 0, my_proc_entry, my_proc_1_show, &my_proc_1_data);
    proc_create_single_data("proc_2", 0, my_proc_entry, my_proc_2_show, &my_proc_2_data);
    return 0;
}

static void __exit my_proc_exit(void)
{
   remove_proc_entry(MY_PROC, NULL);
}

module_init(my_proc_init);
module_exit(my_proc_exit);
MODULE_AUTHOR("hzk");
MODULE_DESCRIPTION("A simple moudule for my proc");
MODULE_VERSION("V1.0");

proc_ops接口

proc_ops结构是对proc文件操作的封装,主要包括文件打开,读写等常用操作,该文件定义在include\linux\proc_fs.h文件中

struct proc_ops {
	unsigned int proc_flags;
	int	(*proc_open)(struct inode *, struct file *);
	ssize_t	(*proc_read)(struct file *, char __user *, size_t, loff_t *);
	ssize_t	(*proc_write)(struct file *, const char __user *, size_t, loff_t *);
	loff_t	(*proc_lseek)(struct file *, loff_t, int);
	int	(*proc_release)(struct inode *, struct file *);
	__poll_t (*proc_poll)(struct file *, struct poll_table_struct *);
	long	(*proc_ioctl)(struct file *, unsigned int, unsigned long);
#ifdef CONFIG_COMPAT
	long	(*proc_compat_ioctl)(struct file *, unsigned int, unsigned long);
#endif
	int	(*proc_mmap)(struct file *, struct vm_area_struct *);
	unsigned long (*proc_get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
} __randomize_layout;

seq_file接口

在早期的/proc文件系统的实现中,都是由驱动开发人员在创建proc文件时直接提供read,write方法,驱动开发人员直接操纵buf,但是存在这样一个问题当读取改文件时 要显示很多或者里面很很多项目是,buf经常不够,很容易造成越界现象,因此后来经过改进seq_file 接口,该接口通过创建一个简单的迭代器对象,该对象用来表示项目序列中的位置,每前进一步,该对象输出序列中的一个项目,这样每此迭代只显示一个迭代。

接口API description描述
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 读取文件
int seq_open(struct file *file, const struct seq_operations *op) 打开文件
loff_t seq_lseek(struct file *file, loff_t offset, int whence) lseek操作
int single_release(struct inode *inode, struct file *file) 单次迭代释放
int seq_release(struct inode *inode, struct file *file) 多次迭代释放
int single_open(struct file *file, int (*show)(struct seq_file *, void *),    void *data) 单次迭代打开
int single_open_size(struct file *file, int (*show)(struct seq_file *, void *),   void *data, size_t size) 代词迭代打开,包括数据大小
int single_open_size(struct file *file, int (*show)(struct seq_file *, void *),     void *data, size_t size) 写操作
struct list_head *seq_list_start(struct list_head *head, loff_t pos) seq list迭代开始
struct list_head *seq_list_start_head(struct list_head *head, loff_t pos) seq list头
struct list_head *seq_list_next(void *v, struct list_head *head, loff_t *ppos) seq 下次迭代

seq file将操作将整个迭代进行封装 每次迭代顺序分为 start->show->next->stop一个完整过程,开发人员只需要实现open 和show方法即可,其他proc_ops结构只需要挂载seq即可。

其中进行show方法实现时,不能使用printk进行打印,而是使用seq_file提供的方法进行打印:

接口API description描述
void seq_printf(struct seq_file *m, const char *f, ...) 等价与printf函数,需要将show函数传入的seq_file结构传递给这个函数,如果seq_printf返回一个非零值,则意味着缓冲区已满
void seq_putc(struct seq_file *m, char c) 打印一个char
void seq_puts(struct seq_file *m, const char *s) 打印一个字符串
void seq_escape(struct seq_file *m, const char *s, const char *esc) 等价于seq_puts函数,若s中的某个字符也存在于esc参数,则该字符以八进制形式打印。传递给esc参数的常见值“\t\n\\”,可以避免要输出的空白字符弄乱屏幕或者迷惑shell脚本

使用seq接口时,一般proc_open函数需要开发自己实现,主要时调用seq_open函数将 seq_operations复制给file接口,以从proc_op便来接管整个操作

 seq按照迭代次数分为单次或者多次迭代,两者在proc_ops挂载时,使用pro_relase接口不同,下面分别展示单次和多次迭代用例

seq单次用例

使用seq接口展示单次show用例,读取my_proc文件时,显示其中内核中的my_proc_data值:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
MODULE_LICENSE("GPL v2");

#define MY_PROC "my_proc"

static struct proc_dir_entry * my_proc_entry=NULL;

static int my_proc_show(struct seq_file *m, void * v)
{
    seq_printf(m,"my proc test\n");
    return 0;
}

static int my_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, my_proc_show, NULL);
}

static const struct proc_ops my_proc_ops = {
    .proc_open = my_proc_open,
    .proc_read = seq_read,
    .proc_lseek= seq_lseek,
    .proc_release = single_release,
};
static int __init my_proc_init(void)
{
   my_proc_entry = proc_create(MY_PROC,S_IRUGO, NULL, &my_proc_ops);
   if (NULL == my_proc_entry)
   {
       return -ENOMEM;
   }

    return 0;
}

static void __exit my_proc_exit(void)
{
   remove_proc_entry(MY_PROC, NULL);
}

module_init(my_proc_init);
module_exit(my_proc_exit);
MODULE_AUTHOR("hzk");
MODULE_DESCRIPTION("A simple moudule for my proc");
MODULE_VERSION("V1.0");

seq多次迭代用例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
MODULE_LICENSE("GPL v2");

#define MY_PROC "my_proc"

static struct proc_dir_entry * my_proc_entry=NULL;
static DEFINE_MUTEX(my_proc_lock);
static int my_proc_data=0;
static void * my_proc_start(struct seq_file *s, loff_t *pos)
{
    mutex_lock(&my_proc_lock);

    printk(KERN_INFO "my_proc start:%d\n", *pos);
    if (*pos>5)
    {
        return NULL;
    }
    return (void *)&my_proc_data;
    
}

static void *my_proc_next(struct seq_file *s, void *v, loff_t *pos)
{
    if (*pos>5)
    {
        return NULL;
    }

    my_proc_data++;
    (*pos)++;
    return (void *)&my_proc_data;
}


static int my_proc_show(struct seq_file *s, void *v)
{
    int * value = v;
    seq_printf(s, "my_proc value:%d\n", *value);
    return 0;
}
static void my_proc_stop(struct seq_file *s, void *v)
{
    my_proc_data = 0;
    mutex_unlock(&my_proc_lock);
}

static const struct seq_operations my_seq_ops = {
    .start = my_proc_start,
    .next  = my_proc_next,
    .stop  = my_proc_stop,
    .show  = my_proc_show,
};
static int my_proc_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &my_seq_ops);
}

static const struct proc_ops my_proc_ops = {
    .proc_open = my_proc_open,
    .proc_read = seq_read,
    .proc_lseek= seq_lseek,
    .proc_release = seq_release,
};
static int __init my_proc_init(void)
{
   my_proc_entry = proc_create(MY_PROC,S_IRUGO, NULL, &my_proc_ops);
   if (NULL == my_proc_entry)
   {
       return -ENOMEM;
   }

    return 0;
}

static void __exit my_proc_exit(void)
{
   remove_proc_entry(MY_PROC, NULL);
}

module_init(my_proc_init);
module_exit(my_proc_exit);
MODULE_AUTHOR("hzk");
MODULE_DESCRIPTION("A simple moudule for my proc");
MODULE_VERSION("V1.0");

 symlink接口

symlink接口主要提供一个软链接功能:

API名称 Description说明
struct proc_dir_entry *proc_symlink(const char *name,
        struct proc_dir_entry *parent, const char *dest)
创建名称为name的 proc 链接文件, parent为该文件所属的目录, dest为要链接为文件名
void proc_remove(struct proc_dir_entry *de) 删除传入的entry
void remove_proc_entry(const char *name, struct proc_dir_entry *parent) 根据名称删除entry, 其中paren为要产出的entry的父entry,parent一定要传对 否则就会因找不到而无法删除。如果parent为NULL,则位于/proc根目录

symlink用例

symlink用例相对比较简单:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
MODULE_LICENSE("GPL v2");

#define MY_PROC "my_proc"

static struct proc_dir_entry * my_proc_entry=NULL;
static struct proc_dir_entry * my_proc_link =NULL;
static DEFINE_MUTEX(my_proc_lock);
static int my_proc_data=0;
static void * my_proc_start(struct seq_file *s, loff_t *pos)
{
    mutex_lock(&my_proc_lock);

    printk(KERN_INFO "my_proc start:%d\n", *pos);
    if (*pos>5)
    {
        return NULL;
    }
    return (void *)&my_proc_data;
    
}

static void *my_proc_next(struct seq_file *s, void *v, loff_t *pos)
{
    if (*pos>5)
    {
        return NULL;
    }

    my_proc_data++;
    (*pos)++;
    return (void *)&my_proc_data;
}


static int my_proc_show(struct seq_file *s, void *v)
{
    int * value = v;
    seq_printf(s, "my_proc value:%d\n", *value);
    return 0;
}
static void my_proc_stop(struct seq_file *s, void *v)
{
    my_proc_data = 0;
    mutex_unlock(&my_proc_lock);
}

static const struct seq_operations my_seq_ops = {
    .start = my_proc_start,
    .next  = my_proc_next,
    .stop  = my_proc_stop,
    .show  = my_proc_show,
};
static int my_proc_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &my_seq_ops);
}

static const struct proc_ops my_proc_ops = {
    .proc_open = my_proc_open,
    .proc_read = seq_read,
    .proc_lseek= seq_lseek,
    .proc_release = seq_release,
};
static int __init my_proc_init(void)
{
   my_proc_entry = proc_create(MY_PROC,S_IRUGO, NULL, &my_proc_ops);
   if (NULL == my_proc_entry)
   {
       return -ENOMEM;
   }

   my_proc_link =proc_symlink("my_link", NULL, MY_PROC);
   if (NULL == my_proc_link)
   {
       remove_proc_entry(MY_PROC, NULL);
       return -ENOMEM;
   }
   return 0;
}

static void __exit my_proc_exit(void)
{
   remove_proc_entry("my_link", NULL);
   remove_proc_entry(MY_PROC, NULL);
}

module_init(my_proc_init);
module_exit(my_proc_exit);
MODULE_AUTHOR("hzk");
MODULE_DESCRIPTION("A simple moudule for my proc");
MODULE_VERSION("V1.0");

总结

现在内核发展要求一般不建议在proc文件再新添加文件,因为proc使用起来显得特别臃肿,由于没有THIS_MODULE,有可能会发生删除正在被使用的文件。另外一个问题就是同一个名字的注册问题。

随着sysfs文件系统完善,一般如果添加新的配置项建议加入到sysfs文件系统中。

上一篇:Track R-CNN代码阅读(一)之KITTI_segtrack.py


下一篇:语雀在线表格自研之路