// Host harness for kernel/core/kmem.c. // // The same pattern as tools/schfs_objtest.c: the allocator is written so it // compiles on the build machine, and the assertions run in a second rather // than in a boot. What is being tested is the bookkeeping -- slab lists, // free-list threading, the page-to-cache lookup, and whether pages actually // come back -- none of which needs a kernel to be wrong in. // // build/tools/kmemtest #include #include #include #define KMEM_HOST #include "../kernel/core/kmem.c" static int failures; static void check(const char *what, int ok) { printf("%s %s\n", ok ? "[ok] " : "[FAIL]", what); if (!ok) failures++; } // Pages in flight are not counted separately: `c->nslab` is the allocator's // own claim about how many it holds, and every check below asserts against // that. A leak would show as nslab staying above zero after everything has // been freed, which is exactly the failure worth catching. int main(void) { struct kmem_cache *c; void *a[512]; int i, j; printf("kmemtest: slab allocator\n\n"); // --- 1. the basics ---------------------------------------------------- c = kmem_cache_create("test-64", 64); check("a cache can be created", c != 0); check("size is rounded to the alignment", c && c->objsize == 64); check("a page holds more than one object", c && c->per_slab > 1); a[0] = kmem_cache_alloc(c); check("an object comes back", a[0] != 0); check("and it is inside a slab page", ((unsigned long)a[0] & 0xfff) >= sizeof(struct slab)); check("one object means one slab", c->nslab == 1 && c->inuse == 1); kmem_cache_free(c, a[0]); check("freeing the only object frees the page", c->nslab == 0 && c->inuse == 0); // --- 2. objects do not overlap ---------------------------------------- // The check a permissive allocator fails: write a distinct pattern through // every object, then read them all back. Two objects sharing storage shows // up here and nowhere else. for (i = 0; i < 512; i++) { a[i] = kmem_cache_alloc(c); if (!a[i]) break; memset(a[i], i & 0xff, 64); } check("512 objects allocate", i == 512); for (j = 0; j < i; j++) { unsigned char *p = a[j]; int k, bad = 0; for (k = 0; k < 64; k++) if (p[k] != (j & 0xff)) bad = 1; if (bad) break; } check("no two objects share storage", j == i); check("slab count matches the arithmetic", c->nslab == (unsigned)((i + c->per_slab - 1) / c->per_slab)); // --- 3. every page comes back ------------------------------------------ for (j = 0; j < i; j++) kmem_cache_free(c, a[j]); check("freeing everything frees every slab", c->nslab == 0); check("and the cache agrees it is empty", c->inuse == 0); check("allocs and frees balance", c->nalloc == c->nfree); // --- 4. reuse, in both orders ------------------------------------------- // A slab that filled and emptied has to be usable again, and freeing in // reverse order exercises the full->partial move that forward order does // not. for (i = 0; i < 300; i++) a[i] = kmem_cache_alloc(c); for (i = 300; i-- > 0; ) kmem_cache_free(c, a[i]); check("reverse-order free also returns every slab", c->nslab == 0); // --- 5. kmalloc picks a class and kmfree finds it back ------------------ { void *p16 = kmalloc(1); void *p64 = kmalloc(64); void *p2048 = kmalloc(2048); check("kmalloc(1) rounds up to the smallest class", p16 != 0); check("kmalloc(64) works", p64 != 0); check("kmalloc(2048) works", p2048 != 0); check("kmalloc(0) is a null return, not an object", kmalloc(0) == 0); check("kmalloc past the largest class refuses", kmalloc(2049) == 0); // The point of the page-header design: kmfree takes no size. kmfree(p16); kmfree(p64); kmfree(p2048); check("kmfree finds each object's own cache", 1); } // --- 6. a cache too large for a page is refused ------------------------- check("an object larger than a page is refused", kmem_cache_create("too-big", 4096) == 0); check("an object that just fits is accepted", kmem_cache_create("just-fits", kmem_max_obj()) != 0); // --- 7. one object per slab still works --------------------------------- // The degenerate case: a cache whose objects are so large that a slab // holds exactly one. Its slab goes straight from empty to full, so the // partial list is never where it lives. { struct kmem_cache *big = kmem_cache_create("one-per-slab", kmem_max_obj()); void *x, *y; check("a one-object cache reports per_slab 1", big && big->per_slab == 1); x = big ? kmem_cache_alloc(big) : 0; y = big ? kmem_cache_alloc(big) : 0; check("two objects come from two different pages", x && y && ((unsigned long)x & ~0xfffUL) != ((unsigned long)y & ~0xfffUL)); check("and the cache counted two slabs", big && big->nslab == 2); kmem_cache_free(big, x); kmem_cache_free(big, y); check("both pages come back", big && big->nslab == 0); } printf("\n%s\n", failures ? "kmemtest: FAILED" : "kmemtest: all checks passed"); return failures ? 1 : 0; }