Linux模块与系统调用
模块与系统调用
1. 编写内核模块代码
首先,编写一个简单的“Hello World”内核模块文件 hello_module.c
。
#include <linux/init.h> // 用于宏 __init 和 __exit
#include <linux/module.h> // 用于模块编程基本宏
#include <linux/kernel.h> // 用于 printk 宏
MODULE_LICENSE("GPL"); // 指定模块许可证
MODULE_AUTHOR("20242826"); // 指定模块作者
MODULE_DESCRIPTION("A simple hello world kernel module"); // 模块描述
MODULE_VERSION("1.0"); // 模块版本
// 初始化函数
static int __init hello_init(void) {
printk(KERN_INFO "Hello, Kernel!\n"); // 向内核日志打印信息
return 0; // 返回0表示成功
}
// 清理函数
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, Kernel!\n"); // 向内核日志打印信息
}
// 注册初始化和清理函数
module_init(hello_init);
module_exit(hello_exit);
在这个模块中:
hello_init
函数在模块加载时运行。hello_exit
函数在模块卸载时运行。printk
是用于内核日志输出的函数,类似于用户空间的printf
。
2. 创建Makefile文件
创建一个 Makefile
,用来编译内核模块。文件内容如下:
# Makefile
obj-m += hello_module.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
在这个 Makefile
中:
obj-m += hello_module.o
指定生成模块的目标文件。make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
命令用当前内核的编译环境编译模块。clean
命令用于清理编译生成的文件。
3. 编译内核模块
在包含 hello_module.c
和 Makefile
的目录中,运行以下命令来编译模块:
make
编译成功后,将生成 hello_module.ko
文件,这就是编译完成的内核模块文件。
4. 加载和卸载内核模块
加载模块
要加载模块,可以使用 insmod
命令:
sudo insmod hello_module.ko
成功加载模块后,内核日志中会出现 Hello, Kernel!
的信息。可以通过以下命令查看内核日志:
sudo dmesg | tail
卸载模块
要卸载模块,可以使用 rmmod
命令:
sudo rmmod hello_module
卸载模块后,内核日志中会显示 Goodbye, Kernel!
信息。
5.编译模块实现输出当前进程信息的功能
#include <linux/init.h> // 用于宏 __init 和 __exit
#include <linux/module.h> // 用于模块编程基本宏
#include <linux/kernel.h> // 用于 printk 宏
#include <linux/sched.h> // 用于获取当前进程信息
#include <linux/sched/signal.h>
MODULE_LICENSE("GPL"); // 模块许可证
MODULE_AUTHOR("20242826"); // 模块作者
MODULE_DESCRIPTION("A module to print current process info"); // 模块描述
MODULE_VERSION("1.0"); // 模块版本
// 初始化函数
static int __init module2_init(void) {
printk(KERN_INFO "Module2 loaded\n");
printk(KERN_INFO "Current Process Info:\n");
printk(KERN_INFO "Process ID: %d\n", current->pid);
printk(KERN_INFO "Process Name: %s\n", current->comm);
return 0; // 返回0表示成功
}
// 清理函数
static void __exit module2_exit(void) {
printk(KERN_INFO "Module2 unloaded\n");
}
// 注册初始化和清理函数
module_init(module2_init);
module_exit(module2_exit);
代码说明
- current 是一个指向当前执行进程的 task_struct 指针,task_struct 是包含进程信息的内核结构体。
- current->pid 表示当前进程的ID。
- current->comm 表示当前进程的名称。
# Makefile
obj-m += module2.o # 指定编译生成的目标对象
all:
# 使用 Tab 键缩进
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
# 使用 Tab 键缩进
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
make
sudo insmod module2.ko
sudo dmesg | tail
# 卸载模块
sudo rmmod module2
6.编译模块实现读取进程链表的功能
#include <linux/init.h> // 用于宏 __init 和 __exit
#include <linux/module.h> // 用于模块编程基本宏
#include <linux/kernel.h> // 用于 printk 宏
#include <linux/sched.h> // 用于 task_struct 结构体
#include <linux/sched/signal.h> // 用于 next_task 宏
MODULE_LICENSE("GPL");
MODULE_AUTHOR("20242826");
MODULE_DESCRIPTION("A module to list all processes in the process list");
MODULE_VERSION("1.0");
static int __init module3_init(void) {
struct task_struct *task;
printk(KERN_INFO "Module3 loaded: Listing all processes\n");
// 遍历进程链表
for_each_process(task) {
printk(KERN_INFO "Process: %s [PID: %d]\n", task->comm, task->pid);
}
return 0;
}
static void __exit module3_exit(void) {
printk(KERN_INFO "Module3 unloaded\n");
}
module_init(module3_init);
module_exit(module3_exit);
代码说明
- for_each_process(task) 是 Linux 内核提供的一个宏,用于遍历所有进程。它会依次遍历所有 task_struct 结构体,将每个进程的指针赋值给 task 变量。
- task->comm 存储进程名称,task->pid 存储进程 ID。
该模块在加载时输出所有进程的名称和进程ID。
# Makefile
obj-m += module3.o # 指定编译生成的目标对象
all:
# 使用 Tab 键缩进
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
# 使用 Tab 键缩进
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
make
sudo insmod module3.ko
sudo dmesg | tail -n 50
# 卸载模块
sudo rmmod module3
7. 清理编译生成的文件
执行以下命令清理生成的文件:
make clean
总结
以上步骤完成了Linux内核模块编写、编译、加载和卸载的流程。