本文分析基于Linux Kernel 1.2.13
作者:闫明
注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析、”(下)“表示分析是从上向下分析。
上一篇博文中我们从宏观上分析了Linux内核中网络栈的初始化过程,这里我们再从宏观上分析一下一个数据包在各网络层的传递的过程。
我们知道网络的OSI模型和TCP/IP模型层次结构如下:
上文中我们看到了网络栈的层次结构:
我们就从最底层开始追溯一个数据包的传递流程。
1、网络接口层
* 硬件监听物理介质,进行数据的接收,当接收的数据填满了缓冲区,硬件就会产生中断,中断产生后,系统会转向中断服务子程序。
*
在中断服务子程序中,数据会从硬件的缓冲区复制到内核的空间缓冲区,并包装成一个数据结构(sk_buff),然后调用对驱动层的接口函数netif_rx()将数据包发送给链路层。该函数的实现在net/inet/dev.c中,(在整个网络栈实现中dev.c文件的作用重大,它衔接了其下的驱动层和其上的网络层,可以称它为链路层模块的实现)
该函数的实现如下:
voidnetif_rx(structsk_buff *skb)
{
staticintdropping = 0;
skb->sk = NULL;
skb->free = 1;
if(skb->stamp.tv_sec==0)
skb->stamp = xtime;
if(!backlog_size)
dropping = 0;
elseif(backlog_size > 300)
dropping = 1;
if(dropping)
{
kfree_skb(skb, FREE_READ);
return;
}
#ifdef CONFIG_SKB_CHECK
IS_SKB(skb);
#endif
skb_queue_tail(&backlog,skb);//加入队列backlog
backlog_size++;
mark_bh(NET_BH);//下半部分bottom half技术可以减少中断处理程序的执行时间
return;
}
该函数中用到了bootom
half技术,该技术的原理是将中断处理程序人为的分为两部分,上半部分是实时性要求较高的任务,后半部分可以稍后完成,这样就可以节省中断程序的处理时间。可整体的提高系统的性能。该技术将会在后续的博文中详细分析。
我们从上一篇分析中知道,在网络栈初始化的时候,已经将NET的下半部分执行函数定义成了net_bh(在socket.c文件中1375行左右)
bh_base[NET_BH].routine= net_bh;//设置NET 下半部分的处理函数为net_bh
* 函数net_bh的实现在net/inet/dev.c中
voidnet_bh(void*tmp)
{
structsk_buff *skb;
structpacket_type *ptype;
structpacket_type *pt_prev;
unsigned shorttype;
if(set_bit(1, (void*)&in_bh))//标记BUSY状态
return;
dev_transmit();//调用dev_tinit()函数发送数据
cli();//防止队列操作错误,需要关中断和开中断
while((skb=skb_dequeue(&backlog))!=NULL)//出队直到队列为空
{
backlog_size--;//队列元素个数减一
sti();
skb->h.raw = skb->data + skb->dev->hard_header_len;
skb->len -= skb->dev->hard_header_len;
type = skb->dev->type_trans(skb, skb->dev);//取出该数据包所属的协议类型
pt_prev = NULL;
for(ptype = ptype_base; ptype != NULL; ptype = ptype->next)//遍历ptype_base所指向的网络协议队列
{
//判断协议号是否匹配
if((ptype->type == type || ptype->type == htons(ETH_P_ALL)) && (!ptype->dev || ptype->dev==skb->dev))
{
if(pt_prev)
{
structsk_buff *skb2;
skb2=skb_clone(skb, GFP_ATOMIC);//复制数据包结构
if(skb2)
pt_prev->func(skb2, skb->dev, pt_prev);//调用相应协议的处理函数,
//这里和网络协议的种类有关系
//如IP 协议的处理函数就是ip_rcv
}
pt_prev=ptype;
}
}
if(pt_prev)
pt_prev->func(skb, skb->dev, pt_prev);
else
kfree_skb(skb, FREE_WRITE);
dev_transmit();
cli();
}
in_bh = 0;//BUSY状态还原
sti();
dev_transmit();//清空缓冲区
}
2、网络层
*
就以IP数据包为例来说明,那么从链路层向网络层传递时将调用ip_rcv函数。该函数完成本层的处理后会根据IP首部中使用的传输层协议来调用相应协议的处理函数。
UDP对应udp_rcv、TCP对应tcp_rcv、ICMP对应icmp_rcv、IGMP对应igmp_rcv(虽然这里的ICMP,IGMP一般成为网络层协议,但是实际上他们都封装在IP协议里面,作为传输层对待)
这个函数比较复杂,后续会详细分析。这里粘贴一下,让我们对整体了解更清楚
intip_rcv(structsk_buff *skb,structdevice *dev,structpacket_type *pt)
{
structiphdr *iph = skb->h.iph;
structsock *raw_sk=NULL;
unsigned charhash;
unsigned charflag = 0;
unsigned charopts_p = 0;
structinet_protocol *ipprot;
staticstructoptions opt;
intbrd=IS_MYADDR;
intis_frag=0;
#ifdef CONFIG_IP_FIREWALL
interr;
#endif
ip_statistics.IpInReceives++;
skb->ip_hdr = iph;
if(skb->len<sizeof(structiphdr) || iph->ihl<5 || iph->version != 4 ||
skb->lentot_len) || ip_fast_csum((unsigned char*)iph, iph->ihl) !=0)
{
ip_statistics.IpInHdrErrors++;
kfree_skb(skb, FREE_WRITE);
return(0);
}
#ifdef CONFIG_IP_FIREWALL
if((err=ip_fw_chk(iph,dev,ip_fw_blk_chain,ip_fw_blk_policy, 0))!=1)
{
if(err==-1)
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0, dev);
kfree_skb(skb, FREE_WRITE);
return0;
}
#endif
skb->len=ntohs(iph->tot_len);
if(iph->ihl != 5)
{
memset((char*) &opt, 0,sizeof(opt));
if(do_options(iph, &opt) != 0)
return0;
opts_p = 1;
}
if(iph->frag_off)
{
if(iph->frag_off & 0x0020)
is_frag|=1;
if(ntohs(iph->frag_off) & 0x1fff)
is_frag|=2;
}
if( iph->daddr != skb->dev->pa_addr && (brd = ip_chk_addr(iph->daddr)) == 0)
{
if(skb->pkt_type!=PACKET_HOST || brd==IS_BROADCAST)
{
kfree_skb(skb,FREE_WRITE);
return0;
}
#ifdef CONFIG_IP_FORWARD
ip_forward(skb, dev, is_frag);
#else
ip_statistics.IpInAddrErrors++;
#endif
kfree_skb(skb, FREE_WRITE);
return(0);
}
#ifdef CONFIG_IP_MULTICAST
if(brd==IS_MULTICAST && iph->daddr!=IGMP_ALL_HOSTS && !(dev->flags&IFF_LOOPBACK))
{
structip_mc_list *ip_mc=dev->ip_mc_list;
do
{
if(ip_mc==NULL)
{
kfree_skb(skb, FREE_WRITE);
return0;
}
if(ip_mc->multiaddr==iph->daddr)
break;
ip_mc=ip_mc->next;
}
while(1);
}
#endif
#ifdef CONFIG_IP_ACCT
ip_acct_cnt(iph,dev, ip_acct_chain);
#endif
if(is_frag)
{
skb=ip_defrag(iph,skb,dev);
if(skb==NULL)
return0;
skb->dev = dev;
iph=skb->h.iph;
}
skb->ip_hdr = iph;
skb->h.raw += iph->ihl*4;
hash = iph->protocol & (SOCK_ARRAY_SIZE-1);
if((raw_sk=raw_prot.sock_array[hash])!=NULL)
{
structsock *sknext=NULL;
structsk_buff *skb1;
raw_sk=get_sock_raw(raw_sk, hash, iph->saddr, iph->daddr);
if(raw_sk)
{
do
{
sknext=get_sock_raw(raw_sk->next, hash, iph->saddr, iph->daddr);
if(sknext)
skb1=skb_clone(skb, GFP_ATOMIC);
else
break;
if(skb1)
raw_rcv(raw_sk, skb1, dev, iph->saddr,iph->daddr);
raw_sk=sknext;
}
while(raw_sk!=NULL);
}
}
hash = iph->protocol & (MAX_INET_PROTOS -1);
for(ipprot = (structinet_protocol *)inet_protos[hash];ipprot != NULL;ipprot=(structinet_protocol *)ipprot->next)
{
structsk_buff *skb2;
if(ipprot->protocol != iph->protocol)
continue;
if(ipprot->copy || raw_sk)
{
skb2 = skb_clone(skb, GFP_ATOMIC);
if(skb2==NULL)
continue;
}
else
{
skb2 = skb;
}
flag = 1;
ipprot->handler(skb2, dev, opts_p ? &opt : 0, iph->daddr,
(ntohs(iph->tot_len) - (iph->ihl * 4)),
iph->saddr, 0, ipprot);
}
if(raw_sk!=NULL)
raw_rcv(raw_sk, skb, dev, iph->saddr, iph->daddr);
elseif(!flag)
{
if(brd != IS_BROADCAST && brd!=IS_MULTICAST)
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0, dev);
kfree_skb(skb, FREE_WRITE);
}
return(0);
}