net: Introduce dev_get_by_index_rcu()
Eric Dumazet [Mon, 19 Oct 2009 19:18:49 +0000 (19:18 +0000)]
Some workloads hit dev_base_lock rwlock pretty hard.
We can use RCU lookups to avoid touching this rwlock.

netdevices are already freed after a RCU grace period, so this patch
adds no penalty at device dismantle time.

dev_ifname() converted to dev_get_by_index_rcu()

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

include/linux/netdevice.h
net/core/dev.c

index 656110a..ffc3106 100644 (file)
@@ -1139,6 +1139,7 @@ extern void               netdev_resync_ops(struct net_device *dev);
 extern int call_netdevice_notifiers(unsigned long val, struct net_device *dev);
 extern struct net_device       *dev_get_by_index(struct net *net, int ifindex);
 extern struct net_device       *__dev_get_by_index(struct net *net, int ifindex);
+extern struct net_device       *dev_get_by_index_rcu(struct net *net, int ifindex);
 extern int             dev_restart(struct net_device *dev);
 #ifdef CONFIG_NETPOLL_TRAP
 extern int             netpoll_trap(void);
index 09551cc..68a1bb6 100644 (file)
@@ -214,12 +214,15 @@ static int list_netdevice(struct net_device *dev)
        write_lock_bh(&dev_base_lock);
        list_add_tail(&dev->dev_list, &net->dev_base_head);
        hlist_add_head(&dev->name_hlist, dev_name_hash(net, dev->name));
-       hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex));
+       hlist_add_head_rcu(&dev->index_hlist,
+                          dev_index_hash(net, dev->ifindex));
        write_unlock_bh(&dev_base_lock);
        return 0;
 }
 
-/* Device list removal */
+/* Device list removal
+ * caller must respect a RCU grace period before freeing/reusing dev
+ */
 static void unlist_netdevice(struct net_device *dev)
 {
        ASSERT_RTNL();
@@ -228,7 +231,7 @@ static void unlist_netdevice(struct net_device *dev)
        write_lock_bh(&dev_base_lock);
        list_del(&dev->dev_list);
        hlist_del(&dev->name_hlist);
-       hlist_del(&dev->index_hlist);
+       hlist_del_rcu(&dev->index_hlist);
        write_unlock_bh(&dev_base_lock);
 }
 
@@ -646,6 +649,31 @@ struct net_device *__dev_get_by_index(struct net *net, int ifindex)
 }
 EXPORT_SYMBOL(__dev_get_by_index);
 
+/**
+ *     dev_get_by_index_rcu - find a device by its ifindex
+ *     @net: the applicable net namespace
+ *     @ifindex: index of device
+ *
+ *     Search for an interface by index. Returns %NULL if the device
+ *     is not found or a pointer to the device. The device has not
+ *     had its reference counter increased so the caller must be careful
+ *     about locking. The caller must hold RCU lock.
+ */
+
+struct net_device *dev_get_by_index_rcu(struct net *net, int ifindex)
+{
+       struct hlist_node *p;
+       struct net_device *dev;
+       struct hlist_head *head = dev_index_hash(net, ifindex);
+
+       hlist_for_each_entry_rcu(dev, p, head, index_hlist)
+               if (dev->ifindex == ifindex)
+                       return dev;
+
+       return NULL;
+}
+EXPORT_SYMBOL(dev_get_by_index_rcu);
+
 
 /**
  *     dev_get_by_index - find a device by its ifindex
@@ -662,11 +690,11 @@ struct net_device *dev_get_by_index(struct net *net, int ifindex)
 {
        struct net_device *dev;
 
-       read_lock(&dev_base_lock);
-       dev = __dev_get_by_index(net, ifindex);
+       rcu_read_lock();
+       dev = dev_get_by_index_rcu(net, ifindex);
        if (dev)
                dev_hold(dev);
-       read_unlock(&dev_base_lock);
+       rcu_read_unlock();
        return dev;
 }
 EXPORT_SYMBOL(dev_get_by_index);
@@ -2939,15 +2967,15 @@ static int dev_ifname(struct net *net, struct ifreq __user *arg)
        if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
                return -EFAULT;
 
-       read_lock(&dev_base_lock);
-       dev = __dev_get_by_index(net, ifr.ifr_ifindex);
+       rcu_read_lock();
+       dev = dev_get_by_index_rcu(net, ifr.ifr_ifindex);
        if (!dev) {
-               read_unlock(&dev_base_lock);
+               rcu_read_unlock();
                return -ENODEV;
        }
 
        strcpy(ifr.ifr_name, dev->name);
-       read_unlock(&dev_base_lock);
+       rcu_read_unlock();
 
        if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
                return -EFAULT;