1. 简介
procfs文件系统(/proc)在linux内核中算是一个比较特殊的文件系统。它是一个虚拟的文件系统:它并没有关联到具体的块设备,而是存在于内存中。procfs中的文件存在的目的在于允许用户程序从内核获取信息(例如proc目录下以数字开头的文件)以及debug程序(如/proc/ksyms).
本文介绍procfs文件系统在linux 内核的使用。首先介绍所有用于管理procfs文件系统文件的函数。然后介绍与用户程序的交互和某些技巧。最后展示一个完整的例子。
注意:/proc/sys目录下的文件是sysctl文件。他们不属于procfs,由完全不同的API管理(参见其他kernel api的相关书籍)
2.管理procfs项
这部分介绍内核空间在procfs目录下中创建文件、符号链接、设备节点和目录所用到的函数。
注意: 必须包含头文件:
#include <linux/proc_fs.h>
创建常规文件:
struct proc_dir_entry* create_proc_entry( | const char* name, |
| mode_t mode, |
| struct proc_dir_entry* parent); |
该函数用于在目录parent下创建名叫name的常规文件,访问权限由mode指定。 parent 为NULL则在/procfs目录下创建文件。 成功时,函数返回指向新创建的结构体 struct proc_dir_entry的指针,失败返回NULL。文章的第三部分将介绍对新创建的文件的一些操作。
注意: 你可以传递一个多级目录,例如 create_proc_entry("dirvers/via0/info") 必要时会以权限0755创建via0目录。
如果想创建只读文件,则可以考虑使用函数create_proc_read_entry来一次性创建并初始化procfs项。
创建符号链接
struct proc_dir_entry* proc_symlink( | const char* name, |
| struct proc_dir_entry* parent, |
| const char* dest); |
该函数在目录prarent下创建指向dest的符号链接name。相当于用户模式的:ln -s
创建目录
struct proc_dir_entry* proc_mkdir( | const char* name, |
| struct proc_dir_entry* parent);
|
该函数在parent目录下创建目录name。
删除项
void remove_proc_entry( | const char* name, |
| struct proc_dir_entry* parent); |
该函数在procfs系统的parent目录下删除name。 节点通过名字来删除,而不是通过创建时返回的proc_dir_entry结构体。本函数并不递归删除项目
注意: 必须在调用remove_proc_entry之前释放掉相对应的结构体struct proc_dir_enrty
3.用户进程交互
procfs文件系统不直接从内核内存中读写数据,而是使用文件的回调函数:当特定文件被读写时嗦调用的函数。这些函数必须在procfs文件创建以后通过设置结构体struct proc_dir_entry(由create_proc_entry函数返回) 中的read_proc跟write_proc字段来进行初始化。示例代码如下:
struct proc_dir_entry* entry;
entry->read_proc = read_proc_foo;
entry->write_proc = write_proc_foo;
如果仅使用read_proc.则可以考虑使用函数create_proc_read_entry来一次性创建并初始化procfs项。
读取数据:
read函数是允许用户程序从内核中读取数据的回调函数,它的声明如下:
int read_func( | char* buffer, |
| char** start, |
| off_t off, |
| int count, |
| int* peof, |
| void* data); |
read函数把它所提供的信息吸入buffer指向的内存,这块内存的大小为PAGE_SIZE。
*peof置为1用作到达文件结尾的标识。
参数data可以被用来创建为多个文件创建同一个回调函数。
函数返回值金和剩余参数在文件fs/proc/generic.c的注释中有所描述,这些描述如下:
将*start置为NULL(默认就这样)。将请求的数据写到buffer中 返回世界写入的字节数
写入数据
写回调函数允许用户程序向内核写入数据,因此可以控制内核的部分。写函数的声明如下:
int write_func( | struct file* file, |
| const char* buffer, |
| unsigned long count, |
| void* data); |
写回调函数从buffer中读取count字节。注意buffer并不在内核内存空间中,所以必须先用copy_from_user拷贝到内科空间,参数file通常被忽略。文章的第五部分有例子。
为多个文件建立单个回调函数
当存在大量几乎相等的文件的时候,为每个文件建立单独的回调函数就很不方便。更好的办法是为这些文件使用同一个回调函数,而这个回调函数通过结构体proc_dir_entry的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;
data字段的类型是void* , 所以可以初始化成任何值。
设置好data字段以后,read_proc和write_proc函数就可以用它来区分不同的文件,因为这个字段通过data参数传入:
int foo_read_func(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int len;
if(data == file_data) {
/* special case for this file */
} else {
/* normal processing */
}
return len;
}
在删除procfs节点时请确保释放data字段。
4. 技巧
方便的函数:
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); |
该函数跟create_proc_entry函数一样创建普通文件,但该函数同时设置读回调函数和data字段。
模块
如果在模块内使用procfs,请将结构体struct proc_dir_entry中的owner字段设置成THIS_MODULE:
struct proc_dir_entry* entry;
entry->owner = THIS_MODULE;
访问权限和所有关系(ownership):
有时候需要改变某个procfs项的访问权限,示例代码如下:
struct proc_dir_entry* entry;
entry->mode = S_IWUSR |S_IRUSR | S_IRGRP | S_IROTH;
entry->uid = 0;
entry->gid = 100;
5.例子
/*
* procfs_example.c: an example proc interface
*
* Copyright (C) 2001, Erik Mouw (mouw@nl.linux.org)
*
* This file accompanies the procfs-guide in the Linux kernel
* source. Its main use is to demonstrate the concepts and
* functions described in the guide.
*
* This software has been developed while working on the LART
* computing board (http://www.lartmaker.nl), which was sponsored
* by the Delt University of Technology projects Mobile Multi-media
* Communications and Ubiquitous Communications.
*
* This program is free software; you can redistribute
* it and/or modify it under the terms of the GNU General
* Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
#define MODULE_VERS "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, *symlink;
struct fb_data_t foo_data, bar_data;
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;
}
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;
/* DON'T DO THAT - buffer overruns are bad */
len = sprintf(page, "%s = '%s'/n",
fb_data->name, fb_data->value);
return len;
}
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;
/* create directory */
example_dir = proc_mkdir(MODULE_NAME, NULL);
if(example_dir == NULL) {
rv = -ENOMEM;
goto out;
}
/* create jiffies using convenience function */
jiffies_file = create_proc_read_entry("jiffies",
0444, example_dir,
proc_read_jiffies,
NULL);
if(jiffies_file == NULL) {
rv = -ENOMEM;
goto no_jiffies;
}
/* create foo and bar files using same callback
* functions
*/
foo_file = create_proc_entry("foo", 0644, example_dir);
if(foo_file == NULL) {
rv = -ENOMEM;
goto no_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_file = create_proc_entry("bar", 0644, example_dir);
if(bar_file == NULL) {
rv = -ENOMEM;
goto no_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;
/* create symlink */
symlink = proc_symlink("jiffies_too", example_dir,
"jiffies");
if(symlink == NULL) {
rv = -ENOMEM;
goto no_symlink;
}
/* everything OK */
printk(KERN_INFO "%s %s initialised/n",
MODULE_NAME, MODULE_VERS);
return 0;
no_symlink:
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("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_VERS);
}
module_init(init_procfs_example);
module_exit(cleanup_procfs_example);
MODULE_AUTHOR("Erik Mouw");
MODULE_DESCRIPTION("procfs examples");
MODULE_LICENSE("GPL");
转自:http://leewez.blog.163.com/blog/static/29589546200972345039123/