Ethernet receive threads and loopback sends can enqueue packets at the
same time: they hold different locks, and the skb queue spinlocks are
no-ops on Hurd. Concurrent updates can corrupt the receive backlog,
causing packet loss and TCP timeouts.
Add backlog_lock to protect the queue and netdev_dropping in netif_rx(),
net_bh() and dev_clear_backlog(). Acquire it after the caller's locks and
release it before freeing packets or waking the worker. Check for an
empty queue and dequeue under the same lock.
* pfinet/pfinet.h (backlog_lock): Declare.
* pfinet/sched.c (backlog_lock): Define.
(net_bh_worker): Correct the locking comment.
* pfinet/linux-src/net/core/dev.c (netif_rx, net_bh, dev_clear_backlog):
Protect the receive backlog and drop state with backlog_lock.
* pfinet/loopback.c (loopback_xmit): Correct the locking comment.
---
pfinet/linux-src/net/core/dev.c | 52 +++++++++++++++++++++------------
pfinet/loopback.c | 5 ++--
pfinet/pfinet.h | 1 +
pfinet/sched.c | 19 ++++--------
4 files changed, 43 insertions(+), 34 deletions(-)
diff --git a/pfinet/linux-src/net/core/dev.c b/pfinet/linux-src/net/core/dev.c
index b47c502..ee59d92 100644
--- a/pfinet/linux-src/net/core/dev.c
+++ b/pfinet/linux-src/net/core/dev.c
@@ -718,7 +718,6 @@ static void netdev_wakeup(void)
static void dev_clear_backlog(struct device *dev)
{
struct sk_buff *curr;
- unsigned long flags;
/*
*
@@ -727,30 +726,35 @@ static void dev_clear_backlog(struct device *dev)
* We are competing here both with netif_rx() and net_bh().
* We don't want either of those to mess with skb ptrs
* while we work on them, thus we must grab the
- * skb_queue_lock.
+ * backlog_lock.
*/
+ pthread_mutex_lock(&backlog_lock);
if (backlog.qlen) {
repeat:
- spin_lock_irqsave(&skb_queue_lock, flags);
for (curr = backlog.next;
curr != (struct sk_buff *)(&backlog);
curr = curr->next)
if (curr->dev == dev)
{
__skb_unlink(curr, &backlog);
- spin_unlock_irqrestore(&skb_queue_lock, flags);
+ pthread_mutex_unlock(&backlog_lock);
kfree_skb(curr);
+ pthread_mutex_lock(&backlog_lock);
goto repeat;
}
- spin_unlock_irqrestore(&skb_queue_lock, flags);
#ifdef CONFIG_NET_HW_FLOWCONTROL
if (netdev_dropping)
+ {
+ pthread_mutex_unlock(&backlog_lock);
netdev_wakeup();
+ return;
+ }
#else
netdev_dropping = 0;
#endif
}
+ pthread_mutex_unlock(&backlog_lock);
}
/*
@@ -771,13 +775,17 @@ void netif_rx(struct sk_buff *skb)
short when CPU is congested, but is still operating.
*/
+ pthread_mutex_lock (&backlog_lock);
+
if (backlog.qlen <= netdev_max_backlog) {
if (backlog.qlen) {
if (netdev_dropping == 0) {
skb_queue_tail(&backlog,skb);
+ pthread_mutex_unlock (&backlog_lock);
mark_bh(NET_BH);
return;
}
+ pthread_mutex_unlock (&backlog_lock);
atomic_inc(&netdev_rx_dropped);
kfree_skb(skb);
return;
@@ -789,10 +797,12 @@ void netif_rx(struct sk_buff *skb)
netdev_dropping = 0;
#endif
skb_queue_tail(&backlog,skb);
+ pthread_mutex_unlock (&backlog_lock);
mark_bh(NET_BH);
return;
}
netdev_dropping = 1;
+ pthread_mutex_unlock (&backlog_lock);
atomic_inc(&netdev_rx_dropped);
kfree_skb(skb);
}
@@ -871,14 +881,10 @@ void net_bh(void)
*/
/*
- * While the queue is not empty..
- *
- * Note that the queue never shrinks due to
- * an interrupt, so we can do this test without
- * disabling interrupts.
+ * Drain the queue under backlog_lock.
*/
- while (!skb_queue_empty(&backlog))
+ for (;;)
{
struct sk_buff * skb;
@@ -889,9 +895,25 @@ void net_bh(void)
#endif
/*
- * We have a packet. Therefore the queue has shrunk
+ * Dequeue and reset the drop state under backlog_lock.
*/
+ pthread_mutex_lock (&backlog_lock);
skb = skb_dequeue(&backlog);
+ if (skb == NULL)
+ {
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+ int dropping = netdev_dropping;
+#else
+ netdev_dropping = 0;
+#endif
+ pthread_mutex_unlock (&backlog_lock);
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+ if (dropping)
+ netdev_wakeup();
+#endif
+ break;
+ }
+ pthread_mutex_unlock (&backlog_lock);
#ifndef _HURD_
#ifdef CONFIG_CPU_IS_SLOW
@@ -1027,12 +1049,6 @@ void net_bh(void)
start_busy = 0;
}
#endif
-#endif
-#ifdef CONFIG_NET_HW_FLOWCONTROL
- if (netdev_dropping)
- netdev_wakeup();
-#else
- netdev_dropping = 0;
#endif
NET_PROFILE_LEAVE(net_bh);
return;
diff --git a/pfinet/loopback.c b/pfinet/loopback.c
index e15e426..66c4b4f 100644
--- a/pfinet/loopback.c
+++ b/pfinet/loopback.c
@@ -73,9 +73,8 @@ static int loopback_xmit(struct sk_buff *skb, struct device
*dev)
#endif
/*
- * Calling netif_rx() requires locking net_bh_lock, which
- * has already been done since this function is called by
- * the net_bh worker thread.
+ * netif_rx() protects the backlog with its own lock. Callers
+ * may hold either global_lock or net_bh_lock.
*/
netif_rx(skb);
diff --git a/pfinet/pfinet.h b/pfinet/pfinet.h
index df8da52..2e9ce0e 100644
--- a/pfinet/pfinet.h
+++ b/pfinet/pfinet.h
@@ -38,6 +38,7 @@
extern pthread_mutex_t global_lock;
extern pthread_mutex_t net_bh_lock;
+extern pthread_mutex_t backlog_lock;
extern struct port_bucket *pfinet_bucket;
extern struct port_class *addrport_class;
diff --git a/pfinet/sched.c b/pfinet/sched.c
index 5a3dd3e..9e9f00c 100644
--- a/pfinet/sched.c
+++ b/pfinet/sched.c
@@ -26,6 +26,7 @@
pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t net_bh_lock = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t backlog_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t net_bh_wakeup = PTHREAD_COND_INITIALIZER;
int net_bh_raised = 0;
@@ -44,19 +45,11 @@ sock_wake_async (struct socket *sock, int how)
}
-/* This function is the "net_bh worker thread".
- The packet receiver thread calls net/core/dev.c::netif_rx with a packet;
- netif_rx either drops the packet, or enqueues it and wakes us up
- via mark_bh which is really condition_broadcast on net_bh_wakeup.
- The packet receiver thread holds net_bh_lock while calling netif_rx.
- We wake up and take global_lock, which locks out RPC service threads.
- We then also take net_bh_lock running net_bh.
- Thus, only this thread running net_bh locks out the packet receiver
- thread (which takes only net_bh_lock while calling netif_rx), so packets
- are quickly moved from the Mach port's message queue to the `backlog'
- queue, or dropped, without synchronizing with RPC service threads.
- (The RPC service threads lock out the running of net_bh, but not
- the queuing/dropping of packets in netif_rx.) */
+/* This is the net_bh worker thread. Packet receivers hold net_bh_lock;
+ RPC service threads hold global_lock. netif_rx protects the shared
+ backlog with backlog_lock, acquired after either caller's lock.
+ mark_bh wakes this thread, which waits with net_bh_lock and takes
+ global_lock before running net_bh. */
void *
net_bh_worker (void *arg)
{