技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 系统运维 --> Linux内核协议栈对于timewait状态的处理

Linux内核协议栈对于timewait状态的处理

浏览:3813次  出处信息

   最近在做操作系统升级时,发现升级后的系统处于TIME_WAIT状态的连接数明显增多(内核版本 2.6.18 -> 2.6.32)。

原因

   2.6.18 与 2.6.32 的 diff 结果

   net/ipv4/inet_timewait_sock.c

@@ -178,15 +212,14 @@
      need_timer = 0;
      if (inet_twdr_do_twkill_work(twdr, twdr->slot)) {
        twdr->thread_slots |= (1 << twdr->slot);
    -   mb();
        schedule_work(&twdr->twkill_work);
        need_timer = 1;
      } else {
        /* We purged the entire slot, anything left?  */  
        if (twdr->tw_count)
          need_timer = 1;
        //这句话位置的变动引起TIME_WAIT状态增多
    +   twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1));
      }   
    - twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1));
      if (need_timer)
        mod_timer(&twdr->tw_timer, jiffies + twdr->period);
    out:

   导致TIME_WAIT状态增多,正是由于

   twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1));

   位置的改变。

具体分析

1. 关键数据结构

   inet_timewait_death_row: 用于管理timewait控制块的数据结构,位置: include/net/inet_timewait_sock.h。

struct inet_timewait_death_row {
    /* Short-time timewait calendar */
    int         twcal_hand;
    int         twcal_jiffie;
    struct timer_list   twcal_timer;
    struct hlist_head   twcal_row[INET_TWDR_RECYCLE_SLOTS];

    spinlock_t      death_lock;
    int         tw_count;
    int         period;
    u32         thread_slots;
    struct work_struct  twkill_work;
    struct timer_list   tw_timer;
    int         slot;
    //INET_TWDR_TWKILL_SLOTS 值为 8
    struct hlist_head   cells[INET_TWDR_TWKILL_SLOTS];
    struct inet_hashinfo    *hashinfo;
    int         sysctl_tw_recycle;
    int         sysctl_max_tw_buckets;
};

   此数据结构,可以分为两部分看,一部分处理 tw_recycle 开启时timewait块的快速回收,另一部分为未开启时用于等待时间较长的timewait块的回收。由于系统没有开启 tw_recycle , 因此我们主要关注等待时间较长的timewait块回收。

   用于等待时间较长的主要成员变量:

   int period : tw_timer 定时器的超时时间固定值为 TCP_TIMEWAIT_LEN / INET_TWDR_TWKILL_SLOTS,其中 TCP_TIMEWAIT_LEN 为 60 * HZ (60s),INET_TWDR_TWKILL_SLOTS 为 8。

   u32 thread_slots : 用于标识未完成的timewait块的位图。

   struct work_struct twkill_work : 分批删除(默认值为每次删除100个)cells中timewait块时的工作队列。

   struct timer_list tw_timer : 定时器,每过 period,触发一次 inet_twdr_hangman()

   以下是此数据结构的初始化:

struct inet_timewait_death_row tcp_death_row = {
    .sysctl_max_tw_buckets = NR_FILE * 2,
    .period   = TCP_TIMEWAIT_LEN / INET_TWDR_TWKILL_SLOTS,
    .death_lock = __SPIN_LOCK_UNLOCKED(tcp_death_row.death_lock),
    .hashinfo = &tcp_hashinfo,
    .tw_timer = TIMER_INITIALIZER(inet_twdr_hangman, 0,
                (unsigned long)&tcp_death_row),
    .twkill_work = __WORK_INITIALIZER(tcp_death_row.twkill_work,
                 inet_twdr_twkill_work,
                 &tcp_death_row),
    /* Short-time timewait calendar */

    .twcal_hand = -1,
    .twcal_timer = TIMER_INITIALIZER(inet_twdr_twcal_tick, 0,
                (unsigned long)&tcp_death_row),
};

   inet_timewait_sock: 用于组成 tcp_timewait_sock 结构,其前部是 sock_common 的前部。位置: include/net/inet_timewait_sock.h。

struct inet_timewait_sock {
    /*  
     * Now struct sock also uses sock_common, so please just
     * don't add nothing before this first member (__tw_common) --acme
     */
    struct sock_common  __tw_common;
#define tw_family   __tw_common.skc_family
#define tw_state    __tw_common.skc_state
#define tw_reuse    __tw_common.skc_reuse
#define tw_bound_dev_if   __tw_common.skc_bound_dev_if
#define tw_node     __tw_common.skc_node
#define tw_bind_node    __tw_common.skc_bind_node
#define tw_refcnt   __tw_common.skc_refcnt
#define tw_hash     __tw_common.skc_hash
#define tw_prot     __tw_common.skc_prot
    volatile unsigned char  tw_substate;
    /* 3 bits hole, try to pack */
    unsigned char   tw_rcv_wscale;
    /* Socket demultiplex comparisons on incoming packets. */
    /* these five are in inet_sock */
    __u16     tw_sport;
    __u32     tw_daddr __attribute__((aligned(INET_TIMEWAIT_ADDRCMP_ALIGN_BYTES)));
    __u32     tw_rcv_saddr;
    __u16     tw_dport;
    __u16     tw_num;
    /* And these are ours. */
    __u8      tw_ipv6only:1;
    /* 15 bits hole, try to pack */
    __u16     tw_ipv6_offset;
    int     tw_timeout;
    unsigned long   tw_ttd;
    struct inet_bind_bucket *tw_tb;
    struct hlist_node tw_death_node;
};

   此数据结构暂时只需要知道 tw_substate 即可。 tw_substate : TCP状态迁移到 FIN_WAIT2 或 TIME_WAIT 状态时,协议栈会用 timewait 块取代 tcp_sock 块,因为这两种状态都需要由定时器处理,超时立即释放。其对外状态都表现为 TIME_WAIT , 但其内部状态还是有分别,通过 tw_substate 进行区分。


2. timewait块释放时的逻辑

   inet_twdr_hangman() 此函数是定时器到期时执行的函数,用于释放timewait块。

void inet_twdr_hangman(unsigned long data)
{
    struct inet_timewait_death_row *twdr;
    int unsigned need_timer;

    twdr = (struct inet_timewait_death_row *)data;
    spin_lock(&twdr->death_lock);

    if (twdr->tw_count == 0)
        goto out;

    need_timer = 0;

    //inet_twdr_do_twkill_work 释放timewait块的具体函数,每次释放100个
    //释放完成返回0, 否则返回1
    if (inet_twdr_do_twkill_work(twdr, twdr->slot)) {
        //一次遍历未完全删除timewait块时,剩余的time块放入twkill_work的工作队列中处理。
        //thread_slots标识未完成的timewait块
        twdr->thread_slots |= (1 << twdr->slot);
        mb();
        schedule_work(&twdr->twkill_work);
        //未删除所有timewait块,需要重新调度定时器
        need_timer = 1;
    } else {
      /* We purged the entire slot, anything left?  */
      if (twdr->tw_count)
          need_timer = 1;
    }

    //此句是关键,代码出自2.6.18内核,不管定时器例程一次有没有释放完timewait块,都进行 + 1 操作
    twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1));
    if (need_timer)
        mod_timer(&twdr->tw_timer, jiffies + twdr->period);
  out:
      spin_unlock(&twdr->death_lock);
}

   用于慢timewait块释放的逻辑可参考下图:

   timewait


3. 结论

   按照 2.6.18 中的逻辑,如果一次没有全部删除一个slot中的timewait控制块, twdr->slot 仍然会执行 + 1 操作。此时如果有一个tcp_sock进入FIN_WAIT2状态,则此时的timewait(tw_substate = fin_wait2)块会被放在上一个slot中,而此时有一个线程正在处理那个队列,因此会导致处于FIN_WAIT2状态的timewait块被提前释放,若此时对端的FIN分节到达,协议栈会回复一个RST分节。

   为了修复此BUG,2.6.32 协议栈中修改了 twdr->slot + 1 的时机,每次必须完全释放一个slot中所有的timewait块后,才会进行 + 1 操作。这也就是说协议栈不保证在 TCP_TWKILL_PERIOD 周期内,移动一个格子,所以当系统繁忙时,会导致timewait块的等待时间大于 TCP_TIMEWAIT_LEN

建议继续学习:

  1. linux内核研究笔记(一)内存管理 – page介绍    (阅读:8763)
  2. Linux IO协议栈框图    (阅读:5405)
  3. PHP内核介绍及扩展开发指南―Extensions 的编写    (阅读:4735)
  4. 我的内核配置文件    (阅读:3784)
  5. PHP内核介绍及扩展开发指南―高级主题    (阅读:3682)
  6. PHP内核介绍及扩展开发指南―基础知识    (阅读:3473)
  7. 在 Dell PowerEdge 1950 上安装 Linux 2.6.32-rc8 内核的问题与解决    (阅读:3087)
  8. 在Ubuntu上使用SystemTap    (阅读:3077)
  9. Linux内核模块开发(笔记)    (阅读:3153)
  10. 内核编译升级失败了以后的处理方案    (阅读:2990)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
<< 前一篇:SSDB 配置文件
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1