IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    手把手教你用Dropwatch诊断问题

    老王发表于 2016-12-15 10:25:00
    love 0

    老实说,Dropwatch 并不是什么新鲜玩意,很多年前霸爷就专门撰文介绍过它,通过它可以大概找出系统为什么会丢包,其原理就是跟踪 kfree_skb 的调用行为。不过虽然很多人知道它的存在,但是却并不知道如何具体使用它,所以我写下了这篇文字。

    以 CentOS 为例,动手前需要了解系统的版本,并确保已经安装了对应的包:

    shell> uname -r
    2.6.32-431.23.3.el6.x86_64
    
    shell> rpm -qa | grep kernel
    kernel-2.6.32-431.23.3.el6.x86_64
    kernel-debuginfo-common-x86_64-2.6.32-431.23.3.el6.x86_64

    Dropwatch 本身有一个交互命令行,命令中的 kas 指的是加载对应的符号表:

    shell> dropwatch -l kas
    Initalizing kallsyms db
    
    dropwatch> start
    Enabling monitoring...
    Kernel monitoring activated.
    Issue Ctrl-C to stop monitoring
    298 drops at init_dummy_netdev+50 (0xffffffff81459d10)
    1 drops at init_dummy_netdev+50 (0xffffffff81459d10)
    14 drops at init_dummy_netdev+50 (0xffffffff81459d10)

    说明:有案例报道直接通过 dropwatch -l kas 使用 /proc/kallsyms 符号表,可能会造成宕机(我没遇到),如果碰到可以使用 /boot/System.Map 符号表(隶属于 kernel 包)。

    在本例子中,Dropwatch 显示在 init_dummy_netdev 附近存在大量丢包现象,提示信息格式的大致说明是:丢包数量 drops at 函数名+偏移量 (地址)。

    下面让我们看看为什么会提示丢包,直接在符号表里搜索:

    shell> grep -w -A 10 init_dummy_netdev /proc/kallsyms
    ffffffff81459cc0 T init_dummy_netdev
    ffffffff81459d10 t net_tx_action
    ffffffff81459ed0 T __napi_complete
    ffffffff81459f10 T netdev_drivername
    ffffffff81459f70 T __dev_getfirstbyhwtype
    ffffffff81459ff0 T dev_getfirstbyhwtype
    ffffffff8145a040 t unlist_netdevice
    ffffffff8145a120 t dev_unicast_flush
    ffffffff8145a1d0 t dev_addr_discard
    ffffffff8145a260 T __dev_remove_pack
    ffffffff8145a310 T dev_add_pack

    可见 init_dummy_netdev 的地址是 ffffffff81459cc0,加上偏移量 50 等于 ffffffff81459d10,正好是 net_tx_action 的地址(注:如果计算后的地址在两个函数之间,那么取前者),于是我们得出结论,实际丢包是发生在 net_tx_action 函数中。

    搞清楚了案发地,接下来可以通过 kernel-debuginfo-common 包来获取源代码路径,在本例子中,安装对应的包后执行命令显示源代码位于 /usr/src/debug 目录:

    shell> rpm -ql kernel-debuginfo-common-x86_64
    /usr/src/debug/kernel-2.6.32-431.23.3.el6

    前面提到过系统通过跟踪 kfree_skb 来确认丢包的,那么看看 kfree_skb 的定义:

    void __kfree_skb(struct sk_buff *skb)
    {
        skb_release_all(skb);
        kfree_skbmem(skb);
    }
    EXPORT_SYMBOL(__kfree_skb);
    
    void kfree_skb(struct sk_buff *skb)
    {
        if (unlikely(!skb))
            return;
        if (likely(atomic_read(&skb->users) == 1))
            smp_rmb();
        else if (likely(!atomic_dec_and_test(&skb->users)))
            return;
        trace_kfree_skb(skb, __builtin_return_address(0));
        __kfree_skb(skb);
    }
    EXPORT_SYMBOL(kfree_skb);

    实际上起作用的是 trace_kfree_skb,所以调用 trace_kfree_skb 和 kfree_skb 的地方就意味着有丢包,不过需要说明的是 __kfree_skb 不表示丢包,可以无视。

    有了如上的准备工作,下面开始搜索 net_tx_action 的源代码:

    shell> grep -wr net_tx_action /usr/src/debug

    终于可以看到庐山真面目了,2.6.32 版本的 net_tx_action 源代码如下:

    static void net_tx_action(struct softirq_action *h)
    {
        struct softnet_data *sd = &__get_cpu_var(softnet_data);
    
        if (sd->completion_queue) {
            struct sk_buff *clist;
    
            local_irq_disable();
            clist = sd->completion_queue;
            sd->completion_queue = NULL;
            local_irq_enable();
    
            while (clist) {
                struct sk_buff *skb = clist;
                clist = clist->next;
    
                WARN_ON(atomic_read(&skb->users));
                trace_kfree_skb(skb, net_tx_action);
                __kfree_skb(skb);
            }
        }
    
    ...

    根据之前的分析,我们可以推断出就是在 trace_kfree_skb(skb, net_tx_action); 这一行丢的包。通常找到代码中丢包的具体位置后,我们需要做的就是代码前后看看是否触发了什么限制,比如说队列太小了,缓冲不够之类的,不过在本例子中,看上去是清除完成队列里的数据,这并没有什么问题。以 dropwatch + net_tx_action 为关键字去搜索后找到一篇文章:net_tx_action: Call trace_consume_skb() instead of trace_kfree_skb(),似乎验证了我们之前的猜测,带着疑惑查看最新版本代码中 net_tx_action 的源代码:

    static __latent_entropy void net_tx_action(struct softirq_action *h)
    {
        struct softnet_data *sd = this_cpu_ptr(&softnet_data);
    
        if (sd->completion_queue) {
            struct sk_buff *clist;
    
            local_irq_disable();
            clist = sd->completion_queue;
            sd->completion_queue = NULL;
            local_irq_enable();
    
            while (clist) {
                struct sk_buff *skb = clist;
                clist = clist->next;
    
                WARN_ON(atomic_read(&skb->users));
                if (likely(get_kfree_skb_cb(skb)->reason
                    == SKB_REASON_CONSUMED))
                    trace_consume_skb(skb);
                else
                    trace_kfree_skb(skb, net_tx_action);
    
                if (skb->fclone != SKB_FCLONE_UNAVAILABLE)
                    __kfree_skb(skb);
                else
                    __kfree_skb_defer(skb);
            }
    
            __kfree_skb_flush();
    	}
    
    ...

    果然,在新版本的源代码中区分了 trace_consume_skb 和 trace_kfree_skb 的使用,而我们知道 trace_kfree_skb 表示丢包,而 trace_consume_skb 是无害的,至此我们可以基本确定:在本例子中所谓的丢包是旧版本内核的误判。虽然这次纠错过程最终被证实为虚惊一场,但是相信大家在过程中已经学会了如何使用 Dropwatch。



沪ICP备19023445号-2号
友情链接