在linux系统中,“/proc”文件系统十分有用,它被内核用于向用户导出信息。“/proc”文件系统是一个虚拟文件系统,通过它可以在linux内核空间和用户空间之间进行通信。在/proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,与普通文件不同的是,这些虚拟文件的内容是动态创建的。(/proc文件系统中的文件是动态创建的,驱动加载的时候创建,关机或卸载的时候删除,与我们使用touch命令创建的文件不一样,touch创建的文件只存在于用户空间,且如果不使用rm等删除的话,会一直保留,且/proc文件可以用在内核空间和用户空间之间进行通信,比如用于tft显示屏的帧缓冲驱动)
"/proc"下的绝大多数文件是只读的,以显示内核信息为主。但是“/proc”下的文件也并不是完全只读的,若节点可写,还可用于一定的控制或配置目的。例如,通过写/proc/sys/kernel/printk可以改变printk()的打印级别。
linux系统的许多命令本身都是通过分析“/proc”下的文件来完成的,如ps、top、uptime和free等。例如,free命令通过分析/proc/meminfo文件得到可用内存信息,下面显示了对应的meminfo文件和free命令的结果
/proc目录下常见的文件介绍:
/*
* 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; /* inode 操作函数集指针 */
const struct file_operations *proc_fops; /* 文件操作函数集指针 */
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; /* 低位 inode 编号 */
nlink_t nlink; /* 链接计数 */
kuid_t uid; /* 用户 ID */
kgid_t gid; /* 组 ID */
loff_t size; /* 文件大小 */
struct proc_dir_entry *parent; /* 目录项指针 */
struct rb_root subdir; /* 子目录 */
struct rb_node subdir_node;
char *name; /* 名称 */
umode_t mode; /* 文件访问权限 */
u8 namelen; /* 名称长度 */
char inline_name[];
} __randomize_layout;
proc_create()函数
/**********************************************************************
* @ name:节点名
* @ mode:权限位
* @ parent: 父目录
* @ proc_fops:文件操作结构体
**********************************************************************/
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);
}
proc_create_data()函数
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct file_operations *proc_fops, void *data)
{
struct proc_dir_entry *p;
BUG_ON(proc_fops == NULL);
p = proc_create_reg(name, mode, &parent, data);
if (!p)
return NULL;
p->proc_fops = proc_fops;
return proc_register(parent, p);
}
/******************************************************************************
* “assert”用于在代码中进行断言,如果条件为真(非零),则程序会继续执行,如果条件为假(0)
* 则程序会引发一个AssertionError异常,表明程序的某些部分没有按照预期工作
*******************************************************************************/
#define BUG_ON(__BUG_ON_cond) assert(!(__BUG_ON_cond))
proc_create_reg()函数
struct proc_dir_entry *proc_create_reg(const char *name, umode_t mode,
struct proc_dir_entry **parent, void *data)
{
struct proc_dir_entry *p;
if ((mode & S_IFMT) == 0)
mode |= S_IFREG;
if ((mode & S_IALLUGO) == 0)
mode |= S_IRUGO;
if (WARN_ON_ONCE(!S_ISREG(mode)))
return NULL;
p = __proc_create(parent, name, mode, 1);
if (p) {
p->proc_iops = &proc_file_inode_operations;
p->data = data;
}
return p;
}
__proc_create()函数
static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,
const char *name,
umode_t mode,
nlink_t nlink)
{
struct proc_dir_entry *ent = NULL;
const char *fn;
struct qstr qstr;
if (xlate_proc_name(name, parent, &fn) != 0)
goto out;
qstr.name = fn;
qstr.len = strlen(fn);
if (qstr.len == 0 || qstr.len >= 256) {
WARN(1, "name len %u\n", qstr.len);
return NULL;
}
if (qstr.len == 1 && fn[0] == '.') {
WARN(1, "name '.'\n");
return NULL;
}
if (qstr.len == 2 && fn[0] == '.' && fn[1] == '.') {
WARN(1, "name '..'\n");
return NULL;
}
if (*parent == &proc_root && name_to_int(&qstr) != ~0U) {
WARN(1, "create '/proc/%s' by hand\n", qstr.name);
return NULL;
}
if (is_empty_pde(*parent)) {
WARN(1, "attempt to add to permanently empty directory");
return NULL;
}
ent = kmem_cache_zalloc(proc_dir_entry_cache, GFP_KERNEL);
if (!ent)
goto out;
if (qstr.len + 1 <= SIZEOF_PDE_INLINE_NAME) {
ent->name = ent->inline_name;
} else {
ent->name = kmalloc(qstr.len + 1, GFP_KERNEL);
if (!ent->name) {
pde_free(ent);
return NULL;
}
}
memcpy(ent->name, fn, qstr.len + 1);
ent->namelen = qstr.len;
ent->mode = mode;
ent->nlink = nlink;
ent->subdir = RB_ROOT;
refcount_set(&ent->refcnt, 1);
spin_lock_init(&ent->pde_unload_lock);
INIT_LIST_HEAD(&ent->pde_openers);
proc_set_user(ent, (*parent)->uid, (*parent)->gid);
ent->proc_dops = &proc_misc_dentry_ops;
out:
return ent;
}
__xlate_proc_name()函数
/*
* This function parses a name such as "tty/driver/serial", and
* returns the struct proc_dir_entry for "/proc/tty/driver", and
* returns "serial" in residual.
*/
static int __xlate_proc_name(const char *name, struct proc_dir_entry **ret,
const char **residual)
{
const char *cp = name, *next;
struct proc_dir_entry *de;
unsigned int len;
de = *ret;
if (!de)
de = &proc_root;
while (1) {
next = strchr(cp, '/');
if (!next)
break;
len = next - cp;
de = pde_subdir_find(de, cp, len);
if (!de) {
WARN(1, "name '%s'\n", name);
return -ENOENT;
}
cp += len + 1;
}
*residual = cp;
*ret = de;
return 0;
}
代码解读:de = &proc_root指定初始父目录为/proc
strchr()用于检查待创建文件的文件名是否含有/,若有,则更改父目录,直到无为止,最后剩下的名字不含有/,比如tty/driver/serial,经过while循环后,其父目录为/proc/tty/driver,文件名为serial。
这段代码查找目录的方式:利用结构体来查找该目录的位置(在已知成员变量的位置后,根据结构体成员变量在结构体中的偏移量,推出结构体位置),以及先判断文件名字长度,长度一致再使用memcmp()函数来判断名字是否一致
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
/* 定义一个proc_dir_entry结构体类型变量*/
struct proc_dir_entry *hello_proc = NULL;
/* 定义一个全局数据,用来保存用户空间返回的数据 */
static char hello_data[20] = {};
/* 如果使用cat此节点,则传入的count为4K,直到读取的数据大小为4K,也就是直到此函数返回0
当此函数返回0时,读取到的内容是不显示的。
*/
static ssize_t hello_proc_read(struct file *fp, char __user *user_buf, size_t count, loff_t *ppos)
{
int ret = 0;
/* 首先清空用户空间的user_buf地址的内容,有可能显示杂乱信息 */
if (clear_user(user_buf, count)) {
printk(KERN_ERR "clear error\n");
return -EIO;
}
/* 从hello_data数组中读取数据到用户空间user_buf,读取的长度应该是字符串的大小 */
ret = simple_read_from_buffer(user_buf, count, ppos, hello_data, strlen(hello_data));
return ret;
}
/*
用户空间使用echo往此节点写入数据,只有要写入的数据写完之后,也就是返回count是,此函数此不会被调用
*/
static ssize_t hello_proc_write(struct file *fp, const char __user *user_buf, size_t count, loff_t *ppos)
{
int ret;
printk("hello_proc_write:count is %d.\n",count);
/* 写入数据之前,将数组清空 */
memset(hello_data,0,sizeof(hello_data));
/* 将用户空间写入的数据保存到数据中 */
ret = simple_write_to_buffer(hello_data, sizeof(hello_data),ppos,user_buf,count);
printk("hello_proc_write:ret is %d.\n",ret);
printk("hello_proc_write:user_buf is %s",hello_data);
/* 返回用户空间写入字符串的大小 */
return count;
}
/* 定义一个file_operations结构体变量 */
static const struct file_operations hello_proc_fops = {
.owner = THIS_MODULE,
.read = hello_proc_read, //使用cat时的回调函数
.write = hello_proc_write, //使用echo时的回调函数
};
/* 驱动入口函数 */
static int __init proc_test_init(void)
{
/* 调用proc_create()函数创建"hello_proc"文件 */
hello_proc = proc_create("hello_proc", S_IRUGO | S_IWUGO,NULL,&hello_proc_fops);
return 0;
}
/* 驱动出口函数 */
static void __exit proc_test_exit(void)
{
/* 删除此文件 */
if(hello_proc)
remove_proc_entry("hello_proc", NULL);
}
module_init(proc_test_init);
module_exit(proc_test_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("proc filesystem test");