From 09ddf29b02c5087e02f358c2e67e27d17d3a21a7 Mon Sep 17 00:00:00 2001
From: Richard Braun <rbraun@sceen.net>
Date: Wed, 29 Jun 2016 15:33:29 +0200
Subject: Fix locking error in the slab allocator

* kern/slab.c (kmem_slab_create): Set `slab->cache` member.
(kmem_cache_reap): Return dead slabs instead of destroying in place.
(slab_collect): Destroy slabs outside of critical section.
* kern/slab.h (struct kmem_slab): New `cache` member.
---
 kern/slab.c | 37 ++++++++++++++++++-------------------
 1 file changed, 18 insertions(+), 19 deletions(-)

(limited to 'kern/slab.c')

diff --git a/kern/slab.c b/kern/slab.c
index eeb94f85..43962e7e 100644
--- a/kern/slab.c
+++ b/kern/slab.c
@@ -495,6 +495,7 @@ static struct kmem_slab * kmem_slab_create(struct kmem_cache *cache,
         slab = (struct kmem_slab *)(slab_buf + cache->slab_size) - 1;
     }
 
+    slab->cache = cache;
     list_node_init(&slab->list_node);
     rbtree_node_init(&slab->tree_node);
     slab->nr_refs = 0;
@@ -925,29 +926,17 @@ static int kmem_cache_grow(struct kmem_cache *cache)
     return !empty;
 }
 
-static void kmem_cache_reap(struct kmem_cache *cache)
+static void kmem_cache_reap(struct kmem_cache *cache, struct list *dead_slabs)
 {
-    struct kmem_slab *slab;
-    struct list dead_slabs;
-    unsigned long nr_free_slabs;
-
     simple_lock(&cache->lock);
-    list_set_head(&dead_slabs, &cache->free_slabs);
+
+    list_concat(dead_slabs, &cache->free_slabs);
     list_init(&cache->free_slabs);
-    nr_free_slabs = cache->nr_free_slabs;
-    cache->nr_bufs -= cache->bufs_per_slab * nr_free_slabs;
-    cache->nr_slabs -= nr_free_slabs;
+    cache->nr_bufs -= cache->bufs_per_slab * cache->nr_free_slabs;
+    cache->nr_slabs -= cache->nr_free_slabs;
     cache->nr_free_slabs = 0;
-    simple_unlock(&cache->lock);
-
-    while (!list_empty(&dead_slabs)) {
-        slab = list_first_entry(&dead_slabs, struct kmem_slab, list_node);
-        list_remove(&slab->list_node);
-        kmem_slab_destroy(slab, cache);
-        nr_free_slabs--;
-    }
 
-    assert(nr_free_slabs == 0);
+    simple_unlock(&cache->lock);
 }
 
 /*
@@ -1286,18 +1275,28 @@ slab_free:
 void slab_collect(void)
 {
     struct kmem_cache *cache;
+    struct kmem_slab *slab;
+    struct list dead_slabs;
 
     if (elapsed_ticks <= (kmem_gc_last_tick + KMEM_GC_INTERVAL))
         return;
 
     kmem_gc_last_tick = elapsed_ticks;
 
+    list_init(&dead_slabs);
+
     simple_lock(&kmem_cache_list_lock);
 
     list_for_each_entry(&kmem_cache_list, cache, node)
-        kmem_cache_reap(cache);
+        kmem_cache_reap(cache, &dead_slabs);
 
     simple_unlock(&kmem_cache_list_lock);
+
+    while (!list_empty(&dead_slabs)) {
+        slab = list_first_entry(&dead_slabs, struct kmem_slab, list_node);
+        list_remove(&slab->list_node);
+        kmem_slab_destroy(slab, slab->cache);
+    }
 }
 
 void slab_bootstrap(void)
-- 
cgit v1.2.3