分类: LINUX
2006-04-01 23:50:09
Linux 2.4 中网络包在网络协议栈中的旅程
翻译自 Harald Welte
2.4 内核由于采用了softirq 机制,因此网络包在内核中旅程与2.2 内核比起来有了很大的不同,所以我们需要重新讲解一下:),关于softirq/tasklet/bottom half 的讲解参看我画的图。
1. 网络包的接收
a) 接收中断
如果网卡探测到一个以太网帧的目的MAC 地址与本网卡的地址匹配的话,或者此帧是链路层的广播包的话,网卡就会接收此帧,放到网卡自己的缓冲区中,然后向cpu 发出一个中断请求,cpu 响应此中断请求,调用此网卡相应的中断处理程序,在中断处理程序中,把此包从网卡的缓冲区中拷贝到内存中,方式可以是DMA/PIO 等等。然后会分配一个skb 结构,最后会呼叫与协议没有关系的netif_rx() 函数(net/core/dev.c:netif_rx(skb) ),此函数主要负责来给此包打上时间戳标记,然后把此包对应的skb 放到对应的cpu 的接收队列中,但是如果接收队列以满的话,此包会被丢弃。Netif_rx 函数最后给接收网络包的软中断(softirq) 打上标记:__cpu_raise_softirq()(include/linux/interrupt.h) 。至此硬件对应的中断处理程序就完成了,即网络包的前期处理完成了,而其后期处理交给了软中断RX softirq 。
b) RX softirq
这部分是与2.2 内核完全不一样的,2.2 内核中使用bottom half 机制来处理,而在2.4 内核中使用的是softirq 机制。Softirq 机制与bottom half 机制相比,优点是可以在多个cpu 上同时处理,但对于bottom half ,会保证任意时刻只会在一个cpu 上运行。
RX softirq 的注册是在net/core/dev/c:net_init() 函数中,使用kernel/softirq.c:open_softirq() 来注册的。
网络包的后期处理是在NET_RX_SOFTIRQ 中完成的。它是在kernel/softirq.c:do_softirq() 中被呼叫的。而do_softirq ()在三种时机会被调用:
1. arch/i386/kernel/irq.c:do_IRQ() ,这个是IRQ 处理(硬件IRQ )函数。
2. arch/i386/kernel/entry.S 内核从系统调用返回。
3. 在进程调度函数中:kernel/sched.c:schedule()
所以,如果cpu 执行到以上三种时刻时,会呼叫do_softirq() 函数。在此函数中,如果检测到NET_RX_SOFTIRQ 被打上标记,就会执行net/core/dev.c:net_rx_action() 。在net_rx_action() 函数中,skb 会被从相应cpu 的接收队列中取出,根据skb 的类型,调用相应的处理函数,对于ip 包来说,就是ip_rcv() 函数。
c) ip 包的处理函数是通过net/core/dev.c:dev_add_pack() 函数注册的。而后者是在net/ipv4/ip_output.c:ip_init() 函数中调用的,ip_init() 用于初始化各种结构,注册ip 包的处理函数。对于ipv4 来说,包处理函数是net/ipv4/ip_input.c:ip_rcv() ,此函数首先会做一些校验,如ip 头的校验,包长的校验,ip 协议版本号的校验等。如果以上的校验出错的话,此ip 包会被丢弃。然后会计算ip 包的长度,以及去处传输层可能加的一些没有用的padding 。此后,第一个NETFILTER 钩子会被调用 。当执行完netfilter 钩子函数后,会呼叫/net/ipv4/ipv_input.c :ipv_rcv_finish() 。在ipv_rcv_finish 函数中,此包的目的地址会由net/ipv4/route.c:ip_route_input() 计算出来。此外如果我们的ip 包包含ip 选项的话,也会在此处理。根据net/ipv4/route.c:ip_route_input_slow() ,我们的ip 包会有两条不同的路走:一条是针对此ip 包是发给本机节点的,一条是针对此ip 包是需要转发的。
1. net/ipv4/ip_input.c:ip_local_deliver() : 此ip 包是发给自己的,内核应该把此包交给上层协议。
2. net/ipv4/ip_forward.c:ip_forward() :需要转发的。
3. net/ipv4/route.c:ip_error() :发生错误了,我们不能为此包找到相应的路由(routing table entry)
4. net/ipv4/ipmr.c:ip_mr_input() :此包是个多播包,我们需要做多播路由。
2 .需要转发的包
如果是需要转发的包,呼叫net/ipv4/ip_forwardd.c:ip_forward() 。
此函数需要做的工作如下:
i. check ttl ,如果ttl <= 1 ,丢弃该包,发送icmp time exceed 消息给发送方。
ii. check 是否skb 的tailroom 足够大来容纳目的地的链路层的头部,如果必要,可以expand skb 。
iii. ttl++
iv. 如果我们的包的长度 > 目的设备的MTU ,并且IP 头部的don’t fragment bit 被置位,则向发送方发送ICMP flag needed 消息。
v. call NF_IP_FORWARD hook
vi. 如果NF_IP_FORWARD hook 返回NF_ACCEPT ,则呼叫net/ipv4/ip_forward.c:ip_forward_finish() 。
在ip_forward_finish() 函数中检测是否需要在ip 头设置其它的一些选项,然后会呼叫include/net/ip.h:ip_send() 。
在ip_send() 中,会检测我们是否需要对此ip 包进行分片,如果需要,则调用ip_fragment() ,否则调用net/ipv4/ip_forwardd:ip_finish_output() 。
在ip_finish_output() 中,不干别的,只是调用另一个NETFILTER hook :NF_IP_POST_ROUTING ,然后呼叫ip_finish_output2() 函数。
Ip_finish_output2() 函数中,呼叫skb 对应的硬件头的发送函数hh->hh_output(skb)