相同端口的UDP套接字数量过多导致软中断占用CPU利用率高的原因分析

  导致出现很多相同端口的UDP套接字的原因有两点:
     1)在reload的时候,调用shutdown()而不是close()来关闭UDP套接字。shutdown()是用来关闭连接 ,但是文件描述符还是会存在。UDP协议并没有提供shutdown接口(参见 udp_prot),所以调用shutdown()并没有真的关闭套 接字,而且reload的次数 太多
     2)设置了SO_REUSEADDR选项。如果没有设置这个选项,在bind的时候就会报错,漏关或者使用shutdown()关闭的错误就很容易发现。UDP套接字没有TIME_WAIT状态,而且要使用的UDP套接字数量不多,所以没有必要设置该选项。
  现在我们结合内核代码来分析一下为什么相同端口的UDP套接字数量过多导致软中断占用CPU利用率高。
  UDP套接字在绑定端口后才会添加到udp_table哈希表中,解决冲突使用的是“拉链法”,放置的槽位是由绑定的端口决定的(参见不考虑CONFIG_NET_NS选项 )。所以绑定到相同端口的套接字会放置到同一个槽位的链表中,这种套接字数量越多,链表就越长。
  在接收到UDP协议的SKB包后,会调用__udp4_lib_lookup()函数来查找对应的UDP套接字,相关代码如下所示:
static  struct sock  *__udp4_lib_lookup( struct net  *net, __be32 saddr,
        __be16 sport, __be32 daddr, __be16 dport,
         int dif,  struct udp_table  *udptable)
{
     struct sock  *sk,  *result;
     struct hlist_nulls_node  *node;
     unsigned  short hnum  = ntohs(dport);
     unsigned  int hash  = udp_hashfn(net, hnum);
     struct udp_hslot  *hslot  =  &udptable - >hash[hash];
     int score, badness;

    rcu_read_lock();
begin :
    result  = NULL;
    badness  =  - 1;
    sk_nulls_for_each_rcu(sk, node,  &hslot - >head) {
        score  = compute_score(sk, net, saddr, hnum, sport,
                      daddr, dport, dif);
         if (score  > badness) {
            result  = sk;
            badness  = score;
        }
    }
    ......
}
  首先根据SKB包的目的端口号来查找要遍历的链表,然后通过 sk_nulls_for_each_rcu宏来遍历链表上的所有套接字。我们可以看到,不管这个链表上有多少个套接字,都要从头到尾遍历一遍。如果遍历的链表越长,耗费的时间就越多,这个处理是在软中断的上下文中,因此会导致软中断占用CPU利用率高。
  下面是使用perf top命令看到的结果( 可以清楚地看到__udp4_lib_lookup会成为热点,在同等压力下是不会出现的 ):

你可能感兴趣的:(Linux内核)