Linux 内核中的 KMP 实现

Linux 内核中使用到了字符串搜索,所以它也有 KMP 算法的实现,代码在 lib/ts_kmp.c 中。

Linux 内核中用到 KMP 算法的地方有三处:iptables string match 模块、iptables conntrack amanda 模块(不知道这个是用来干什么的)、以及 ematch qdisc。iptables string match 是通过字符串搜索来匹配一个包,然后进行相应的处理,比如用下面的命令可以阻止对domain.com服务器的HTTP请求:

iptables -I INPUT 1 -p tcp —dport 80 -m string —string “domain.com” —algo kmp -j DROP

至于 ematch qdisc,和它类似,可以通过字符串匹配到对应的包进行 QoS,比如这个例子

tc filter add dev eth0 parent 10:12 prio 10 protocol ip basic match ‘text(kmp foobar from 0 to 200)’ flowid 10:1

总之,在内核中实现 KMP 算法是有必要的。下面来看具体实现。

我们知道,KMP 算法中最核心的地方就是 prefix 的计算,也称为 next 数组,它用来表示当字符 pattern[i] 匹配失败后应该从 pattern[next[i]] 字符继续进行匹配,而不总是从头开始,因此它的时间复杂度是O(n)。如果你对 KMP 不熟悉的话,网上有很多介绍,我觉得这篇文章还算不错,在继续看下面的代码之前可以读一下它。

内核中实现比网上的很多代码都更容易理解,因为在匹配开始之前,它就先把 prefix 计算好了。计算 prefix 的函数是:

[c]
static inline void compute_prefix_tbl(const u8 pattern, unsigned int len,
unsigned int
prefix_tbl, int flags)
{
unsigned int k, q;
const u8 icase = flags & TS_IGNORECASE;

    for (k = 0, q = 1; q  0 && (icase ? toupper(pattern[k]) : pattern[k])
                != (icase ? toupper(pattern[q]) : pattern[q]))
                    k = prefix_tbl[k-1];
            if ((icase ? toupper(pattern[k]) : pattern[k])
                == (icase ? toupper(pattern[q]) : pattern[q]))
                    k++;
            prefix_tbl[q] = k;
    }

}
[/c]

结合 KMP 实现的主函数来理解更一目了然:

[c]
static unsigned int kmp_find(struct ts_config conf, struct ts_state state)
{
struct ts_kmp kmp = ts_config_priv(conf);
unsigned int i, q = 0, text_len, consumed = state->offset;
const u8
text;
const int icase = conf->flags & TS_IGNORECASE;

    for (;;) {
            text_len = conf->get_next_block(consumed, &text, conf, state);

            if (unlikely(text_len == 0))
                    break;

            for (i = 0; i  0 && kmp->pattern[q]
                        != (icase ? toupper(text[i]) : text[i]))
                            q = kmp->prefix_tbl[q - 1];
                    if (kmp->pattern[q]
                        == (icase ? toupper(text[i]) : text[i]))
                            q++;
                    if (unlikely(q == kmp->pattern_len)) {
                            state->offset = consumed + i + 1;
                            return state->offset - kmp->pattern_len;
                    }
            }

            consumed += text_len;
    }

    return UINT_MAX;

}
[/c]

内核中的 KMP 函数接口是封装过的,你不能直接调用它。如果你的内核模块中要使用它,可以参考 lib/textsearch.c 中给的例子:

[c]
int pos;
struct ts_config conf;
struct ts_state state;
const char
pattern = “chicken”;
const char *example = “We dance the funky chicken”;

conf = textsearch_prepare(“kmp”, pattern, strlen(pattern),
GFP_KERNEL, TS_AUTOLOAD);
if (IS_ERR(conf)) {
err = PTR_ERR(conf);
goto errout;
}

pos = textsearch_find_continuous(conf, &state, example, strlen(example));
if (pos != UINT_MAX)
panic(“Oh my god, dancing chickens at %dn”, pos);

textsearch_destroy(conf);
[/c]