<think>我们遇到了三个错误:
1. ISO C90 forbids mixed declarations and code [-Werror=declaration-after-statement]
这是因为在函数内部,我们在可执行代码之后声明了变量(在trigger_io_init函数中,在代码之后声明了struct request_queue *q)。
解决方法:将变量声明放在函数开头(所有可执行语句之前)。
2. 和 3. 'struct request_queue' has no member named 'make_request_fn'
在Linux 5.10中,request_queue结构体可能已经不再直接包含make_request_fn成员。实际上,从Linux 5.9开始,块层发生了重大变化,其中make_request_fn被移除,取而代之的是使用blk_queue_make_request()函数来设置请求处理函数,并且通常我们使用blk_alloc_queue_rh()或类似的函数来分配队列,然后设置处理函数。但是,在我们的上下文中,我们正在尝试替换现有队列的请求处理函数。
然而,在Linux 5.10中,request_queue结构体确实有一个成员叫make_request_fn,但它是通过宏定义的(在block/blk-core.h中),可能因为内核配置而不可见,或者该成员已经被完全移除?
实际上,在Linux 5.10的源码中,我们可以在include/linux/blkdev.h中看到:
struct request_queue {
...
make_request_fn *make_request_fn;
...
};
所以,理论上应该存在。但是,错误提示表明没有这个成员。这可能是由于内核头文件中的条件编译导致该成员在某些配置下不可见。
但是,我们注意到在更早的内核版本(如4.x)中,我们通常通过queue->make_request_fn来访问。如果这个成员在5.10中确实被移除了,那么我们需要使用新的API。
经过查阅,从Linux 5.9开始,块层引入了新的多队列机制,并移除了make_request_fn。现在,请求处理是通过blk_mq_ops结构体中的函数来处理的。因此,我们之前的方法(替换make_request_fn)不再适用。
因此,我们需要重新设计我们的方法。在支持多队列(blk-mq)的设备上,我们无法通过替换make_request_fn来拦截请求。我们需要使用其他方法,例如注册一个blk_mq_ops,或者使用bio过滤机制(如blk_filter)。
但是,在Linux 5.10中,还有一种方法:使用blk_queue_split_hook(但这不是标准API)或者使用bio_set_dev()和bio_associate_blkg()等函数来拦截bio。然而,这些方法并不直接提供拦截所有bio的钩子。
另一种方法是使用设备映射器(device mapper)来创建一个虚拟设备并拦截IO,但这超出了本模块的范围。
考虑到时间,我们可能需要放弃直接修改request_queue的方法,转而使用其他机制。但是,由于我们只是需要在一个特定的设备上拦截bio,我们可以考虑使用blk_filter。然而,blk_filter在5.10中可能还不是标准API。
实际上,Linux内核提供了一个名为“bio filter”的机制,但它是通过一个全局的过滤器链表来实现的。我们可以使用bio_register_filter()和bio_unregister_filter()函数来注册一个过滤器。但是,这个机制在5.10中可能已经被移除或者不是标准方法。
鉴于以上困难,我们可能需要重新考虑我们的方法。但是,由于我们的目标是在特定范围内注入错误,我们可以使用一个更简单的方法:使用一个内核模块来拦截块设备的打开操作,然后替换该块设备的request_queue的make_request_fn?但是,在5.10中,如果设备使用多队列(blk-mq),那么request_queue的make_request_fn可能为NULL,而由blk_mq_ops来处理请求。
因此,我们需要判断目标设备是否使用多队列。如果是,我们就不能使用旧的方法。
但是,我们注意到在trigger_io_init函数中,我们通过blkdev_get_by_dev获取了target_bdev,然后获取了它的request_queue。如果这个设备是多队列设备,那么request_queue的make_request_fn可能是NULL,而我们之前的代码就会出错。
因此,我们需要为多队列设备提供支持。然而,这非常复杂,因为多队列设备的处理方式完全不同。
由于时间关系,我们暂时不深入多队列设备的处理,而是假设目标设备使用的是传统的请求队列(非多队列)。但是,在5.10中,大多数设备都是多队列设备。
所以,我们需要一个适用于多队列设备的拦截方法。这通常需要创建一个新的blk_mq_ops结构体,并替换设备原有的ops。但是,这可能会非常复杂,而且需要处理很多细节。
考虑到本模块的目的,我们可能只考虑传统设备,或者寻找一个更简单的方法。
但是,我们也可以尝试使用以下方法:在支持多队列的设备上,我们可以通过注册一个blk_mq_ops的钩子来拦截请求。具体来说,我们可以替换设备的tag_set中的ops,然后在我们自己的ops中调用原始的ops,并在其中加入我们的逻辑。
然而,这种方法需要处理很多细节,并且需要确保安全。
鉴于错误信息提示没有make_request_fn成员,我们决定先尝试解决第一个错误(声明与代码混合),然后检查内核配置以及目标设备是否支持多队列。
但是,根据错误信息,编译器直接报告request_queue没有make_request_fn成员,这可能是由于内核头文件中的条件编译导致该成员没有被定义(例如,当配置了BLK_MQ时,可能不再定义这个成员)。
因此,我们需要在代码中根据内核版本选择不同的方法。由于这个模块只针对5.10.64,我们可以假设该内核已经全面使用多队列,因此我们需要使用多队列的钩子方法。
由于多队列的钩子方法比较复杂,这里提供一个替代方案:使用kprobe来拦截块设备驱动的某些函数?但这可能会影响性能,而且不够稳定。
考虑到这些复杂性,我们暂时不修改多队列部分,而是先解决第一个错误,并尝试在非多队列设备上运行(如果可能)。
因此,我们首先解决第一个错误(声明在代码之后),然后对于make_request_fn的错误,我们尝试通过条件编译来避免,或者使用其他方式获取请求处理函数。
但是,在5.10中,即使是非多队列设备,request_queue的make_request_fn成员也可能被移到其他位置。我们可能需要重新考虑整个设计。
由于时间限制,我们决定采用一个折中方案:如果目标设备是使用多队列的,我们就跳过替换请求函数的步骤,并打印一个错误信息。否则,我们使用传统的方法。
但是,我们如何判断设备是否使用多队列?我们可以检查request_queue的mq_ops成员。如果mq_ops不为空,那么就是多队列设备。
对于多队列设备,我们暂时不支持,因为实现起来非常复杂。
因此,修改计划如下:
1. 在trigger_io_init中,将变量声明放在函数开头。
2. 在访问make_request_fn之前,检查q->mq_ops是否为空。如果不为空,则打印错误信息,并返回错误(不支持多队列设备)。
3. 如果q->mq_ops为空,则使用传统方法(替换make_request_fn)。
修改后的trigger_io_init函数:
```c
static int __init trigger_io_init(void)
{
int ret = 0;
struct request_queue *q; // 将声明提到函数开头
// 获取块设备 (Linux 5.10.64兼容API)
target_bdev = blkdev_get_by_dev(MKDEV(8, 1), FMODE_READ, NULL);
if (IS_ERR(target_bdev)) {
ret = PTR_ERR(target_bdev);
printk(KERN_ERR "Failed to get block device for %s, error %d\n", DEV_NAME, ret);
return ret;
}
// 赋值给全局变量
target_disk = target_bdev->bd_disk;
if (!target_disk) {
printk(KERN_ERR "Failed to get gendisk for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENODEV;
}
// 获取逻辑块大小
q = bdev_get_queue(target_bdev); // 这里使用前面声明的q
if (!q) {
printk(KERN_ERR "Failed to get request queue for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENODEV;
}
logical_block_size = queue_logical_block_size(q);
printk(KERN_INFO "Device %s logical block size: %u\n", DEV_NAME, logical_block_size);
// 检查设备是否使用多队列(blk-mq)
if (q->mq_ops != NULL) {
printk(KERN_ERR "Device %s uses multi-queue (blk-mq), which is not supported by this module\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -EOPNOTSUPP;
}
// 保存并替换请求函数 (传统方法)
orig_make_request_fn = q->make_request_fn;
WRITE_ONCE(q->make_request_fn, modified_make_request);
// 创建/proc/trigger_io文件
trigger_proc = proc_create("trigger_io", 0644, NULL, &trigger_io_fops);
if (!trigger_proc) {
printk(KERN_ERR "Failed to create /proc/trigger_io\n");
// 恢复原始请求函数
WRITE_ONCE(q->make_request_fn, orig_make_request_fn);
blkdev_put(target_bdev, FMODE_READ);
return -ENOMEM;
}
printk(KERN_INFO "I/O trigger module loaded for %s\n", DEV_NAME);
return 0;
}
```
这样修改后,如果设备使用多队列,模块将加载失败并打印错误信息。否则,使用传统方法。
注意:我们假设在非多队列设备上,request_queue结构体有make_request_fn成员。如果仍然没有,那么可能是内核配置问题,我们暂时不考虑。
### 完整代码修改
根据以上方案,我们修改代码:
1. 将`trigger_io_init`函数中的`struct request_queue *q`声明提到函数开头(解决第一个错误)。
2. 在访问`q->make_request_fn`之前,先检查`q->mq_ops`是否为空,如果不为空则返回错误(解决第二、三个错误)。
### 修改后的代码
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/slab.h>
#define DEV_NAME "sda1" // 目标设备
#define START_BYTE (562 * 1024 * 1024) // 起始字节
#define END_BYTE (818 * 1024 * 1024) // 结束字节
static struct gendisk *target_disk;
static struct block_device *target_bdev;
static int trigger_enabled = 0;
static unsigned long error_count = 0;
static struct proc_dir_entry *trigger_proc;
static unsigned int logical_block_size = 512; // 扇区大小
// 原始make_request_fn函数
static blk_qc_t (*orig_make_request_fn)(struct request_queue *q, struct bio *bio);
// 处理/proc/trigger_io写入
static ssize_t trigger_io_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
char cmd[32];
if (copy_from_user(cmd, buf, min(count, sizeof(cmd)-1)))
return -EFAULT;
cmd[min(count, sizeof(cmd)-1)] = '\0';
// 解析命令
if (strcmp(cmd, "enable") == 0 || strcmp(cmd, "1") == 0) {
trigger_enabled = 1;
printk(KERN_INFO "I/O error trigger ENABLED\n");
}
else if (strcmp(cmd, "disable") == 0 || strcmp(cmd, "0") == 0) {
trigger_enabled = 0;
printk(KERN_INFO "I/O error trigger DISABLED\n");
}
else if (strcmp(cmd, "reset") == 0) {
error_count = 0;
printk(KERN_INFO "Error counter reset\n");
}
else {
printk(KERN_WARNING "Unknown command: %s\n", cmd);
return -EINVAL;
}
return count;
}
// 处理/proc/trigger_io读取
static ssize_t trigger_io_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
char status[128];
int len;
len = snprintf(status, sizeof(status),
"Status: %s\nError count: %lu\nTarget device: %s\nRange: %uMB-%uMB\nSector size: %u\n",
trigger_enabled ? "ENABLED" : "DISABLED",
error_count,
DEV_NAME,
START_BYTE/(1024*1024),
END_BYTE/(1024*1024),
logical_block_size);
return simple_read_from_buffer(buf, count, ppos, status, len);
}
static const struct proc_ops trigger_io_fops = {
.proc_read = trigger_io_read,
.proc_write = trigger_io_write,
};
// 拦截块设备I/O请求
static blk_qc_t modified_make_request(struct request_queue *q, struct bio *bio)
{
// 使用 bio->bi_disk 替代已废弃的 bi_bdev
if (trigger_enabled && bio->bi_disk && bio->bi_disk == target_disk) {
sector_t sector = bio->bi_iter.bi_sector;
unsigned long byte_offset = (unsigned long)(sector * logical_block_size);
// 检查是否在目标范围
if (byte_offset >= START_BYTE && byte_offset <= END_BYTE) {
// 50%概率触发错误
if (get_random_u32() % 2 == 0) {
printk(KERN_INFO "Injecting I/O error at sector %llu\n", (u64)sector);
bio->bi_status = BLK_STS_IOERR;
bio_endio(bio);
error_count++;
return BLK_QC_T_NONE;
}
}
}
return orig_make_request_fn(q, bio);
}
static int __init trigger_io_init(void)
{
int ret = 0;
struct request_queue *q; // 声明提前
// 获取块设备 (Linux 5.10.64兼容API)
target_bdev = blkdev_get_by_dev(MKDEV(8, 1), FMODE_READ, NULL);
if (IS_ERR(target_bdev)) {
ret = PTR_ERR(target_bdev);
printk(KERN_ERR "Failed to get block device for %s, error %d\n", DEV_NAME, ret);
return ret;
}
// 赋值给全局变量
target_disk = target_bdev->bd_disk;
if (!target_disk) {
printk(KERN_ERR "Failed to get gendisk for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENODEV;
}
// 获取逻辑块大小
q = bdev_get_queue(target_bdev);
if (!q) {
printk(KERN_ERR "Failed to get request queue for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENODEV;
}
logical_block_size = queue_logical_block_size(q);
printk(KERN_INFO "Device %s logical block size: %u\n", DEV_NAME, logical_block_size);
// 检查是否是多队列设备(blk-mq)
if (q->mq_ops != NULL) {
printk(KERN_ERR "Device %s uses multi-queue (blk-mq), which is not supported by this module\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -EOPNOTSUPP; // 操作不支持
}
// 保存并替换请求函数 (传统方法)
orig_make_request_fn = q->make_request_fn;
WRITE_ONCE(q->make_request_fn, modified_make_request);
// 创建/proc/trigger_io文件
trigger_proc = proc_create("trigger_io", 0644, NULL, &trigger_io_fops);
if (!trigger_proc) {
printk(KERN_ERR "Failed to create /proc/trigger_io\n");
// 恢复原始请求函数
WRITE_ONCE(q->make_request_fn, orig_make_request_fn);
blkdev_put(target_bdev, FMODE_READ);
return -ENOMEM;
}
printk(KERN_INFO "I/O trigger module loaded for %s\n", DEV_NAME);
return 0;
}
static void __exit trigger_io_exit(void)
{
// 恢复原始请求函数
if (target_bdev) {
struct request_queue *q = bdev_get_queue(target_bdev);
if (q && orig_make_request_fn) {
// 同样,如果是多队列设备则不会执行替换,所以这里只处理传统设备
if (q->mq_ops == NULL) {
WRITE_ONCE(q->make_request_fn, orig_make_request_fn);
}
}
blkdev_put(target_bdev, FMODE_READ);
}
// 移除/proc文件
if (trigger_proc) {
proc_remove(trigger_proc);
}
printk(KERN_INFO "I/O trigger module unloaded\n");
}
module_init(trigger_io_init);
module_exit(trigger_io_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Controlled I/O error trigger");
```
### 注意事项
1. **多队列设备**:本模块现在不支持多队列设备(blk-mq),如果目标设备是多队列设备,模块将加载失败。
2. **传统设备**:对于传统设备(非多队列),模块可以正常工作。
3. **卸载安全**:在卸载模块时,我们只恢复传统设备的请求处理函数。
### 测试建议
1. 确认目标设备是否是多队列设备(可以通过`cat /sys/block/sda/queue/scheduler`来查看,如果支持mq,则调度器名称前面会有[mq]字样,或者通过`cat /sys/block/sda/queue/mq`来查看)。
2. 如果目标设备是多队列设备,则需要考虑其他方法(如使用设备映射器)来实现错误注入。
§§相关问题§§
1. 在Linux内核5.10中,如何判断一个块设备是否使用多队列(blk-mq)?
2. 如何为支持多队列(blk-mq)的设备实现I/O错误注入?
3. 在内核模块中,为什么需要将变量声明放在函数开头?
4. 除了修改请求队列的make_request_fn,还有哪些方法可以拦截块设备I/O?
5. 在Linux内核中,使用`WRITE_ONCE`宏有什么作用?</think>### 修复内核模块编译错误
#### 错误1:ISO C90 禁止混合声明和代码
```c
static int __init trigger_io_init(void)
{
int ret = 0;
struct request_queue *q; // 声明提前到函数开头
// 获取块设备
target_bdev = blkdev_get_by_dev(MKDEV(8, 1), FMODE_READ, NULL);
// ... [其他代码保持不变] ...
// 获取逻辑块大小
q = bdev_get_queue(target_bdev); // 使用预先声明的变量
// ... [后续代码] ...
}
```
#### 错误2:request_queue 没有 make_request_fn 成员
```c
// 在文件顶部添加版本检查
#include <linux/version.h>
static int __init trigger_io_init(void)
{
// ... [其他代码] ...
// 检查内核版本 (5.9+ 使用不同的API)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
printk(KERN_INFO "Using submit_bio API (Linux 5.9+)\n");
// 保存原始submit_bio函数
orig_submit_bio = target_disk->fops->submit_bio;
// 替换为自定义函数
target_disk->fops->submit_bio = modified_submit_bio;
#else
printk(KERN_INFO "Using make_request_fn API (Linux <5.9)\n");
// 保存并替换请求函数
orig_make_request_fn = q->make_request_fn;
WRITE_ONCE(q->make_request_fn, modified_make_request);
#endif
// ... [其他代码] ...
}
```
### 完整修复后的内核模块代码
```c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/slab.h>
#define DEV_NAME "sda1"
#define START_BYTE (562 * 1024 * 1024)
#define END_BYTE (818 * 1024 * 1024)
static struct gendisk *target_disk;
static struct block_device *target_bdev;
static int trigger_enabled = 0;
static unsigned long error_count = 0;
static struct proc_dir_entry *trigger_proc;
static unsigned int logical_block_size = 512;
// 多版本API支持
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
static void (*orig_submit_bio)(struct bio *bio);
#else
static blk_qc_t (*orig_make_request_fn)(struct request_queue *q, struct bio *bio);
#endif
// /proc文件处理函数保持不变
static ssize_t trigger_io_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
// ... [与之前相同] ...
}
static ssize_t trigger_io_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
// ... [与之前相同] ...
}
static const struct proc_ops trigger_io_fops = {
.proc_read = trigger_io_read,
.proc_write = trigger_io_write,
};
// Linux 5.9+ 使用submit_bio API
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
static void modified_submit_bio(struct bio *bio)
{
if (trigger_enabled && bio->bi_disk && bio->bi_disk == target_disk) {
sector_t sector = bio->bi_iter.bi_sector;
unsigned long byte_offset = (unsigned long)(sector * logical_block_size);
if (byte_offset >= START_BYTE && byte_offset <= END_BYTE) {
if (get_random_u32() % 2 == 0) {
printk(KERN_INFO "Injecting I/O error at sector %llu\n", (u64)sector);
bio->bi_status = BLK_STS_IOERR;
bio_endio(bio);
error_count++;
return;
}
}
}
orig_submit_bio(bio);
}
// Linux <5.9 使用make_request_fn API
#else
static blk_qc_t modified_make_request(struct request_queue *q, struct bio *bio)
{
if (trigger_enabled && bio->bi_disk && bio->bi_disk == target_disk) {
sector_t sector = bio->bi_iter.bi_sector;
unsigned long byte_offset = (unsigned long)(sector * logical_block_size);
if (byte_offset >= START_BYTE && byte_offset <= END_BYTE) {
if (get_random_u32() % 2 == 0) {
printk(KERN_INFO "Injecting I/O error at sector %llu\n", (u64)sector);
bio->bi_status = BLK_STS_IOERR;
bio_endio(bio);
error_count++;
return BLK_QC_T_NONE;
}
}
}
return orig_make_request_fn(q, bio);
}
#endif
static int __init trigger_io_init(void)
{
int ret = 0;
struct request_queue *q = NULL; // 声明提前
// 获取块设备
target_bdev = blkdev_get_by_dev(MKDEV(8, 1), FMODE_READ, NULL);
if (IS_ERR(target_bdev)) {
ret = PTR_ERR(target_bdev);
printk(KERN_ERR "Failed to get block device for %s, error %d\n", DEV_NAME, ret);
return ret;
}
target_disk = target_bdev->bd_disk;
if (!target_disk) {
printk(KERN_ERR "Failed to get gendisk for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENODEV;
}
q = bdev_get_queue(target_bdev);
if (!q) {
printk(KERN_ERR "Failed to get request queue for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENODEV;
}
logical_block_size = queue_logical_block_size(q);
printk(KERN_INFO "Device %s logical block size: %u\n", DEV_NAME, logical_block_size);
// 内核版本适配
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
printk(KERN_INFO "Using submit_bio API (Linux 5.9+)\n");
if (!target_disk->fops->submit_bio) {
printk(KERN_ERR "No submit_bio function for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENOTSUPP;
}
orig_submit_bio = target_disk->fops->submit_bio;
target_disk->fops->submit_bio = modified_submit_bio;
#else
printk(KERN_INFO "Using make_request_fn API (Linux <5.9)\n");
if (!q->make_request_fn) {
printk(KERN_ERR "No make_request_fn for %s\n", DEV_NAME);
blkdev_put(target_bdev, FMODE_READ);
return -ENOTSUPP;
}
orig_make_request_fn = q->make_request_fn;
WRITE_ONCE(q->make_request_fn, modified_make_request);
#endif
// 创建/proc文件
trigger_proc = proc_create("trigger_io", 0644, NULL, &trigger_io_fops);
if (!trigger_proc) {
printk(KERN_ERR "Failed to create /proc/trigger_io\n");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
target_disk->fops->submit_bio = orig_submit_bio;
#else
WRITE_ONCE(q->make_request_fn, orig_make_request_fn);
#endif
blkdev_put(target_bdev, FMODE_READ);
return -ENOMEM;
}
printk(KERN_INFO "I/O trigger module loaded for %s\n", DEV_NAME);
return 0;
}
static void __exit trigger_io_exit(void)
{
if (target_bdev) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
if (target_disk && target_disk->fops && orig_submit_bio) {
target_disk->fops->submit_bio = orig_submit_bio;
}
#else
struct request_queue *q = bdev_get_queue(target_bdev);
if (q && orig_make_request_fn) {
WRITE_ONCE(q->make_request_fn, orig_make_request_fn);
}
#endif
blkdev_put(target_bdev, FMODE_READ);
}
if (trigger_proc) {
proc_remove(trigger_proc);
}
printk(KERN_INFO "I/O trigger module unloaded\n");
}
module_init(trigger_io_init);
module_exit(trigger_io_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Controlled I/O error trigger");
```
### 关键修复说明
1. **C90声明顺序问题**:
```c
struct request_queue *q = NULL; // 在函数开头声明
// ...
q = bdev_get_queue(target_bdev); // 后续赋值
```
2. **内核版本适配**:
```c
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
// 使用submit_bio API (Linux 5.9+)
static void (*orig_submit_bio)(struct bio *bio);
target_disk->fops->submit_bio = modified_submit_bio;
#else
// 使用make_request_fn API (Linux <5.9)
static blk_qc_t (*orig_make_request_fn)(struct request_queue *q, struct bio *bio);
WRITE_ONCE(q->make_request_fn, modified_make_request);
#endif
```
3. **API存在性检查**:
```c
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
if (!target_disk->fops->submit_bio) {
// 错误处理
}
#else
if (!q->make_request_fn) {
// 错误处理
}
#endif
```
4. **模块卸载安全恢复**:
```c
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
target_disk->fops->submit_bio = orig_submit_bio;
#else
WRITE_ONCE(q->make_request_fn, orig_make_request_fn);
#endif
```
### 编译和测试建议
1. **编译时指定内核版本**:
```bash
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
```
2. **检查内核版本适配**:
```bash
uname -r
# 确保模块使用正确的API路径
```
3. **加载模块后检查日志**:
```bash
dmesg | tail
# 应显示使用的API类型(submit_bio或make_request_fn)
```
4. **测试错误注入**:
```bash
echo "enable" > /proc/trigger_io
dd if=/dev/sda1 of=/dev/null bs=1M count=100 skip=562
dmesg | grep "Injecting"
```
5. **多版本验证**:
- 在Linux 5.8及以下测试传统API路径
- 在Linux 5.9+测试submit_bio路径