内核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文件系统中。