/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux® 内核空间和用户空间之间进行通信。在 /proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的.

/proc 文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。

创建一个/proc文件

在3.8内核之前,使用create_proc_entry创建一个文件,原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,
struct proc_dir_entry *parent );//创建一个虚拟文件
struct proc_dir_entry {
const char *name; // 节点名称
mode_t mode; // 权限,与chmod的一样,可以使用八进制表示
uid_t uid; // File's user id
gid_t gid; // File's group id
struct inode_operations *proc_iops; // 索引节点操作函数
struct file_operations *proc_fops; // 文件操作函数
struct proc_dir_entry *parent; // 父目录,如果是NULL,就代表/proc目录
...
read_proc_t *read_proc; // 输出给cat的函数
write_proc_t *write_proc; // 读取用户输入的函数
void *data; // 指向private 数据
atomic_t count; // 使用计数
...
};
void remove_proc_entry( const char *name, struct proc_dir_entry *parent );//不仅可以删除节点,还能删除proc_mkdir创建的目录
struct proc_dir_entry *proc_mkdir(const char *name,
struct proc_dir_entry *parent); //创建一个虚拟目录

parent 参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值

proc_dir_entry 在文件系统中的位置
proc_root_fs /proc
proc_net /proc/net
proc_bus /proc/bus
proc_root_driver /proc/driver

如果我们想创建一个文件为test_modul的虚拟文件,就这样初始化

1
2
3
4
create_proc_entry( "test_module", 0644, NULL );
或者
pt_root = proc_mkdir("test_menu", NULL);
pt_entry1 = create_proc_entry(USER_ENTRY1, 0666, pt_root);

/proc文件交互函数

read_proc 供用户读取的函数

1
2
3
4
5
6
7
int mod_read( char *page,//数据写入的位置,该page缓冲区在内核
char **start,
off_t off,
int count,//定义写入的最大字节数
int *eof, //当数据写入完后,需要设置为1
void *data //private 数据
);

当需要写入多页数据时(一般一页4Kb),需要用到 start,off.

write_proc 读取用户的输入

1
2
3
4
5
int mod_write( struct file *filp, //指向一个打开的文件结构
const char __user *buff, //用户输入的数据,buff在用户空间,内核要读取需要用到copy_from_user
unsigned long len, //长度
void *data
);

其它需要用到的函数

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
/* 创建一个符号链接 */
struct proc_dir_entry *proc_symlink( const char *name,
struct proc_dir_entry *parent,
const char *dest );
/* Create a proc_dir_entry with a read_proc_t in one call */
struct proc_dir_entry *create_proc_read_entry( const char *name,
mode_t mode,
struct proc_dir_entry *base,
read_proc_t *read_proc,
void *data );
/* 从内核空间复制数据到用户空间 */
unsigned long copy_to_user( void __user *to,
const void *from,
unsigned long n );
/* 从用户空间到内核 */
unsigned long copy_from_user( void *to,
const void __user *from,
unsigned long n );
/* 创建虚拟的连续内存块 */
void *vmalloc( unsigned long size );
/* 释放vmalloc创建的块 */
void vfree( void *addr );
/* Export a symbol to the kernel (make it visible to the kernel) */
EXPORT_SYMBOL( symbol );
/* Export all symbols in a file to the kernel (declare before module.h) */
EXPORT_SYMTAB

创建内核驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <linux/module.h>
/* Defines the license for this LKM */
MODULE_LICENSE("GPL");

int my_module_init( void )//自定义的初始化函数
{
printk(KERN_INFO "my_module_init called. Module is now loaded.\n");
return 0;
}

void my_module_cleanup( void )//自定义的退出函数
{
printk(KERN_INFO "my_module_cleanup called. Module is now unloaded.\n");
return;
}

module_init( my_module_init );//声明初始化函数
module_exit( my_module_cleanup );//声明退出函数

从3.10内核开始,create_proc_entry() 函数被替换成proc_create() 函数, 函数区别如下:
修改前

1
2
3
4
5
6
struct proc_dir_entry *proc_file = create_proc_entry("file",0600,NULL);
if (proc_file) {
proc_file->read_proc = file_read;
proc_file->write_proc = file_write;
proc_file->owner = THIS_MODULE;
}

修改后

1
2
3
4
5
6
7
8
struct file_operations proc_fops=
{
.read=file_read,
.write=file_write,
.owner=THIS_MODULE,
};

proc_file = proc_create("file", 0600, proc_dir, &proc_fops);

编译安装驱动

Makefile

1
2
3
4
5
6
7
obj-m += simple-km.o

all:
make -C /lib/modules/`uname -r`/build SUBDIRS=$(PWD) modules

clean:
make -C /lib/modules/`uname -r`/build SUBDIRS=$(PWD) modules

开头的是tab, 不是空格, 一定要注意

执行如下命令

1
2
3
4
5
$ make
$ insmod simple-km.ko
$ dmesg | tail -5 查看最后5行信息
$ lsmod
$ rmmod simple-km.ko

一个简单的示例

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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fortune Cookie Kernel Module");
MODULE_AUTHOR("VictorV");
#define MAX_COOKIE_LENGTH PAGE_SIZE
static struct proc_dir_entry *proc_entry;
static char *cookie_pot; // 内存缓冲区
static int cookie_index; // 指向缓冲区的数据尾部
static int next_fortune; // 通过\0分开字符串,此处用来指向下一个需要输出的缓冲字符串

ssize_t fortune_write( struct file *filp,
const char __user *buff,
unsigned long len,
void *data )
{
int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1;
if (len > space_available) {
printk(KERN_INFO "fortune: cookie pot is full!\n");
return -ENOSPC;
}
if (copy_from_user( &cookie_pot[cookie_index], buff, len )) {
//从用户空间读取数据到cookie_pot,
return -EFAULT;
}
cookie_index += len;
cookie_pot[cookie_index-1] = 0;//将最后一位置零
return len;
}

int fortune_read( char *page, char **start, off_t off,int count, int *eof, void *data ){
int len;
if (off > 0) {
*eof = 1;
return 0;
}
if (next_fortune >= cookie_index) next_fortune = 0;//超过数据个数,就循环读取
len = sprintf(page, "%s\n", &cookie_pot[next_fortune]);//将一段字符串写入page
next_fortune += len;
return len;
}

int init_fortune_module( void )//初始化
{
int ret = 0;
cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH );//申请一段内核空间
if (!cookie_pot) {
ret = -ENOMEM;
} else {
memset( cookie_pot, 0, MAX_COOKIE_LENGTH );
proc_entry = create_proc_entry( "fortune", 0644, NULL );
if (proc_entry == NULL) {
ret = -ENOMEM;
vfree(cookie_pot);
printk(KERN_INFO "fortune: Couldn't create proc entry\n");
} else {
cookie_index = 0;
next_fortune = 0;
proc_entry->read_proc = fortune_read;//这里定义输出函数
proc_entry->write_proc = fortune_write;//定义输入函数
proc_entry->owner = THIS_MODULE;
printk(KERN_INFO "fortune: Module loaded.\n");
}
}
return ret;
}
void cleanup_fortune_module( void )
{
remove_proc_entry("fortune", &proc_root);
vfree(cookie_pot);
printk(KERN_INFO "fortune: Module unloaded.\n");
}
module_init( init_fortune_module );
module_exit( cleanup_fortune_module );

效果:

1
2
3
4
5
6
7
8
9
10
11
12
[root@plato]# insmod fortune.ko
[root@plato]# echo "Success is an individual proposition.
Thomas Watson" > /proc/fortune
[root@plato]# echo "If a man does his best, what else is there?
Gen. Patton" > /proc/fortune
[root@plato]# echo "Cats: All your base are belong to us.
Zero Wing" > /proc/fortune
[root@plato]# cat /proc/fortune
Success is an individual proposition. Thomas Watson
[root@plato]# cat /proc/fortune
If a man does his best, what else is there? General Patton
[root@plato]#