任务
写一个内核模块,在内核模块中实现
1.一个定时器,定期读取内存信息,并记录到一个日志文件
2.日志文件包含完整的时间戳
(提示:使用hrtimer
一系列接口)
思路:
关于定时器的解决:Linux中主要有两种解决方案:简单的timer_list
和更精确的hrtimer
。由于需求中没有特别指出对定时精度的要求,但使用hrtimer
可以提供更高的精度和灵活性,所以我决定使用hrtimer
接口。
然后是获取内存信息:经过查找可知,Linux内核提供了获取系统内存信息的接口si_meminfo
,它可以填充一个sysinfo
结构体,这个结构体包含了系统当前的内存使用情况。
文件的操作:内核空间处理文件和用户态中处理文件使用的接口不同,经过网上查找,在内核空间中写文件,需要使用vfs_write
等函数,并且要小心处理文件系统的状态改变(比如,需要使用set_fs
来临时改变内核的地址限制)。找到并引入以下头文件:<linux/fs.h>包含了对文件系统进行操作的函数原型和相关的结构体定义、<linux/uaccess.h>:包含了对用户空间和内核空间数据交互的函数原型和宏定义、<linux/kernel.h>:包含了内核编程中常用的一些宏定义和函数原型。
基于上述的工具和接口选择,我设计出了模块的大体结构,包括初始化部分、定时器回调函数以及清理部分:
1.初始化部分 (my_init
):打开or创建日志文件,初始化定时器,然后启动。
2.定时器回调函数 (timer_callback
):这个函数是定时器到期时调用的,它会读取内存信息,获取当前时间,并将这些信息写入之前打开的日志文件。
3.模块清理部分 (my_exit
):在卸载模块时,需要取消定时器(如果它还在运行),并且关闭打开的日志文件。
代码
kernel_timer_file.c
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/mm.h> // 用于si_meminfo存储内存信息
#include <linux/timekeeping.h> // 用于获取内存时间 current_kernel_time64
#define TIMER_INTERVAL_NS 1e9 // 时间间隔 1秒
static struct hrtimer hr_timer;
struct file *file = NULL;
mm_segment_t old_fs;
// 定时器回调函数,周期性获取系统内存信息
static enum hrtimer_restart timer_callback(struct hrtimer *timer) {
struct sysinfo si;
char buf[128];
int bytes;
struct timespec64 ts;
// 获取系统内存信息和当前时间
si_meminfo(&si);
ktime_get_real_ts64(&ts);
/* 准备带有时间戳和内存信息的消息
并格式化为字符串存在buf缓冲区中*/
bytes = snprintf(buf, sizeof(buf), "[%lld.%09ld] Total RAM: %lu, Free RAM: %lu\n",
(long long)ts.tv_sec, ts.tv_nsec, si.totalram * si.mem_unit / 1024, si.freeram * si.mem_unit / 1024);
// 在内核空间中写入文件
if (file) {
old_fs = get_fs();
set_fs(get_ds());
vfs_write(file, buf, bytes, &file->f_pos);
set_fs(old_fs);
}
// 重启定时器
hrtimer_forward_now(timer, ns_to_ktime(TIMER_INTERVAL_NS));
return HRTIMER_RESTART;
}
// 模块初始化函数
static int __init my_init(void) {
ktime_t ktime;
// 以写入模式打开文件,将输出的内存信息打印到当前目录下的kernel_memory_log.txt
file = filp_open("./kernel_memory_log.txt", O_CREAT | O_WRONLY | O_APPEND, 0644);
if (IS_ERR(file)) {
file = NULL;
pr_alert("无法打开文件\n");
return -EFAULT;
}
ktime = ktime_set(0, TIMER_INTERVAL_NS);
hrtimer_init(&hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hr_timer.function = &timer_callback;
hrtimer_start(&hr_timer, ktime, HRTIMER_MODE_REL);
return 0;
}
// 模块退出函数,在模块被卸载时调用
static void __exit my_exit(void) {
int ret;
ret = hrtimer_cancel(&hr_timer);
if (ret)
pr_info("定时器仍在使用...\n");
if (file) {
filp_close(file, NULL);
}
pr_info("HR定时器模块已卸载\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("PHM");
MODULE_DESCRIPTION("在内核空间中记录内存信息到文件的HR(高精度定时器)定时器模块");
Makefile
obj-m += kernel_timer_file.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
操作步骤
将两个文件上传到服务器上。
准备
在服务器上安装必要的模块:
sudo yum groupinstall "Development Tools"
sudo yum install gcc
sudo yum install kernel-devel
我遇到了版本不匹配的问题,升级:
sudo yum update
内核开发包:
sudo yum install kernel-devel-$(uname -r)
切换到root用户:
su -
操作流程
1.编译模块
在包含kernel_timer_file.c
源代码和Makefile
的目录中运行make
命令。生成.ko
文件,即可加载的内核模块。
2.加载模块
编译完成后,使用insmod
命令加载模块到内核:
sudo insmod kernel_timer_file.ko
可以使用modinfo
查看模块信息,使用lsmod
查看已加载的模块。
3.验证和监控
加载模块后,可以通过dmesg
命令查看内核日志,验证模块是否按预期工作。
dmesg | tail #查看最后几行
4.卸载模块
使用rmmod
命令:
sudo rmmod kernel_timer_file