---
 include/linux/slab.h |   58 ++++++++++++++++++++++++++++++++++++++++++
 mm/page_alloc.c      |   70 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 127 insertions(+), 1 deletion(-)

diff -r 0736904934de include/linux/slab.h
--- a/include/linux/slab.h	Mon May 05 09:26:27 2008 +1000
+++ b/include/linux/slab.h	Mon May 05 09:26:36 2008 +1000
@@ -281,4 +281,62 @@ ssize_t slabinfo_write(struct file *, co
 ssize_t slabinfo_write(struct file *, const char __user *, size_t, loff_t *);
 #endif
 
+/**
+ * alloc_goodsize_table - Allocate a table depending on amount of memory.
+ * @name: the name for the table, for printing.
+ * @type: the type of the table contents (for size and type safety)
+ * @min: the minimum amount of entries (at 1MB of kernel memory)
+ * @num: pointer to set to the number of entries: if non-zero, force this many.
+ * @relfunc: the relationship between memory and table size.
+ *
+ * Many places in the kernel have to choose a size to allocate a hash
+ * table of some arbitrary size.  This function is a single point for
+ * these allocations, and should contain all the heuristics for determining
+ * that size.
+ *
+ * @num usually points to a module parameter: if it's non-zero, it
+ * will force the hash table to be that size.  Otherwise it will be
+ * set to a power of 2, dependent on @min and the relfunc (usually
+ * this is int_sqrt) which operates on the kernel memory size in
+ * megabytes.
+ *
+ * The allocation is returned, or NULL.
+ */
+#define alloc_goodsize_table(name, type, min, num, relfunc) ({		\
+	unsigned long _s = relfunc(nr_kernel_pages>>(20-PAGE_SHIFT)); \
+	(type *)__alloc_goodsize_table(name,sizeof(type),(min),_s,false,num); \
+})
+
+/**
+ * free_goodsize_table - free a table allocated with alloc_goodsize_table
+ * @ptr: the pointer to the table
+ * @num: the number of entries
+ *
+ * This doesn't work for boot_alloc_goodsize_table().
+ */
+#define free_goodsize_table(table, num) \
+	__free_goodsize_table((table), sizeof(*(table)) * (num))
+
+/**
+ * boot_alloc_goodsize_table - Allocate a table at boot.
+ * @name: the name for the table, for printing.
+ * @type: the type of the table contents (for size and type safety)
+ * @min: the minimum amount of entries (at 1MB of kernel memory)
+ * @num: pointer to set to the number of entries: if non-zero, force this many.
+ * @relfunc: the relationship between memory and table size.
+ *
+ * Like alloc_goodsize_table, but uses alloc_bootmem().
+ */
+#define boot_alloc_goodsize_table(name, type, min, num, relfunc) ({	\
+	unsigned long _s = relfunc(nr_kernel_pages>>(20-PAGE_SHIFT)); \
+	(type *)__alloc_goodsize_table(name,sizeof(type),(min),_s,true,num); \
+})
+
+void *__alloc_goodsize_table(const char *tablename,
+			     size_t elemsize, unsigned long min,
+			     unsigned long approx,
+			     bool bootalloc,
+			     unsigned long *num);
+void __free_goodsize_table(void *ptr, size_t size);
+
 #endif	/* _LINUX_SLAB_H */
diff -r 0736904934de mm/page_alloc.c
--- a/mm/page_alloc.c	Mon May 05 09:26:27 2008 +1000
+++ b/mm/page_alloc.c	Mon May 05 09:26:36 2008 +1000
@@ -120,7 +120,7 @@ static char * const zone_names[MAX_NR_ZO
 
 int min_free_kbytes = 1024;
 
-unsigned long __meminitdata nr_kernel_pages;
+unsigned long nr_kernel_pages;
 unsigned long __meminitdata nr_all_pages;
 static unsigned long __meminitdata dma_reserve;
 
@@ -4409,6 +4409,74 @@ void *__init alloc_large_system_hash(con
 	return table;
 }
 
+void *__alloc_goodsize_table(const char *tablename,
+			     size_t elemsize, unsigned long min,
+			     unsigned long approx,
+			     bool bootalloc,
+			     unsigned long *num)
+{
+	unsigned long aim;
+	void *table;
+
+	BUG_ON(!min);
+	BUG_ON(!approx);
+
+	/* How much do they want? */
+	if (*num)
+		/* Override */
+		aim = *num;
+	else {
+		/* min == amount for 1 MB, so scale by that. */
+		aim = approx * min;
+		/* FIXME: We should never allocate more than, say, 1/2
+		 * the cache size, since that's going to suck however
+		 * much RAM we have. */
+	}
+
+again:
+	if (bootalloc)
+		table = alloc_bootmem(aim * elemsize);
+	else if (hashdist)
+		table = __vmalloc(aim * elemsize, GFP_KERNEL, PAGE_KERNEL);
+	else {
+		unsigned long pages;
+
+		/* We're aiming for big continuous allocations: adjust
+		 * aim accordingly. */
+		pages = DIV_ROUND_UP(aim * elemsize, PAGE_SIZE);
+		pages = roundup_pow_of_two(pages);
+		table = (void *)__get_free_pages(GFP_KERNEL, ilog2(pages));
+		aim = rounddown_pow_of_two(pages * PAGE_SIZE / elemsize);
+	}
+
+	/* If it failed and they didn't specify exact, try asking for less. */
+	if (!table && !*num && aim/2 >= min) {
+		aim /= 2;
+		goto again;
+	}
+
+	if (!*num)
+		*num = aim;
+
+	return table;
+}
+
+void __free_goodsize_table(void *table, size_t size)
+{
+	if (hashdist)
+		vfree(table);
+	else {
+		unsigned long pages;
+
+		/* How much did we actually allocate?  If they
+		 * specified the number, we won't have rounded it up,
+		 * otherwise this is a noop. */
+		pages = DIV_ROUND_UP(size, PAGE_SIZE);
+		pages = roundup_pow_of_two(pages);
+		free_pages((unsigned long)table, ilog2(pages));
+	}
+}
+
 #ifdef CONFIG_OUT_OF_LINE_PFN_TO_PAGE
 struct page *pfn_to_page(unsigned long pfn)
 {
