diff options
author | Herbert Xu <herbert@gondor.apana.org.au> | 2006-06-20 02:57:59 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2006-06-20 02:57:59 -0400 |
commit | 48d83325b61043e3bbd24dd37b9fe433744cf330 (patch) | |
tree | 00dc0682be0f096676ac885152b4ae14c4137338 | |
parent | d6cc7f1a3b33c89c91b3dfce1ff053178893470e (diff) |
[NET]: Prevent multiple qdisc runs
Having two or more qdisc_run's contend against each other is bad because
it can induce packet reordering if the packets have to be requeued. It
appears that this is an unintended consequence of relinquinshing the queue
lock while transmitting. That in turn is needed for devices that spend a
lot of time in their transmit routine.
There are no advantages to be had as devices with queues are inherently
single-threaded (the loopback device is not but then it doesn't have a
queue).
Even if you were to add a queue to a parallel virtual device (e.g., bolt
a tbf filter in front of an ipip tunnel device), you would still want to
process the queue in sequence to ensure that the packets are ordered
correctly.
The solution here is to steal a bit from net_device to prevent this.
BTW, as qdisc_restart is no longer used by anyone as a module inside the
kernel (IIRC it used to with netif_wake_queue), I have not exported the
new __qdisc_run function.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/netdevice.h | 1 | ||||
-rw-r--r-- | include/net/pkt_sched.h | 7 | ||||
-rw-r--r-- | net/sched/sch_generic.c | 11 |
3 files changed, 14 insertions, 5 deletions
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index e432b743dda2..39919c882a25 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h | |||
@@ -233,6 +233,7 @@ enum netdev_state_t | |||
233 | __LINK_STATE_RX_SCHED, | 233 | __LINK_STATE_RX_SCHED, |
234 | __LINK_STATE_LINKWATCH_PENDING, | 234 | __LINK_STATE_LINKWATCH_PENDING, |
235 | __LINK_STATE_DORMANT, | 235 | __LINK_STATE_DORMANT, |
236 | __LINK_STATE_QDISC_RUNNING, | ||
236 | }; | 237 | }; |
237 | 238 | ||
238 | 239 | ||
diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h index b94d1ad92c4d..75b5b9333fc7 100644 --- a/include/net/pkt_sched.h +++ b/include/net/pkt_sched.h | |||
@@ -218,12 +218,13 @@ extern struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, | |||
218 | struct rtattr *tab); | 218 | struct rtattr *tab); |
219 | extern void qdisc_put_rtab(struct qdisc_rate_table *tab); | 219 | extern void qdisc_put_rtab(struct qdisc_rate_table *tab); |
220 | 220 | ||
221 | extern int qdisc_restart(struct net_device *dev); | 221 | extern void __qdisc_run(struct net_device *dev); |
222 | 222 | ||
223 | static inline void qdisc_run(struct net_device *dev) | 223 | static inline void qdisc_run(struct net_device *dev) |
224 | { | 224 | { |
225 | while (!netif_queue_stopped(dev) && qdisc_restart(dev) < 0) | 225 | if (!netif_queue_stopped(dev) && |
226 | /* NOTHING */; | 226 | !test_and_set_bit(__LINK_STATE_QDISC_RUNNING, &dev->state)) |
227 | __qdisc_run(dev); | ||
227 | } | 228 | } |
228 | 229 | ||
229 | extern int tc_classify(struct sk_buff *skb, struct tcf_proto *tp, | 230 | extern int tc_classify(struct sk_buff *skb, struct tcf_proto *tp, |
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index b1e4c5e20ac7..d7aca8ef524a 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c | |||
@@ -90,7 +90,7 @@ void qdisc_unlock_tree(struct net_device *dev) | |||
90 | NOTE: Called under dev->queue_lock with locally disabled BH. | 90 | NOTE: Called under dev->queue_lock with locally disabled BH. |
91 | */ | 91 | */ |
92 | 92 | ||
93 | int qdisc_restart(struct net_device *dev) | 93 | static inline int qdisc_restart(struct net_device *dev) |
94 | { | 94 | { |
95 | struct Qdisc *q = dev->qdisc; | 95 | struct Qdisc *q = dev->qdisc; |
96 | struct sk_buff *skb; | 96 | struct sk_buff *skb; |
@@ -179,6 +179,14 @@ requeue: | |||
179 | return q->q.qlen; | 179 | return q->q.qlen; |
180 | } | 180 | } |
181 | 181 | ||
182 | void __qdisc_run(struct net_device *dev) | ||
183 | { | ||
184 | while (qdisc_restart(dev) < 0 && !netif_queue_stopped(dev)) | ||
185 | /* NOTHING */; | ||
186 | |||
187 | clear_bit(__LINK_STATE_QDISC_RUNNING, &dev->state); | ||
188 | } | ||
189 | |||
182 | static void dev_watchdog(unsigned long arg) | 190 | static void dev_watchdog(unsigned long arg) |
183 | { | 191 | { |
184 | struct net_device *dev = (struct net_device *)arg; | 192 | struct net_device *dev = (struct net_device *)arg; |
@@ -620,6 +628,5 @@ EXPORT_SYMBOL(qdisc_create_dflt); | |||
620 | EXPORT_SYMBOL(qdisc_alloc); | 628 | EXPORT_SYMBOL(qdisc_alloc); |
621 | EXPORT_SYMBOL(qdisc_destroy); | 629 | EXPORT_SYMBOL(qdisc_destroy); |
622 | EXPORT_SYMBOL(qdisc_reset); | 630 | EXPORT_SYMBOL(qdisc_reset); |
623 | EXPORT_SYMBOL(qdisc_restart); | ||
624 | EXPORT_SYMBOL(qdisc_lock_tree); | 631 | EXPORT_SYMBOL(qdisc_lock_tree); |
625 | EXPORT_SYMBOL(qdisc_unlock_tree); | 632 | EXPORT_SYMBOL(qdisc_unlock_tree); |