proc编程
与sysfs虚拟文件系统相比,proc的使用没有sysfs那么组织严谨,更加随意,因此在内核模块中用proc文件系统与用户空间进行信息交互还是很方便的。
在内核模块中使用proc,需要包含头文件<linux/proc_fs.h>
其中常用的函数包括:
- 创建一个文件:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent);
name: 文件名称
mode:文件属性,常用S_IWUSR | S_IRUGO
parent:父目录,即创建的文件在那个目录下,若为NULL,则在/proc目录下创建文件
2. 创建一个只读的文件:
struct proc_dir_entry* create_proc_read_entry(const char* name, mode_t mode, struct proc_dir_entry* parent, read_proc_t* read_proc, void* data);
read_proc是读操作回调函数,data是多个文件共享一个操作函数时区分文件的私有数据
3. 创建一个目录:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);
参数意义同上,只是这个函数在proc文件系统中创建的是一个目录
4. 创建一个符号链接:
struct proc_dir_entry* proc_symlink(const chat *name, struct proc_dir_entry *parent, const char *dest)
在parent目录下创建符号链接,效果如同:ln –s dest name
5. 创建一个设备节点
struct proc_dir_entry* proc_mknod(const char *name, mode_t mode, struct proc_dir_entry *parent, kdev_t rdev)
在parent目录下创建一个设备节点,其中rdev可以由MKDEV宏生成,mode参数必须包含S_IFBLK或S_IFCHR表示创建的是块设备还是字符设备。
效果如同:mknod --mode=mode name rdev
6. 移除proc文件系统中的入口
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
上面创建的所有对象,都可以通过这个函数移除
为了对proc文件系统中的文件进行读写操作,需要设置相应的回调函数,在返回的对应proc文件的struct proc_dir_entry结构中,设置其中的:
entry->read_proc和entry->write_proc参数。
其中entry->read_proc的函数原型为:
int read_func(char* page, char** start, off_t off, int count, int* eof, void* data); 从内核读取数据
内核返回的数据需要写入page,其中page为内核地址,off和count分别是写入在page中的偏移地址和可以写入的最大字节,则两个参数主要是用于more和less命令,一般若内容较少可以忽略这两个参数。若这两个参数被使用,则需要设置eof为1来表示已到达文件尾部。
data参数用于同一个read_func函数为多个proc文件服务时,进行区分所操作的proc文件,并可以存放私有数据。
这个函数的返回值是复制到page内存中的字节数。
entry->write_proc的函数原型为:
int write_func(struct file* file, const char* buffer, unsigned long count, void* data); 写入数据到内核
参数count表示可以从buffer中访问的最大字节数,由于buffer是用户空间内存,需要使用copy_from_user复制buffer中的数据到内核空间。file参数一般被忽略,而data同上也是用于区分多个文件时的操作对象。
参数data的使用实例一般为:
struct proc_dir_entry* entry;
struct my_file_data *file_data; //文件数据
file_data = kmalloc(sizeof(struct my_file_data), GFP_KERNEL); //分配空间
entry->data = file_data; //这里是重点,需要将其赋值给对应proc文件的proc_dir_entry结构,在read和write是才能区分
int foo_read_func(char *page, char **start, off_t off, int count, int *eof, void *data)
{
int len;
if(data == file_data) {
/* 操作对应的文件*/
}
else {
/* 操作其他文件*/
}
return len;
}
需要注意的是,若entry->data的参数是动态分配的,在调用remove_proc_entry时,需要先将对应的data空间释放。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>#define MODULE_VERSION "1.0"
#define MODULE_NAME "procfs_example"
#define FOOBAR_LEN 8/*文件私有数据结构*/
struct fb_data_t {
char name[FOOBAR_LEN + 1];
char value[FOOBAR_LEN + 1];
};static struct proc_dir_entry *example_dir, *foo_file, *bar_file, *jiffies_file, *tty_device, *symlink;
struct fb_data_t foo_data, bar_data;/*读取内核jiffies*/
static int proc_read_jiffies(char *page, char **start, off_t off, int count, int *eof, void *data)
{
int len;len = sprintf(page, "jiffies = %ld\n", jiffies);
return len;
}/*读取foo和bar文件*/
static int proc_read_foobar(char *page, char **start, off_t off, int count, int *eof, void *data)
{
int len;
struct fb_data_t *fb_data = (struct fb_data_t *)data;len = sprintf(page, "%s = ’%s’\n", fb_data->name, fb_data->value);
return len;
}/*写入foo和bar文件*/
static int proc_write_foobar(struct file *file, const char *buffer, unsigned long count, void *data)
{
int len;
struct fb_data_t *fb_data = (struct fb_data_t *)data;if(count > FOOBAR_LEN)
len = FOOBAR_LEN;
else
len = count;
if(copy_from_user(fb_data->value, buffer, len))
return -EFAULT;fb_data->value[len] = ’\0’;
return len;
}/*模块初始化函数*/
static int __init init_procfs_example(void)
{
int rv = 0;
/* 在/proc下创建一个父目录 */
example_dir = proc_mkdir(MODULE_NAME, NULL);
if(example_dir == NULL) {
rv = -ENOMEM;
goto out;
}/* 创建读取jiffies的proc文件 */
jiffies_file = create_proc_read_entry("jiffies", 0444, example_dir, proc_read_jiffies, NULL);
if(jiffies_file == NULL) {
rv = -ENOMEM;
goto no_jiffies;
}/* 创建foo和bar文件,使用同一读写回调函数 */
foo_file = create_proc_entry("foo", 0644, example_dir);
if(foo_file == NULL) {
rv = -ENOMEM;
goto no_foo;
}
/*设置foo文件的私有数据和读写回调函数*/
strcpy(foo_data.name, "foo");
strcpy(foo_data.value, "foo");
foo_file->data = &foo_data;
foo_file->read_proc = proc_read_foobar;
foo_file->write_proc = proc_write_foobar;
/* 创建bar文件 */
bar_file = create_proc_entry("bar", 0644, example_dir);
if(bar_file == NULL) {
rv = -ENOMEM;
goto no_bar;
}/*设置bar私有数据和读写回调函数*/
strcpy(bar_data.name, "bar");
strcpy(bar_data.value, "bar");
bar_file->data = &bar_data;
bar_file->read_proc = proc_read_foobar;
bar_file->write_proc = proc_write_foobar;/* 创建设备文件 */
tty_device = proc_mknod("tty", S_IFCHR | 0666, example_dir, MKDEV(5, 0));
if(tty_device == NULL) {
rv = -ENOMEM;
goto no_tty;
}/* 创建符号链接 */
symlink = proc_symlink("jiffies_too", example_dir, "jiffies");
if(symlink == NULL) {
rv = -ENOMEM;
goto no_symlink;
}printk(KERN_INFO "%s %s initialised\n", MODULE_NAME, MODULE_VERSION);
return 0;no_symlink:
remove_proc_entry("tty", example_dir);
no_tty:
remove_proc_entry("bar", example_dir);
no_bar:
remove_proc_entry("foo", example_dir);
no_foo:
remove_proc_entry("jiffies", example_dir);
no_jiffies:
remove_proc_entry(MODULE_NAME, NULL);
out:
return rv;
}/*模块卸载函数*/
static void __exit cleanup_procfs_example(void)
{
remove_proc_entry("jiffies_too", example_dir);
remove_proc_entry("tty", example_dir);
remove_proc_entry("bar", example_dir);
remove_proc_entry("foo", example_dir);
remove_proc_entry("jiffies", example_dir);
remove_proc_entry(MODULE_NAME, NULL);
printk(KERN_INFO "%s %s removed\n",
MODULE_NAME, MODULE_VERSION);
}module_init(init_procfs_example);
module_exit(cleanup_procfs_example);
MODULE_AUTHOR("Erik Mouw");
MODULE_DESCRIPTION("procfs examples");