From 835cc0c8477fdbc59e0217891d6f11061b1ac4e2 Mon Sep 17 00:00:00 2001 From: Don Mullis Date: Fri, 5 Mar 2010 13:43:15 -0800 Subject: lib: more scalable list_sort() XFS and UBIFS can pass long lists to list_sort(); this alternative implementation scales better, reaching ~3x performance gain when list length exceeds the L2 cache size. Stand-alone program timings were run on a Core 2 duo L1=32KB L2=4MB, gcc-4.4, with flags extracted from an Ubuntu kernel build. Object size is 581 bytes compared to 455 for Mark J. Roberts' code. Worst case for either implementation is a list length just over a power of two, and to roughly the same degree, so here are timing results for a range of 2^N+1 lengths. List elements were 16 bytes each including malloc overhead; initial order was random. time (msec) Tatham-Roberts | generic-Mullis-v2 loop_count length | | ratio 4000000 2 206 294 1.427 2000000 3 176 227 1.289 1000000 5 199 172 0.864 500000 9 235 178 0.757 250000 17 243 182 0.748 125000 33 261 196 0.750 62500 65 277 209 0.754 31250 129 292 219 0.75 15625 257 317 235 0.741 7812 513 340 252 0.741 3906 1025 362 267 0.737 1953 2049 388 283 0.729 ~ L1 size 976 4097 556 323 0.580 488 8193 678 361 0.532 244 16385 773 395 0.510 122 32769 844 418 0.495 61 65537 917 454 0.495 30 131073 1128 543 0.481 15 262145 2355 869 0.369 ~ L2 size 7 524289 5597 1714 0.306 3 1048577 6218 2022 0.325 Mark's code does not actually implement the usual or generic mergesort, but rather a variant from Simon Tatham described here: http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html Simon's algorithm performs O(log N) passes over the entire input list, doing merges of sublists that double in size on each pass. The generic algorithm instead merges pairs of equal length lists as early as possible, in recursive order. For either algorithm, the elements that extend the list beyond power-of-two length are a special case, handled as nearly as possible as a "rounding-up" to a full POT. Some intuition for the locality of reference implications of merge order may be gotten by watching this animation: http://www.sorting-algorithms.com/merge-sort Simon's algorithm requires only O(1) extra space rather than the generic algorithm's O(log N), but in my non-recursive implementation the actual O(log N) data is merely a vector of ~20 pointers, which I've put on the stack. Long-running list_sort() calls: If the list passed in may be long, or the client's cmp() callback function is slow, the client's cmp() may periodically invoke cond_resched() to voluntarily yield the CPU. All inner loops of list_sort() call back to cmp(). Stability of the sort: distinct elements that compare equal emerge from the sort in the same order as with Mark's code, for simple test cases. A boot-time test is provided to verify this and other correctness requirements. A kernel that uses drm.ko appears to run normally with this change; I have no suitable hardware to similarly test the use by UBIFS. [akpm@linux-foundation.org: style tweaks, fix comment, make list_sort_test __init] Signed-off-by: Don Mullis Cc: Dave Airlie Cc: Andi Kleen Cc: Dave Chinner Cc: Artem Bityutskiy Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 252 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 183 insertions(+), 69 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 19d11e0bb958..362c10f1653f 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -4,99 +4,213 @@ #include #include +#define MAX_LIST_LENGTH_BITS 20 + +/* + * Returns a list organized in an intermediate format suited + * to chaining of merge() calls: null-terminated, no reserved or + * sentinel head node, "prev" links not maintained. + */ +static struct list_head *merge(void *priv, + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b), + struct list_head *a, struct list_head *b) +{ + struct list_head head, *tail = &head; + + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(priv, a, b) <= 0) { + tail->next = a; + a = a->next; + } else { + tail->next = b; + b = b->next; + } + tail = tail->next; + } + tail->next = a?:b; + return head.next; +} + +/* + * Combine final list merge with restoration of standard doubly-linked + * list structure. This approach duplicates code from merge(), but + * runs faster than the tidier alternatives of either a separate final + * prev-link restoration pass, or maintaining the prev links + * throughout. + */ +static void merge_and_restore_back_links(void *priv, + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b), + struct list_head *head, + struct list_head *a, struct list_head *b) +{ + struct list_head *tail = head; + + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(priv, a, b) <= 0) { + tail->next = a; + a->prev = tail; + a = a->next; + } else { + tail->next = b; + b->prev = tail; + b = b->next; + } + tail = tail->next; + } + tail->next = a ? : b; + + do { + /* + * In worst cases this loop may run many iterations. + * Continue callbacks to the client even though no + * element comparison is needed, so the client's cmp() + * routine can invoke cond_resched() periodically. + */ + (*cmp)(priv, tail, tail); + + tail->next->prev = tail; + tail = tail->next; + } while (tail->next); + + tail->next = head; + head->prev = tail; +} + /** * list_sort - sort a list. * @priv: private data, passed to @cmp * @head: the list to sort * @cmp: the elements comparison function * - * This function has been implemented by Mark J Roberts . It - * implements "merge sort" which has O(nlog(n)) complexity. The list is sorted - * in ascending order. + * This function implements "merge sort" which has O(nlog(n)) complexity. + * The list is sorted in ascending order. * * The comparison function @cmp is supposed to return a negative value if @a is * less than @b, and a positive value if @a is greater than @b. If @a and @b * are equivalent, then it does not matter what this function returns. */ void list_sort(void *priv, struct list_head *head, - int (*cmp)(void *priv, struct list_head *a, - struct list_head *b)) + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b)) { - struct list_head *p, *q, *e, *list, *tail, *oldhead; - int insize, nmerges, psize, qsize, i; + struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists + -- last slot is a sentinel */ + int lev; /* index into part[] */ + int max_lev = 0; + struct list_head *list; if (list_empty(head)) return; + memset(part, 0, sizeof(part)); + + head->prev->next = NULL; list = head->next; - list_del(head); - insize = 1; - for (;;) { - p = oldhead = list; - list = tail = NULL; - nmerges = 0; - - while (p) { - nmerges++; - q = p; - psize = 0; - for (i = 0; i < insize; i++) { - psize++; - q = q->next == oldhead ? NULL : q->next; - if (!q) - break; - } - qsize = insize; - while (psize > 0 || (qsize > 0 && q)) { - if (!psize) { - e = q; - q = q->next; - qsize--; - if (q == oldhead) - q = NULL; - } else if (!qsize || !q) { - e = p; - p = p->next; - psize--; - if (p == oldhead) - p = NULL; - } else if (cmp(priv, p, q) <= 0) { - e = p; - p = p->next; - psize--; - if (p == oldhead) - p = NULL; - } else { - e = q; - q = q->next; - qsize--; - if (q == oldhead) - q = NULL; - } - if (tail) - tail->next = e; - else - list = e; - e->prev = tail; - tail = e; + while (list) { + struct list_head *cur = list; + list = list->next; + cur->next = NULL; + + for (lev = 0; part[lev]; lev++) { + cur = merge(priv, cmp, part[lev], cur); + part[lev] = NULL; + } + if (lev > max_lev) { + if (unlikely(lev >= ARRAY_SIZE(part)-1)) { + printk_once(KERN_DEBUG "list passed to" + " list_sort() too long for" + " efficiency\n"); + lev--; } - p = q; + max_lev = lev; } + part[lev] = cur; + } - tail->next = list; - list->prev = tail; + for (lev = 0; lev < max_lev; lev++) + if (part[lev]) + list = merge(priv, cmp, part[lev], list); - if (nmerges <= 1) - break; + merge_and_restore_back_links(priv, cmp, head, part[max_lev], list); +} +EXPORT_SYMBOL(list_sort); - insize *= 2; - } +#ifdef DEBUG_LIST_SORT +struct debug_el { + struct list_head l_h; + int value; + unsigned serial; +}; - head->next = list; - head->prev = list->prev; - list->prev->next = head; - list->prev = head; +static int cmp(void *priv, struct list_head *a, struct list_head *b) +{ + return container_of(a, struct debug_el, l_h)->value + - container_of(b, struct debug_el, l_h)->value; } -EXPORT_SYMBOL(list_sort); +/* + * The pattern of set bits in the list length determines which cases + * are hit in list_sort(). + */ +#define LIST_SORT_TEST_LENGTH (512+128+2) /* not including head */ + +static int __init list_sort_test(void) +{ + int i, r = 1, count; + struct list_head *head = kmalloc(sizeof(*head), GFP_KERNEL); + struct list_head *cur; + + printk(KERN_WARNING "testing list_sort()\n"); + + cur = head; + for (i = 0; i < LIST_SORT_TEST_LENGTH; i++) { + struct debug_el *el = kmalloc(sizeof(*el), GFP_KERNEL); + BUG_ON(!el); + /* force some equivalencies */ + el->value = (r = (r * 725861) % 6599) % (LIST_SORT_TEST_LENGTH/3); + el->serial = i; + + el->l_h.prev = cur; + cur->next = &el->l_h; + cur = cur->next; + } + head->prev = cur; + + list_sort(NULL, head, cmp); + + count = 1; + for (cur = head->next; cur->next != head; cur = cur->next) { + struct debug_el *el = container_of(cur, struct debug_el, l_h); + int cmp_result = cmp(NULL, cur, cur->next); + if (cur->next->prev != cur) { + printk(KERN_EMERG "list_sort() returned " + "a corrupted list!\n"); + return 1; + } else if (cmp_result > 0) { + printk(KERN_EMERG "list_sort() failed to sort!\n"); + return 1; + } else if (cmp_result == 0 && + el->serial >= container_of(cur->next, + struct debug_el, l_h)->serial) { + printk(KERN_EMERG "list_sort() failed to preserve order" + " of equivalent elements!\n"); + return 1; + } + kfree(cur->prev); + count++; + } + kfree(cur); + if (count != LIST_SORT_TEST_LENGTH) { + printk(KERN_EMERG "list_sort() returned list of" + "different length!\n"); + return 1; + } + return 0; +} +module_init(list_sort_test); +#endif -- cgit v1.2.2 From 02b12b7a28faa2e9ed5a361cd08ea576ab1f1509 Mon Sep 17 00:00:00 2001 From: Don Mullis Date: Fri, 5 Mar 2010 13:43:15 -0800 Subject: lib: revise list_sort() header comment Clarify and correct header comment of list_sort(). Signed-off-by: Don Mullis Cc: Dave Airlie Cc: Andi Kleen Cc: Dave Chinner Cc: Artem Bityutskiy Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 362c10f1653f..4b5cb794c38b 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -81,17 +81,18 @@ static void merge_and_restore_back_links(void *priv, } /** - * list_sort - sort a list. - * @priv: private data, passed to @cmp + * list_sort - sort a list + * @priv: private data, opaque to list_sort(), passed to @cmp * @head: the list to sort * @cmp: the elements comparison function * - * This function implements "merge sort" which has O(nlog(n)) complexity. - * The list is sorted in ascending order. + * This function implements "merge sort", which has O(nlog(n)) + * complexity. * - * The comparison function @cmp is supposed to return a negative value if @a is - * less than @b, and a positive value if @a is greater than @b. If @a and @b - * are equivalent, then it does not matter what this function returns. + * The comparison function @cmp must return a negative value if @a + * should sort before @b, and a positive value if @a should sort after + * @b. If @a and @b are equivalent, and their original relative + * ordering is to be preserved, @cmp must return 0. */ void list_sort(void *priv, struct list_head *head, int (*cmp)(void *priv, struct list_head *a, -- cgit v1.2.2 From f015ac3edd84ad72f88e08a4d83c56c360aae404 Mon Sep 17 00:00:00 2001 From: Don Mullis Date: Thu, 30 Sep 2010 15:15:32 -0700 Subject: lib/list_sort: do not pass bad pointers to cmp callback If the original list is a POT in length, the first callback from line 73 will pass a==b both pointing to the original list_head. This is dangerous because the 'list_sort()' user can use 'container_of()' and accesses the "containing" object, which does not necessary exist for the list head. So the user can access RAM which does not belong to him. If this is a write access, we can end up with memory corruption. Signed-off-by: Don Mullis Tested-by: Artem Bityutskiy Signed-off-by: Artem Bityutskiy Cc: Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 4b5cb794c38b..a7616fa3162e 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -70,7 +70,7 @@ static void merge_and_restore_back_links(void *priv, * element comparison is needed, so the client's cmp() * routine can invoke cond_resched() periodically. */ - (*cmp)(priv, tail, tail); + (*cmp)(priv, tail->next, tail->next); tail->next->prev = tail; tail = tail->next; -- cgit v1.2.2 From 6d411e6c01608cefb7b9ea6712110538a1432f9f Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Tue, 26 Oct 2010 14:23:05 -0700 Subject: lib/Kconfig.debug: add list_sort debugging switch While hunting a non-existing bug in 'list_sort()', I've improved the 'list_sort_test()' function which tests the 'list_sort()' library call. Although at the end I found a bug in my code, but not in 'list_sort()', I think my clean-ups and improvements are worth merging because they make the test function better. This patch: Make the self-tests selectable via Kconfig rather than by manual enabling of DEBUG_LIST_SORT. Signed-off-by: Artem Bityutskiy Cc: Don Mullis Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index a7616fa3162e..827794016bfb 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -141,7 +141,7 @@ void list_sort(void *priv, struct list_head *head, } EXPORT_SYMBOL(list_sort); -#ifdef DEBUG_LIST_SORT +#ifdef CONFIG_TEST_LIST_SORT struct debug_el { struct list_head l_h; int value; @@ -214,4 +214,4 @@ static int __init list_sort_test(void) return 0; } module_init(list_sort_test); -#endif +#endif /* CONFIG_TEST_LIST_SORT */ -- cgit v1.2.2 From bb2ab10fa693110cffa7087ffe2749d6e9a27d5f Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Tue, 26 Oct 2010 14:23:06 -0700 Subject: lib/list_sort: test: use more reasonable printk levels I do not see any reason to use KERN_WARN for normal messages and KERN_EMERG for error messages in the lib_sort testing routine. Let's use more reasonable KERN_NORM and KERN_ERR levels. Signed-off-by: Artem Bityutskiy Cc: Don Mullis Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 827794016bfb..679b3a060e7e 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -166,7 +166,7 @@ static int __init list_sort_test(void) struct list_head *head = kmalloc(sizeof(*head), GFP_KERNEL); struct list_head *cur; - printk(KERN_WARNING "testing list_sort()\n"); + printk(KERN_DEBUG "testing list_sort()\n"); cur = head; for (i = 0; i < LIST_SORT_TEST_LENGTH; i++) { @@ -189,17 +189,17 @@ static int __init list_sort_test(void) struct debug_el *el = container_of(cur, struct debug_el, l_h); int cmp_result = cmp(NULL, cur, cur->next); if (cur->next->prev != cur) { - printk(KERN_EMERG "list_sort() returned " - "a corrupted list!\n"); + printk(KERN_ERR "list_sort() returned " + "a corrupted list!\n"); return 1; } else if (cmp_result > 0) { - printk(KERN_EMERG "list_sort() failed to sort!\n"); + printk(KERN_ERR "list_sort() failed to sort!\n"); return 1; } else if (cmp_result == 0 && el->serial >= container_of(cur->next, struct debug_el, l_h)->serial) { - printk(KERN_EMERG "list_sort() failed to preserve order" - " of equivalent elements!\n"); + printk(KERN_ERR "list_sort() failed to preserve order " + "of equivalent elements!\n"); return 1; } kfree(cur->prev); @@ -207,8 +207,8 @@ static int __init list_sort_test(void) } kfree(cur); if (count != LIST_SORT_TEST_LENGTH) { - printk(KERN_EMERG "list_sort() returned list of" - "different length!\n"); + printk(KERN_ERR "list_sort() returned list of " + "different length!\n"); return 1; } return 0; -- cgit v1.2.2 From eeee9ebb54b76a33a13d2c926ffb018a4aea410f Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Tue, 26 Oct 2010 14:23:06 -0700 Subject: lib/list_sort: test: use generic random32 Instead of using own pseudo-random generator, use generic linux 'random32()' function. Presumably, this should improve test coverage. At the same time, do the following changes: o Use shorter macro name for test list length o Do not use strange 'l_h' name for 'struct list_head' element, use 'list', because it is traditional name and thus, makes the code more obvious and readable. Signed-off-by: Artem Bityutskiy Cc: Don Mullis Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 679b3a060e7e..8f3c24415ae5 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -142,42 +142,45 @@ void list_sort(void *priv, struct list_head *head, EXPORT_SYMBOL(list_sort); #ifdef CONFIG_TEST_LIST_SORT + +#include + struct debug_el { - struct list_head l_h; + struct list_head list; int value; unsigned serial; }; static int cmp(void *priv, struct list_head *a, struct list_head *b) { - return container_of(a, struct debug_el, l_h)->value - - container_of(b, struct debug_el, l_h)->value; + return container_of(a, struct debug_el, list)->value + - container_of(b, struct debug_el, list)->value; } /* * The pattern of set bits in the list length determines which cases * are hit in list_sort(). */ -#define LIST_SORT_TEST_LENGTH (512+128+2) /* not including head */ +#define TEST_LIST_LEN (512+128+2) /* not including head */ static int __init list_sort_test(void) { - int i, r = 1, count; + int i, count; struct list_head *head = kmalloc(sizeof(*head), GFP_KERNEL); struct list_head *cur; printk(KERN_DEBUG "testing list_sort()\n"); cur = head; - for (i = 0; i < LIST_SORT_TEST_LENGTH; i++) { + for (i = 0; i < TEST_LIST_LEN; i++) { struct debug_el *el = kmalloc(sizeof(*el), GFP_KERNEL); BUG_ON(!el); /* force some equivalencies */ - el->value = (r = (r * 725861) % 6599) % (LIST_SORT_TEST_LENGTH/3); + el->value = random32() % (TEST_LIST_LEN/3); el->serial = i; - el->l_h.prev = cur; - cur->next = &el->l_h; + el->list.prev = cur; + cur->next = &el->list; cur = cur->next; } head->prev = cur; @@ -186,7 +189,7 @@ static int __init list_sort_test(void) count = 1; for (cur = head->next; cur->next != head; cur = cur->next) { - struct debug_el *el = container_of(cur, struct debug_el, l_h); + struct debug_el *el = container_of(cur, struct debug_el, list); int cmp_result = cmp(NULL, cur, cur->next); if (cur->next->prev != cur) { printk(KERN_ERR "list_sort() returned " @@ -197,7 +200,7 @@ static int __init list_sort_test(void) return 1; } else if (cmp_result == 0 && el->serial >= container_of(cur->next, - struct debug_el, l_h)->serial) { + struct debug_el, list)->serial) { printk(KERN_ERR "list_sort() failed to preserve order " "of equivalent elements!\n"); return 1; @@ -206,7 +209,7 @@ static int __init list_sort_test(void) count++; } kfree(cur); - if (count != LIST_SORT_TEST_LENGTH) { + if (count != TEST_LIST_LEN) { printk(KERN_ERR "list_sort() returned list of " "different length!\n"); return 1; -- cgit v1.2.2 From f3dc0e384248ea6fda0987f909007fa9ab5fb51a Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Tue, 26 Oct 2010 14:23:07 -0700 Subject: lib/list_sort: test: improve errors handling The 'lib_sort()' test does not free memory if it fails, and it makes the kernel panic if it cannot allocate memory. This patch fixes the problem. This patch also changes several small things: o use 'list_add()' helper instead of adding manually o introduce temporary 'el1' variable to avoid ugly and unreadalbe "if" statement o make 'head' to be stack variable instead of 'kmalloc()'ed, which simplifies code a bit Overall, this patch is of clean-up type. Signed-off-by: Artem Bityutskiy Cc: Don Mullis Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 65 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 27 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 8f3c24415ae5..2b99ff80f4be 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -165,56 +165,67 @@ static int cmp(void *priv, struct list_head *a, struct list_head *b) static int __init list_sort_test(void) { - int i, count; - struct list_head *head = kmalloc(sizeof(*head), GFP_KERNEL); - struct list_head *cur; + int i, count = 1, err = -EINVAL; + struct debug_el *el; + struct list_head *cur, *tmp; + LIST_HEAD(head); printk(KERN_DEBUG "testing list_sort()\n"); - cur = head; for (i = 0; i < TEST_LIST_LEN; i++) { - struct debug_el *el = kmalloc(sizeof(*el), GFP_KERNEL); - BUG_ON(!el); + el = kmalloc(sizeof(*el), GFP_KERNEL); + if (!el) { + printk(KERN_ERR "cancel list_sort() testing - cannot " + "allocate memory\n"); + goto exit; + } /* force some equivalencies */ el->value = random32() % (TEST_LIST_LEN/3); el->serial = i; - - el->list.prev = cur; - cur->next = &el->list; - cur = cur->next; + list_add_tail(&el->list, &head); } - head->prev = cur; - list_sort(NULL, head, cmp); + list_sort(NULL, &head, cmp); + + for (cur = head.next; cur->next != &head; cur = cur->next) { + struct debug_el *el1; + int cmp_result; - count = 1; - for (cur = head->next; cur->next != head; cur = cur->next) { - struct debug_el *el = container_of(cur, struct debug_el, list); - int cmp_result = cmp(NULL, cur, cur->next); if (cur->next->prev != cur) { printk(KERN_ERR "list_sort() returned " "a corrupted list!\n"); - return 1; - } else if (cmp_result > 0) { + goto exit; + } + + cmp_result = cmp(NULL, cur, cur->next); + if (cmp_result > 0) { printk(KERN_ERR "list_sort() failed to sort!\n"); - return 1; - } else if (cmp_result == 0 && - el->serial >= container_of(cur->next, - struct debug_el, list)->serial) { + goto exit; + } + + el = container_of(cur, struct debug_el, list); + el1 = container_of(cur->next, struct debug_el, list); + if (cmp_result == 0 && el->serial >= el1->serial) { printk(KERN_ERR "list_sort() failed to preserve order " "of equivalent elements!\n"); - return 1; + goto exit; } - kfree(cur->prev); count++; } - kfree(cur); + if (count != TEST_LIST_LEN) { printk(KERN_ERR "list_sort() returned list of " "different length!\n"); - return 1; + goto exit; + } + + err = 0; +exit: + list_for_each_safe(cur, tmp, &head) { + list_del(cur); + kfree(container_of(cur, struct debug_el, list)); } - return 0; + return err; } module_init(list_sort_test); #endif /* CONFIG_TEST_LIST_SORT */ -- cgit v1.2.2 From 014afa943d44f0df8e65bc4bd071c67772277d93 Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Tue, 26 Oct 2010 14:23:08 -0700 Subject: lib/list_sort: test: unify test messages This patch unifies 'list_sort_test()' messages a bit and makes sure all of them start with the "list_sort_test:" prefix to make it obvious for users where the messages come from. Signed-off-by: Artem Bityutskiy Cc: Don Mullis Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 2b99ff80f4be..01aff9e80821 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -170,12 +170,12 @@ static int __init list_sort_test(void) struct list_head *cur, *tmp; LIST_HEAD(head); - printk(KERN_DEBUG "testing list_sort()\n"); + printk(KERN_DEBUG "list_sort_test: start testing list_sort()\n"); for (i = 0; i < TEST_LIST_LEN; i++) { el = kmalloc(sizeof(*el), GFP_KERNEL); if (!el) { - printk(KERN_ERR "cancel list_sort() testing - cannot " + printk(KERN_ERR "list_sort_test: error: cannot " "allocate memory\n"); goto exit; } @@ -192,30 +192,31 @@ static int __init list_sort_test(void) int cmp_result; if (cur->next->prev != cur) { - printk(KERN_ERR "list_sort() returned " - "a corrupted list!\n"); + printk(KERN_ERR "list_sort_test: error: list is " + "corrupted\n"); goto exit; } cmp_result = cmp(NULL, cur, cur->next); if (cmp_result > 0) { - printk(KERN_ERR "list_sort() failed to sort!\n"); + printk(KERN_ERR "list_sort_test: error: list is not " + "sorted\n"); goto exit; } el = container_of(cur, struct debug_el, list); el1 = container_of(cur->next, struct debug_el, list); if (cmp_result == 0 && el->serial >= el1->serial) { - printk(KERN_ERR "list_sort() failed to preserve order " - "of equivalent elements!\n"); + printk(KERN_ERR "list_sort_test: error: order of " + "equivalent elements not preserved\n"); goto exit; } count++; } if (count != TEST_LIST_LEN) { - printk(KERN_ERR "list_sort() returned list of " - "different length!\n"); + printk(KERN_ERR "list_sort_test: error: bad list length %d", + count); goto exit; } -- cgit v1.2.2 From 041b78f232bb87b2de8ca3fed50384bc7dc9c2de Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Tue, 26 Oct 2010 14:23:08 -0700 Subject: lib/list_sort: test: check element addresses Improve 'lib_sort()' test and check that: o 'cmp()' is called only for elements which were present in the original list, i.e., the 'a' and 'b' parameters are valid o the resulted (sorted) list consists onlly of the original elements o intdoruce "poison" fields to make sure data around 'struc list_head' field are not corrupted. Signed-off-by: Artem Bityutskiy Cc: Don Mullis Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- lib/list_sort.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 8 deletions(-) (limited to 'lib/list_sort.c') diff --git a/lib/list_sort.c b/lib/list_sort.c index 01aff9e80821..d7325c6b103f 100644 --- a/lib/list_sort.c +++ b/lib/list_sort.c @@ -145,23 +145,65 @@ EXPORT_SYMBOL(list_sort); #include +/* + * The pattern of set bits in the list length determines which cases + * are hit in list_sort(). + */ +#define TEST_LIST_LEN (512+128+2) /* not including head */ + +#define TEST_POISON1 0xDEADBEEF +#define TEST_POISON2 0xA324354C + struct debug_el { + unsigned int poison1; struct list_head list; + unsigned int poison2; int value; unsigned serial; }; -static int cmp(void *priv, struct list_head *a, struct list_head *b) +/* Array, containing pointers to all elements in the test list */ +static struct debug_el **elts __initdata; + +static int __init check(struct debug_el *ela, struct debug_el *elb) { - return container_of(a, struct debug_el, list)->value - - container_of(b, struct debug_el, list)->value; + if (ela->serial >= TEST_LIST_LEN) { + printk(KERN_ERR "list_sort_test: error: incorrect serial %d\n", + ela->serial); + return -EINVAL; + } + if (elb->serial >= TEST_LIST_LEN) { + printk(KERN_ERR "list_sort_test: error: incorrect serial %d\n", + elb->serial); + return -EINVAL; + } + if (elts[ela->serial] != ela || elts[elb->serial] != elb) { + printk(KERN_ERR "list_sort_test: error: phantom element\n"); + return -EINVAL; + } + if (ela->poison1 != TEST_POISON1 || ela->poison2 != TEST_POISON2) { + printk(KERN_ERR "list_sort_test: error: bad poison: %#x/%#x\n", + ela->poison1, ela->poison2); + return -EINVAL; + } + if (elb->poison1 != TEST_POISON1 || elb->poison2 != TEST_POISON2) { + printk(KERN_ERR "list_sort_test: error: bad poison: %#x/%#x\n", + elb->poison1, elb->poison2); + return -EINVAL; + } + return 0; } -/* - * The pattern of set bits in the list length determines which cases - * are hit in list_sort(). - */ -#define TEST_LIST_LEN (512+128+2) /* not including head */ +static int __init cmp(void *priv, struct list_head *a, struct list_head *b) +{ + struct debug_el *ela, *elb; + + ela = container_of(a, struct debug_el, list); + elb = container_of(b, struct debug_el, list); + + check(ela, elb); + return ela->value - elb->value; +} static int __init list_sort_test(void) { @@ -172,6 +214,13 @@ static int __init list_sort_test(void) printk(KERN_DEBUG "list_sort_test: start testing list_sort()\n"); + elts = kmalloc(sizeof(void *) * TEST_LIST_LEN, GFP_KERNEL); + if (!elts) { + printk(KERN_ERR "list_sort_test: error: cannot allocate " + "memory\n"); + goto exit; + } + for (i = 0; i < TEST_LIST_LEN; i++) { el = kmalloc(sizeof(*el), GFP_KERNEL); if (!el) { @@ -182,6 +231,9 @@ static int __init list_sort_test(void) /* force some equivalencies */ el->value = random32() % (TEST_LIST_LEN/3); el->serial = i; + el->poison1 = TEST_POISON1; + el->poison2 = TEST_POISON2; + elts[i] = el; list_add_tail(&el->list, &head); } @@ -211,6 +263,12 @@ static int __init list_sort_test(void) "equivalent elements not preserved\n"); goto exit; } + + if (check(el, el1)) { + printk(KERN_ERR "list_sort_test: error: element check " + "failed\n"); + goto exit; + } count++; } @@ -222,6 +280,7 @@ static int __init list_sort_test(void) err = 0; exit: + kfree(elts); list_for_each_safe(cur, tmp, &head) { list_del(cur); kfree(container_of(cur, struct debug_el, list)); -- cgit v1.2.2