seq_file 在下一个 returns NULL 之后无法正常工作

seq_file not working properly after next returns NULL

这段代码有什么问题(我猜是关于与不同内核版本的兼容性)?

#define BUFFER_SIZE 2048
#define BUFFER_STEP 128

static char buffer[BUFFER_SIZE] = { 0 }; // this is filled with repeating letters a-z in module init

static void* seqf_ex_start (struct seq_file* m, loff_t* pos)
{
    if (*pos >= BUFFER_SIZE) {
        return NULL;
    }

    return buffer + *pos;
}

static void* seqf_ex_next (struct seq_file* m, void* v, loff_t* pos)
{
    *pos += BUFFER_STEP;

    if (*pos >= BUFFER_SIZE) {
        return NULL;
    }

    return buffer + *pos;
}

static int seqf_ex_show (struct seq_file* m, void* v)
{
    seq_printf(m, "%.*s\n", BUFFER_STEP, (char*)v);

    return 0;
}

当运行 qemuarm 中的内核模块(内核v5.0.19)时,我得到正确的输出:

$ cat /proc/seqf-ex | awk '{ print "line" OFS NR ":" OFS length([=13=]) }'
line 1: 128
line 2: 128
line 3: 128
line 4: 128
line 5: 128
line 6: 128
line 7: 128
line 8: 128
line 9: 128
line 10: 128
line 11: 128
line 12: 128
line 13: 128
line 14: 128
line 15: 128
line 16: 128

但是当 运行 在内核 v4.15.0 上时,我得到这个:

$ cat /proc/seqf-ex | awk '{ print "line" OFS NR ":" OFS length([=14=]) }'
line 1: 128
line 2: 128
line 3: 128
line 4: 128
line 5: 128
line 6: 128
line 7: 128
line 8: 128
line 9: 128
line 10: 128
line 11: 128
line 12: 128
line 13: 128
line 14: 128
line 15: 128
line 16: 128
line 17: 127
line 18: 126
line 19: 125
line 20: 124
line 21: 123
line 22: 122
line 23: 121
line 24: 120
line 25: 119
line 26: 118
line 27: 117
line 28: 116
line 29: 115
line 30: 114
line 31: 113
line 32: 112
line 33: 111
line 34: 110
line 35: 109
line 36: 108
line 37: 107
line 38: 106
line 39: 105
line 40: 104
line 41: 103
line 42: 102
line 43: 101
line 44: 100
line 45: 99
line 46: 98
line 47: 97
line 48: 96
line 49: 95
line 50: 94
line 51: 93
line 52: 92
line 53: 91
line 54: 90
line 55: 89
line 56: 88
line 57: 87
line 58: 86
line 59: 85
line 60: 84
line 61: 83
line 62: 82
line 63: 81
line 64: 80
line 65: 79
line 66: 78
line 67: 77
line 68: 76
line 69: 75
line 70: 74
line 71: 73
line 72: 72
line 73: 71
line 74: 70
line 75: 69
line 76: 68
line 77: 67
line 78: 66
line 79: 65
line 80: 64
line 81: 63
line 82: 62
line 83: 61
line 84: 60
line 85: 59
line 86: 58
line 87: 57
line 88: 56
line 89: 55
line 90: 54
line 91: 53
line 92: 52
line 93: 51
line 94: 50
line 95: 49
line 96: 48
line 97: 47
line 98: 46
line 99: 45
line 100: 44
line 101: 43
line 102: 42
line 103: 41
line 104: 40
line 105: 39
line 106: 38
line 107: 37
line 108: 36
line 109: 35
line 110: 34
line 111: 33
line 112: 32
line 113: 31
line 114: 30
line 115: 29
line 116: 28
line 117: 27
line 118: 26
line 119: 25
line 120: 24
line 121: 23
line 122: 22
line 123: 21
line 124: 20
line 125: 19
line 126: 18
line 127: 17
line 128: 16
line 129: 15
line 130: 14
line 131: 13
line 132: 12
line 133: 11
line 134: 10
line 135: 9
line 136: 8
line 137: 7
line 138: 6
line 139: 5
line 140: 4
line 141: 3
line 142: 2
line 143: 1

似乎seqf_start函数在seqf_nextreturnsNULL之后被重复调用。为什么会这样?

在 Linux 内核 4.x 和 5.x 版本之间存在一些 不兼容性 关于处理传递给 [=] 的 *pos 参数12=]回调函数。

Linux 内核 5.x 仅将 *pos 设置为 0 并将其他修改留给 seq_file 回调(.start.next 函数在 struct seq_operations).

但是,在 Linux 内核中 4.x *pos 可以在回调 之外递增 1 。如果最后一个 .show 调用产生 恰好 字节数来满足 read 系统调用的请求,则会完成此操作。

因为您的代码仅准备好可以被 BUFFER_STEP 整除的 *pos 值,所以当 *pos 在您的函数之外递增时它无法正常工作,因此它可以被 [整除=22=] 不再。


我不知道 Linux 4.x 的行为是否正确,但是如果你想让你的代码处理这样的行为,你需要准备好 [=11= 的所有值], 不仅是你的代码生成的。

您的代码的简单修复是将 *pos 解释为 块索引 而不是 字节索引 :

static void* seqf_ex_start (struct seq_file* m, loff_t* pos)
{
    if (*pos >= (BUFFER_SIZE/BUFFER_STEP)) {
        return NULL;
    }

    return buffer + (*pos * BUFFER_STEP);
}

static void* seqf_ex_next (struct seq_file* m, void* v, loff_t* pos)
{
    *pos += 1;

    if (*pos >= (BUFFER_SIZE/BUFFER_STEP)) {
        return NULL;
    }

    return buffer + (*pos * BUFFER_STEP);
}

static int seqf_ex_show (struct seq_file* m, void* v)
{
    seq_printf(m, "%.*s\n", BUFFER_STEP, (char*)v);

    return 0;
}

这样您的代码将适用于 *pos 的所有值。 (那些太高的会被检查 if (*pos >= (BUFFER_SIZE/BUFFER_STEP)) 切断)。在您的代码之外执行的 *pos 增加 1 相当于调用您的 .next 回调。