proc文件系统怎么玩三?

前言

前面我们看了这个怎么玩,这里我们来看看怎么用?

使用“/proc”

在Linux系统中,“/proc”文件系统十分有用,它被内核用于向用户导出信息。“/proc”文件系统是一个虚拟文件系统,通过它可以在Linux内核空间和用户空间之间进行通信。在/proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,与普通文件不同的是,这些虚拟文件的内容都是动态创建的。

“/proc”下的绝大多数文件是只读的,以显示内核信息为主。但是“/proc”下的文件也并不是完全只读的,若节点可写,还可用于一定的控制或配置目的,例如前面介绍的写/proc/sys/kernel/printk可以改变printk()的打印级别。

Linux系统的许多命令本身都是通过分析“/proc”下的文件来完成的,如ps、top、uptime和free等。例如,free命令通过分析/proc/meminfo文件得到可用内存信息,下面显示了对应的meminfo文件和free命令的结果。

  • 1.meminfo文件
[root@localhost proc]# cat meminfo
MemTotal:        29516 kB
MemFree:          1472 kB
Buffers:          4096 kB
Cached:          12648 kB
SwapCached:          0 kB
Active:          14208 kB
Inactive:         8844 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:        29516 kB
LowFree:          1472 kB
SwapTotal:      265064 kB
SwapFree:       265064 kB
Dirty:              20 kB
Writeback:           0 kB
Mapped:          10052 kB
Slab:             3864 kB
CommitLimit:    279820 kB
Committed_AS:    13760 kB
PageTables:        444 kB
VmallocTotal:   999416 kB
VmallocUsed:       560 kB
VmallocChunk:   998580 kB
    1. free命令
[root@localhost proc]# free
           total       used      free     shared    buffers     cached
Mem:       29516       28104     1412     0         4100        12700
-/+ buffers/cache:     11304     18212
Swap:      265064      0         265064

在Linux 3.9以及之前的内核版本中,可用如下函数创建“/proc”节点:

struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
            struct proc_dir_entry *parent);
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);

create_proc_entry()函数用于创建“/proc”节点,而create_proc_read_entry()调用create_proc_entry()创建只读的“/proc”节点。参数name为“/proc”节点的名称,parent/base为父目录的节点,如果为NULL,则指“/proc”目录,read_proc是“/proc”节点的读函数指针。当read()系统调用在“/proc”文件系统中执行时,它映像到一个数据产生函数,而不是一个数据获取函数。

下列函数用于创建“/proc”目录:

struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);

结合create_proc_entry()和proc_mkdir(),代码清单21.5中的程序可用于先在/proc下创建一个目录procfs_example,而后在该目录下创建一个文件example_file。代码清单21.5 proc_mkdir()和create_proc_entry()函数使用范例

 1 /* 创建/proc下的目录 */
 2 example_dir = proc_mkdir("procfs_example", NULL);
 3 if (example_dir == NULL) {
 4 rv =  -ENOMEM;
 5 goto out;
 6 }
 7
 8 example_dir->owner = THIS_MODULE;
 9
10 /* 创建一个/proc文件 */
11 example_file = create_proc_entry("example_file", 0666, example_dir);
12 if (example_file == NULL) {
13 rv =  -ENOMEM;
14 goto out;
15 }
16
17 example_file->owner = THIS_MODULE;
18 example_file->read_proc = example_file_read;
19 example_file->write_proc = example_file_write;

作为上述函数返回值的proc_dir_entry结构体包含了“/proc”节点的读函数指针(read_proc_tread_proc)、写函数指针(write_proc_twrite_proc)以及父节点、子节点信息等。/proc节点的读写函数的类型分别为:

typedef int (read_proc_t)(char *page, char **start, off_t off,
                                  int count, int *eof, void *data);
typedef int (write_proc_t)(struct file *file, const char __user *buffer,
                                  unsigned long count, void *data);

读函数中page指针指向用于写入数据的缓冲区,start用于返回实际的数据并写到内存页的位置,eof是用于返回读结束标志,offset是读的偏移,count是要读的数据长度。start参数比较复杂,对于/proc只包含简单数据的情况,通常不需要在读函数中设置*start,这意味着内核将认为数据保存在内存页偏移0的地方。

写函数与file_operations中的write()成员函数类似,需要一次从用户缓冲区到内存空间的复制过程。在Linux系统中可用如下函数删除/proc节点:

void remove_proc_entry(const char *name, struct proc_dir_entry *parent);

在Linux系统中已经定义好的可使用的/proc节点宏包括:proc_root_fs(/proc)、proc_net(/proc/net)、proc_bus(/proc/bus)、proc_root_driver(/proc/driver)等,proc_root_fs实际上就是NULL。

代码清单21.6所示为一个简单的“/proc”文件系统使用范例,这段代码在模块加载函数中创建/proc/test_dir目录,并在该目录中创建/proc/test_dir/test_rw文件节点,在模块卸载函数中撤销“/proc”节点,而/proc/test_dir/test_rw文件中只保存了一个32位的整数。

 1 #include <linux/module.h>
 2 #include <linux/kernel.h>
 3 #include <linux/init.h>
 4 #include <linux/proc_fs.h>
 5
 6 static unsigned int variable;
 7 static struct proc_dir_entry *test_dir, *test_entry;
 8
 9 static int test_proc_read(char *buf, char **start, off_t off, int count,
10      int *eof, void *data)
11 {
12 unsigned int *ptr_var = data;
13 return sprintf(buf, "%u\n", *ptr_var);
14 }
15
16 static int test_proc_write(struct file *file, const char *buffer,
17      unsigned long count, void *data)
18 {
19 unsigned int *ptr_var = data;
20
21 *ptr_var = simple_strtoul(buffer, NULL, 10);
22
23 return count;
24 }
25
26 static __init int test_proc_init(void)
27{
28 test_dir = proc_mkdir("test_dir", NULL);
29 if (test_dir) {
30     test_entry = create_proc_entry("test_rw", 0666, test_dir);
31     if (test_entry) {
32         test_entry->nlink = 1;
33         test_entry->data = &variable;
34         test_entry->read_proc = test_proc_read;
35         test_entry->write_proc = test_proc_write;
36         return 0;
37     }
38 }
39
40 return -ENOMEM;
41 }
42 module_init(test_proc_init);
43
44 static __exit void test_proc_cleanup(void)
45{
46 remove_proc_entry("test_rw", test_dir);
47 remove_proc_entry("test_dir", NULL);
48 }
49 module_exit(test_proc_cleanup);
50
51 MODULE_AUTHOR("Barry Song <[email protected]>");
52 MODULE_DESCRIPTION("proc exmaple");
53 MODULE_LICENSE("GPL v2");

上述代码第21行调用的simple_strtoul()用于将用户输入的字符串转换为无符号长整数,第3个参数10意味着转化方式是十进制。

编译上述简单的proc.c为proc.ko,运行insmod proc.ko加载该模块后,“/proc”目录下将多出一个目录test_dir,该目录下包含一个test_rw,ls–l的结果如下:

$ ls -l /proc/test_dir/test_rw
-rw-rw-rw- 1 root root 0 Aug 16 20:45 /proc/test_dir/test_rw

测试/proc/test_dir/test_rw的读写:

$ cat /proc/test_dir/test_rw
0
$ echo 111 > /proc/test_dir/test_rw
$ cat /proc/test_dir/test_rw

说明我们上一步执行的写操作是正确的。在Linux 3.10及以后的版本中,“/proc”的内核API和实现架构变更较大,create_proc_entry()、create_proc_read_entry()之类的API都被删除了,取而代之的是直接使用proc_create()、proc_create_data()API。同时,也不再存在read_proc()、write_proc()之类的针对proc_dir_entry的成员函数了,而是直接把file_operations结构体的指针传入proc_create()或者proc_create_data()函数中,其原型为:

static inline struct proc_dir_entry *proc_create(
const char *name, umode_t mode, struct proc_dir_entry *parent,
const struct file_operations *proc_fops);
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);

我们把代码清单21.6的范例改造为同时支持Linux 3.10以前的内核和Linux3.10以后的内核。改造结果如代码清单21.7所示。#if LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0)中的部分是旧版本的代码,与21.6相同,所以省略了。代码清单21.7 支持Linux 3.10以后内核的/proc文件系统使用范例

 1 #include <linux/module.h>
 2 #include <linux/kernel.h>
 3 #include <linux/init.h>
 4 #include <linux/version.h>
 5 #include <linux/proc_fs.h>
 6 #include <linux/seq_file.h>
 7
 8 static unsigned int variable;
 9 static struct proc_dir_entry *test_dir, *test_entry;
10
11 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
12 ...
13 #else
14 static int test_proc_show(struct seq_file *seq, void *v)
15 {
16 unsigned int *ptr_var = seq->private;
17 seq_printf(seq, "%u\n", *ptr_var);
18 return 0;
19 }
20
21 static ssize_t test_proc_write(struct file *file, const char __user *buffer,
22      size_t count, loff_t *ppos)
23 {
24 struct seq_file *seq = file->private_data;
25 unsigned int *ptr_var = seq->private;
26
27 *ptr_var = simple_strtoul(buffer, NULL, 10);
28 return count;
29 }
30
31 static int test_proc_open(struct inode *inode, struct file *file)
32 {
33 return single_open(file, test_proc_show, PDE_DATA(inode));
34 }
35
36 static const struct file_operations test_proc_fops =
37 {
38 .owner = THIS_MODULE,
39 .open = test_proc_open,
40 .read = seq_read,
41 .write = test_proc_write,
42 .llseek = seq_lseek,
43 .release = single_release,
44 };
45 #endif
46
47 static __init int test_proc_init(void)
48 {
49 test_dir = proc_mkdir("test_dir", NULL);
50 if (test_dir) {
51 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
52      ...
53 #else
54 test_entry = proc_create_data("test_rw",0666, test_dir, &test_proc_fops, &variable);
55 if (test_entry)
56     return 0;
57 #endif
58 }
59
60 return -ENOMEM;
61}
62 module_init(test_proc_init);
63
64 static __exit void test_proc_cleanup(void)
65{
66 remove_proc_entry("test_rw", test_dir);
67 remove_proc_entry("test_dir", NULL);
68 }
69 module_exit(test_proc_cleanup);

内容来自宋宝华老师的书,超级不错的Linux书籍。

猜你喜欢

转载自blog.csdn.net/weixin_45264425/article/details/131103193
今日推荐