@(学习资料)[OS, 文件系统, Proc伪文件系统]
理解Linux操作系统文件系统的概念
- 岳友(yoyo9170@163.com)
上节课我们主要介绍了动态加载内核模块,并完成了相应的实验,本次实验主要讲解和考察的是Linux 文件系统,其中核心主要涉及到三个方面:Proc文件系统介绍、文件创建、文件读写、内核空间与用户空间交互。
Proc文件系统简介
Proc文件系统
是一个虚拟文件系统,只存在于内存中,通过它可以查询、设置系统的运行情况及各种系统参数。
在 /proc
目录下,每个文件的读写都可以绑定到一个内核函数上;当文件被读写时,被绑定的相应的内核函数就会被触发,由这些函数处理文件的输出与输入。
而 /proc
目录下几乎包含了系统所有信息:
- 可以通过
Proc文件系统
收集系统信息; - 也可以用于程序调试;
- 还可以动态的修改内核的数据结构,改变内核的运行状态
大家可以在自己的Linux系统里,进入到/proc
目录下,会看到很多以数字命名的文件夹,这些一个个数字即对应着进程PID
,根据上面提到的三点,不难理解这里主要收集系统运行进程的状态信息,具体对应参数和意义为:
文件/目录名 | 描述 |
---|---|
Cmdline | 该进程的命令行参数 |
Cwd | 进程运行的当前路径的符号链接 |
Environ | 该进程运行的环境变量 |
Exe | 该进程相关的程序的符号链接 |
Fd | 包含该进程使用的文件描述符 |
Maps | 可执行文件或库文件的内存映像 |
Mem | 该进程使用的内存 |
Root | 该进程所有者的家(home)目录 |
Stat | 进程状态 |
Statm | 进程的内存状态 |
Status | 用易读的方式表示的进程状态 |
当然/proc
目录下还有一系列的文件,这些文件的作用
为:
用cat /proc/cpuinfo
命令获得系统的CPU信息
用cat /proc/meminfo
命令获得系统内存信息
用cat /proc/loadvag
命令获得CPU平均负载
……(请大家自己查看)
1、核心数据结构
- proc_dir_entry结构
代表各文件节点,管理操作系统从用户空间到内核空间对文件读写的驱动1
2
3
4
5
6
7
8
9
10struct proc_dir_entry {
...
const char *name; //名字;
mode_t mode; //权限属性;
nlink_t nlink; //目录下子目录的数目;
read_proc_t *read_proc;//读操作函数
write_proc_t *write_proc;//写操作函数
struct file_operations *proc_fops; //文件操作函数;
...
}
2、创建文件
- 头文件
1
#include <linux/proc_fs.h>
- 创建文件系统
1
struct proc_dir_entry* create_proc_entry(const char* name, mode_t mode, struct proc_dir_entry* parent);
name:文件名
mode:文件类型和访问权限 parent:父目录,如果parent为根目录,则为NULL
返回值:成功,返回指向文件的proc_dir_entry结构指针.
- 实例
1
foo_file = create_proc_entry(“foo”, 0644, example_dir);
3、删除文件
内核通过函数remove_proc_entry()
删除proc
文件1
void remove_proc_entry(const char* name, struct proc_dir_entry* parent);
从
parent
目录下移除名为name
的文件proc
文件不能使用rmdir
、rm
命令删除,只能使用上述函数 在删除文件之前,先释放proc_dir_entry
结构中的data
4、创建目录
创建一个目录
1
2
3struct proc_dir_entry* proc_mkdir(const char* name, struct
proc_dir_entry* parent);
//目录名为name,父目录为parent创建符号连接
1
2
3struct proc_dir_entry* proc_symlink(const char* name, struct
proc_dir_entry* parent, const char* dest);
//在parent目录下创建一个名字为name的符号连接,链接目标是dest
5、用户空间与内核空间数据传输
proc_dir_entry
结构中的read_proc
, write_proc
是回调函数,分别定义了对文件的读写函数;
1 | //实现读写文件接口 |
6、读取文件
- 读文件接口函数原型
1
2int read_func(char* page, char** start, off_t off, int count, int* eof,
void* data);
其中,
Page
指针指向将写入数据的缓冲区Start
说明数据写在页面的位置Count
写入的字节数Offset
写入数据的位置相对于缓冲区的偏移量Eof
返回参数,当读到文件结尾时,该标志被置位Data
保存私有数据
7、写入文件
- 写文件接口函数原型
1
2int write_func(struct file* file, const char* buffer, unsigned long count, void*
data);
从
buffer
数据缓冲区中读取count
字节数的数据,写入到file
文件
一般buffer
指向用户空间缓冲区,应先调用copy_from_user
将数据拷贝到内 核空间
一般省略file
参数
代码示例
1、实现在/Proc目录下床间一个可读可写的my_procfile_1K文件
编写 rw_proc.c 文件,如下
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 <asm/uaccess.h> /* for copy_from_user */
#define PROCFS_MAX_SIZE 1024
#define PROCFS_NAME "my_procfile_1k"
MODULE_LICENSE("GPL");
static struct proc_dir_entry *Our_Proc_File;
static char procfs_buffer[PROCFS_MAX_SIZE];
static unsigned long procfs_buffer_size = 0;
static int procfile_read(char *buffer,char **buffer_location, off_t offset, int buffer_length, int *eof,
{
int ret;
void *data)
printk(KERN_INFO "procfile_read (/proc/%s) called\n", PROCFS_NAME);
if (offset > 0)
{
ret=0;
}
else{
memcpy(buffer, procfs_buffer, procfs_buffer_size);
ret = procfs_buffer_size;
}
return ret;
}
static int procfile_write(struct file *file, const char *buffer, unsigned long count, void *data)
{
procfs_buffer_size = count;
if(procfs_buffer_size > PROCFS_MAX_SIZE )
{
procfs_buffer_size = PROCFS_MAX_SIZE;
}
if(copy_from_user(procfs_buffer, buffer, procfs_buffer_size) )
{
return EFAULT;
}
return procfs_buffer_size;
}
static int rw_proc_init(void)
{
Our_Proc_File = create_proc_entry(PROCFS_NAME, 0644, NULL);
if (Our_Proc_File == NULL) {
remove_proc_entry(PROCFS_NAME, &proc_root);
printk(KERN_ALERT "Error: Could not initialize /proc/%s\n", PROCFS_NAME);
return ENOMEM;
}
Our_Proc_File->read_proc = procfile_read;
Our_Proc_File->write_proc = procfile_write;
Our_Proc_File->owner = THIS_MODULE;
Our_Proc_File->mode = S_IFREG | S_IRUGO;
Our_Proc_File->uid = 0;
Our_Proc_File->gid = 0;
Our_Proc_File->size = 37;
printk(KERN_INFO "/proc/%s created\n", PROCFS_NAME);
return 0;
}
static void rw_proc_exit(void) {
remove_proc_entry(PROCFS_NAME, &proc_root);
printk(KERN_INFO "/proc/%s removed\n", PROCFS_NAME);
}
module_init(r w_proc_init);
module_exit(rw_proc_exit);
/*代码注释
A> Our_Proc_File: /proc 文件系统下的每一个文件或目录项都对应一个proc_dir_entry 结构体实例。
B> procfs_buffer: 这个缓冲区是我们 my_procfile_1k 的真实载体,本质上我们将来对my_procfile_1k 文件的读写都是对该缓冲区的读写。
C> PROCFS_MAX_SIZE 和 procfs_buffer_size: 这两个值的关系就好比你有 4G(PROCFS_MAX_SIZE)的 U 盘,里面已用空间为 2.3G(procfs_buffer_size)一样。因此每次写入文件
D> procfile_read:我们为文件 my_procfile_1k 实现的 read 函数。buffer 为写出内核空间的缓冲区(一个 page 大小),buffer_location 是用来写出大于一个 page 大 小的数据是用的参数。offset 是用来标记一个进程中对当前文件读写的偏移量。 buffer_len 是要读的大小,这里被忽略掉,直接返回了文件(buffer)的全部内容。eof 用来标识文件已经到达末尾。
E> procfile_write:我们为文件 my_procfile_1k 实现的 write 函数。使用copy_from_user 函数的原因是 buffer 是一个指向用户地址空间的指针。
F> create_proc_entry:用来创建一个 proc 文件系统的文件实例,创建目录实例的 函数为 proc_mkdir(char * dirname, struct proc_dir_entr * father_dir);两个函数的末尾参数置 NULL,表示父目录为/proc
*/编写 Makefile 文件
1
2
3
4
5
6
7
8
9
10
11
12KVER=$(shell uname -r)
KDIR=/lib/modules/$(KVER)/build
PWD=$(shell pwd)
obj-m += rw_proc.o
default:
make -C $(KDIR) M=$(PWD) modules
clean:
-rm *.ko
-rm *.o
-rm -f Mo*
-rm *.mod.*编译模块 #make
安装模块
1
#insmod rw_proc.ko
测试/proc/my_procfile_1k 文件功能
1
2
3
4#cat /proc/my_procfile_1k
#ls * > /proc/my_procfile_1k
#cat /proc/my_procfile_1k
#dmesg | tail -10 //查看系统日志里的打印信息卸载模块
1
rmmod
实验题目
1、(考查内核与用户空间的交互方式)在文档中给出的示例代码中procfile_read
函数里面的offset>0
分支里面添加printk
打印信息到系统日志,并编写一个读my_procfile_1k
的c语言程序。在程序路循环五次读取my_procfile_1k
文件并打印读取的信息。(贴出代码和截图)查看内核日志信息。想想为什么。
2、(考查创建可读写的proc文件)在/proc
目录下创建一个以task_info
目录,并在里面创建一个pid
文件和task_info
文件:pid
文件可读写,task_info
文件只读。若pid=-1
,则task_info
文件的内容为当前系统所有进程的ID
:应用程序名称的记录集合;若pid=>0
则task_info
为进程pid
的详细信息(自定义)。(请贴出代码并展示截图结果)
提示:
遍历所有进程可以用Linux/sched.h
中的for_each_process
宏实现:
struct tast_struct * task;
for_each_prcess(task){
}