简介
procfs文件系统是内核中的一个特殊文件系统。它是一个虚拟文件系统: 它不是实际的存储设备中的文件,而是存在于内存中。procfs中的文件是用来允许用户空间的程序访问内核中的某些信息(比如进程信息在 /proc/[0-9]+/中),或者用来做调试用途(/proc/ksyms,这个文件列出了已经登记的内核符号,这些符号给出了变量或函数的地址。每行给出一个符号的地址,符号名称以及登记这个符号的模块。程序ksyms、insmod和kmod使用这个文件。它还列出了正在运行的任务数,总任务数和***分配的PID。)
这个文档描述了内核中procfs文件系统的使用。它以介绍所有和管理文件系统相关的函数开始。在函数介绍后,它还展示了怎么和用户空间通信,和一些小技巧。在文档的***,还给出了一个完整的例子。
注意/proc/sys中的文件属于sysctl文件,它们不属于procfs文件系统,被另外一套完全不同的api管理。
seq_file
procfs在处理大文件时有点笨拙。为了清理procfs文件系统并且使内核编程简单些,引入了seq_file机制。seq_file机制提供了大量简单的接口去实现大内核虚拟文件。
seq_file机制适用于你利用结构序列去创建一个返回给用户空间的虚拟文件。要使用seq_file机制,你必须创建一个”iterator”对象,这个对象指向这个序列,并且能逐个指向这个序列中的对象,此外还要能输出这个序列中的任一个对象。它听起来复杂,实际上,操作过程相当简单。接下来将用实际的例子展示到底怎么做。
首先,你必须包含头文件<Linux/seq_file.h>。接下来,你必须创建迭代器方法:start, next, stop, and show。
start方法通常被首先调用。这个方法的函数原型是:
void *start(struct seq_file *sfile, loff_t *pos);
- 1.
sfile没什么作用,通常被忽略。pos参数是一个整型,表示从哪个位置开始读。关于位置的定义完全取决于函数实现;它不一定要是结果文件中的一个字节位置。 由于seq_file机制通常是利用一个特定的结构序列实现的,所以位置通常是一个指向序列中下一个结构体的指针。在wing驱动中,每一个设备表示序列中的一个结构,所以,入参pos代表g_pstWingDevices数组的索引。因此,在wing驱动中start方法的实现为:
static void *wing_seq_start(struct seq_file *s, loff_t *pos)
{
if (*pos >= g_iWingDevicesNum)
return NULL; /* No more to read */
return g_pstWingDevices + *pos;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
返回值如果不为NULL,代表一个可以被迭代器使用的私有数据。
next函数应该移动迭代器到下一个位置,如果序列中没有数据,返回NULL。这个方法的函数原型为:
void *next(struct seq_file *sfile, void *v, loff_t *pos);
- 1.
这里,参数v代表上一个函数调用(可能是start函数,或者是next函数)返回的迭代器,, 参数pos是文件中的当前位置。next函数应该改变pos的指向,具体是逐步改变还是跳跃改变取决于迭代器的工作机制。next函数在wing驱动中的实现为:
static void* wing_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if (*pos >= g_iWingDevicesNum)
return NULL;
return g_pstWingDevices + *pos;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
当内核停止了迭代器的工作,它调用stop函数清理现场:
void stop(struct seq_file *sfile, void *v);
- 1.
wing驱动没有清理工作要做,所以stop函数为空。
void wing_seq_stop(struct seq_file *sfile, void *v)
{
}
- 1.
- 2.
- 3.
要是seq_file代码在调用start和stop时不执行睡眠或是非原子的操作,那么这种机制将毫无意义。你要保证从start函数调用到stop函数调用是很短暂的。因此,在开始函数中获得一个信号量或者自旋锁是比较安全的做法。要是seq_file其他方法是原子的,整个调用链必须是原子的。
在这些函数调用中,内核调用call函数向内核空间输出特性的信息。这个函数的函数原型是:
int show(struct seq_file *sfile, void *v);
- 1.
这个方法应该创建序列中由指示器v指定项的输出。不能使用printk,而是使用以下这些特定函数:
int seq_printf(struct seq_file *sfile, const char *fmt, ...)
- 1.
这个函数是seq_file机制中类似于printf的实现;它使用通常的格式字符串和参数组成输出字符串。你必须把show函数中的seq_file结构体传给这个函数。如果它返回一个非零的值,表示buffer已经填充好,输出被丢出去了。在大多数实现中,都选择忽略返回值。
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s);
- 1.
- 2.
- 3.
这两个函数相当于用户层的putc和puts。
int seq_escape(struct seq_file *m, const char *s, const char *esc);
- 1.
这个函数是 seq_puts 的对等体, 除了 s 中的任何也在 esc 中出现的字符以八进制格式打印. esc 的一个通用值是”\t\n\”, 它使内嵌的空格不会搞乱输出和可能搞乱 shell 脚本.
int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);
- 1.
这个函数能够用来输出和给定命令项关联的文件名子. 它在设备驱动中不可能有用;我们是为了完整在此包含它.
wing设备中的show函数例子:
static int wing_seq_show(struct seq_file *s, void *v)
{
ST_Wing_Dev_Type* pDev = (ST_Wing_Dev_Type* ) v;
seq_printf(s, "\nThis Device is %i\n", pDev->iData);
return 0;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
在我的例子中,我将一个ST_Wing_Dev_Type结构体表示为迭代器。
上面就是完整的迭代器操作,wing驱动必须将它们打包到一起好连接到procfs文件系统。首先要做的就是利用它们组成一个seq_operations结构体:
static struct seq_operations s_stWingSeqOps = {
.start = wing_seq_start,
.next = wing_seq_next,
.stop = wing_seq_stop,
.show = wing_seq_show
};
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
有了这个结构,我们必须创建一个内核能理解的文件实现。我们不使用前面说过的read_proc方法;在使用seq_file时, ***在一个稍低的级别上连接到procfs。这意味着创建一个file_operations(和字符设备一样的结构),这个结构实现了内核对文件的reads和seeks操作。幸运的是,这个操做很简单。首先创建一个把文件和seq_file方法联接起来的open方法:
static int wing_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &s_stWingSeqOps);
}
- 1.
- 2.
- 3.
- 4.
调用seq_open函数的时候将文件和上面定义的序列操作关联到一起。open是唯一要我们实现的函数接口,所以我们的file_operations结构体是:
static struct file_operations s_stWingProcFops = {
.owner = THIS_MODULE,
.open = wing_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
***我们要在procfs文件系统中创建文件:
proc_create("wingdevices", 0644, NULL, &s_stWingProcFops);
- 1.
关键结构体
struct proc_dir_entry代表的是/proc目录下的一个目录或者文件,他是procfs文件系统的主要结构体,它的定义在/fs/internal.h中:
/*
* 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.
*
* The "next" pointer creates a linked list of one /proc directory,
* while parent/subdir create the directory structure (every
* /proc file has a parent, but "subdir" is NULL for all
* non-directory entries).
*/
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[];
};
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
主要接口
procfs应该包含的头文件<linux/proc_fs.h>。
在3.x内核中procfs主要接口有:
- proc_symlink
- proc_mkdir
- proc_mkdir_data
- proc_mkdir_mode
- proc_create_data
- proc_create
- proc_set_size
- proc_set_user
- PDE_DATA
- proc_get_parent_data
- proc_remove
- remove_proc_entry
- remove_proc_subtree
proc_mkdir
说明:在/proc下创建目录
函数原型:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent)
- 1.
参数:
- name
要创建的目录名称
- parent
父目录,如果为NULL,表示直接在/proc下面创建目录。
proc_mkdir_data
说明:在/proc下创建目录
函数原型:
struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode, struct proc_dir_entry *parent, void *data)
- 1.
参数:
- name
要创建的目录名称
- mode
指定要创建目录的权限
- parent
父目录,如果为NULL,表示直接在/proc下面创建目录。
- data
proc_create_data
说明:创建proc虚拟文件系统文件
函数原型:
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)
- 1.
参数:
- name
你要创建的文件名。
- mode
为创建的文件指定权限
- parent
为你要在哪个文件夹下建立名字为name的文件,如:init_net.proc_net是要在/proc/net/下建立文件。
- proc_fops
为struct file_operations
- data
保存私有数据的指针,如不要为NULL。
例子:
////////////////////////test.c////////////////////////////////////////
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <net/net_namespace.h>
#include <linux/mm.h>
MODULE_LICENSE("GPL");
typedef struct {
int data1;
int data2;
}ST_Data_Info_Type;
static ST_Data_Info_Type g_astDataInfo[2];
static int test_proc_show(struct seq_file *m, void *v)
{
ST_Data_Info_Type* pInfo = (ST_Data_Info_Type*)m->private;
if(pInfo != NULL)
{
seq_printf(m, "%d----%d\n", pInfo->data1, pInfo->data2);
}
return 0;
}
static int test_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, test_proc_show, PDE_DATA(inode));
}
static const struct file_operations dl_file_ops = {
.owner = THIS_MODULE,
.open = test_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static struct proc_dir_entry *s_pstRootTestDir;
void init_mem(void)
{
/* create /proc/test */
s_pstRootTestDir = proc_mkdir("test", NULL);
if (!s_pstRootTestDir)
return;
g_astDataInfo[0].data1=1;
g_astDataInfo[0].data2=2;
proc_create_data("proc_test1", 0644, s_pstRootTestDir, &dl_file_ops, &g_astDataInfo[0]);
g_astDataInfo[1].data1=3;
g_astDataInfo[1].data2=4;
proc_create_data("proc_test2", 0644, s_pstRootTestDir, &dl_file_ops, &g_astDataInfo[1]);
}
static int __init test_module_init(void)
{
printk("[test]: module init\n");
init_mem();
return 0;
}
static void __exit test_module_exit(void)
{
printk("[test]: module exit\n");
remove_proc_entry("proc_test1", s_pstRootTestDir);
remove_proc_entry("proc_test2", s_pstRootTestDir);
remove_proc_entry("test", NULL);
}
module_init(test_module_init);
module_exit(test_module_exit);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
proc_create
说明:创建proc虚拟文件系统文件
函数原型:
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops)
- 1.
参数:
- name
你要创建的文件名。
- mode
为创建的文件指定权限
- parent
为你要在哪个文件夹下建立名字为name的文件,如:init_net.proc_net是要在/proc/net/下建立文件。
- proc_fops
为struct file_operations
注意:这个接口和proc_create_data的区别在于他不能保存私有数据指针。
PDE_DATA
获取proc_create_data传入的私有数据。
proc_symlink
说明:这个函数在procfs目录下创建一个从name指向dest的符号链接. 它在用户空间等效为ln -s dest name。
函数原型:
struct proc_dir_entry *proc_symlink(const char *name, struct proc_dir_entry *parent, const char *dest)
- 1.
参数:
- name
原始符号。
- parent
符号所在的目录。
- dest
所要创建的符号链接名字。
remove_proc_entry
说明:删除procfs文件系统中的文件或者目录。
函数原型:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
- 1.
参数:
- name
要删除的文件或者目录名。
- parent
符号所在的目录,如果为NULL,表示在/proc目录下。