diff options
author | Jason Wang <jasowang@redhat.com> | 2012-10-31 19:46:02 +0000 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-11-01 11:14:09 -0400 |
commit | 96442e42429e5f268ab97a3586c7694a3acc55a7 (patch) | |
tree | be875826c9239f2586f81c602077c7fe94bd131a | |
parent | cde8b15f1aabe327038ee4e0e11dd6b798572f69 (diff) | |
download | blackbird-op-linux-96442e42429e5f268ab97a3586c7694a3acc55a7.tar.gz blackbird-op-linux-96442e42429e5f268ab97a3586c7694a3acc55a7.zip |
tuntap: choose the txq based on rxq
This patch implements a simple multiqueue flow steering policy - tx follows rx
for tun/tap. The idea is simple, it just choose the txq based on which rxq it
comes. The flow were identified through the rxhash of a skb, and the hash to
queue mapping were recorded in a hlist with an ageing timer to retire the
mapping. The mapping were created when tun receives packet from userspace, and
was quired in .ndo_select_queue().
I run co-current TCP_CRR test and didn't see any mapping manipulation helpers in
perf top, so the overhead could be negelected.
Signed-off-by: Jason Wang <jasowang@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/tun.c | 227 |
1 files changed, 224 insertions, 3 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 79b6f9ecc12c..9e287680cd2e 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -115,6 +115,8 @@ struct tap_filter { */ #define MAX_TAP_QUEUES 1024 +#define TUN_FLOW_EXPIRE (3 * HZ) + /* A tun_file connects an open character device to a tuntap netdevice. It * also contains all socket related strctures (except sock_fprog and tap_filter) * to serve as one transmit queue for tuntap device. The sock_fprog and @@ -138,6 +140,18 @@ struct tun_file { u16 queue_index; }; +struct tun_flow_entry { + struct hlist_node hash_link; + struct rcu_head rcu; + struct tun_struct *tun; + + u32 rxhash; + int queue_index; + unsigned long updated; +}; + +#define TUN_NUM_FLOW_ENTRIES 1024 + /* Since the socket were moved to tun_file, to preserve the behavior of persist * device, socket fileter, sndbuf and vnet header size were restore when the * file were attached to a persist device. @@ -163,8 +177,164 @@ struct tun_struct { #ifdef TUN_DEBUG int debug; #endif + spinlock_t lock; + struct kmem_cache *flow_cache; + struct hlist_head flows[TUN_NUM_FLOW_ENTRIES]; + struct timer_list flow_gc_timer; + unsigned long ageing_time; }; +static inline u32 tun_hashfn(u32 rxhash) +{ + return rxhash & 0x3ff; +} + +static struct tun_flow_entry *tun_flow_find(struct hlist_head *head, u32 rxhash) +{ + struct tun_flow_entry *e; + struct hlist_node *n; + + hlist_for_each_entry_rcu(e, n, head, hash_link) { + if (e->rxhash == rxhash) + return e; + } + return NULL; +} + +static struct tun_flow_entry *tun_flow_create(struct tun_struct *tun, + struct hlist_head *head, + u32 rxhash, u16 queue_index) +{ + struct tun_flow_entry *e = kmem_cache_alloc(tun->flow_cache, + GFP_ATOMIC); + if (e) { + tun_debug(KERN_INFO, tun, "create flow: hash %u index %u\n", + rxhash, queue_index); + e->updated = jiffies; + e->rxhash = rxhash; + e->queue_index = queue_index; + e->tun = tun; + hlist_add_head_rcu(&e->hash_link, head); + } + return e; +} + +static void tun_flow_free(struct rcu_head *head) +{ + struct tun_flow_entry *e + = container_of(head, struct tun_flow_entry, rcu); + kmem_cache_free(e->tun->flow_cache, e); +} + +static void tun_flow_delete(struct tun_struct *tun, struct tun_flow_entry *e) +{ + tun_debug(KERN_INFO, tun, "delete flow: hash %u index %u\n", + e->rxhash, e->queue_index); + hlist_del_rcu(&e->hash_link); + call_rcu(&e->rcu, tun_flow_free); +} + +static void tun_flow_flush(struct tun_struct *tun) +{ + int i; + + spin_lock_bh(&tun->lock); + for (i = 0; i < TUN_NUM_FLOW_ENTRIES; i++) { + struct tun_flow_entry *e; + struct hlist_node *h, *n; + + hlist_for_each_entry_safe(e, h, n, &tun->flows[i], hash_link) + tun_flow_delete(tun, e); + } + spin_unlock_bh(&tun->lock); +} + +static void tun_flow_delete_by_queue(struct tun_struct *tun, u16 queue_index) +{ + int i; + + spin_lock_bh(&tun->lock); + for (i = 0; i < TUN_NUM_FLOW_ENTRIES; i++) { + struct tun_flow_entry *e; + struct hlist_node *h, *n; + + hlist_for_each_entry_safe(e, h, n, &tun->flows[i], hash_link) { + if (e->queue_index == queue_index) + tun_flow_delete(tun, e); + } + } + spin_unlock_bh(&tun->lock); +} + +static void tun_flow_cleanup(unsigned long data) +{ + struct tun_struct *tun = (struct tun_struct *)data; + unsigned long delay = tun->ageing_time; + unsigned long next_timer = jiffies + delay; + unsigned long count = 0; + int i; + + tun_debug(KERN_INFO, tun, "tun_flow_cleanup\n"); + + spin_lock_bh(&tun->lock); + for (i = 0; i < TUN_NUM_FLOW_ENTRIES; i++) { + struct tun_flow_entry *e; + struct hlist_node *h, *n; + + hlist_for_each_entry_safe(e, h, n, &tun->flows[i], hash_link) { + unsigned long this_timer; + count++; + this_timer = e->updated + delay; + if (time_before_eq(this_timer, jiffies)) + tun_flow_delete(tun, e); + else if (time_before(this_timer, next_timer)) + next_timer = this_timer; + } + } + + if (count) + mod_timer(&tun->flow_gc_timer, round_jiffies_up(next_timer)); + spin_unlock_bh(&tun->lock); +} + +static void tun_flow_update(struct tun_struct *tun, struct sk_buff *skb, + u16 queue_index) +{ + struct hlist_head *head; + struct tun_flow_entry *e; + unsigned long delay = tun->ageing_time; + u32 rxhash = skb_get_rxhash(skb); + + if (!rxhash) + return; + else + head = &tun->flows[tun_hashfn(rxhash)]; + + rcu_read_lock(); + + if (tun->numqueues == 1) + goto unlock; + + e = tun_flow_find(head, rxhash); + if (likely(e)) { + /* TODO: keep queueing to old queue until it's empty? */ + e->queue_index = queue_index; + e->updated = jiffies; + } else { + spin_lock_bh(&tun->lock); + if (!tun_flow_find(head, rxhash)) + tun_flow_create(tun, head, rxhash, queue_index); + + if (!timer_pending(&tun->flow_gc_timer)) + mod_timer(&tun->flow_gc_timer, + round_jiffies_up(jiffies + delay)); + spin_unlock_bh(&tun->lock); + } + +unlock: + rcu_read_unlock(); +} + /* We try to identify a flow through its rxhash first. The reason that * we do not check rxq no. is becuase some cards(e.g 82599), chooses * the rxq based on the txq where the last packet of the flow comes. As @@ -175,6 +345,7 @@ struct tun_struct { static u16 tun_select_queue(struct net_device *dev, struct sk_buff *skb) { struct tun_struct *tun = netdev_priv(dev); + struct tun_flow_entry *e; u32 txq = 0; u32 numqueues = 0; @@ -183,8 +354,12 @@ static u16 tun_select_queue(struct net_device *dev, struct sk_buff *skb) txq = skb_get_rxhash(skb); if (txq) { - /* use multiply and shift instead of expensive divide */ - txq = ((u64)txq * numqueues) >> 32; + e = tun_flow_find(&tun->flows[tun_hashfn(txq)], txq); + if (e) + txq = e->queue_index; + else + /* use multiply and shift instead of expensive divide */ + txq = ((u64)txq * numqueues) >> 32; } else if (likely(skb_rx_queue_recorded(skb))) { txq = skb_get_rx_queue(skb); while (unlikely(txq >= numqueues)) @@ -234,6 +409,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean) sock_put(&tfile->sk); synchronize_net(); + tun_flow_delete_by_queue(tun, tun->numqueues + 1); /* Drop read queue */ skb_queue_purge(&tfile->sk.sk_receive_queue); tun_set_real_num_queues(tun); @@ -631,6 +807,37 @@ static const struct net_device_ops tap_netdev_ops = { #endif }; +static int tun_flow_init(struct tun_struct *tun) +{ + int i; + + tun->flow_cache = kmem_cache_create("tun_flow_cache", + sizeof(struct tun_flow_entry), 0, 0, + NULL); + if (!tun->flow_cache) + return -ENOMEM; + + for (i = 0; i < TUN_NUM_FLOW_ENTRIES; i++) + INIT_HLIST_HEAD(&tun->flows[i]); + + tun->ageing_time = TUN_FLOW_EXPIRE; + setup_timer(&tun->flow_gc_timer, tun_flow_cleanup, (unsigned long)tun); + mod_timer(&tun->flow_gc_timer, + round_jiffies_up(jiffies + tun->ageing_time)); + + return 0; +} + +static void tun_flow_uninit(struct tun_struct *tun) +{ + del_timer_sync(&tun->flow_gc_timer); + tun_flow_flush(tun); + + /* Wait for completion of call_rcu()'s */ + rcu_barrier(); + kmem_cache_destroy(tun->flow_cache); +} + /* Initialize net device. */ static void tun_net_init(struct net_device *dev) { @@ -973,6 +1180,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, tun->dev->stats.rx_packets++; tun->dev->stats.rx_bytes += len; + tun_flow_update(tun, skb, tfile->queue_index); return total_len; } @@ -1150,6 +1358,14 @@ out: return ret; } +static void tun_free_netdev(struct net_device *dev) +{ + struct tun_struct *tun = netdev_priv(dev); + + tun_flow_uninit(tun); + free_netdev(dev); +} + static void tun_setup(struct net_device *dev) { struct tun_struct *tun = netdev_priv(dev); @@ -1158,7 +1374,7 @@ static void tun_setup(struct net_device *dev) tun->group = INVALID_GID; dev->ethtool_ops = &tun_ethtool_ops; - dev->destructor = free_netdev; + dev->destructor = tun_free_netdev; } /* Trivial set of netlink ops to allow deleting tun or tap @@ -1381,10 +1597,15 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) tun->filter_attached = false; tun->sndbuf = tfile->socket.sk->sk_sndbuf; + spin_lock_init(&tun->lock); + security_tun_dev_post_create(&tfile->sk); tun_net_init(dev); + if (tun_flow_init(tun)) + goto err_free_dev; + dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST | TUN_USER_FEATURES; dev->features = dev->hw_features; |