由于在connect函数中涉及数据包的发送与接收问题,事实上,发送与接收函数不限于connect函数,所以这里单独剖析。
承前文继续剖析 connect 函数,数据包的发送和接收在 ip_queue_xmit 函数和 release_sock 函数中实现。本文着重分析 ip_queue_xmit 函数,下篇将补充分析 connect 函数剩下的部分。
值得注意的是:这些函数是数据包发送函数,在数据传输阶段,基本上都会调用该函数,因为connect涉及该函数,就放在这里介绍了,不意味着这个函数只属于connect下层函数。
1、网络层——ip_queue_xmit 函数
/*
* Queues a packet to be sent, and starts the transmitter
* if necessary. if free = 1 then we free the block after
* transmit, otherwise we don't. If free==2 we not only
* free the block but also don't assign a new ip seq number.
* This routine also needs to put in the total length,
* and compute the checksum
*/
//数据包发送函数
//sk:被发送数据包对应的套接字;dev:发送数据包的网络设备
//skb:被发送的数据包 ;flags:是否对数据包进行缓存以便于此后的超时重发
void ip_queue_xmit(struct sock *sk, struct device *dev,
struct sk_buff *skb, int free)
{
struct iphdr *iph;
unsigned char *ptr;
/* Sanity check */
//发送设备检查
if (dev == NULL)
{
printk("IP: ip_queue_xmit dev = NULL\n");
return;
}
IS_SKB(skb);//数据包合法性检查
/*
* Do some book-keeping in the packet for later
*/
skb->dev = dev;
skb->when = jiffies;//重置数据包的发送时间,只有一个定时器,每次发数据包时,都要重新设置
/*
* Find the IP header and set the length. This is bad
* but once we get the skb data handling code in the
* hardware will push its header sensibly and we will
* set skb->ip_hdr to avoid this mess and the fixed
* header length problem
*/
//skb->data指向的地址空间的布局: MAC首部 | IP首部 | TCP首部 | 有效负载
ptr = skb->data;//获取数据部分首地址
ptr += dev->hard_header_len;//后移硬件(MAC)首部长度个字节,定位到ip首部
iph = (struct iphdr *)ptr;//获取ip首部
skb->ip_hdr = iph;//skb对应字段建立关联
//ip数据报的总长度(ip首部+数据部分) = skb的总长度 - 硬件首部长度
iph->tot_len = ntohs(skb->len-dev->hard_header_len);
#ifdef CONFIG_IP_FIREWALL
//数据包过滤,用于防火墙安全性检查
if(ip_fw_chk(iph, dev, ip_fw_blk_chain, ip_fw_blk_policy, 0) != 1)
/* just don't send this packet */
return;
#endif
/*
* No reassigning numbers to fragments...
*/
//如果不是分片数据包,就需要递增id字段
//free==2,表示这是个分片数据包,所有分片数据包必须具有相同的id字段,方便以后分片数据包重组
if(free!=2)
iph->id = htons(ip_id_count++);//ip数据报标识符
//ip_id_count是全局变量,用于下一个数据包中ip首部id字段的赋值
else
free=1;
/* All buffers without an owner socket get freed */
if (sk == NULL)//没有对应sock结构,则无法对数据包缓存
free = 1;
skb->free = free;//用于标识数据包发送之后是缓存还是立即释放,=1表示无缓存
/*
* Do we need to fragment. Again this is inefficient.
* We need to somehow lock the original buffer and use
* bits of it.
*/
//数据包拆分
//如果ip层数据包的数据部分(各层首部+有效负载)长度大于网络设备的最大传输单元,就需要拆分发送
//实际是skb->len - dev->hard_header_len > dev->mtu
//因为MTU最大报文长度表示的仅仅是IP首部及其数据负载的长度,所以要考虑MAC首部长度
if(skb->len > dev->mtu + dev->hard_header_len)
{
//拆分成分片数据包传输
ip_fragment(sk,skb,dev,0);
IS_SKB(skb);//检查数据包skb相关字段
kfree_skb(skb,FREE_WRITE);
return;
}
/*
* Add an IP checksum
*/
//ip首部校验和计算
ip_send_check(iph);
/*
* Print the frame when debugging
*/
/*
* More debugging. You cannot queue a packet already on a list
* Spot this and moan loudly.
*/
if (skb->next != NULL)
{
printk("ip_queue_xmit: next != NULL\n");
skb_unlink(skb);
}
/*
* If a sender wishes the packet to remain unfreed
* we add it to his send queue. This arguably belongs
* in the TCP level since nobody else uses it. BUT
* remember IPng might change all the rules.
*/
//free=0,表示对数据包进行缓存,一旦发生丢弃的情况,进行数据包重传(可靠性数据传输协议)
if (!free)
{
unsigned long flags;
/* The socket now has more outstanding blocks */
sk->packets_out++;//本地发送出去但未得到应答的数据包数目
/* Protect the list for a moment */
save_flags(flags);
cli();
//数据包重发队列
if (skb->link3 != NULL)
{
printk("ip.c: link3 != NULL\n");