全部博文(41)
分类: LINUX
2009-08-06 17:50:39
tcp.c文件的tcp_do_retransmit函数
978计划工作组
/*
* A socket has timed out on its send queue and wants to do a
* little retransmitting. Currently this means TCP.
*/
void tcp_do_retransmit(struct sock *sk, int all)
{
struct sk_buff * skb;
struct proto *prot;
struct device *dev;
int ct=0;
prot = sk->prot;
skb = sk->send_head;
while (skb != NULL)
{
struct tcphdr *th;
struct iphdr *iph;
int size;
dev = skb->dev;
IS_SKB(skb);
skb->when = jiffies;
/*
* In general it's OK just to use the old packet. However we
* need to use the current ack and window fields. Urg and
* urg_ptr could possibly stand to be updated as well, but we
* don't keep the necessary data. That shouldn't be a problem,
* if the other end is doing the right thing. Since we're
* changing the packet, we have to issue a new IP identifier.
*/
iph = (struct iphdr *)(skb->data + dev->hard_header_len);
th = (struct tcphdr *)(((char *)iph) + (iph->ihl << 2));
size = skb->len - (((unsigned char *) th) - skb->data);
/*
* Note: We ought to check for window limits here but
* currently this is done (less efficiently) elsewhere.
* We do need to check for a route change but can't handle
* that until we have the new 1.3.x buffers in.
*
*/
iph->id = htons(ip_id_count++);
ip_send_check(iph);
/*
* This is not the right way to handle this. We have to
* issue an up to date window and ack report with this
* retransmit to keep the odd buggy tcp that relies on
* the fact BSD does this happy.
* We don't however need to recalculate the entire
* checksum, so someone wanting a small problem to play
* with might like to implement RFC1141/RFC1624 and speed
* this up by avoiding a full checksum.
*/
th->ack_seq = ntohl(sk->acked_seq);
th->window = ntohs(tcp_select_window(sk));
tcp_send_check(th, sk->saddr, sk->daddr, size, sk);
/*
* If the interface is (still) up and running, kick it.
*/
if (dev->flags & IFF_UP)
{
/*
* If the packet is still being sent by the device/protocol
* below then don't retransmit. This is both needed, and good -
* especially with connected mode AX.25 where it stops resends
* occurring of an as yet unsent anyway frame!
* We still add up the counts as the round trip time wants
* adjusting.
*/
if (sk && !skb_device_locked(skb))
{
/* Remove it from any existing driver queue first! */
skb_unlink(skb);
/* Now queue it */
ip_statistics.IpOutRequests++;
dev_queue_xmit(skb, dev, sk->priority);
}
}
/*
* Count retransmissions
*/
ct++;
sk->prot->retransmits ++;
/*
* Only one retransmit requested.
*/
if (!all)
break;
/*
* This should cut it off before we send too many packets.
*/
if (ct >= sk->cong_window)
break;
skb = skb->link3;
}
}
用于TCP 协议数据包重传,该函数遍历相应套接字重传队列,依照要求重传该队列中数据包。
skb = sk->send_head;
sk->prot:指向传输层的处理函数集。
sk->send_head:tcp协议重发队列,指向队列的头,sk->send_tail指向同一个队列的尾,存储数据包已经发送出去了但未得到应答的数据报。是双向非循环链表。
skb->dev:此数据结构存储网络设备的相关信息。
IS_SKB:是一个宏函数,宏展开后是调用skb_check函数,此函数的功能是检查sk_buff的合理性并打印相关信息。
skb->when:该数据包的发送时间。该字段用于计算往返时间RTT。
jiffies:是记录着从电脑开机到现在总共的时钟中断次数。在linux内核中jiffies远比xtime重要,那么他取决于系统的频率,单位是Hz,这里不得不说一下频率的单位,1MHz=1000,000Hz(6个零),1KHz=1000Hz(3个零).频率是周期的倒数,一般是一秒钟中断产生的次数,所以,假如我们需要知道系统的精确的时间单位时,需要换算了,假如我们系统的频率是200Mhz,那么一次中断的间隔是1秒/ 200,000,000Hz = 0.000 000 005秒看一下上面我们的时间单位,对照一下小数点后面是9个零,所以理论上我们系统的精确度是5纳秒。LINUX系统时钟频率是一个常数HZ来决定的,通常HZ=100,那么他的精度就是10ms(毫秒)。也就是说每10ms一次中断。所以一般来说Linux的精确度是10毫秒。也就是x86机10ms等于1滴答。
dev->hard_header_len:链路层的头长度。
skb->data:传输的数据包(包括所有头)。
skb->data + dev->hard_header_len:指针移动到ip头位置。
iph:指向ip头的位置。
iph->ihl<<2:iph->ihl是ip头的首部长度域,在ip头结构中占4bit,首部长度指的是IP层头部(包括选项域)占32 bit字的数目或者说有多少个字(32位机)。<<2表示左移2位相当于乘以4,所以iph->ihl<<2表示以字节为单位的首部长度。
th:指向tcp头的位置。
skb->len:值等于链路头长度 + Ip头长度 + tcp 头长度 + tcp层数据。
size:值等于tcp 头长度 + tcp层数据。
iph:ip头数据结构。
ip_id_count:id计数器。
iph->id:16位的标识字段,顺序递增。
Ip_send_check:根据ip头计算验证和码并将结果赋给iph->check(16位的首部校验和域)。计算数据报的IP检验和方法:首先把检验和字段置为0。然后对首部中每个16 bit进行二进制反码求和(整个首部看成是由一串16 bit的字组成),结果存在检验和字段中。此处注意求和产生的进位要看作一个新的16bit加到上一步的求和结果中。
当收到一份IP数据报后,同样对首部中每个16 bit进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全1。如果结果不是全1(即检验和错误),那么IP就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。
th->ack_seq:tcp头数据结构的32位确认号域,按字节数递增。
sk->acked_seq:本地希望从远端接收的下一个字节的序列号。
th->window:tcp头数据结构的16位窗口大小域,以字节为单位。
tcp_select_window:得到窗口大小,详情请参见《tcp.c文件的tcp_select_window函数(1)》。
sk->saddr:本地地址。
sk->daddr:远端地址。
tcp_send_check:计算校验和并将结果赋给th->check(16位tcp校验和域)。
dev->flags:设备状态,未启用则无法工作。
IFF_UP:设备已启用。
skb_device_locked:判读当前skb节点是否被锁定。
skb_unlink:删除当前的skb节点。
ip_statistics:ip_mib结构,是对ip协议相关信息的统计。
IpOutRequests:设备高层发出的包数量。
sk->priority:发送优先级。
dev:此数据结构存储网络设备的相关信息。
dev_queue_xmit:链路层函数,实现发送skb的当前节点信息。
ct:重发数据包的个数。
sk->prot->retransmits:本数据包重传的次数。
all:值为0,则表示只发送一个数据包后即可退出。
sk->cong_window:拥塞窗口大小, 表示的是本地最大可同时发送但未得到应答的数据字节个数。
skb = skb->link3: 重发链表,是一个单向非循环链表。