TCP TIME

您所在的位置:网站首页 网线和hdmi线一起走线有干扰吗怎么办 TCP TIME

TCP TIME

2024-07-01 02:42:34| 来源: 网络整理| 查看: 265

注:本文分析基于3.10.107内核版本

其实TIME_WAIT定时器是挺奇怪的,从名字上来看,是在socket进入TIME_WAIT之后工作的,但实际上并不是,或者说不全是,因为在FIN_WAIT2上也会使用TIME_WAIT定时器,正如之前讲述FIN_WAIT2定时器所描述。

下面我们就来谈一谈TIME_WAIT定时器。

注册

其实TIME_WAIT定时器由两个定时器构成,在全局变量tcp_death_row中定义

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), /* Short-time timewait calendar */ .twcal_hand = -1, .twcal_timer = TIMER_INITIALIZER(inet_twdr_twcal_tick, 0, (unsigned long)&tcp_death_row), };

分别是慢速定时器tw_timer和再生定时器twcal_timer。其他的我们先按下不表,容后道来,我们先看下有哪些情况会使用到TIME_WAIT定时器。

触发

关于TIME_WAIT定时器的触发,一共有5个场景。其实可以通过查询tcp_time_wait()函数的调用就能看到是哪5个场景。

1、在FIN_WAIT2状态下调用close()。但是我不太明白,socket是怎么进入FIN_WAIT2状态的,正常不就是调用close()吗,为什么还能在FIN_WAIT2状态再调用close()?求大神指教。

void tcp_close(struct sock *sk, long timeout) { ... if (sk->sk_state == TCP_FIN_WAIT2) { struct tcp_sock *tp = tcp_sk(sk); if (tp->linger2 < 0) {//使用linger2选项 tcp_set_state(sk, TCP_CLOSE); tcp_send_active_reset(sk, GFP_ATOMIC);//直接发送RST报文 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONLINGER); } else { const int tmo = tcp_fin_time(sk); if (tmo > TCP_TIMEWAIT_LEN) { inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN); } else { tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); goto out; } } } ... }

2、这个场景是比较正常的,在FIN_WAIT2状态下收到对端的FIN报文,发送完最后一个ACK报文之后就进入TIME_WAIT状态。

static void tcp_fin(struct sock *sk) { ... case TCP_FIN_WAIT2: /* Received a FIN -- send ACK and enter TIME_WAIT. */ tcp_send_ack(sk);//发送ACK报文,即第四挥手的最后一个报文 tcp_time_wait(sk, TCP_TIME_WAIT, 0); break; ... }

3、这个场景是在FIN_WAIT2状态下工作的。在接收到对端对FIN报文的回复后,且tcp_fin_timeout的值 TCP_TIMEWAIT_LEN) { //这里便是FIN_WAIT2定时器的激活 inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN); } else if (th->fin || sock_owned_by_user(sk)) { ... } else { tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);//这里是进入TIME_WAIT定时器了 goto discard; } } } break; ... } } ... }

4、这个场景就是在客户端和服务端同时关闭的时候触发的,双发都进入CLOSING状态,此时收到对端的FIN-ACK报文,就进入TIME_WAIT状态。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len) { ... case TCP_CLOSING: if (tp->snd_una == tp->write_seq) { tcp_time_wait(sk, TCP_TIME_WAIT, 0); goto discard; } break; ... }

5、这个场景就是刚才说的更怪的了,它和FIN_WAIT2定时器一前一后,一起工作。它是在FIN_WAIT2定时器的超时函数里触发,同时要求tcp_fin_timeout的值大于60s。而且,超时时间是(tmo - 60),和FIN_WAIT2定时器的超时时间一样。也就是同一个超时时间先后在两个定时器里使用。不知道为何这么使用,有何用意,请大伙赐教。

static void tcp_keepalive_timer (unsigned long data) { ... if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) { if (tp->linger2 >= 0) { const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN; if (tmo > 0) { tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); goto out; } } tcp_send_active_reset(sk, GFP_ATOMIC); goto death; } ... } 定时器操作

理完这些触发场景,我们来看看tcp_time_wait()到底做了啥。

/* * Move a socket to time-wait or dead fin-wait-2 state. */ void tcp_time_wait(struct sock *sk, int state, int timeo) { struct inet_timewait_sock *tw = NULL; const struct inet_connection_sock *icsk = inet_csk(sk); const struct tcp_sock *tp = tcp_sk(sk); bool recycle_ok = false; //判断是否开启快速回收标识,由/proc/sys/net/ipv4/tcp_tw_recycle参数决定,默认不开启,值为0 if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp) recycle_ok = tcp_remember_stamp(sk); //这是会重新分配一个tw sock,替代原先socket处理旧连接中的包,阻止其危害新连接 //是否能重新分配取决于此时这个哈希桶长度是否超标,上限由sysctl_max_tw_buckets决定 //该参数即为/proc/sys/net/ipv4/tcp_max_tw_buckets,默认值为65536 if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets) tw = inet_twsk_alloc(sk, state); if (tw != NULL) { struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw); //此时超时时间为3.5倍icsk_rto,icsk_rto在初始化时是1HZ,即1s //不过该值会随着网络状况动态调整 const int rto = (icsk->icsk_rto icsk_rto >> 1); struct inet_sock *inet = inet_sk(sk); //将旧连接的一些状态值传递给新分配的sock结构 tw->tw_transparent = inet->transparent; tw->tw_rcv_wscale = tp->rx_opt.rcv_wscale; tcptw->tw_rcv_nxt = tp->rcv_nxt; tcptw->tw_snd_nxt = tp->snd_nxt; .... //将tw sock放入time_wait hash表和bind hash表中, //将sk从ESTABLISHED hash表中移除 __inet_twsk_hashdance(tw, sk, &tcp_hashinfo); /* Get the TIME_WAIT timeout firing. */ //保证timeo最少为3.5RTO if (timeo < rto) timeo = rto; if (recycle_ok) {//系统默认为0 tw->tw_timeout = rto; } else { tw->tw_timeout = TCP_TIMEWAIT_LEN; //可见由TIME_WAIT状态进来的场景,timeo都会被设置为TCP_TIMEWAIT_LEN(60) if (state == TCP_TIME_WAIT) timeo = TCP_TIMEWAIT_LEN; } //这是个重点函数,会根据超时时间注册慢速定时器或者再生定时器 inet_twsk_schedule(tw, &tcp_death_row, timeo, TCP_TIMEWAIT_LEN); inet_twsk_put(tw); } else { /* Sorry, if we're out of memory, just CLOSE this * socket up. We've got bigger problems than * non-graceful socket closings. */ NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPTIMEWAITOVERFLOW); } tcp_update_metrics(sk); tcp_done(sk); }

可以看到,socket进入TIME_WAIT状态后,协议栈生成了一个tw sock代替socket存放在hash表中。tw sock占用空间比socket小,能从一定程度上节约内存空间。而定时器的触发设置都在inet_twsk_schedule()函数。

void inet_twsk_schedule(struct inet_timewait_sock *tw, struct inet_timewait_death_row *twdr, const int timeo, const int timewait_len) { struct hlist_head *list; int slot; //根据超时时间将tw sock划分到不同slot管理,其中INET_TWDR_RECYCLE_TICK和系统HZ有关 slot = (timeo + (1 > INET_TWDR_RECYCLE_TICK; spin_lock(&twdr->death_lock); if (inet_twsk_del_dead_node(tw))//如果已经有这个连接,删除 twdr->tw_count--; else atomic_inc(&tw->tw_refcnt); if (slot >= INET_TWDR_RECYCLE_SLOTS) {//进入慢速定时器 /* Schedule to slow timer */ if (timeo >= timewait_len) {//超时时间最大为timewait_len,即60s slot = INET_TWDR_TWKILL_SLOTS - 1; } else { slot = DIV_ROUND_UP(timeo, twdr->period);//slot索引号向上取整 if (slot >= INET_TWDR_TWKILL_SLOTS) slot = INET_TWDR_TWKILL_SLOTS - 1; } tw->tw_ttd = jiffies + timeo; //使用循环队列的方式,将tw sock按照超时时间分配到各个slot上 slot = (twdr->slot + slot) & (INET_TWDR_TWKILL_SLOTS - 1); list = &twdr->cells[slot]; } else { tw->tw_ttd = jiffies + (slot twcal_hand < 0) {//遍历标识位,之前未开启定时器,或者定时器已失效 twdr->twcal_hand = 0; twdr->twcal_jiffie = jiffies;//记录定时器启动时间 twdr->twcal_timer.expires = twdr->twcal_jiffie + (slot twcal_timer);//启动再生定时器 } else { //将定时器的超时时间定为最早超时的tw sock的超时时间,避免多余的超时动作 if (time_after(twdr->twcal_timer.expires, jiffies + (slot twcal_timer, jiffies + (slot twcal_hand + slot) & (INET_TWDR_RECYCLE_SLOTS - 1); } list = &twdr->twcal_row[slot]; } hlist_add_head(&tw->tw_death_node, list); if (twdr->tw_count++ == 0)//启动慢速定时器,定时周期为twdr->period(7.5s) mod_timer(&twdr->tw_timer, jiffies + twdr->period); spin_unlock(&twdr->death_lock); }

由上可知,慢速定时器和再生定时器的管理都是一样的,利用循环队列的方式,将tw sock按照超时时间挂在不同超时时间的slot上。不同的地方主要有以下几点:

1、管理的tw sock的超时时间

由上可知,slot和INET_TWDR_RECYCLE_TICK有关,它的定义和系统时钟中断HZ相关,定义如下:

#define INET_TWDR_RECYCLE_SLOTS_LOG 5 #define INET_TWDR_RECYCLE_SLOTS (1 4sec, it is "slow" path, no recycling is required, * so that we select tick to get range about 4 seconds. */ #if HZ 4096 # error Unsupported: HZ 4096 #elif HZ 取整为2,而如果超时时间为8s也是同样的效果。另外此时slot作为数组下标,因此实际在第三个slot,实际超时时间为3*7.5=22.5s。同样,再生定时器也是如此,只不过其slot的间隔较小,因此差异相比于慢速定时器要小很多。

超时处理函数

从全局变量tcp_death_row中定义可知,慢速定时器的超时处理函数为inet_twdr_hangman(),再生定时器的超时处理函数为inet_twdr_twcal_tick()。先看慢速定时器的处理。

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; if (inet_twdr_do_twkill_work(twdr, twdr->slot)) { //如果这个slot超时的tw sock过多(大于100),放到工作队列操作 twdr->thread_slots |= (1 slot); schedule_work(&twdr->twkill_work);//启动工作队列 need_timer = 1; } else { /* We purged the entire slot, anything left? */ if (twdr->tw_count)//如果还有tw sock,需要定时器继续超时 need_timer = 1; twdr->slot = ((twdr->slot + 1) & (INET_TWDR_TWKILL_SLOTS - 1));//下个slot } if (need_timer)//重置定时器,周期仍为twdr->period(7.5s) mod_timer(&twdr->tw_timer, jiffies + twdr->period); out: spin_unlock(&twdr->death_lock); } #define INET_TWDR_TWKILL_QUOTA 100 /* Returns non-zero if quota exceeded. */ static int inet_twdr_do_twkill_work(struct inet_timewait_death_row *twdr, const int slot) { struct inet_timewait_sock *tw; unsigned int killed; int ret; killed = 0; ret = 0; rescan: inet_twsk_for_each_inmate(tw, &twdr->cells[slot]) {//遍历每个tw sock __inet_twsk_del_dead_node(tw); spin_unlock(&twdr->death_lock); //将tw sock从establish 和bind hash表中删除 __inet_twsk_kill(tw, twdr->hashinfo); #ifdef CONFIG_NET_NS NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_TIMEWAITED); #endif inet_twsk_put(tw); killed++; spin_lock(&twdr->death_lock); //释放的tw sock数量大于100,返回,后续的释放交给工作队列执行 if (killed > INET_TWDR_TWKILL_QUOTA) { ret = 1; break; } goto rescan; } twdr->tw_count -= killed; #ifndef CONFIG_NET_NS NET_ADD_STATS_BH(&init_net, LINUX_MIB_TIMEWAITED, killed); #endif return ret; }

总体就是每隔7.5s遍历一次slot队列,因为slot的时间间隔也是7.5s,所以定时器超时后,这个slot的tw sock肯定是都已经超时,删除就好。

同样,对于再生定时器操作也差不多,只不多定时器周期不一样。

void inet_twdr_twcal_tick(unsigned long data) { struct inet_timewait_death_row *twdr; int n, slot; unsigned long j; unsigned long now = jiffies; int killed = 0; int adv = 0; twdr = (struct inet_timewait_death_row *)data; spin_lock(&twdr->death_lock); if (twdr->twcal_hand < 0) goto out; slot = twdr->twcal_hand;//获取上次遍历到的位置 j = twdr->twcal_jiffie; //不知道为什么这里还要遍历一次,很明显定时器超时后当前slot的tw sock肯定超时,和慢速定时器一样 //如果当前正好是最后一个slot,那不是还要白白比较前面31个slot吗,想不通 for (n = 0; n < INET_TWDR_RECYCLE_SLOTS; n++) { if (time_before_eq(j, now)) {//已超时,话说定时器进来不就已经超时了吗。。。。 struct hlist_node *safe; struct inet_timewait_sock *tw; inet_twsk_for_each_inmate_safe(tw, safe, &twdr->twcal_row[slot]) {//这里就是遍历slot的tw sock了 __inet_twsk_del_dead_node(tw); //将tw sock从establish和bind hash表中删除,其实超时后对tw sock的处理和慢速定时器一样 __inet_twsk_kill(tw, twdr->hashinfo); #ifdef CONFIG_NET_NS NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_TIMEWAITKILLED); #endif inet_twsk_put(tw); killed++; } } else { if (!adv) { adv = 1; twdr->twcal_jiffie = j;//还没超时,更新时间 twdr->twcal_hand = slot;//记录尚未超时的slot的位置 } if (!hlist_empty(&twdr->twcal_row[slot])) {//不为空定时器继续工作 mod_timer(&twdr->twcal_timer, j); goto out; } } //更新j的时间为下个slot超时时间,用于定时器超时时间设置 //所以再生定时器的周期为2^7=128ms j += 1 twcal_hand = -1;//如果遍历到最后说明所有tw sock已经释放,标识再生定时器失效 out: if ((twdr->tw_count -= killed) == 0)//没有tw sock,定时器就没必要工作了,删除定时器 del_timer(&twdr->tw_timer); #ifndef CONFIG_NET_NS NET_ADD_STATS_BH(&init_net, LINUX_MIB_TIMEWAITKILLED, killed); #endif spin_unlock(&twdr->death_lock); }


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭