由于我们这边主要考虑的是设置为TCP_TIMEWAIT_LEN(60s)的处理时间,所以直接考察slow_timer时间轮处理函数,也就是inet_twdr_hangman。这个函数还是比较简短的:
void inet_twdr_hangman(unsigned long data) { struct inet_timewait_death_row *twdr; unsigned int need_timer; twdr = (struct inet_timewait_death_row *)data; spin_lock(&twdr->death_lock); if (twdr->tw_count == 0) goto out; need_timer = 0; // 如果此slot处理的time_wait socket已经达到了100个,且还没处理完 if (inet_twdr_do_twkill_work(twdr, twdr->slot)) { twdr->thread_slots |= (1 << twdr->slot); // 将余下的任务交给work queue处理 schedule_work(&twdr->twkill_work); need_timer = 1; } else { /* We purged the entire slot, anything left? */ // 判断是否还需要继续处理 if (twdr->tw_count) need_timer = 1; // 如果当前slot处理完了,才跳转到下一个slot twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1)); } // 如果还需要继续处理,则在7.5s后再运行此函数 if (need_timer) mod_timer(&twdr->tw_timer, jiffies + twdr->period); out: spin_unlock(&twdr->death_lock); }虽然简单,但这个函数里面有不少细节。第一个细节,就在inet_twdr_do_twkill_work,为了防止这个slot的time_wait过多,卡住当前的流程,其会在处理完100个time_wait socket之后就回返回。这个slot余下的time_wait会交给Kernel的work_queue机制去处理。
值得注意的是。由于在这个slow_timer时间轮判断里面,根本不判断精确时间,直接全部删除。所以轮到某个slot,例如到了52.5-60s这个slot,直接清理52.5-60s的所有time_wait。即使time_wait还没有到60s也是如此。而小时间轮(tw_cal)会精确的判定时间,由于篇幅原因,就不在这里细讲了。 注: 小时间轮(tw\_cal)在tcp\_tw\_recycle开启的情况下会使用 先作出一个假设
我们假设,一个时间轮的数据最多能在一个slot间隔时间,也就是(60/8=7.5)内肯定能处理完毕。由于系统有tcp_tw_max_buckets设置,如果设置的比较合理,这个假设还是比较靠谱的。
注: 这里的60/8为什么需要精确到小数,而不是7。 因为实际计算的时候是拿60*HZ进行计算, 如果HZ是1024的话,那么period应该是7680,即精度精确到ms级。 所以在本文中计算的时候需要精确到小数。 如果一个slot中的TIME_WAIT<=100如果一个slot的TIME_WAIT<=100,很自然的,我们的处理函数并不会启用work_queue。同时,还将slot+1,使得在下一个period的时候可以处理下一个slot。如下图所示:
如果一个slot的TIME_WAIT>100,Kernel会将余下的任务交给work_queue处理。同时,slot不变!也即是说,下一个period(7.5s后)到达的时候,还会处理同样的slot。按照我们的假设,这时候slot已经处理完毕,那么在第7.5s的时候才将slot向前推进。也就是说,假设slot一开始为0,到真正处理slot 1需要15s!
假设每一个slot的TIME_WAIT都>100的话,那么每个slot的处理都需要15s。
对于这种情况,笔者写了个程序进行模拟。
public class TimeWaitSimulator { public static void main(String[] args) { double delta = (60) * 1.0 / 8; // 0表示开始清理,1表示清理完毕 // 清理完毕之后slot向前推进 int startPurge = 0; double sum = 0; int slot = 0; while (slot < 8) { if (startPurge == 0) { sum += delta; startPurge = 1; if (slot == 7) { // 因为假设进入work_queue之后,很快就会清理完 // 所以在slot为7的时候并不需要等最后的那个purge过程7.5s System.out.println("slot " + slot + " has reach the last " + sum); break; } } if (startPurge == 1) { sum += delta; startPurge = 0; System.out.println("slot " + "move to next at time " + sum); // 清理完之后,slot才应该向前推进 slot++; } } } }得出结果如下面所示:
slot move to next at time 15.0 slot move to next at time 30.0 slot move to next at time 45.0 slot move to next at time 60.0 slot move to next at time 75.0 slot move to next at time 90.0 slot move to next at time 105.0 slot 7 has reach the last 112.5