Browse Source

net: extend net_device allocation to vmalloc()

Joby Poriyath provided a xen-netback patch to reduce the size of
xenvif structure as some netdev allocation could fail under
memory pressure/fragmentation.

This patch is handling the problem at the core level, allowing
any netdev structures to use vmalloc() if kmalloc() failed.

As vmalloc() adds overhead on a critical network path, add __GFP_REPEAT
to kzalloc() flags to do this fallback only when really needed.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: Joby Poriyath <joby.poriyath@citrix.com>
Cc: Ben Hutchings <bhutchings@solarflare.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Eric Dumazet 11 years ago
parent
commit
74d332c13b
4 changed files with 24 additions and 11 deletions
  1. 5 5
      Documentation/networking/netdevices.txt
  2. 1 0
      include/linux/netdevice.h
  3. 17 5
      net/core/dev.c
  4. 1 1
      net/core/net-sysfs.c

+ 5 - 5
Documentation/networking/netdevices.txt

@@ -10,12 +10,12 @@ network devices.
 struct net_device allocation rules
 struct net_device allocation rules
 ==================================
 ==================================
 Network device structures need to persist even after module is unloaded and
 Network device structures need to persist even after module is unloaded and
-must be allocated with kmalloc.  If device has registered successfully,
-it will be freed on last use by free_netdev.  This is required to handle the
-pathologic case cleanly (example: rmmod mydriver </sys/class/net/myeth/mtu )
+must be allocated with alloc_netdev_mqs() and friends.
+If device has registered successfully, it will be freed on last use
+by free_netdev(). This is required to handle the pathologic case cleanly
+(example: rmmod mydriver </sys/class/net/myeth/mtu )
 
 
-There are routines in net_init.c to handle the common cases of
-alloc_etherdev, alloc_netdev.  These reserve extra space for driver
+alloc_netdev_mqs()/alloc_netdev() reserve extra space for driver
 private data which gets freed when the network device is freed. If
 private data which gets freed when the network device is freed. If
 separately allocated data is attached to the network device
 separately allocated data is attached to the network device
 (netdev_priv(dev)) then it is up to the module exit handler to free that.
 (netdev_priv(dev)) then it is up to the module exit handler to free that.

+ 1 - 0
include/linux/netdevice.h

@@ -1800,6 +1800,7 @@ static inline void unregister_netdevice(struct net_device *dev)
 
 
 int netdev_refcnt_read(const struct net_device *dev);
 int netdev_refcnt_read(const struct net_device *dev);
 void free_netdev(struct net_device *dev);
 void free_netdev(struct net_device *dev);
+void netdev_freemem(struct net_device *dev);
 void synchronize_net(void);
 void synchronize_net(void);
 int init_dummy_netdev(struct net_device *dev);
 int init_dummy_netdev(struct net_device *dev);
 
 

+ 17 - 5
net/core/dev.c

@@ -6196,6 +6196,16 @@ void netdev_set_default_ethtool_ops(struct net_device *dev,
 }
 }
 EXPORT_SYMBOL_GPL(netdev_set_default_ethtool_ops);
 EXPORT_SYMBOL_GPL(netdev_set_default_ethtool_ops);
 
 
+void netdev_freemem(struct net_device *dev)
+{
+	char *addr = (char *)dev - dev->padded;
+
+	if (is_vmalloc_addr(addr))
+		vfree(addr);
+	else
+		kfree(addr);
+}
+
 /**
 /**
  *	alloc_netdev_mqs - allocate network device
  *	alloc_netdev_mqs - allocate network device
  *	@sizeof_priv:	size of private data to allocate space for
  *	@sizeof_priv:	size of private data to allocate space for
@@ -6239,7 +6249,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
 	/* ensure 32-byte alignment of whole construct */
 	/* ensure 32-byte alignment of whole construct */
 	alloc_size += NETDEV_ALIGN - 1;
 	alloc_size += NETDEV_ALIGN - 1;
 
 
-	p = kzalloc(alloc_size, GFP_KERNEL);
+	p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT);
+	if (!p)
+		p = vzalloc(alloc_size);
 	if (!p)
 	if (!p)
 		return NULL;
 		return NULL;
 
 
@@ -6248,7 +6260,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
 
 
 	dev->pcpu_refcnt = alloc_percpu(int);
 	dev->pcpu_refcnt = alloc_percpu(int);
 	if (!dev->pcpu_refcnt)
 	if (!dev->pcpu_refcnt)
-		goto free_p;
+		goto free_dev;
 
 
 	if (dev_addr_init(dev))
 	if (dev_addr_init(dev))
 		goto free_pcpu;
 		goto free_pcpu;
@@ -6301,8 +6313,8 @@ free_pcpu:
 	kfree(dev->_rx);
 	kfree(dev->_rx);
 #endif
 #endif
 
 
-free_p:
-	kfree(p);
+free_dev:
+	netdev_freemem(dev);
 	return NULL;
 	return NULL;
 }
 }
 EXPORT_SYMBOL(alloc_netdev_mqs);
 EXPORT_SYMBOL(alloc_netdev_mqs);
@@ -6339,7 +6351,7 @@ void free_netdev(struct net_device *dev)
 
 
 	/*  Compatibility with error handling in drivers */
 	/*  Compatibility with error handling in drivers */
 	if (dev->reg_state == NETREG_UNINITIALIZED) {
 	if (dev->reg_state == NETREG_UNINITIALIZED) {
-		kfree((char *)dev - dev->padded);
+		netdev_freemem(dev);
 		return;
 		return;
 	}
 	}
 
 

+ 1 - 1
net/core/net-sysfs.c

@@ -1263,7 +1263,7 @@ static void netdev_release(struct device *d)
 	BUG_ON(dev->reg_state != NETREG_RELEASED);
 	BUG_ON(dev->reg_state != NETREG_RELEASED);
 
 
 	kfree(dev->ifalias);
 	kfree(dev->ifalias);
-	kfree((char *)dev - dev->padded);
+	netdev_freemem(dev);
 }
 }
 
 
 static const void *net_namespace(struct device *d)
 static const void *net_namespace(struct device *d)