CTF-内核pwn入门1:linux内核模块基础原理
- 游戏开发
- 2025-09-01 16:03:02

本文由A5rZ在2025-2-18-21:00编写
1.可加载内核模块是什么?内核可加载模块(*.ko 文件)是内核的一种扩展机制,可以在不重启系统的情况下加载和卸载代码。它们允许动态地向内核添加新的功能或支持。
以下是一些内核模块常见的功能:
1 驱动程序内核模块最常见的用途是为硬件设备提供驱动程序支持。内核驱动程序可以管理设备的输入/输出操作,处理硬件中断、读取传感器数据或控制硬件设备。
例如,常见的硬件设备驱动模块有:
网络接口卡(NIC)驱动:支持不同类型的网卡和网络协议。磁盘驱动:支持硬盘、SSD、光驱等设备。USB驱动:支持USB设备,如鼠标、键盘、打印机等。 2 文件系统支持内核模块也可以提供新的文件系统支持。例如,ext4 是一种文件系统,但如果你想支持其他文件系统(如 ntfs, xfs, btrfs 等),通常会加载相应的文件系统模块。
3 网络协议栈内核模块还可以用于添加网络协议的支持。例如,TCP/IP协议栈是内核的一部分,但可以通过加载相应的模块来支持新的协议或扩展现有协议。例如:
VPN协议支持:如 IPsec 和 WireGuard。无线网络支持:Wi-Fi 驱动和协议栈。 4 安全性和调试功能有时,内核模块用于提供安全性增强或调试功能。例如:
SELinux模块:强化内核的安全性,提供基于策略的访问控制。内核调试模块:提供内核日志记录、追踪功能,或允许内核代码被动态调试。内存保护模块:例如提供 Address Space Layout Randomization(ASLR)功能。 5 性能监控和系统管理内核模块还可以用于性能监控、调度管理、系统资源的动态调整等。例如:
CPU性能计数器:动态监控 CPU 使用情况,执行性能分析。进程调度模块:调整调度策略或优先级。 6 虚拟化支持内核模块可以用于提供虚拟化支持,例如:
KVM模块:实现虚拟化支持,允许创建虚拟机。虚拟网络接口:如 tun/tap 接口,支持用户空间和内核空间之间的虚拟网络通信。 总结内核模块(*.ko 文件)可以扩展内核的功能,允许内核在运行时动态加载或卸载代码。常见用途包括硬件驱动、文件系统支持、网络协议栈、安全性增强、性能监控等。
2.怎么加载内核模块 1. 使用 insmod 命令加载内核模块insmod 是最直接的方式来加载一个内核模块。它会将指定的 .ko 文件加载到内核中,并立即执行该模块。
语法: insmod .ko 例如,加载一个名为 example.ko 的内核模块:sudo insmod example.ko 注意: insmod 只会加载指定的模块文件,不会处理依赖关系。如果该模块依赖其他模块,它们需要提前加载。只有 root 用户或具有足够权限的用户才能加载内核模块。 2. 使用 modprobe 命令加载内核模块相比 insmod,modprobe 是一个更为智能的工具,它不仅可以加载模块,还能自动处理模块的依赖关系,加载所需的依赖模块。
语法: modprobe 例如,加载一个名为 example 的模块:sudo modprobe example modprobe 的优点: 自动解决依赖关系:如果模块依赖于其他模块,modprobe 会自动加载这些依赖模块。模块文件存放位置:modprobe 会根据 /lib/modules/$(uname -r)/ 目录中的模块配置文件来加载模块,而不需要指定 .ko 文件的具体路径。支持配置文件:modprobe 使用 /etc/modprobe.d/ 目录下的配置文件(例如 blacklist.conf)来指定哪些模块不加载或加载时的特定参数。 3. 查看已加载的内核模块使用 lsmod 命令可以查看当前系统中已加载的内核模块。lsmod 显示一个模块的列表以及其依赖关系。
lsmod输出中通常包含以下信息:
Module:模块的名称。Size:模块的大小。Used by:其他使用该模块的模块或进程。 4. 卸载内核模块如果你想卸载已加载的模块,可以使用 rmmod 或 modprobe -r 命令。
rmmod 卸载模块: sudo rmmod modprobe -r 卸载模块: sudo modprobe -r modprobe -r 还会自动处理模块的依赖关系,卸载该模块时会卸载依赖的模块。 5. 通过 /etc/modules-load.d/ 配置自动加载如果你希望在系统启动时自动加载某个模块,可以通过创建一个配置文件,在 /etc/modules-load.d/ 目录下配置。
步骤:创建一个新的文件(例如 my_module.conf)并在其中指定要加载的模块名:
sudo nano /etc/modules-load.d/my_module.conf在文件中输入模块的名称(不需要 .ko 后缀):
example保存并关闭文件。
当系统启动时,example 模块将会被自动加载。
6. 使用 modinfo 查看模块信息如果你想查看内核模块的详细信息(如版本、描述、依赖关系等),可以使用 modinfo 命令。
语法: modinfo .ko例如:
modinfo example.ko它会输出诸如模块的版本、作者、依赖关系、许可证、描述等信息。
总结 insmod:手动加载模块,但不会处理依赖。modprobe:推荐的加载模块方式,支持自动处理依赖关系。lsmod:查看已加载的模块。rmmod / modprobe -r:卸载模块。自动加载:通过 /etc/modules-load.d/ 配置文件实现。modinfo:查看模块详细信息。如果你需要加载一个模块,并且该模块有依赖关系,使用 modprobe 会更方便,因为它能自动加载依赖的模块。
3.初始化函数init_module与析构函数module_exitinit_module 函数在 Linux 内核模块中是一个特殊的函数,它是模块加载时调用的入口点。它通常用于模块初始化,例如分配资源、注册设备驱动、创建 /proc 文件系统条目等。以下是详细解释和示例:
1. init_module 函数的作用init_module 函数在内核模块加载时被调用,其主要职责包括:
初始化模块所需的数据结构和资源。注册设备驱动或文件系统。创建内核对象或文件系统条目。设置模块的初始状态。 2. init_module 的定义在最简单的形式下,init_module 函数可能如下所示:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int __init init_module(void) { printk(KERN_INFO "Module initialized.\n"); return 0; // 返回0表示成功 } __init:这是一个宏,告诉内核这个函数在初始化后可以被丢弃,释放内存。printk:类似于用户空间的 printf,用于向内核日志输出信息。return 0:返回0表示模块初始化成功,返回非零值表示初始化失败。 3. 使用 module_init 宏通常,内核模块不会直接定义 init_module 函数,而是使用 module_init 宏来指定初始化函数。这使得代码更清晰,且与 module_exit 宏对称。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> static int __init my_module_init(void) { printk(KERN_INFO "Module initialized.\n"); return 0; } module_init(my_module_init); // 指定模块初始化函数 4. 完整示例以下是一个完整的内核模块示例,它在加载时创建一个 /proc 文件系统条目,并在卸载时删除它:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #define PROC_NAME "my_module" static struct proc_dir_entry *proc_entry; static int my_proc_show(struct seq_file *m, void *v) { seq_printf(m, "Hello, World!\n"); return 0; } static int my_proc_open(struct inode *inode, struct file *file) { return single_open(file, my_proc_show, NULL); } static const struct file_operations my_proc_fops = { .owner = THIS_MODULE, .open = my_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init my_module_init(void) { proc_entry = proc_create(PROC_NAME, 0, NULL, &my_proc_fops); if (!proc_entry) { return -ENOMEM; // 内存分配失败 } printk(KERN_INFO "/proc/%s created\n", PROC_NAME); return 0; } static void __exit my_module_exit(void) { proc_remove(proc_entry); printk(KERN_INFO "/proc/%s removed\n", PROC_NAME); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple example of a Linux kernel module."); 解释头文件:
#include :内核模块的基本定义。#include :内核信息输出功能。#include :内核初始化和退出功能。#include :与 /proc 文件系统交互。#include :用于简化 /proc 文件系统的读写操作。宏:
#define PROC_NAME "my_module":定义 /proc 文件系统条目的名字。文件操作结构:
my_proc_show:在 /proc 文件被读取时调用,输出 “Hello, World!”。my_proc_open:在 /proc 文件被打开时调用。my_proc_fops:定义文件操作,包括打开、读取、查找和释放操作。初始化函数:
my_module_init:在模块加载时调用,创建 /proc 文件系统条目,输出日志信息。退出函数:
my_module_exit:在模块卸载时调用,删除 /proc 文件系统条目,输出日志信息。模块宏:
module_init(my_module_init):指定模块的初始化函数。module_exit(my_module_exit):指定模块的退出函数。MODULE_LICENSE 等:模块的元数据,如许可证、作者、描述等。 总结init_module 函数(或者通过 module_init 宏指定的初始化函数)是内核模块加载时的入口点,用于初始化模块的各项功能。初始化函数可以执行多种操作,如资源分配、设备注册、创建 /proc 条目等。当模块被卸载时,对应的退出函数(通过 module_exit 宏指定)会被调用,以清理资源。
4.proc文件/proc 文件系统是 Linux 内核提供的一种虚拟文件系统,用于访问内核信息。它在系统启动时由内核自动挂载,并且通常挂载在 /proc 目录下。/proc 文件系统的内容并不存储在磁盘上,而是动态生成的,它提供了一种方便的方式来获取运行时的内核和系统信息。
主要功能 系统信息访问:例如,/proc/cpuinfo 提供 CPU 信息,/proc/meminfo 提供内存使用信息。进程信息访问:每个运行中的进程在 /proc 目录下都有一个与其 PID 对应的子目录,例如 /proc/1234。内核参数调整:某些文件可以用来调整内核参数,例如 /proc/sys 目录下的文件。 常见的 /proc 文件和目录 /proc/cpuinfo:显示 CPU 相关信息。/proc/meminfo:显示内存使用情况。/proc/uptime:显示系统启动后的运行时间。/proc/version:显示内核版本信息。/proc/[pid]:每个运行中的进程都有一个与其 PID 对应的目录,包含该进程的各种信息,如环境变量、当前工作目录、内存映射等。 使用 /proc 文件系统的示例 1. 查看 CPU 信息 cat /proc/cpuinfo 2. 查看内存使用情况 cat /proc/meminfo 3. 查看系统运行时间 cat /proc/uptime 在内核模块中使用 /proc 文件系统内核模块可以创建自己的 /proc 文件,以便用户空间程序与内核模块交互。以下是一个简单的示例,展示如何在内核模块中创建一个 /proc 文件。
示例:创建一个 /proc 文件 内核模块代码 #include #include #include #include #include #define PROC_NAME "my_module" static struct proc_dir_entry *proc_entry; static int my_proc_show(struct seq_file *m, void *v) { seq_printf(m, "Hello, World!\n"); return 0; } static int my_proc_open(struct inode *inode, struct file *file) { return single_open(file, my_proc_show, NULL); } static const struct file_operations my_proc_fops = { .owner = THIS_MODULE, .open = my_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init my_module_init(void) { proc_entry = proc_create(PROC_NAME, 0, NULL, &my_proc_fops); if (!proc_entry) { return -ENOMEM; } printk(KERN_INFO "/proc/%s created\n", PROC_NAME); return 0; } static void __exit my_module_exit(void) { proc_remove(proc_entry); printk(KERN_INFO "/proc/%s removed\n", PROC_NAME); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple example of a /proc file."); 编译和加载模块 make sudo insmod my_module.ko 读取 /proc 文件 cat /proc/my_module输出将是:
Hello, World! 卸载模块 sudo rmmod my_module 解释头文件:
#include :内核模块的基本定义。#include :内核信息输出功能。#include :内核初始化和退出功能。#include :与 /proc 文件系统交互。#include :用于简化 /proc 文件系统的读写操作。宏:
#define PROC_NAME "my_module":定义 /proc 文件系统条目的名字。文件操作结构:
my_proc_show:在 /proc 文件被读取时调用,输出 “Hello, World!”。my_proc_open:在 /proc 文件被打开时调用。my_proc_fops:定义文件操作,包括打开、读取、查找和释放操作。初始化函数:
my_module_init:在模块加载时调用,创建 /proc 文件系统条目,输出日志信息。退出函数:
my_module_exit:在模块卸载时调用,删除 /proc 文件系统条目,输出日志信息。模块宏:
module_init(my_module_init):指定模块的初始化函数。module_exit(my_module_exit):指定模块的退出函数。MODULE_LICENSE 等:模块的元数据,如许可证、作者、描述等。 总结/proc 文件系统是 Linux 内核提供的一个强大的工具,用于访问和管理系统信息。它不仅提供了一种查看和修改内核参数的机制,还允许内核模块创建自定义的 /proc 文件,以便用户空间程序与内核模块交互。
5.proc_create()proc_create 是 Linux 内核中的一个函数,用于创建一个新的 /proc 文件系统条目。这个函数常用于内核模块中,以便在 /proc 文件系统下创建一个新的文件,使得用户空间程序可以通过这个文件与内核模块进行交互。
函数原型 struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *fops); 参数说明name: 这是要创建的 /proc 文件的名称。它是一个字符串,表示文件的名称。
mode: 这是文件的权限模式,通常使用 S_IRUGO、S_IWUSR 等宏来设置读、写权限等。umode_t 是一个表示文件模式的类型。
parent: 这是一个指向父目录条目的指针。如果为 NULL,则在根目录下创建文件。
fops: 这是一个指向 file_operations 结构的指针,包含了对这个文件的操作函数的定义,例如打开、读取、写入等操作。
返回值 如果创建成功,函数返回一个指向新创建的 proc_dir_entry 结构的指针。如果失败,返回 NULL。 使用示例下面是一个简单的示例,展示了如何使用 proc_create 函数:
#include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/module.h> static int my_proc_show(struct seq_file *m, void *v) { seq_printf(m, "Hello from proc!\n"); return 0; } static int my_proc_open(struct inode *inode, struct file *file) { return single_open(file, my_proc_show, NULL); } static const struct file_operations my_proc_fops = { .owner = THIS_MODULE, .open = my_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init my_module_init(void) { proc_create("myprocfile", 0, NULL, &my_proc_fops); return 0; } static void __exit my_module_exit(void) { remove_proc_entry("myprocfile", NULL); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL"); 解释示例 在这个示例中,我们定义了一个名为 myprocfile 的 /proc 文件。my_proc_show 函数负责处理读取操作,向用户空间输出 “Hello from proc!”。my_proc_open 函数用于打开这个文件。在模块初始化时,调用 proc_create 创建文件,并在退出时调用 remove_proc_entry 删除文件。 6.file_operations结构体file_operations 结构体是 Linux 内核中用于定义文件操作函数的一组函数指针集合。它在字符设备驱动程序、块设备驱动程序以及其他文件系统实现中扮演着关键角色。通过实现和注册 file_operations 结构体中的函数,驱动程序能够响应用户空间对设备文件的各种操作,如打开、读取、写入和关闭等。
file_operations 结构体简介file_operations 结构体定义在 `` 头文件中,其基本定义如下:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*splice_write) (struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read) (struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease) (struct file *, long, struct file_lock **); long (*move_mmap) (struct file *, struct vm_area_struct *); ssize_t (*dedupe_file_range) (struct file *, loff_t, loff_t, struct file *, loff_t, loff_t, unsigned); void (*show_fdinfo) (struct seq_file *m, struct file *f); unsigned (*atomic_open) (struct inode *, struct file *, unsigned); };虽然结构体中包含许多成员,但通常驱动程序只需要实现其中的一部分,根据具体需求进行选择。
常用的 file_operations 成员以下是一些常用的 file_operations 成员及其功能说明:
owner: 指向该 file_operations 结构体所属的模块。通常设为 THIS_MODULE。
open: 当用户空间调用 open() 系统调用打开设备文件时,此函数被调用。用于初始化设备状态、分配资源等。
read: 用户空间调用 read() 系统调用时,此函数被调用。用于从设备读取数据到用户空间。
write: 用户空间调用 write() 系统调用时,此函数被调用。用于将用户空间的数据写入设备。
release: 用户空间调用 close() 系统调用关闭设备文件时,此函数被调用。用于释放设备资源。
ioctl (unlocked_ioctl 和 compat_ioctl): 处理设备的控制请求,用户空间通过 ioctl() 系统调用向设备发送控制命令。
mmap: 当用户空间调用 mmap() 系统调用映射设备内存到用户空间时,此函数被调用。
llseek: 处理文件偏移量的调整,如用户调用 lseek()。
poll: 实现设备的异步 I/O 多路复用,如 select()、poll()、epoll() 等系统调用。
示例:字符设备驱动中的 file_operations下面是一个简单的字符设备驱动示例,展示如何定义和使用 file_operations 结构体。
#include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #define DEVICE_NAME "mychardev" #define BUF_LEN 80 static int major_number; static char message[BUF_LEN] = {0}; static short message_len = 0; static struct class* mychardev_class = NULL; static struct device* mychardev_device = NULL; // 函数声明 static int dev_open(struct inode *, struct file *); static int dev_release(struct inode *, struct file *); static ssize_t dev_read(struct file *, char __user *, size_t, loff_t *); static ssize_t dev_write(struct file *, const char __user *, size_t, loff_t *); // 定义 file_operations 结构体 static struct file_operations fops = { .owner = THIS_MODULE, .open = dev_open, .read = dev_read, .write = dev_write, .release = dev_release, }; // 模块初始化函数 static int __init mychardev_init(void){ printk(KERN_INFO "MyCharDev: Initializing the MyCharDev\n"); // 动态分配一个主设备号 major_number = register_chrdev(0, DEVICE_NAME, &fops); if (major_number < 0){ printk(KERN_ALERT "MyCharDev failed to register a major number\n"); return major_number; } printk(KERN_INFO "MyCharDev: registered correctly with major number %d\n", major_number); // 创建设备类 mychardev_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(mychardev_class)){ unregister_chrdev(major_number, DEVICE_NAME); printk(KERN_ALERT "Failed to register device class\n"); return PTR_ERR(mychardev_class); } printk(KERN_INFO "MyCharDev: device class registered correctly\n"); // 创建设备 mychardev_device = device_create(mychardev_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME); if (IS_ERR(mychardev_device)){ class_destroy(mychardev_class); unregister_chrdev(major_number, DEVICE_NAME); printk(KERN_ALERT "Failed to create the device\n"); return PTR_ERR(mychardev_device); } printk(KERN_INFO "MyCharDev: device class created correctly\n"); return 0; } // 模块卸载函数 static void __exit mychardev_exit(void){ device_destroy(mychardev_class, MKDEV(major_number, 0)); class_unregister(mychardev_class); class_destroy(mychardev_class); unregister_chrdev(major_number, DEVICE_NAME); printk(KERN_INFO "MyCharDev: Goodbye from the LKM!\n"); } // 打开设备文件 static int dev_open(struct inode *inodep, struct file *filep){ printk(KERN_INFO "MyCharDev: Device has been opened\n"); return 0; } // 读取设备文件 static ssize_t dev_read(struct file *filep, char __user *buffer, size_t len, loff_t *offset){ int error_count = 0; // 将内核空间的数据复制到用户空间 error_count = copy_to_user(buffer, message, message_len); if (error_count == 0){ printk(KERN_INFO "MyCharDev: Sent %d characters to the user\n", message_len); return (message_len=0); // 清空缓冲区并返回读取的字节数 } else{ printk(KERN_INFO "MyCharDev: Failed to send %d characters to the user\n", error_count); return -EFAULT; // 返回错误 } } // 写入设备文件 static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset){ // 将用户空间的数据复制到内核空间 if (len > BUF_LEN){ message_len = BUF_LEN; } else{ message_len = len; } if (copy_from_user(message, buffer, message_len) != 0){ return -EFAULT; } printk(KERN_INFO "MyCharDev: Received %zu characters from the user\n", len); return len; } // 关闭设备文件 static int dev_release(struct inode *inodep, struct file *filep){ printk(KERN_INFO "MyCharDev: Device successfully closed\n"); return 0; } module_init(mychardev_init); module_exit(mychardev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple Linux char driver"); MODULE_VERSION("0.1"); 解释定义 file_operations:
static struct file_operations fops = { .owner = THIS_MODULE, .open = dev_open, .read = dev_read, .write = dev_write, .release = dev_release, }; owner 指定该结构体所属的模块,防止模块在操作进行时被卸载。open、read、write 和 release 分别指向相应的函数,实现设备文件的打开、读取、写入和关闭操作。注册字符设备:
major_number = register_chrdev(0, DEVICE_NAME, &fops); 动态分配主设备号,并注册设备。实现操作函数:
dev_open: 打开设备时输出日志。dev_read: 将内核缓冲区的数据复制到用户空间。dev_write: 将用户空间的数据复制到内核缓冲区。dev_release: 关闭设备时输出日志。模块初始化与卸载:
mychardev_init 函数负责注册设备、创建类和设备文件。mychardev_exit 函数负责注销设备和清理资源。 使用 file_operations 的注意事项线程安全: file_operations 中的函数可能会被多个进程并发调用,因此在实现这些函数时需要注意线程安全,使用适当的同步机制(如自旋锁、互斥锁等)保护共享资源。
错误处理: 确保在各个操作函数中正确处理错误,返回合适的错误码,以便用户空间能够识别和处理。
内存管理: 在 read 和 write 操作中,需正确管理内核和用户空间之间的数据传输,避免内存泄漏或非法访问。
权限控制: 对设备文件的访问权限需要在驱动初始化时设置合适的文件权限,确保只有授权的用户可以访问设备。
常见扩展功能除了基本的 open、read、write 和 release 操作外,file_operations 还支持许多高级功能,如:
异步 I/O: 通过实现 poll 和 fasync 函数,支持异步 I/O 操作,提高性能。
内存映射: 实现 mmap 函数,允许用户空间直接访问设备内存,减少数据拷贝,提高效率。
控制命令: 通过 unlocked_ioctl 或 compat_ioctl 实现自定义的控制命令,扩展设备的功能。
文件锁定: 实现 lock 函数,支持文件级别的锁定,避免并发访问导致的数据不一致。
总结file_operations 结构体是 Linux 设备驱动开发中至关重要的一部分,通过定义和实现这个结构体中的函数,开发者可以控制设备文件的各种操作行为。理解和正确使用 file_operations 是编写高效、稳定的 Linux 驱动程序的基础。
什么是一切皆文件?在 Linux 内核中是,“一切皆文件”(Everything is a File)的设计理念,/proc 是一个虚拟文件系统(pseudo-filesystem),它并不位于实际的存储设备上,而是在内存中动态生成。通过 /proc,用户空间的程序可以方便地访问和交互内核内部的数据结构和信息。例如,/proc/cpuinfo 提供处理器信息,/proc/meminfo 提供内存使用情况等。
示例:创建一个proc文件的内核模块
我们编写一个内核模块,将一个proc文件注册到文件系统中。
#include <linux/init.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/uaccess.h> #include <linux/kernel.h> #define PROC_NAME "myprocfile" #define MSG "Hello, World from Kernel!\n" static ssize_t proc_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { int len = strlen(MSG); if (*ppos > 0 || count < len) return 0; if (copy_to_user(ubuf, MSG, len)) return -EFAULT; *ppos = len; return len; } static const struct proc_ops proc_file_ops = { .proc_read = proc_read, }; static int __init myproc_init(void) { proc_create(PROC_NAME, 0, NULL, &proc_file_ops); printk(KERN_INFO "/proc/%s created\n", PROC_NAME); return 0; } static void __exit myproc_exit(void) { remove_proc_entry(PROC_NAME, NULL); printk(KERN_INFO "/proc/%s removed\n", PROC_NAME); } module_init(myproc_init); module_exit(myproc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A Simple Proc File Module");测试模块:
现在,模块已经创建了一个/proc/myprocfile文件。
读取proc文件
cat /proc/myprocfile输出应为:
Hello, World from Kernel!查看proc文件信息
你也可以使用ls命令查看该文件的信息:
ls -l /proc/myprocfile在内核中,每一个文件(包括 /proc 下的文件)都有一个关联的 file_operations 结构体。这种结构体定义了一组函数指针,这些函数负责处理对文件的各种操作,如打开、读取、写入、关闭等。对于 /proc 文件系统中的文件,当对这些文件进行读写操作时,实际上是调用了内核中定义的驱动函数。这些驱动函数可以访问和修改内核的数据结构,执行特定的任务,而不仅仅是进行简单的文件 I/O 操作。
解释:
/proc文件系统:这是一个内核提供的虚拟文件系统,用于暴露内核和进程的信息。文件内容并不存储在磁盘上,而是动态生成的。
proc_create函数:用于在/proc文件系统中创建一个新的文件。当用户访问该文件时,会触发我们定义的操作函数。
proc_read函数:当用户读取/proc/myprocfile文件时,该函数被调用,将内核中的数据复制到用户空间。
copy_to_user函数:用于将数据从内核空间复制到用户空间,确保安全地传递数据。
通过这个模块,我们展示了如何通过文件系统与内核交互,这正是“一切皆文件”的体现。
CTF-内核pwn入门1:linux内核模块基础原理由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“CTF-内核pwn入门1:linux内核模块基础原理”
下一篇
Linux文件与目录管理