-
本文档的
Copyleft
归
< br>wwwlkk
所有,使用
GPL
发布,可以自由拷贝、转载,转载时请保持文档的完整性,严禁
用于任何商业用途。
p>
E-mail:
wwwlkk@
来源
:
/?business&aid=6&un=wwwlkk#7
linux2.6.35
内核
IMQ<
/p>
源码实现分析
(
1
)数据包截留并重新注入协议栈技术
.........
..................................................
......................................
1
(
2
p>
)及时处理数据包技术
...................
..................................................
..................................................
..
2
(
3
)
IMQ
设备数据包重新注入协议栈
流程
.
.............
..................................................
............................
4
(
4
p>
)
IMQ
截留数据包流程
< br>
.
.......................
..................................................
..............................................
4
(
5
p>
)
IMQ
在软中断中及时将数据包重新注入
协议栈
.
............
..................................................
.............
7
(
6
)结束语
..................................................
..................................................
.............................................
9
前言:
I
MQ
用于入口流量整形和全局的流量控制,
IMQ
的配置是很简单的,但很少人分析过
IMQ
的内核实
现,网络上也没有
IMQ
的源码分析文档,为了搞清楚
IMQ
的性能,稳定性,以及
借鉴
IMQ
的技术,本文分析了
IMQ
< br>的内核实现机制。
首先揭示
I
MQ
的核心技术:
1.
如何从协议栈中截留数据包,并能把数据包重新注入协议栈。
2.
如何做到及时的将数据包重新注入协议栈。
< br>实际上
linux
的标准内核已经解决了以上
2
个技术难点,第
1
个技
术可以在
NF_QUEUE
机制
中看到
,第二个技术可以在发包软中断中看到。下面先介绍这
2
个技术
。
(
1
)数
据包截留并重新注入协议栈技术
okfn
参数:
int
ip_rcv
(struct
sk_buff
*skb,
struct
net_device
*dev,
struct
packet_type
*pt,
struct
net_device
*orig_dev){
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。<
/p>
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
return NF_HOOK(NFPROTO_IPV4,
NF_INET_PRE_ROUTING
, skb, dev, NULL,
ip_rcv_finish
);<
/p>
//
下一流程是进入函数
ip_rcv_
finish
。
}
static inline int
NF_HOOK(uint8_t pf, unsigned int hook,
struct sk_buff *skb,
struct
net_device *in, struct net_device *out,
int (*okfn)(struct sk_buff
*))
//
将
ip_rcv_fini
sh
地址作为参数传入。
{
return
NF_HOOK_THRESH
(pf, hook,
skb, in, out,
okfn
,
INT_MIN);
}
static inline
int
NF_HOOK_THRESH(uint8_t pf, unsigned
int hook, struct sk_buff *skb,
struct net_device *in, struct
net_device *out,
int
(*okfn)(struct sk_buff *), int thresh)
{
int ret =
nf_hook_thresh(pf, hook, skb, in, out, okfn,
thresh);
if (ret == 1)
ret = okfn(skb)
;//
根据函数地址进入之前的
ip_rcv_finish<
/p>
函数。
linux-2.6.35
p>
内核
IMQ
源码实现分析
< br>
2011-8-23
1 /
9
}
return ret;
okf
n
应该是下一个流程函数,不应该是本流程函数,这样做就很稳定了。
< br>
IMQ
设备的做法就是使用到了这个
< br>okfn
,
IMQ
设备发送数据
包不是调用网卡驱动,而是根据
okfn
,将数据包发给下一个
流程函数。
这里函数的概念和模块的概念是一样的,
一个函数就是一个模块,
一个模块处理完就将数据扔
给下一个模块处理,各个模块间的数据处理是互补干扰的。从这个概念看使用
okf
n
是很合理的。
(
< br>2
)及时处理数据包技术
Qo
S
有个技术难点:将数据包入队,然后发送队列中合适的数据包,那么如何做到队列中的
数
据包被及时的发送出去呢?
linux
有一套机制来解决这个问题,下面先了解这套机制:
int
dev_queue_xmit(struct sk_buff *skb)
{ <
/p>
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。
。<
/p>
。
txq =
dev_pick_tx(dev, skb);
q =
rcu_dereference_bh(txq->qdisc);
获得设备上的发送
队列
。
。
。
。
。
。
。
p>
。
。
。
。
。
。
。
。
。
。
。
。
< br>。
。
。
。
。
。
。
if (q->enqueue) {
rc = __dev_xmit_skb(skb, q,
dev, txq);
goto
out;
数据包入队后,整个入队流程就结束了
}
}
以
上是入队过程,那么需要保证入队的数据包被及时的发送出去。
static inline int __dev_xmit_skb(struct
sk_buff *skb, struct Qdisc *q,
struct net_device *dev,
struct netdev_queue *txq)
{
spinlock_t *root_lock =
qdisc_lock(q);
int rc;
spin_lock(root_lock);
if (unlikely(test_bit(
__QDISC_STATE_DEACTIV
ATED
< br>, &q->state))) {
kfree_skb(skb);
如果这个队列是未运行的,那么释放这个数
据包
rc = NET_XMIT_DROP;
} else if ((q->flags &
TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&
p>
!test_and_set_bit(
__QDISC_STAT
E_RUNNING
, &q->state)) {
如果已经
有
CPU
在运行这
个队列,那么字节返
回,因为一个队列只能由一个
CPU
运行。
/*
*
This is a work-conserving queue; there are no old
skbs
* waiting to be sent out; and the qdisc
is not running -
* xmit the skb directly.
*/<
/p>
如果一个队列是位运行的,说明这个队列里面没有数据包,此时可以直接发送这个包
if
(!(dev->priv_flags & IFF_XMIT_DST_RELEASE))
skb_dst_force(skb);
__qdisc_update_bstats(q,
skb->len);
if
(
sch_direct_xmit
(skb, q,
dev, txq, root_lock))
试图直接发送数据包,
< br>如果没有发送成功,
或
linux-2.6.35
内核
IMQ
源码实现分析
2011-8-23
2 /
9
者队列中还有待发数据包,
返回值会大于
0
,那么,此时需要激活这个队列。
< br>__qdisc_run(q);
激活运行队列
else
cle
ar_bit(__QDISC_STATE_RUNNING
,
&q->state);
rc = NET_XMIT_SUCCESS;
} else {
skb_dst_force(skb);
rc =
qdisc_enqueue_root(skb, q);
qdisc_run(q);
}
spin_unlock(root_lock);
return rc;
}
上面看到了,发送队列被激活了,
只要队列中有数据包待发送,队列总是处于激活状态,那么
激活状态的队列是否能保证队
列中的数据包被及时的发送吗?接下来看一下,激活状态的队列的
特点。
void __qdisc_run(struct Qdisc *q)
{
unsigned long
start_time = jiffies;
while (qdisc_restart(q))
{
队列会一直发送数据包,
/*
* Postpone
processing if
* 1. another process needs the CPU;
*
2. we've been doing it for too long.
*/
if (need_resched() ||
jiffies != start_time) {
如果占用的
CPU
的时间太长了,
有其他的路径需要
占用
CPU
,此时暂时停止队列的发包。
< br>
__netif_schedule(q);
将队列加入发送
软中断
NET_TX_SOFTIRQ
的处理队列,
当软中
断被执行时,队列又会继续发送数据包。
break;
}
}
clear_bit(__QDISC_STATE_RUN
NING
, &q->state);
}
由于软中断被激活,软中断的优先
级仅次于硬中断,
这样就保证了队列会被及时的运行,
即保
p>
证了数据包会被及时的发送。
这是
linux
内核发送软中断的机制,
IMQ
就是利用了这个机制,不同点在于:正
常的发送队列是将数据包发送给网卡驱动,而
IMQ
队列是将数
据包发送给
okfn
函
数
。
linux-2.6.35
内核
IMQ
源码实现分析
2011-8-23
3 /
9
以上
2
个技术点就是
IMQ
的关键技术,
下面是
IMQ
的具体流程。
(
3
)
IMQ
设备数据包重新注入协议栈流程
IMQ
网络设备:
static const struct net_device_ops
imq_netdev_ops
= {
.ndo_open
= imq_open,
.ndo_stop
= imq_close,
.ndo_start_xmit
=
imq_dev_xmit
,
.ndo_get_stats
= imq_get_stats,
};
static netdev_tx_t
imq_dev_xmit
(struct sk_buff
*skb, struct net_device *dev)
-->
nf_reinject(entry,
NF_ACCEPT);
-->
struct
sk_buff *skb = entry->skb;
-->
entry->okfn(skb);
对
IMQ
网络设备来说,发送一个数据包,实际上就是将数据包重新注入到协议栈中。
(
4
p>
)
IMQ
截留数据包流程
< br>
那么协议栈如何将数据包注入到
IMQ
设备中:
static unsigned int
imq_nf_hook(unsigned int hook, struct sk_buff
*pskb,
const struct net_device
*indev,
const struct net_device
*outdev,
int (*okfn)(struct sk_buff
*))
{
return
(pskb->imq_flags & IMQ_F_ENQUEUE) ? NF_IMQ_QUEUE :
NF_ACCEPT;
}
static struct
nf_hook_ops
imq_ops
[] = {
{
/*
imq_ingress_ipv4 */
.hook
=
imq_nf_hook,
.owner
=
THIS_MODULE,
.pf
= PF_INET,
.hooknum
= NF_INET_PRE_ROUTING
,
#if
defined(CONFIG_IMQ_BEHA
VIOR_BA) ||
defined(CONFIG_IMQ_BEHA
VIOR_BB)
.priority =
NF_IP_PRI_MANGLE + 1,
#else
.priority =
NF_IP_PRI_NAT_DST + 1,
#endif
},
{
/* imq_egress_ipv4 */
.hook
= imq_nf_hook,
l
inux-2.6.35
内核
IMQ
源
码实现分析
2011-8-23
4 /
9
-
-
-
-
-
-
-
-
-
上一篇:华为OLT双上联保护方式
下一篇:S7706 配置帮助