Linux 内核网络协议栈 ------ tcp_ack 函数处理接收到的ACK包之后


注意 tcp_ack 是来处理接收到的ACK的,那么到底怎么去做呢?看下面:


先还上把tcp_sock的结构放在这里,下面一些数据的分析需要用到:

struct tcp_sock {
         /* inet_connection_sock has to be the first member of tcp_sock */
         struct inet_connection_sock     inet_conn;
         u16     tcp_header_len; /* Bytes of tcp header to send          */   // tcp头部长度
         u16     xmit_size_goal_segs; /* Goal for segmenting output packets */// 分段数据包的数量
 
/*
 *      Header prediction flags
 *      0x5?10 << 16 + snd_wnd in net byte order
 */
         __be32  pred_flags;  // 头部预置位(用于检测头部标识位处理ACK和PUSH之外还有没有其他位,从而判断是不是可以使用快速路径处理数据)
 
/*
 *      RFC793 variables by their proper names. This means you can
 *      read the code and the spec side by side (and laugh ...)
 *      See RFC793 and RFC1122. The RFC writes these in capitals.
 */
         u32     rcv_nxt;        /* What we want to receive next         */  // 下一个想要收到的第一个数据的字节编号
         u32     copied_seq;     /* Head of yet unread data              */  // 没还有读出的数据的头
         u32     rcv_wup;        /* rcv_nxt on last window update sent   */  // rcv_nxt在最后一个窗口更新时的值
         u32     snd_nxt;        /* Next sequence we send                */  // 下一个发送的第一个字节编号
 
         u32     snd_una;        /* First byte we want an ack for        */  // 对于发出的数据,都需要对方的ACK,这里标示当前需要被确认的第一个字节
         u32     snd_sml;        /* Last byte of the most recently transmitted small packet */ // 最近发送的小数据包的最后一个字节
         u32     rcv_tstamp;     /* timestamp of last received ACK (for keepalives) */ // 最后一次接收到ACK的时间戳
         u32     lsndtime;       /* timestamp of last sent data packet (for restart window) */ // 最后一次发送数据包时间戳
 
         u32     tsoffset;       /* timestamp offset */	// 时间戳偏移
 
         struct list_head tsq_node; /* anchor in tsq_tasklet.head list */ // 
         unsigned long   tsq_flags;

         // 注意下面这个ucopy:就是将用户数据从skb中拿出来放进去,然后传给应用进程!!!
         /* Data for direct copy to user */
         struct {
                 struct sk_buff_head     prequeue; // 预处理队列
                 struct task_struct      *task;    // 预处理进程
                 struct iovec            *iov;     // 用户程序(应用程序)接收数据的缓冲区
                 int                     memory;   // 用于预处理计数
                 int                     len;      // 预处理长度
#ifdef CONFIG_NET_DMA
                 /* members for async copy */
                 struct dma_chan         *dma_chan;
                 int                     wakeup;
                 struct dma_pinned_list  *pinned_list;
                 dma_cookie_t            dma_cookie;
#endif
        } ucopy;

         //  snd_wl1:记录发送窗口更新时,造成窗口更新的那个数据报的第一个序号。 它主要用于在下一次判断是否需要更新发送窗口。 
         u32     snd_wl1;        /* Sequence for window update           */ // 窗口更新序列号( 每一次收到确认之后都会改变 )
         u32     snd_wnd;        /* The window we expect to receive      */ // 我们期望收到的窗口
         u32     max_window;     /* Maximal window ever seen from peer   */ // 从对方接收到的最大窗口
         u32     mss_cache;      /* Cached effective mss, not including SACKS */ // 有效的MSS,SACKS不算
 
         u32     window_clamp;   /* Maximal window to advertise          */ // 对外公布的最大的窗口
         u32     rcv_ssthresh;   /* Current window clamp                 */ // 当前窗口值
  
         u16     advmss;         /* Advertised MSS                       */ // 对外公布的MSS
         u8      unused;
         u8      nonagle     : 4,/* Disable Nagle algorithm?             */ // Nagle算法是否有效
                 thin_lto    : 1,/* Use linear timeouts for thin streams */ // 使用线性超时处理
                 thin_dupack : 1,/* Fast retransmit on first dupack      */ // 收到第一个重复的ACK的时候是否快速重传
                 repair      : 1,
                 frto        : 1;/* F-RTO (RFC5682) activated in CA_Loss */
         u8      repair_queue;
         u8      do_early_retrans:1,/* Enable RFC5827 early-retransmit  */ // 是否可以使用之前的重传
                 syn_data:1,     /* SYN includes data */                   // data中是否包含SYN
                 syn_fastopen:1, /* SYN includes Fast Open option */       // SYN选项
                 syn_data_acked:1;/* data in SYN is acked by SYN-ACK */    // SYN回复
         u32     tlp_high_seq;   /* snd_nxt at the time of TLP retransmit. */ // tlp重传时候snd_nxt的值
 
/* RTT measurement */
         u32     srtt;           /* smoothed round trip time << 3        */ // 往返时间
         u32     mdev;           /* medium deviation                     */ // 
         u32     mdev_max;       /* maximal mdev for the last rtt period */ // 最大mdev
         u32     rttvar;         /* smoothed mdev_max                    */ 
         u32     rtt_seq;        /* sequence number to update rttvar     */ 
 
         u32     packets_out;    /* Packets which are "in flight"        */ // 已经发出去的尚未收到确认的包
         u32     retrans_out;    /* Retransmitted packets out            */ // 重传的包
 
         u16     urg_data;       /* Saved octet of OOB data and control flags */ // OOB数据和控制位
         u8      ecn_flags;      /* ECN status bits.                     */ // ECN状态位
         u8      reordering;     /* Packet reordering metric.            */ // 包重排度量
         u32     snd_up;         /* Urgent pointer               */ // 紧急指针
 
         u8      keepalive_probes; /* num of allowed keep alive probes   */
/*
 *      Options received (usually on last packet, some only on SYN packets).
 */
         struct tcp_options_received rx_opt; // tcp接收选项
 
/*
 *      Slow start and congestion control (see also Nagle, and Karn & Partridge)
 */
         u32     snd_ssthresh;   /* Slow start size threshold            */ // 慢启动的开始大小
         u32     snd_cwnd;       /* Sending congestion window            */ // 发送阻塞窗口
         u32     snd_cwnd_cnt;   /* Linear increase counter              */ // 线性增长计数器(为了窗口的扩大)
         u32     snd_cwnd_clamp; /* Do not allow snd_cwnd to grow above this */ // snd_cwnd值不可以超过这个门限
         u32     snd_cwnd_used;
         u32     snd_cwnd_stamp;
         u32     prior_cwnd;     /* Congestion window at start of Recovery. */ // 在刚刚恢复时候的阻塞窗口大小
         u32     prr_delivered;  /* Number of newly delivered packets to // 在恢复期间接收方收到的包
                                  * receiver in Recovery. */
         u32     prr_out;        /* Total number of pkts sent during Recovery. */ // 在恢复期间发出去的包
 
         u32     rcv_wnd;        /* Current receiver window              */ // 当前接收窗口
         u32     write_seq;      /* Tail(+1) of data held in tcp send buffer */ // tcp发送buf中数据的尾部
         u32     notsent_lowat;  /* TCP_NOTSENT_LOWAT */ 
         u32     pushed_seq;     /* Last pushed seq, required to talk to windows */ // push序列
         u32     lost_out;       /* Lost packets                 */ // 丢失的包
         u32     sacked_out;     /* SACK'd packets               */ // SACK包
         u32     fackets_out;    /* FACK'd packets               */ // FACK包
         u32     tso_deferred;
 
         /* from STCP, retrans queue hinting */
         struct sk_buff* lost_skb_hint; // 用于丢失的包
         struct sk_buff *retransmit_skb_hint; // 用于重传的包
 
         struct sk_buff_head     out_of_order_queue; /* Out of order segments go here */ // 接收到的无序的包保存
 
         /* SACKs data, these 2 need to be together (see tcp_options_write) */
         struct tcp_sack_block duplicate_sack[1]; /* D-SACK block */
         struct tcp_sack_block selective_acks[4]; /* The SACKS themselves*/
 
         struct tcp_sack_block recv_sack_cache[4];
 
         struct sk_buff *highest_sack;   /* skb just after the highest
                                          * skb with SACKed bit set
                                          * (validity guaranteed only if
                                          * sacked_out > 0)
                                          */
 
         int     lost_cnt_hint;
         u32     retransmit_high;        /* L-bits may be on up to this seqno */
 
         u32     lost_retrans_low;       /* Sent seq after any rxmit (lowest) */
 
         u32     prior_ssthresh; /* ssthresh saved at recovery start     */
         u32     high_seq;       /* snd_nxt at onset of congestion       */
 
         u32     retrans_stamp;  /* Timestamp of the last retransmit,
                                  * also used in SYN-SENT to remember stamp of
                                  * the first SYN. */
         u32     undo_marker;    /* tracking retrans started here. */
         int     undo_retrans;   /* number of undoable retransmissions. */
         u32     total_retrans;  /* Total retransmits for entire connection */
 
         u32     urg_seq;        /* Seq of received urgent pointer */
         unsigned int            keepalive_time;   /* time before keep alive takes place */
         unsigned int            keepalive_intvl;  /* time interval between keep alive probes */
 
         int                     linger2;
 
/* Receiver side RTT estimation */
         struct {
                 u32     rtt;
                 u32     seq;
                 u32     time;
         } rcv_rtt_est;
 
/* Receiver queue space */
         struct {
                 int     space;
                 u32     seq;
                 u32     time;
         } rcvq_space;
 
/* TCP-specific MTU probe information. */
         struct {
                 u32               probe_seq_start;
                 u32               probe_seq_end;
         } mtu_probe;
         u32     mtu_info; /* We received an ICMP_FRAG_NEEDED / ICMPV6_PKT_TOOBIG
                            * while socket was owned by user.
                            */
 
#ifdef CONFIG_TCP_MD5SIG
/* TCP AF-Specific parts; only used by MD5 Signature support so far */
         const struct tcp_sock_af_ops    *af_specific;
 
/* TCP MD5 Signature Option information */
         struct tcp_md5sig_info  __rcu *md5sig_info;
#endif


/* TCP fastopen related information */
         struct tcp_fastopen_request *fastopen_req;
         /* fastopen_rsk points to request_sock that resulted in this big
          * socket. Used to retransmit SYNACKs etc.
          */
         struct request_sock *fastopen_rsk;
};

关于窗口的操作值snd_una,snd_wnd等可以看下面的图形:

先看发送窗口:



再看接收窗口:



还有tcp_skb_cb结构:

struct tcp_skb_cb {
	union {
		struct inet_skb_parm	h4;
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
		struct inet6_skb_parm	h6;
#endif
	} header;	/* For incoming frames		*/
	__u32		seq;     // 当前tcp包的第一个序列号	
	__u32		end_seq; // 表示结束的序列号:seq + FIN + SYN + 数据长度len
	__u32		when;    // 用于计算RTT	
	__u8		flags;	 // tcp头的flag
	__u8		sacked;	 // SACK/FACK的状态flag或者是sack option的偏移
	__u32		ack_seq; // ack(确认)的序列号
};

关于tcp头的标识(flags)可以取:

#define TCPCB_FLAG_FIN		0x01  // FIN 结束符
#define TCPCB_FLAG_SYN		0x02  // SYN 握手
#define TCPCB_FLAG_RST		0x04  // RST 重置
#define TCPCB_FLAG_PSH		0x08  // PSH 接收方要立即处理
#define TCPCB_FLAG_ACK		0x10  // ACK ack段有效
#define TCPCB_FLAG_URG		0x20  // URG 紧急指针
#define TCPCB_FLAG_ECE		0x40  // ECE 有拥塞情况(可能是传播线路上的拥塞,例如路由器提供的信息)
#define TCPCB_FLAG_CWR		0x80  // CWR (发生某种拥塞,例如ICMP源抑制、本地设备拥塞)

sack的标识:

#define TCPCB_SACKED_ACKED	0x01 // tcp的cb结构上是被sack确认的
#define TCPCB_SACKED_RETRANS	0x02 // 重传帧
#define TCPCB_LOST		0x04 // 丢失
#define TCPCB_TAGBITS		0x07 // tag bits ?
#define TCPCB_EVER_RETRANS	0x80	
#define TCPCB_RETRANS		(TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)

OK,回到tcp_ack函数,函数的主要功能是:

1 更新重传队列。 
2 更新发送窗口。 
3 从sack的信息或者重复ack来决定是否进入拥塞模式。 

/* This routine deals with incoming acks, but not outgoing ones. */
static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
{
         struct inet_connection_sock *icsk = inet_csk(sk);   // 获得连接sock
         struct tcp_sock *tp = tcp_sk(sk);   // 获得tcp_sock
         u32 prior_snd_una = tp->snd_una;    // 获得未发送确认的序号
         u32 ack_seq = TCP_SKB_CB(skb)->seq; // 获得数据序号
         u32 ack = TCP_SKB_CB(skb)->ack_seq; // 获得ack序号(用于确认的序号)
         u32 prior_in_flight;
         u32 prior_fackets;
         int prior_packets;
         int frto_cwnd = 0;
 
         /* If the ack is newer than sent or older than previous acks
          * then we can probably ignore it.
          */
         if (after(ack, tp->snd_nxt))    // 如果确认序号比我还下一个准备发送的序号还要大,即确认了我们尚未发送的数据,那么显然不合理
                 goto uninteresting_ack; // 没有意义的ACK
 
         if (before(ack, prior_snd_una)) // 如果ack确认比我期望的确认序号小,那么可能是以前老的ack,丢弃!!!
                 goto old_ack;           // 老的ack
 
         if (after(ack, prior_snd_una))  // 如果ack确认比我期望的第一个ack要大,但是经过上面我们还知道没有超过我没有发送的数据序号,范围
                 flag |= FLAG_SND_UNA_ADVANCED;  // 那么设置标识~
 
         if (sysctl_tcp_abc) { // 是否设置了tcp_abc,若有则我们不需要对每个ack确认都要拥塞避免,所以我们需要计算已经ack(确认)的字节数。
                 if (icsk->icsk_ca_state < TCP_CA_CWR)
                         tp->bytes_acked += ack - prior_snd_una;  // 已经(确定)ack的字节数增大了( ack - prior_snd_una )大小
                 else if (icsk->icsk_ca_state == TCP_CA_Loss)
                         /* we assume just one segment left network */
                         tp->bytes_acked += min(ack - prior_snd_una,
                                                tp->mss_cache);
         }
 
         prior_fackets = tp->fackets_out;              // 得到fack的数据包的字节数
         prior_in_flight = tcp_packets_in_flight(tp);  // 计算还在传输的数据段的字节数,下面会说手这个函数!( 1 )
 
         if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) { // 如果不是“慢路径” && ack确认比其需要的第一个大(正确的确认序号)
                 /* Window is constant, pure forward advance.
                  * No more checks are required.
                  * Note, we use the fact that SND.UNA>=SND.WL2.
                  */
                 tcp_update_wl(tp, ack, ack_seq);   // 需要更新sock中的snd_wl1字段:tp->snd_wl1 = ack_seq;( 记录造成发送窗口更新的第一个数据 )
                 tp->snd_una = ack;                 // snd_una更新为已经确认的序列号!下一次期待从这里开始的确认!!!
                 flag |= FLAG_WIN_UPDATE;           // 窗口更新标识
 
                 tcp_ca_event(sk, CA_EVENT_FAST_ACK);  // 重要函数!!!进入拥塞操作!这个函数最后看,这里处理的是“正常的ACK”事件(999999)
 
                 NET_INC_STATS_BH(LINUX_MIB_TCPHPACKS);
         } else {
                 if (ack_seq != TCP_SKB_CB(skb)->end_seq) // 如果不相等,那么说明还是带有数据一起的~不仅仅是一个ACK的包
                         flag |= FLAG_DATA;   // 说明还是有数据的~
                 else
                         NET_INC_STATS_BH(LINUX_MIB_TCPPUREACKS); // 否则仅仅是ACK的包

                 flag |= tcp_ack_update_window(sk, skb, ack, ack_seq);  // 下面需要更新发送窗口~(2)
 
                 if (TCP_SKB_CB(skb)->sacked)  // 然后判断是否有sack段,有的话,我们进入sack段的处理。
                         flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una); // ~~~~~处理SACK(选择确认),以后单独解释
 
                 if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb))) // 判断是否有ecn标记,如果有的话,设置ecn标记。
                         flag |= FLAG_ECE;                   // ECE 也是用于判断是否阻塞情况
 
                 tcp_ca_event(sk, CA_EVENT_SLOW_ACK); // 重要函数!!!进入拥塞操作!这个函数最后看,这里处理“其他ACK”事件(999999)
         }
 
         /* We passed data and got it acked, remove any soft error
          * log. Something worked...
          */
         sk->sk_err_soft = 0;
         tp->rcv_tstamp = tcp_time_stamp;
         prior_packets = tp->packets_out;  // 获得发出去没有收到确认的包数量
         if (!prior_packets) // 如果为0,则可能是0窗口探测包
                 goto no_queue;
 
         /* See if we can take anything off of the retransmit queue. */
         flag |= tcp_clean_rtx_queue(sk, prior_fackets);  // 清理重传队列中的已经确认的数据段。(3)
 
         if (tp->frto_counter)  // 处理F-RTO (4)
                 frto_cwnd = tcp_process_frto(sk, flag);   // 处理超时重传,暂时先不多说
         /* Guarantee sacktag reordering detection against wrap-arounds */
         if (before(tp->frto_highmark, tp->snd_una))
                 tp->frto_highmark = 0;
 
         if (tcp_ack_is_dubious(sk, flag)) {     // 判断ack是否可疑,其实本质就是判断可不可以增大拥塞窗口,下面会有详细解释(5)
                 /* Advance CWND, if state allows this. */
                 if ((flag & FLAG_DATA_ACKED) && !frto_cwnd && // 如果是数据确认包
                     tcp_may_raise_cwnd(sk, flag))  // 检测flag以及是否需要update拥塞窗口的大小!!!(6)--->被怀疑也有可能增大窗口哦~~~
                         tcp_cong_avoid(sk, ack, prior_in_flight);   // 为真则更新拥塞窗口,拥塞避免算法(7)--->如果允许增大窗口,那么拥塞算法处理窗口
                 tcp_fastretrans_alert(sk, prior_packets - tp->packets_out, // 这里进入拥塞状态的处理,非常重要的函数(对于拥塞处理来说)!!!(8)
                                       flag);   // 处理完窗口变化之后,进入快速重传处理!!!
         } else {  // 没有被怀疑(说明是正常的确认ACK)
                 if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)
                         tcp_cong_avoid(sk, ack, prior_in_flight);  // 如果没有被怀疑,直接拥塞算法处理窗口变化
         }
 
         if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP))
                 dst_confirm(sk->sk_dst_cache);
 
         return 1;
 
no_queue:
         icsk->icsk_probes_out = 0;
 
         /* If this ack opens up a zero window, clear backoff.  It was
          * being used to time the probes, and is probably far higher than
          * it needs to be for normal retransmission.
          */
         if (tcp_send_head(sk)) // 这里判断发送缓冲区是否为空,如果不为空,则进入判断需要重启keepalive定时器还是关闭定时器
                 tcp_ack_probe(sk);   // 处理定时器函数~(暂时不看)
         return 1;
 
old_ack:
         if (TCP_SKB_CB(skb)->sacked) // 如果有sack标识
                 tcp_sacktag_write_queue(sk, skb, prior_snd_una); // 进入sack段处理(暂时不看)
 
uninteresting_ack:
         SOCK_DEBUG(sk, "Ack %u out of %u:%u\n", ack, tp->snd_una, tp->snd_nxt);// 没有意义的包~
         return 0;
}

-----------------------------------------------------------------------------------------------------------------------------------------

看看这个函数:tcp_packets_in_flight,就是计算还在传输中的字节数:

static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
         return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}

static inline unsigned int tcp_left_out(const struct tcp_sock *tp)
{
         return tp->sacked_out + tp->lost_out;
}

我们从tcp_sock可以知道:其实就是返回 packets_out - sacked_out - lost_out + retrans_out,就是还没有到达对方的数据段的字节数


-----------------------------------------------------------------------------------------------------------------------------------------

下面我们看一下“发送窗口”的更新tcp_ack_update_window:

static int tcp_ack_update_window(struct sock *sk, struct sk_buff *skb, u32 ack,
                                  u32 ack_seq)
{
         struct tcp_sock *tp = tcp_sk(sk);             // 获得tcp_sock
         int flag = 0;
         u32 nwin = ntohs(tcp_hdr(skb)->window);       // 获得skb发送方的可以接收的窗口值
 
         if (likely(!tcp_hdr(skb)->syn))               // 如果不是建立连接时候,即是普通传递数据时候,窗口缩放
                 nwin <<= tp->rx_opt.snd_wscale;       // 接收方要求对窗口进行缩放
         // 下面正式更新窗口
         if (tcp_may_update_window(tp, ack, ack_seq, nwin)) { // 可能需要更新窗口大小,在函数tcp_may_update_window中实际处理(1)
                 flag |= FLAG_WIN_UPDATE;                     // 窗口更新成功
                 tcp_update_wl(tp, ack, ack_seq);       // 更新snd_wl1 
 
                 if (tp->snd_wnd != nwin) {  // 如果发送窗口!=缩放后的新窗口(注意skb发送方的接收窗口和本tp的发送窗口应该一致)
                         tp->snd_wnd = nwin; // 改变窗口值
 
                         /* Note, it is the only place, where
                          * fast path is recovered for sending TCP.
                          */
                         tp->pred_flags = 0;
                         tcp_fast_path_check(sk);  // 检验是否能够开启“快速路径”(2)看下面
 
                         if (nwin > tp->max_window) {   // 如果调整之后的窗口大于从对方接收到的最大的窗口值
                                 tp->max_window = nwin; // 调整为小的
                                 tcp_sync_mss(sk, inet_csk(sk)->icsk_pmtu_cookie); // 改变mss_cache
                         }
                 }
         }
 
         tp->snd_una = ack;    // 下一个第一个需要确认就是所有当前已经确认序号之后~~~~
 
         return flag;
}


下面看这个函数:tcp_may_update_window

/* Check that window update is acceptable.
 * The function assumes that snd_una<=ack<=snd_next.
 */ // 这个函数是检查窗口是否可变,只要确认ack在snd_una~下一个需要发送的数据之间,就是需要改变窗口的!
static inline int tcp_may_update_window(const struct tcp_sock *tp,
                                         const u32 ack, const u32 ack_seq,
                                         const u32 nwin)
{
         return (after(ack, tp->snd_una) ||                       // snd_una<=ack
                 after(ack_seq, tp->snd_wl1) ||                   // 看上面的窗口图可以知道
                 (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd)); // 调整的新窗口大于原始发送窗口
}


看一下检测是否可以进入“快速路径”处理函数:tcp_fast_path_check

static inline void tcp_fast_path_check(struct sock *sk)
{
         struct tcp_sock *tp = tcp_sk(sk);
 
         if (skb_queue_empty(&tp->out_of_order_queue) &&
            tp->rcv_wnd &&
            atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
             !tp->urg_data)
                 tcp_fast_path_on(tp);
}

能够进入“快速路径”处理的基本条件是:

1 :是否ofo(乱序包队列)队列为空,如果不为空也就是说有乱序数据不可以进入快速路径。 
2: 当前的接收窗口是否大于0.,如果不是,不可以进入。
3 :当前的已经提交的数据包大小是否小于接收缓冲区的大小,能够放的下才可以进入快速路径。 
4: 是否含有urgent 数据,不含有才可以进入快速路径。


再看看 为tp开启快速路径函数tcp_fast_path_on:

static inline void tcp_fast_path_on(struct tcp_sock *tp)
{
         __tcp_fast_path_on(tp, tp->snd_wnd >> tp->rx_opt.snd_wscale);
}


static inline void __tcp_fast_path_on(struct tcp_sock *tp, u32 snd_wnd)
{
         tp->pred_flags = htonl((tp->tcp_header_len << 26) |    // 看见没有。本质就是设置pred_flags变量
                                ntohl(TCP_FLAG_ACK) |
                                snd_wnd);
}

其实就是就是说标志位除了ACK和PSH外,如果其他的存在的话,就不能用快速路径!


-----------------------------------------------------------------------------------------------------------------------------------------


下面看进入拥塞操作函数tcp_ca_event:


有必要先看看拥塞控制结构体:

struct tcp_congestion_ops {
         struct list_head        list;
         unsigned long flags;
 
         /* initialize private data (optional) */
         void (*init)(struct sock *sk);                    // 初始化
         /* cleanup private data  (optional) */
         void (*release)(struct sock *sk);                  // 清除数据
 
         /* return slow start threshold (required) */
         u32 (*ssthresh)(struct sock *sk);                // 返回慢开始门限
         /* lower bound for congestion window (optional) */
         u32 (*min_cwnd)(const struct sock *sk);          // 拥塞窗口最小值
         /* do new cwnd calculation (required) */
         void (*cong_avoid)(struct sock *sk, u32 ack, u32 in_flight);  // 计算新的拥塞窗口
         /* call before changing ca_state (optional) */
         void (*set_state)(struct sock *sk, u8 new_state); // 设置拥塞状态
         /* call when cwnd event occurs (optional) */
         void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);  // 拥塞事件发生时候处理
         /* new value of cwnd after loss (optional) */
         u32  (*undo_cwnd)(struct sock *sk);  // 丢包之后,拥塞窗口新的值
         /* hook for packet ack accounting (optional) */
         void (*pkts_acked)(struct sock *sk, u32 num_acked, s32 rtt_us); // 包的ack计数器
         /* get info for inet_diag (optional) */
         void (*get_info)(struct sock *sk, u32 ext, struct sk_buff *skb); // 
 
         char            name[TCP_CA_NAME_MAX];
         struct module   *owner;
};

看一下TCP拥塞事件:

/* Events passed to congestion control interface */

enum tcp_ca_event {
    CA_EVENT_TX_START, /* first transmit when no packets in flight */
    CA_EVENT_CWND_RESTART, /* congestion window restart */
    CA_EVENT_COMPLETE_CWR, /* end of congestion recovery */
    CA_EVENT_FRTO, /* fast recovery timeout */
    CA_EVENT_LOSS, /* loss timeout */
    CA_EVENT_FAST_ACK, /* in sequence ack */
    CA_EVENT_SLOW_ACK, /* other ack */
};

static inline void tcp_ca_event(struct sock *sk, const enum tcp_ca_event event) // 第三个参数在上面的来说分别是:CA_EVENT_SLOW_ACK和CA_EVENT_FAST_ACK
{
         const struct inet_connection_sock *icsk = inet_csk(sk);  // 获得连接sock
 
         if (icsk->icsk_ca_ops->cwnd_event) // 注意这是一个函数指针:结构体struct tcp_congestion_ops中的!当拥塞事件发生时候执行
                 icsk->icsk_ca_ops->cwnd_event(sk, event);  // 执行这个事件
}

拥塞窗口事件初始化在这:

if (icsk->icsk_ca_ops == &tcp_init_congestion_ops) {    初始化结构体
                 rcu_read_lock();
                 list_for_each_entry_rcu(ca, &tcp_cong_list, list) {
                         if (try_module_get(ca->owner)) {
                                 icsk->icsk_ca_ops = ca;
                                 break;
                         }
 
                         /* fallback to next available */
                 }
                 rcu_read_unlock();
         }

struct tcp_congestion_ops tcp_init_congestion_ops  = {
         .name           = "",
         .owner          = THIS_MODULE,
         .ssthresh       = tcp_reno_ssthresh,
         .cong_avoid     = tcp_reno_cong_avoid,
         .min_cwnd       = tcp_reno_min_cwnd,
};


这有点问题!对于这个函数没有进行实现?还是我没有找到处理赋值的地方?无语了~~~~~~~~~~~~~~~~~~~~~~~~

或许这里面初始化:下面是关于cwnd_event所有的初始化

static struct tcp_congestion_ops tcp_veno = {
         .flags          = TCP_CONG_RTT_STAMP,
         .init           = tcp_veno_init,
         .ssthresh       = tcp_veno_ssthresh,
         .cong_avoid     = tcp_veno_cong_avoid,
         .pkts_acked     = tcp_veno_pkts_acked,
         .set_state      = tcp_veno_state,
         .cwnd_event     = tcp_veno_cwnd_event,   ///
 
         .owner          = THIS_MODULE,
         .name           = "veno",
};

static struct tcp_congestion_ops tcp_vegas = {
         .flags          = TCP_CONG_RTT_STAMP,
         .init           = tcp_vegas_init,
         .ssthresh       = tcp_reno_ssthresh,
         .cong_avoid     = tcp_vegas_cong_avoid,
         .min_cwnd       = tcp_reno_min_cwnd,
         .pkts_acked     = tcp_vegas_pkts_acked,
         .set_state      = tcp_vegas_state,
         .cwnd_event     = tcp_vegas_cwnd_event,  ///
         .get_info       = tcp_vegas_get_info,
 
         .owner          = THIS_MODULE,
         .name           = "vegas",
};

static struct tcp_congestion_ops tcp_yeah = {
         .flags          = TCP_CONG_RTT_STAMP,
         .init           = tcp_yeah_init,
         .ssthresh       = tcp_yeah_ssthresh,
         .cong_avoid     = tcp_yeah_cong_avoid,
         .min_cwnd       = tcp_reno_min_cwnd,
         .set_state      = tcp_vegas_state,
         .cwnd_event     = tcp_vegas_cwnd_event,  
         .get_info       = tcp_vegas_get_info,
         .pkts_acked     = tcp_yeah_pkts_acked,
 
         .owner          = THIS_MODULE,
         .name           = "yeah",
};

-------------------------------------------------------------------------------------------------------------------------------------------

下面清理重传队列中已经确认的数据,看函数tcp_clean_rtx_queue:

看这个链接:tcp_clean_rtx_queue


---------------------------------------------------------------------------------------------------------------------------------

现在可以看一下tcp_ack_is_dubious函数,来判断是不是进入了拥塞状态:

先可以看一下状态的定义:

#define FLAG_DATA               0x01 /* Incoming frame contained data.          */     // 来了一个包含数据的包
#define FLAG_WIN_UPDATE         0x02 /* Incoming ACK was a window update.       */     // 来了一个ACK用于更新窗口
#define FLAG_DATA_ACKED         0x04 /* This ACK acknowledged new data.         */     // 对于数据的确认
#define FLAG_RETRANS_DATA_ACKED 0x08 /* "" "" some of which was retransmitted.  */     // 对于重传数据的确认
#define FLAG_SYN_ACKED          0x10 /* This ACK acknowledged SYN.              */     // 对于SYN的确认
#define FLAG_DATA_SACKED        0x20 /* New SACK.                               */     // 这是对数据的一个选择确认
#define FLAG_ECE                0x40 /* ECE in this ACK                         */     // 确认中旅带有ECE信息
#define FLAG_DATA_LOST          0x80 /* SACK detected data lossage.             */     // SACK检测到数据丢失
#define FLAG_SLOWPATH           0x100 /* Do not skip RFC checks for window update.*/   // slowpath,需要做一些检查
#define FLAG_ONLY_ORIG_SACKED   0x200 /* SACKs only non-rexmit sent before RTO */      
#define FLAG_SND_UNA_ADVANCED   0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED) */   // snd-una改变
#define FLAG_DSACKING_ACK       0x800 /* SACK blocks contained D-SACK info */          // 包含DSACK信息
#define FLAG_NONHEAD_RETRANS_ACKED      0x1000 /* Non-head rexmitted data was ACKed */ 
#define FLAG_SACK_RENEGING      0x2000 /* snd_una advanced to a sacked seq */          // snd_una移动到一个sack中的一个位置
 
#define FLAG_ACKED              (FLAG_DATA_ACKED|FLAG_SYN_ACKED)         // 表示数据确认或者SYN确认
#define FLAG_NOT_DUP            (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)   // 表示ACK是不重复的
#define FLAG_CA_ALERT           (FLAG_DATA_SACKED|FLAG_ECE)              // 表示是否在进入拥塞状态的时候被alert(原因可能是SACK丢包或者路由器提示拥塞)
#define FLAG_FORWARD_PROGRESS   (FLAG_ACKED|FLAG_DATA_SACKED)            // 选择确认

再看一下:

TCP_CA_Open:TCP连接的初始化的状态。TCP连接会在慢启动和拥塞避免阶段(调用tcp_cong_avoid)增加拥塞窗口。每个接收到的ACK都要调用tcp_ack_is_dubious,检查它是否可疑。如果是ACK可疑,就调用 tcp_fastretrans_alert()就切换到其他CA拥塞状态。但是对于可疑的ACK,若窗口也允许增大(tcp_may_raise_cwnd),那么(tcp_fastretrans_alert)仍然可能增大拥塞窗口。

TCP_CA_Disorder:注意如果收到重复的ACK或者SACK,那么可能出现乱序情况,进入这个状态处理。

TCP_CA_CWR:表示发生某些道路拥塞,需要减慢发送速度。

TCP_CA_Recovery:正在进行快速重传丢失的数据包。

TCP_CA_Loss:超时重传情况下,如果接收到的ACK与SACK信息不一样,则阻塞丢包状态。


static inline int tcp_ack_is_dubious(const struct sock *sk, const int flag)
{ 
         return (!(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) ||  // 是重复的ACK   或者   在进入拥塞状态的时候出现警告
                 inet_csk(sk)->icsk_ca_state != TCP_CA_Open);         // 或者拥塞状态不是“增大拥塞窗口”状态 
}                                                                     // 则这个ACK是可疑的,其实意思就是,不是一个正常的ACK,不能随便增大拥塞窗口


下面就两条路:

1:如果被怀疑

2:如果没有被怀疑


先看如果被怀疑了,那么:

先看函数:tcp_may_raise_cwnd

static inline int tcp_may_raise_cwnd(const struct sock *sk, const int flag)
{
         const struct tcp_sock *tp = tcp_sk(sk);
         return (!(flag & FLAG_ECE) || tp->snd_cwnd < tp->snd_ssthresh) &&  // 没有其他阻塞  或者  (发送窗口小于门限&&不是Recovery ,也不是CWR)
                 !((1 << inet_csk(sk)->icsk_ca_state) & (TCPF_CA_Recovery | TCPF_CA_CWR));  // 那么这样还是可以增大窗口的嘛~~~~~ ^_^
}

如果可以增大窗口,那么就需要使用tcp_cong_avoid执行这个函数用来实现慢启动和快速重传拥塞避免算法:

这个函数也是在“没有被怀疑”的情况下执行的函数,所以

如果没有被怀疑,执行的也是tcp_cong_avoid,一起解释:

static void tcp_cong_avoid(struct sock *sk, u32 ack, u32 in_flight)
{
         const struct inet_connection_sock *icsk = inet_csk(sk);
         icsk->icsk_ca_ops->cong_avoid(sk, ack, in_flight); // 这才是重要处理函数
         tcp_sk(sk)->snd_cwnd_stamp = tcp_time_stamp;       // 发送窗口改变时间戳
}

我们看到上面说的拥塞结构体的初始化:

struct tcp_congestion_ops tcp_init_congestion_ops  = {
         .name           = "",
         .owner          = THIS_MODULE,
         .ssthresh       = tcp_reno_ssthresh,
         .cong_avoid     = tcp_reno_cong_avoid,  //这个函数
         .min_cwnd       = tcp_reno_min_cwnd,
};

那么实际执行的就是tcp_reno_cong_avoid函数!!!

看这个链接:tcp_reno_cong_avoid

----------------------------------------------------------------------------------------------------------------------------------

OK,下面再看看tcp_fastretrans_alert函数:TCP拥塞状态机主要是在tcp_fastretrans_alert()中实现的,只有在ACK被怀疑的时候才会执行这个提醒函数

此函数被调用的条件也就是怀疑的条件: 
1:进来一个ACK,但是状态不是 Open 
 2:收到的是   SACK 、Duplicate ACK、ECN、ECE 等警告信息

请看这个链接:tcp_fastretrans_alert


到此为止,处理接收到的ACK基本结束。。。。





  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 含插口地址结构的mbuf 11 1.9.2 含数据的mbuf 12 1.9.3 添加IP和UDP首部 13 1.9.4 IP输出 14 1.9.5 以太网输出 14 1.9.6 UDP输出小结 14 1.10 输入处理 15 1.10.1 以太网输入 15 1.10.2 IP输入 15 1.10.3 UDP输入 16 1.10.4 进程输入 17 1.11 网络实现概述(续) 17 1.12 中断级别与并发 18 1.13 源代码组织 20 1.14 测试网络 21 1.15 小结 22 第2章 mbuf:存储器缓存 24 2.1 引言 24 2.2 代码介绍 27 2.2.1 全局变量 27 2.2.2 统计 28 2.2.3 内核统计 28 2.3 mbuf的定义 29 2.4 mbuf结构 29 2.5 简单的mbuf宏和函数 31 2.5.1 m_get函数 32 2.5.2 MGET宏 32 2.5.3 m_retry函数 33 2.5.4 mbuf锁 34 2.6 m_devget和m_pullup函数 34 2.6.1 m_devget函数 34 2.6.2 mtod和dtom宏 36 2.6.3 m_pullup函数和连续的协议首部 36 2.6.4 m_pullup和IP的分片与重组 37 2.6.5 TCP重组避免调用m_pullup 39 2.6.6 m_pullup使用总结 40 2.7 mbuf宏和函数的小结 40 2.8 Net/3联网数据结构小结 42 2.9 m_copy和簇引用计数 43 2.10 其他选择 47 2.11 小结 47 第3章 接口层 49 3.1 引言 49 3.2 代码介绍 49 3.2.1 全局变量 49 3.2.2 SNMP变量 50 3.3 ifnet结构 51 3.4 ifaddr结构 57 3.5 sockaddr结构 58 3.6 ifnet与ifaddr的专用化 59 3.7 网络初始化概述 60 3.8 以太网初始化 61 3.9 SLIP初始化 64 3.10 环回初始化 65 3.11 if_attach函数 66 3.12 ifinit函数 72 3.13 小结 73 第4章 接口:以太网 74 4.1 引言 74 4.2 代码介绍 75 4.2.1 全局变量 75 4.2.2 统计量 75 4.2.3 SNMP变量 76 4.3 以太网接口 77 4.3.1 leintr函数 79 4.3.2 leread函数 79 4.3.3 ether_input函数 81 4.3.4 ether_output函数 84 4.3.5 lestart函数 87 4.4 ioctl系统调用 89 4.4.1 ifioctl函数 90 4.4.2 ifconf函数 91 4.4.3 举例 94 4.4.4 通用接口ioctl命令 95 4.4.5 if_down和if_up函数 96 4.4.6 以太网、SLIP和环回 97 4.5 小结 98 第5章 接口:SLIP和环回 100 5.1 引言 100 5.2 代码介绍 100 5.2.1 全局变量 100 5.2.2 统计量 101 5.3 SLIP接口 101 5.3.1 SLIP线路规程:SLIPDISC 101 5.3.2 SLIP初始化:slopen和slinit 103 5.3.3 SLIP输入处理:slinput 105 5.3.4 SLIP输出处理:sloutput 109 5.3.5 slstart函数 111 5.3.6 SLIP分组丢失 116 5.3.7 SLIP性能考虑 117 5.3.8 slclose函数 117 5.3.9 sltioctl函数 118 5.4 环回接口 119 5.5 小结 121 第6章 IP编址 123 6.1 引言 123 6.1.1 IP地址 123 6.1.2 IP地址的印刷规定 123 6.1.3 主机和路由器 124 6.2 代码介绍 125 6.3 接口和地址小结 125 6.4 sockaddr_in结构 126 6.5 in_ifaddr结构 127 6.6 地址指派 128 6.6.1 ifioctl函数 130 6.6.2 in_control函数 130 6.6.3 前提条件:SIOCSIFADDR、 SIOCSIFNETMASK和 SIOCSIFDSTADDR 132 6.6.4 地址指派:SIOCSIFADDR 133 6.6.5 in_ifinit函数 133 6.6.6 网络掩码指派:SIOCSIFNETMASK 136 6.6.7 目的地址指派:SIOCSIFDSTADDR 137 6.6.8 获取接口信息 137 6.6.9 每个接口多个IP地址 138 6.6.10 附加IP地址:SIOCAIFADDR 139 6.6.11 删除IP地址:SIOCDIFADDR 140 6.7 接口ioctl处理 141 6.7.1 leioctl函数 141 6.7.2 slioctl函数 142 6.7.3 loioctl函数 143 6.8 Internet实用函数 144 6.9 ifnet实用函数 144 6.10 小结 145 第7章 域和协议 146 7.1 引言 146 7.2 代码介绍 146 7.2.1 全局变量 147 7.2.2 统计量 147 7.3 domain结构 147 7.4 protosw结构 148 7.5 IP 的domain和protosw结构 150 7.6 pffindproto和pffindtype函数 155 7.7 pfctlinput函数 157 7.8 IP初始化 157 7.8.1 Internet传输分用 157 7.8.2 ip_init函数 158 7.9 sysctl系统调用 159 7.10 小结 161 第8章 IP:网际协议 162 8.1 引言 162 8.2 代码介绍 163 8.2.1 全局变量 163 8.2.2 统计量 163 8.2.3 SNMP变量 164 8.3 IP分组 165 8.4 输入处理:ipintr函数 167 8.4.1 ipintr概观 167 8.4.2 验证 168 8.4.3 转发或不转发 171 8.4.4 重装和分用 173 8.5 转发:ip_forward函数 174 8.6 输出处理:ip_output函数 180 8.6.1 首部初始化 181 8.6.2 路由选择 182 8.6.3 源地址选择和分片 184 8.7 Internet检验和:in_cksum函数 186 8.8 setsockopt和getsockopt系统调用 190 8.8.1 PRCO_SETOPT的处理 192 8.8.2 PRCO_GETOPT的处理 193 8.9 ip_sysctl函数 193 8.10 小结 194 第9章 IP选项处理 196 9.1 引言 196 9.2 代码介绍 196 9.2.1 全局变量 196 9.2.2 统计量 197 9.3 选项格式 197 9.4 ip_dooptions函数 198 9.5 记录路由选项 200 9.6 源站和记录路由选项 202 9.6.1 save_rte函数 205 9.6.2 ip_srcroute函数 206 9.7 时间戳选项 207 9.8 ip_insertoptions函数 210 9.9 ip_pcbopts函数 214 9.10 一些限制 217 9.11 小结 217 第10章 IP的分片与重装 218 10.1 引言 218 10.2 代码介绍 219 10.2.1 全局变量 220 10.2.2 统计量 220 10.3 分片 220 10.4 ip_optcopy函数 223 10.5 重装 224 10.6 ip_reass函数 227 10.7 ip_slowtimo函数 237 10.8 小结 238 第11章 ICMP:Internet控制报文协议 239 11.1 引言 239 11.2 代码介绍 242 11.2.1 全局变量 242 11.2.2 统计量 242 11.2.3 SNMP变量 243 11.3 icmp结构 244 11.4 ICMP 的protosw结构 245 11.5 输入处理:icmp_input函数 246 11.6 差错处理 249 11.7 请求处理 251 11.7.1 回显询问:ICMP_ECHO和 ICMP_ECHOREPLY 252 11.7.2 时间戳询问:ICMP_TSTAMP和 ICMP_TSTAMPREPLY 253 11.7.3 地址掩码询问:ICMP_MASKREQ和 ICMP_MASKREPLY 253 11.7.4 信息询问:ICMP_IREQ和ICMP_ IREQREPLY 255 11.7.5 路由器发现:ICMP_ROUTERADVERT 和ICMP_ROUTERSOLICIT 255 11.8 重定向处理 255 11.9 回答处理 257 11.10 输出处理 257 11.11 icmp_error函数 258 11.12 icmp_reflect函数 261 11.13 icmp_send函数 265 11.14 icmp_sysctl函数 266 11.15 小结 266 第12章 IP多播 268 12.1 引言 268 12.2 代码介绍 269 12.2.1 全局变量 270 12.2.2 统计量 270 12.3 以太网多播地址 270 12.4 ether_multi结构 271 12.5 以太网多播接收 273 12.6 in_multi结构 273 12.7 ip_moptions结构 275 12.8 多播的插口选项 276 12.9 多播的TTL值 277 12.9.1 MBONE 278 12.9.2 扩展环搜索 278 12.10 ip_setmoptions函数 278 12.10.1 选择一个明确的多播接口:IP_ MULTICAST_IF 280 12.10.2 选择明确的多播TTL: IP_ MULTICAST_TTL 281 12.10.3 选择多播环回:IP_MULTICAST_ LOOP 281 12.11 加入一个IP多播组 282 12.11.1 in_addmulti函数 285 12.11.2 slioctl和loioctl函数:SIOCADDMULTI和SIOCDELMULTI 287 12.11.3 leioctl函数:SIOCADDMULTI和 SIOCDELMULTI 288 12.11.4 ether_addmulti函数 288 12.12 离开一个IP多播组 291 12.12.1 in_delmulti函数 292 12.12.2 ether_delmulti函数 293 12.13 ip_getmoptions函数 295 12.14 多播输入处理:ipintr函数 296 12.15 多播输出处理:ip_output函数 298 12.16 性能的考虑 301 12.17 小结 301 第13章 IGMP:Internet组管理协议 303 13.1 引言 303 13.2 代码介绍 304 13.2.1 全局变量 304 13.2.2 统计量 304 13.2.3 SNMP变量 305 13.3 igmp结构 305 13.4 IGMP的protosw的结构 306 13.5 加入一个组:igmp_joingroup函数 306 13.6 igmp_fasttimo函数 308 13.7 输入处理:igmp_input函数 311 13.7.1 成员关系查询:IGMP_HOST_ MEMBERSHIP_QUERY 312 13.7.2 成员关系报告:IGMP_HOST_ MEMBERSHIP_REPORT 313 13.8 离开一个组:igmp_leavegroup函数 314 13.9 小结 315 第14章 IP多播选路 316 14.1 引言 316 14.2 代码介绍 316 14.2.1 全局变量 316 14.2.2 统计量 317 14.2.3 SNMP变量 317 14.3 多播输出处理(续) 317 14.4 mrouted守护程序 318 14.5 虚拟接口 321 14.5.1 虚拟接口表 322 14.5.2 add_vif函数 324 14.5.3 del_vif函数 326 14.6 IGMP(续) 327 14.6.1 add_lgrp函数 328 14.6.2 del_lgrp函数 329 14.6.3 grplst_member函数 330 14.7 多播选路 331 14.7.1 多播选路表 334 14.7.2 del_mrt函数 335 14.7.3 add_mrt函数 336 14.7.4 mrtfind函数 337 14.8 多播转发:ip_mforward函数 338 14.8.1 phyint_send函数 343 14.8.2 tunnel_send函数 344 14.9 清理:ip_mrouter_done函数 345 14.10 小结 346 第15章 插口层 348 15.1 引言 348 15.2 代码介绍 349 15.3 socket结构 349 15.4 系统调用 354 15.4.1 举例 355 15.4.2 系统调用小结 355 15.5 进程、描述符和插口 357 15.6 socket系统调用 358 15.6.1 socreate函数 359 15.6.2 超级用户特权 361 15.7 getsock和sockargs函数 361 15.8 bind系统调用 363 15.9 listen系统调用 364 15.10 tsleep和wakeup函数 365 15.11 accept系统调用 366 15.12 sonewconn和soisconnected 函数 369 15.13 connect系统调用 372 15.13.1 soconnect函数 374 15.13.2 切断无连接插口和外部地址的 关联 375 15.14 shutdown系统调用 375 15.15 close系统调用 377 15.15.1 soo_close函数 377 15.15.2 soclose函数 378 15.16 小结 380 第16章 插口I/O 381 16.1 引言 381 16.2 代码介绍 381 16.3 插口缓存 381 16.4 write、writev、sendto和sendmsg 系统调用 384 16.5 sendmsg系统调用 387 16.6 sendit函数 388 16.6.1 uiomove函数 389 16.6.2 举例 390 16.6.3 sendit代码 391 16.7 sosend函数 392 16.7.1 可靠的协议缓存 393 16.7.2 不可靠的协议缓存 393 16.7.3 sosend函数小结 401 16.7.4 性能问题 401 16.8 read、readv、recvfrom和recvmsg 系统调用 401 16.9 recvmsg系统调用 402 16.10 recvit函数 403 16.11 soreceive函数 405 16.11.1 带外数据 406 16.11.2 举例 406 16.11.3 其他的接收操作选项 407 16.11.4 接收缓存的组织:报文边界 407 16.11.5 接收缓存的组织:没有报文边界 408 16.11.6 控制信息和带外数据 409 16.12 soreceive代码 410 16.13 select系统调用 421 16.13.1 selscan函数 425 16.13.2 soo_select函数 425 16.13.3 selrecord函数 427 16.13.4 selwakeup函数 428 16.14 小结 429 第17章 插口选项 431 17.1 引言 431 17.2 代码介绍 431 17.3 setsockopt系统调用 432 17.4 getsockopt系统调用 437 17.5 fcntl和ioctl系统调用 440 17.5.1 fcntl代码 441 17.5.2 ioctl代码 443 17.6 getsockname系统调用 444 17.7 getpeername系统调用 445 17.8 小结 447 第18章 Radix树路由表 448 18.1 引言 448 18.2 路由表结构 448 18.3 选路插口 456 18.4 代码介绍 456 18.4.1 全局变量 458 18.4.2 统计量 458 18.4.3 SNMP变量 459 18.5 Radix结点数据结构 460 18.6 选路结构 463 18.7 初始化:route_init和rtable_init 函数 465 18.8 初始化:rn_init和rn_inithead 函数 468 18.9 重复键和掩码列表 471 18.10 rn_match函数 473 18.11 rn_search函数 480 18.12 小结 481 第19章 选路请求和选路消息 482 19.1 引言 482 19.2 rtalloc和rtalloc1函数 482 19.3 宏RTFREE和rtfree函数 484 19.4 rtrequest函数 486 19.5 rt_setgate函数 491 19.6 rtinit函数 493 19.7 rtredirect函数 495 19.8 选路消息的结构 498 19.9 rt_missmsg函数 501 19.10 rt_ifmsg函数 503 19.11 rt_newaddrmsg函数 504 19.12 rt_msg1函数 505 19.13 rt_msg2函数 507 19.14 sysctl_rtable函数 510 19.15 sysctl_dumpentry函数 514 19.16 sysctl_iflist函数 515 19.17 小结 517 第20章 选路插口 518 20.1 引言 518 20.2 routedomain和protosw结构 518 20.3 选路控制块 519 20.4 raw_init函数 520 20.5 route_output函数 520 20.6 rt_xaddrs函数 530 20.7 rt_setmetrics函数 531 20.8 raw_input函数 532 20.9 route_usrreq函数 534 20.10 raw_usrreq函数 535 20.11 raw_attach、raw_detach和raw_disconnect函数 539 20.12 小结 540 第21章 ARP:地址解析协议 542 21.1 介绍 542 21.2 ARP和路由表 542 21.3 代码介绍 544 21.3.1 全局变量 544 21.3.2 统计量 544 21.3.3 SNMP变量 546 21.4 ARP结构 546 21.5 arpwhohas函数 548 21.6 arprequest函数 548 21.7 arpintr函数 551 21.8 in_arpinput函数 552 21.9 ARP定时器函数 557 21.9.1 arptimer函数 557 21.9.2 arptfree函数 557 21.10 arpresolve函数 558 21.11 arplookup函数 562 21.12 代理ARP 563 21.13 arp_rtrequest函数 564 21.14 ARP和多播 569 21.15 小结 570 第22章 协议控制块 572 22.1 引言 572 22.2 代码介绍 573 22.2.1 全局变量 574 22.2.2 统计量 574 22.3 inpcb的结构 574 22.4 in_pcballoc和in_pcbdetach函数 575 22.5 绑定、连接和分用 577 22.6 in_pcblookup函数 581 22.7 in_pcbbind函数 584 22.8 in_pcbconnect函数 589 22.9 in_pcbdisconnect函数 594 22.10 in_setsockaddr和in_setpeeraddr 函数 595 22.11 in_pcbnotify、in_rtchange和in_losing函数 595 22.11.1 in_rtchange函数 598 22.11.2 重定向和原始插口 599 22.11.3 ICMP差错和UDP插口 600 22.11.4 in_losing函数 601 22.12 实现求精 602 22.13 小结 602 第23章 UDP:用户数据报协议 605 23.1 引言 605 23.2 代码介绍 605 23.2.1 全局变量 606 23.2.2 统计量 606 23.2.3 SNMP变量 607 23.3 UDP 的protosw结构 607 23.4 UDP的首部 608 23.5 udp_init函数 609 23.6 udp_output函数 609 23.6.1 在前面加上IP/UDP首部和mbuf簇 612 23.6.2 UDP检验和计算和伪首部 612 23.7 udp_input函数 616 23.7.1 对收到的UDP数据报的一般确认 616 23.7.2 分用单播数据报 619 23.7.3 分用多播和广播数据报 622 23.7.4 连接上的UDP插口和多接口主机 625 23.8 udp_saveopt函数 625 23.9 udp_ctlinput函数 627 23.10 udp_usrreq函数 628 23.11 udp_sysctl函数 633 23.12 实现求精 633 23.12.1 UDP PCB高速缓存 633 23.12.2 UDP检验和 634 23.13 小结 635 第24章 TCP:传输控制协议 636 24.1 引言 636 24.2 代码介绍 636 24.2.1 全局变量 636 24.2.2 统计量 637 24.2.3 SNMP变量 640 24.3 TCP 的protosw结构 641 24.4 TCP的首部 641 24.5 TCP的控制块 643 24.6 TCP的状态变迁图 645 24.7 TCP的序号 646 24.8 tcp_init函数 650 24.9 小结 652 第25章 TCP的定时器 654 25.1 引言 654 25.2 代码介绍 655 25.3 tcp_canceltimers函数 657 25.4 tcp_fasttimo函数 657 25.5 tcp_slowtimo函数 658 25.6 tcp_timers函数 659 25.6.1 FIN_WAIT_2和2MSL定时器 660 25.6.2 持续定时器 662 25.6.3 连接建立定时器和保活定时器 662 25.7 重传定时器的计算 665 25.8 tcp_newtcpcb算法 666 25.9 tcp_setpersist函数 668 25.10 tcp_xmit_timer函数 669 25.11 重传超时:tcp_timers函数 673 25.11.1 慢起动和避免拥塞 675 25.11.2 精确性 677 25.12 一个RTT的例子 677 25.13 小结 679 第26章 TCP输出 680 26.1 引言 680 26.2 tcp_output概述 680 26.3 决定是否应发送一个报文段 682 26.4 TCP选项 691 26.5 窗口大小选项 692 26.6 时间戳选项 692 26.6.1 哪个时间戳需要回显,RFC1323 算法 694 26.6.2 哪个时间戳需要回显,正确的 算法 695 26.6.3 时间戳与延迟ACK 695 26.7 发送一个报文段 696 26.8 tcp_template函数 707 26.9 tcp_respond函数 708 26.10 小结 710 第27章 TCP函数 712 27.1 引言 712 27.2 tcp_drain函数 712 27.3 tcp_drop函数 712 27.4 tcp_close函数 713 27.4.1 路由特性 713 27.4.2 资源释放 716 27.5 tcp_mss函数 717 27.6 tcp_ctlinput函数 722 27.7 tcp_notify函数 723 27.8 tcp_quench函数 724 27.9 TCP_REASS宏和tcp_reass函数 724 27.9.1 TCP_REASS宏 725 27.9.2 tcp_reass函数 727 27.10 tcp_trace函数 732 27.11 小结 736 第28章 TCP的输入 737 28.1 引言 737 28.2 预处理 739 28.3 tcp_dooptions函数 745 28.4 首部预测 747 28.5 TCP输入:缓慢的执行路径 752 28.6 完成被动打开或主动打开 752 28.6.1 完成被动打开 753 28.6.2 完成主动打开 756 28.7 PAWS:防止序号回绕 760 28.8 裁剪报文段使数据在窗口内 762 28.9 自连接和同时打开 768 28.10 记录时间戳 770 28.11 RST处理 770 28.12 小结 772 第29章 TCP的输入(续) 773 29.1 引言 773 29.2 ACK处理概述 773 29.3 完成被动打开和同时打开 774 29.4 快速重传和快速恢复的算法 775 29.5 ACK处理 778 29.6 更新窗口信息 784 29.7 紧急方式处理 786 29.8 tcp_pulloutofband函数 788 29.9 处理接收的数据 789 29.10 FIN处理 791 29.11 最后的处理 793 29.12 实现求精 795 29.13 首部压缩 795 29.13.1 引言 796 29.13.2 首部字段的压缩 799 29.13.3 特殊情况 801 29.13.4 实例 802 29.13.5 配置 803 29.14 小结 803 第30章 TCP的用户需求 805 30.1 引言 805 30.2 tcp_usrreq函数 805 30.3 tcp_attach函数 814 30.4 tcp_disconnect函数 815 30.5 tcp_usrclosed函数 816 30.6 tcp_ctloutput函数 817 30.7 小结 820 第31章 BPF:BSD 分组过滤程序 821 31.1 引言 821 31.2 代码介绍 821 31.2.1 全局变量 821 31.2.2 统计量 822 31.3 bpf_if结构 822 31.4 bpf_d结构 825 31.4.1 bpfopen函数 826 31.4.2 bpfioctl函数 827 31.4.3 bpf_setif函数 830 31.4.4 bpf_attachd函数 831 31.5 BPF的输入 832 31.5.1 bpf_tap函数 832 31.5.2 catchpacket函数 833 31.5.3 bpfread函数 835 31.6 BPF的输出 837 31.7 小结 838 第32章 原始IP 839 32.1 引言 839 32.2 代码介绍 839 32.2.1 全局变量 839 32.2.2 统计量 840 32.3 原始 IP的protosw结构 840 32.4 rip_init函数 842 32.5 rip_input函数 842 32.6 rip_output函数 844 32.7 rip_usrreq函数 846 32.8 rip_ctloutput函数 850 32.9 小结 852 结束语 853 附录A 部分习题的解答 854 附录B 源代码的获取 872 附录C RFC 1122 的有关内容 874 参考文献 895

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值