一、实验目的
1、 理解Linux中虚拟文件系统的内容
2、 学习编写内核模块的方法
3、 在虚拟文件系统/proc中实现文件操作算法
二、实验内容
编写一个内核模块,在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world。
实验涉及的内核函数:
1、在proc中创建目录函数 proc_mkdir
该函数原型为:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);
name:要创建的目录的名称
parent:指向该目录的父目录的指针
返回指向当前目录的指针结构体。
2、创建version文件的函数为proc_create
该函数原型为:
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);
第一个参数name为文件名,
第二个参数mode为文件的读写权限,
第三个参数parent为其父目录的结构体指针,第四个参数proc_fops为文件的读写操作结构体。
3、实现内核空间与用户空间的数据传递函数:
copy_to_user()和copy_from_user()这两个函数。
(旧内核是raw_copy_to_user() 和 raw_copy_from_user()这两个函数)
copy_to_user() 函数的完整形态为
unsigned long copy_to_user(void *to, const void *from, unsigned long count);
函数的作用是将内核空间的数据复制到用户空间。其中
to:目标地址(用户空间)
from:源地址(内核空间)
count:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
4、从 /proc 中删除一个文件函数 remove_proc_entry()
函数的原型是:
remove_proc_entry(const char *name, struct proc_dir_entry *parent)
name:要删除的文件名
parent :文件在 /proc 文件系统中的位置
三、实验原理
1、/proc 文件系统是Linux上的一种虚拟文件系统,存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息。
2、最初开发 /proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息或启用动态运行时配置。
用户一般不可以随便在/proc 文件系统中添加信息
3、 /proc 目录下文件详解
(1)内存
/proc/buddyinfo | 伙伴系统的信息 |
/proc/pagetypeinfo | 伙伴系统进一步细分信息 |
/proc/zoneinfo | 内存区域使用情况 |
/proc/slabinfo | 内核对象缓存(slab caches)的统计信息 |
/proc/meminfo | 当前内存信息 |
/proc/vmstat | 虚拟内存统计信息 |
/proc/vmallocinfo | 虚拟内存分配信息 |
/proc/swaps | swap分区使用情况 |
/proc/mtd | 内存设备分区表信息 |
/proc/dma | DMA(直接内存访问)通道的列表 |
/proc/mtrr | 系统使用的Memory Type Range Registers (MTRRs) |
/proc/kpagecount | 系统中物理页的引用计数 |
/proc/kpageflags | 系统中物理页的标志 |
(2)I/O
/proc/filesystems | 目前系统支持的文件系统 |
/proc/diskstats | 磁盘设备的统计信息 |
/proc/ioports | 当前系统硬件设备使用的IO端口列表 |
/proc/iomem | I/O 内存映射 |
/proc/locks | 当前被内核锁定的文件 |
/proc/mounts | 当前挂载信息 |
(3)CPU
/proc/cpuinfo | cpu相关信息 |
/proc/loadavg | 当前系统负载 |
/proc/softirqs | 系统软中断信息 |
/proc/schedstat | 调度器信息 |
/proc/sched_debug | 调度器debug信息 |
(4)kernel
/proc/cmdline | 在引导启动时传递给Linux内核的参数 |
/proc/crypto | 内核支持的加密方式 |
/proc/modules | 当前系统已经加载的模块(lsmod) |
/proc/version | 内核版本信息 |
/proc/stat | 系统和内核的统计信息 |
/proc/fb | 内核编译期间帧缓冲信息 |
/proc/kmsg | 内核日志信息 |
/proc/kcore | 表示系统物理内存,可以用gdb检查内核数据结构的当前状态 |
/proc/kallsyms | 内核符号信息,主要用于调试 |
/proc/timer_list | 内核各种计时器信息 |
/proc/timer_stats | 系统中定时器的使用情况 |
/proc/sysrq-trigger | 内核触发器(危险!) |
/proc/execdomains | Linux内核当前支持的execution domains |
(5)other
/proc/interrupts | 中断表 |
/proc/uptime | 系统运行时间 |
/proc/devices | 设备信息(主设备号等) |
/proc/mdstat | 虚拟设备信息(软raid等) |
/proc/misc | 其他的主要设备(设备号为10)上注册的驱动 |
/proc/cgroup | cgroup相关信息 |
/proc/consoles | 系统中所有控制台的信息 |
/proc/keys | 证书相关 |
/proc/key-users | 关于密钥用户的信息,包括密钥使用情况的统计数据 |
4、/proc 目录下的目录
acpi | 包含高级配置和电源接口(ACPI)相关的信息 |
bus | 提供了系统中所有总线和连接到这些总线上的设备的信息 |
driver | 包含内核识别的驱动程序的信息。这个目录通常用于内核开发和调试 |
fs | 文件系统相关的信息 |
ipmi | 包含通过 Intelligent Platform Management Interface (IPMI) 接口收集的硬件监控和管理信息 |
irq | 包含中断请求(IRQ)的信息 |
scsi | 提供 SCSI 设备的信息 |
sys | 是一个非常重要的目录,包含了系统级别的参数和设置 |
sysvipc | 包含系统虚拟进程通信(SysV IPC)的相关信息 |
tty | 包含了与终端(TTY)设备相关的信息 |
四、实验步骤
要求写出实验过程和思路(用文字表述,或画流程图,或写出伪代码 都可以)。
本实验是编写一个内核模块,在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world。实验步骤如下:
(1)利用proc_mkdir()函数创建目录:
proc_parent = proc_mkdir("hello",NULL);
(2)在/proc目录下有version和softirqs等文件,本实验利用proc_create函数创建一个version文件world :
显然,创建version文件的函数为proc_create,查阅文档可知,该函数原型为:
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);
该函数接收4个参数,第一个参数为文件名,第二个参数为文件的读写权限,第三个参数为其父目录的结构体指针,第四个参数为文件的读写操作结构体。
参照/proc/version文件的实现代码和本实验要求,可写出创建world文件的主要代码:
其中,world文件的父目录指针即为先前创建hello目录所返回的指针,proc_fops定义了该文件所能执行的操作,由于本实验只要求读取文件内容,因此只实现了read属性:
其中,read_proc为实现读取文件内容的函数指针,其实现如下:
(4)定义模块的初始化和清理函数
其实现如下:
(5)编写Makefile
为编译该模块,还需编写Makefile文件
五、实验数据及源代码
Proc_hello.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <asm/uaccess.h>static struct proc_dir_entry* proc_parent;
int len, temp;
char* msg; //字符数组static ssize_t read_proc(struct file* filp, char __user* buf, size_t count, loff_t* offp) {//该函数将文件内容通过msg复制给buf,以此实现文件内容的读取。//read_proc为实现读取文件内容的函数指针if (count > temp)count = temp;temp = temp - count;raw_copy_to_user(buf, msg, count);if (count == 0)temp = len;return count;
}static const struct file_operations proc_fops = {//赋予文件world只读的属性.read = read_proc
};void create_new_proc_entry(void) {//create a new directory named hello, and return a pointer point to this dir//创建一个名为hello的新目录,并返回指向该目录的指针proc_parent = proc_mkdir("hello", NULL); //创建一个目录helloif (!proc_parent) //创建目录失败printk(KERN_INFO "Error creating proc entry");//create a file named world, add read attribute to this file using proc_fops//创建一个名为world的文件,使用proc_fops将read属性添加到此文件//world文件的父目录指针即为先前创建hello目录所返回的指针proc_create("world", 0, proc_parent,&proc_fops); //第一个参数为文件名,第二个参数为文件的读写权限,第三个参数为其父目录的结构体指针,第四个参数为文件的读写操作结构体。msg = "hello world\n"; //文件内容len = strlen(msg); //获得字符串msg的长度temp = len;printk(KERN_INFO "1.len = %d", len);printk(KERN_INFO "proc initialized");
}int proc_init(void) { //模块的初始化函数create_new_proc_entry();return 0;
}void proc_cleanup(void) { //模块的退出和清理函数printk(KERN_INFO " Inside cleanup_module\n");remove_proc_entry("hello", proc_parent); //移除目录helloremove_proc_entry("world", NULL); //移除文件world
}module_init(proc_init); //向内核注册模块提供新功能
module_exit(proc_cleanup); //注销由模块提供所有的功能
MODULE_LICENSE("GPL"); //模块具有GUN公共许可证
Makefile文件
ifneq ($(KERNELRELEASE),) obj-m := proc_hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
六、实验结果分析(截屏的实验结果,与实验结果对应的实验分析)
1、实验结果与实验程序、实验步骤、实验原理的对应分析;
2、实验过程中的问题及原因和解决办法。
首先下载Linux内核版本并解压,然后将proc_hello.c文件拷贝至Linux/fs/proc目录下,由于该目录下原本就有Makefile文件,因此要对原Makefile文件备份后再拷贝本实验中的Makefile:
mv Makefile Makefile.bak
mv my_makefile Makefile
然后,再使用make命令编译模块:
编译成功后,再使用insmod命令安装模块,可以看到该目录下多了一个ko文件,该文件即为编译成功的模块文件。
最后切换至/proc目录,即可看到结果。
出现的问题以及可能出现的问题:
权限问题:
原因:编写和加载内核模块通常需要特权,而非特权用户可能会遇到权限问题。
解决办法:使用 sudo 或在超级用户权限下执行相关操作。
模块加载失败:
原因:可能是由于编译错误、符号冲突等原因引起的。
解决办法:检查模块加载时的日志输出(使用 dmesg 命令),查找错误消息以便识别和解决问题。
七、实验思考题
1、编写一个有两个进程并发运行的程序,并在结尾设定一个死循环,编译完成后让其在后台运行,然后在前台用cat 命令查看/proc目录下的文件/proc/meminfo (当前内存信息)、/proc/vmstat (虚拟内存统计信息)、/proc/vmallocinfo(虚拟内存分配息)、/proc/filesystems(目前系统支持的文件系统)、/proc/locks(当前被内核锁定的文件)、/proc/cpuinfo ( cpu相关信息)和/proc/modules(当前系统已经加载的模块(lsmod))中信息,分析其含义。
创建一个简单的C程序,该程序创建两个并发运行的进程,然后进入死循环。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>void processFunction(int processNumber) {for (int i = 0; i < 5; i++) {printf("Process %d: Iteration %d\n", processNumber, i);sleep(1);}
}int main() {pid_t childPid;// 创建第一个子进程if ((childPid = fork()) == 0) {// 子进程1执行的代码processFunction(1);exit(0);}// 创建第二个子进程if ((childPid = fork()) == 0) {// 子进程2执行的代码processFunction(2);exit(0);}// 父进程执行的代码printf("Parent process: Waiting for child processes to finish...\n");// 进入死循环while (1) {// 保持程序运行sleep(1);}return 0;
}
编译这个程序
使用cat命令查看/proc目录下的信息。