Process test, it is based on the pool test but involves two pools, one representing...
authorNick Downing <nick@ndcode.org>
Thu, 14 Mar 2019 14:31:39 +0000 (01:31 +1100)
committerNick Downing <nick@ndcode.org>
Thu, 14 Mar 2019 14:31:39 +0000 (01:31 +1100)
17 files changed:
.gitignore
Makefile
core.c [new file with mode: 0644]
core.h [new file with mode: 0644]
n.sh
o.sh [new file with mode: 0755]
pool.c
pool.h
pool_test_gen.c
pool_test_run.c
process.c [new file with mode: 0644]
process.h [new file with mode: 0644]
process_test_gen.c [new file with mode: 0644]
process_test_run.c [new file with mode: 0644]
rassert.h [new file with mode: 0644]
swap.c [new file with mode: 0644]
swap.h [new file with mode: 0644]

index d254333..eff955e 100644 (file)
@@ -2,3 +2,6 @@
 /pool_test.txt
 /pool_test_gen
 /pool_test_run
+/process_test.txt
+/process_test_gen
+/process_test_run
index 51ab692..334dbb7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 CFLAGS=-g -Wall
 
-all: pool_test_gen pool_test_run
+all: pool_test_gen pool_test_run process_test_gen process_test_run
 
 pool_test_gen: pool_test_gen.o
 pool_test_gen.o: pool_test_gen.c
@@ -10,5 +10,15 @@ pool_test_run.o: pool_test_run.c pool.h
 
 pool.o: pool.c pool.h
 
+process_test_gen: process_test_gen.o
+process_test_gen.o: process_test_gen.c
+
+process_test_run: process_test_run.o process.o core.o swap.o pool.o
+process_test_run.o: process_test_run.c process.h core.h swap.h pool.h 
+
+process.o: process.c process.h core.h swap.h pool.h
+core.o: core.c core.h pool.h
+swap.o: swap.c swap.h core.h pool.h
+
 clean:
-       rm -f pool_test_gen pool_test_run *.o
+       rm -f pool_test_gen pool_test_run process_test_gen process_test_run *.o
diff --git a/core.c b/core.c
new file mode 100644 (file)
index 0000000..3b9b9c3
--- /dev/null
+++ b/core.c
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "core.h"
+#include "rassert.h"
+
+struct pool_head core_head;
+int *core_mem;
+
+void core_move(struct pool_item *item, int new_base) {
+  int base = item->base;
+  int size = item->limit - base;
+  printf(
+    "core_move [%d,%d) to [%d,%d)\n",
+    base,
+    base + size,
+    new_base,
+    new_base + size
+  );
+  assert(new_base < base || new_base >= base + size);
+  for (int i = 0; i < size; ++i) {
+    core_mem[new_base + i] = core_mem[base + i];
+    core_mem[base + i] = 0xaaaaaaaa;
+  }
+}
+
+void core_move_up(struct pool_item *item, int new_limit) {
+  int limit = item->limit;
+  assert(limit != new_limit);
+  int neg_size = item->base - limit;
+  printf(
+    "core_move_up [%d,%d) to [%d,%d)\n",
+    limit + neg_size,
+    limit,
+    new_limit + neg_size,
+    new_limit
+  );
+  assert(new_limit > limit || new_limit <= limit + neg_size);
+  for (int i = -1; i >= neg_size; --i) {
+    core_mem[new_limit + i] = core_mem[limit + i];
+    core_mem[limit + i] = 0xaaaaaaaa;
+  }
+}
+
+void core_init(int core, int spare) {
+  pool_init(&core_head, 0, core, spare, core_move, core_move_up);
+
+  core_mem = malloc(core * sizeof(int));
+  memset(core_mem, 0xaa, core * sizeof(int));
+}
diff --git a/core.h b/core.h
new file mode 100644 (file)
index 0000000..026b07e
--- /dev/null
+++ b/core.h
@@ -0,0 +1,11 @@
+#ifndef _CORE_H
+#define _CORE_H 1
+
+#include "pool.h"
+
+extern struct pool_head core_head;
+extern int *core_mem;
+
+void core_init(int core, int spare);
+
+#endif
diff --git a/n.sh b/n.sh
index d945c4e..2f9173a 100755 (executable)
--- a/n.sh
+++ b/n.sh
@@ -1,10 +1,10 @@
 #!/bin/sh
 
 echo "generate test script"
-./pool_test_gen 64 256 256 16 >pool_test.txt
+./pool_test_gen 64 240 256 16 >pool_test.txt
 
 echo "run non-moveable test"
-./pool_test_run 64 256 <pool_test.txt
+./pool_test_run 64 256 16 <pool_test.txt
 
 echo "run moveable test"
-./pool_test_run 64 256 1 <pool_test.txt
+./pool_test_run 64 256 16 1 <pool_test.txt
diff --git a/o.sh b/o.sh
new file mode 100755 (executable)
index 0000000..9871ee5
--- /dev/null
+++ b/o.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+echo "generate test script"
+./process_test_gen 16 224 1024 32 >process_test.txt
+
+echo "run test"
+./process_test_run 16 64 4 192 12 16 <process_test.txt
diff --git a/pool.c b/pool.c
index e7c2d6f..0ea8228 100644 (file)
--- a/pool.c
+++ b/pool.c
@@ -7,10 +7,26 @@
 #define TRY_BLOCKER 1
 #define TRY_REALLOC 1
 
+#if 1
+void check_invariants(struct pool_head *head) {
+  int avail;
+  struct pool_item *p;
+
+  avail = head->item.base - head->item.limit - head->spare;
+  for (p = head->item.next; p != &head->item; p = p->next) {
+    assert(p->prev->next == p && p->next->prev == p);
+    assert(p->base >= p->prev->limit && p->limit <= p->next->base);
+    avail += p->base - p->limit;
+  }
+  assert(avail == head->avail);
+}
+#endif
+
 void pool_init(
   struct pool_head *head,
   int base,
   int limit,
+  int spare,
   void (*move)(struct pool_item *item, int new_base),
   void (*move_up)(struct pool_item *item, int new_limit)
 ) {
@@ -18,9 +34,11 @@ void pool_init(
   head->item.next = &head->item;
   head->item.base = limit; // note: swapped
   head->item.limit = base; // note: swapped
-  head->avail = limit - base;
+  head->avail = limit - base - spare;
+  head->spare = spare;
   head->move = move;
   head->move_up = move_up;
+ check_invariants(head);
 }
  
 bool pool_alloc(
@@ -34,9 +52,15 @@ bool pool_alloc(
   // item must not be allocated yet
   assert(item->prev == NULL && item->next == NULL);
 
+  // check size, disregarding fragmentation
+  avail = head->avail - size;
+  if (avail < 0)
+    return false;
+  avail += head->spare;
+
   // first-fit algorithm
   q = &head->item;
-  for (avail = head->avail - size; avail >= 0; avail -= gap) {
+  do {
     p = q;
     q = p->next;
     gap = q->base - p->limit;
@@ -44,7 +68,8 @@ bool pool_alloc(
       goto found;
     if (q == &head->item)
       break;
-  }
+    avail -= gap;
+  } while (avail >= 0);
   return false;
 
 found:
@@ -59,12 +84,7 @@ found:
 
   // keep track of total allocation
   head->avail -= size;
-#ifndef NDEBUG
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
-#endif
+ check_invariants(head);
   return true;
 }
 
@@ -84,9 +104,15 @@ bool pool_realloc(
   if (item->base + size <= item->next->base)
     goto resize;
 
+  // check size, disregarding fragmentation
+  avail = head->avail - size_change;
+  if (avail < 0)
+    return false;
+  avail += head->spare;
+
   // first-fit algorithm, until our own list entry
   q = &head->item;
-  for (avail = head->avail - size_change; avail >= 0; avail -= gap) {
+  do {
     p = q;
     q = p->next;
     if (q == item)
@@ -95,7 +121,8 @@ bool pool_realloc(
     if (size <= gap)
       goto found;
     assert(q != &head->item);
-  }
+    avail -= gap;
+  } while (avail >= 0);
   return false;
 
 ourself:
@@ -103,18 +130,18 @@ ourself:
   // the first iteration (ours) is different because q != p->next,
   // this causes us to ignore our own entry in the gap calculation
   q = q->next;
-  while (true) {
+  goto loop_entry;
+  do {
+    p = q;
+    q = p->next;
+  loop_entry:
     gap = q->base - p->limit;
     if (size <= gap)
       goto found;
     if (q == &head->item)
       break;
     avail -= gap;
-    if (avail < 0)
-      break;
-    p = q;
-    q = p->next;
-  }
+  } while (avail >= 0);
   return false;
 
 found:
@@ -140,12 +167,7 @@ resize:
 
   // keep track of total allocation
   head->avail -= size_change;
-#ifndef NDEBUG
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
-#endif
+ check_invariants(head);
   return true;
 }
 
@@ -163,6 +185,7 @@ void pool_free(struct pool_head *head, struct pool_item *item) {
 
   // keep track of total allocation
   head->avail += item->limit - item->base;
+ check_invariants(head);
 }
 
 bool pool_alloc_moveable(
@@ -176,10 +199,13 @@ bool pool_alloc_moveable(
   // item must not be allocated yet
   assert(item->prev == NULL && item->next == NULL);
 
-  // first-fit algorithm
+  // check size, disregarding fragmentation
   avail = head->avail - size;
   if (avail < 0)
     return false;
+  avail += head->spare;
+
+  // first-fit algorithm
   q = &head->item;
   while (true) {
     p = q;
@@ -220,12 +246,7 @@ found:
 
   // keep track of total allocation
   head->avail -= size;
-#ifndef NDEBUG
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
-#endif
+ check_invariants(head);
   return true;
 }
 
@@ -254,10 +275,13 @@ bool pool_realloc_moveable(
     goto resize;
 #endif
 
-  // first-fit algorithm, until our own list entry
+  // check size, disregarding fragmentation
   avail = head->avail - size_change;
   if (avail < 0)
     return false;
+  avail += head->spare;
+  // first-fit algorithm, until our own list entry
   q = &head->item;
 
 #ifdef TRY_BLOCKER
@@ -275,10 +299,6 @@ blocker_test:
 #endif
 
   while (true) {
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
     p = q;
     q = p->next;
     if (q == item)
@@ -293,7 +313,7 @@ blocker_test:
 
 #ifdef TRY_BLOCKER
     // see if blocker fits in gap
-    if (blocker_size <= gap && blocker != q) {
+    if (blocker_size <= gap) {
       // if moveable realloc is used, then user must provide move routine
       assert(head->move);
       head->move(blocker, p->limit);
@@ -312,10 +332,6 @@ blocker_test:
 
       // calculate new blocker
       q = blocker;
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
       goto blocker_test;
     }
 #endif
@@ -346,22 +362,18 @@ ourself:
   // the first iteration (ours) is different because q != p->next,
   // this causes us to ignore our own entry in the gap calculation
   q = q->next;
-  while (true) {
for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
+  goto loop_entry;
 do {
+    p = q;
+    q = p->next;
+  loop_entry:
     gap = q->base - p->limit;
     if (size <= gap)
       goto found;
     if (q == &head->item)
       break;
     avail -= gap;
-    if (avail < 0)
-      break;
-    p = q;
-    q = p->next;
-  }
+  } while (avail >= 0);
 
   avail = avail_save;
   p = item->prev;
@@ -369,10 +381,6 @@ ourself:
 
   // calculate gap that would be left if we didn't move ourself,
   // if too large, move block after gap (ourself) down into gap
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
   gap = item->base - p->limit;
   avail -= gap;
   if (avail < 0) {
@@ -396,10 +404,6 @@ ourself:
   // soon want to resize again), compact the remainder down from top
   p = &head->item;
   while (true) {
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
     q = p;
     p = q->prev;
     if (p == item) {
@@ -482,11 +486,6 @@ resize:
 
   // keep track of total allocation
   head->avail -= size_change;
-#ifndef NDEBUG
- for (struct pool_item *r = head->item.next; r != &head->item; r = r->next) {
-  assert(r->prev->next == r && r->next->prev == r);
-  assert(r->base >= r->prev->limit && r->limit <= r->next->base);
- }
-#endif
+ check_invariants(head);
   return true;
 }
diff --git a/pool.h b/pool.h
index e2b505a..8c0e228 100644 (file)
--- a/pool.h
+++ b/pool.h
@@ -13,6 +13,7 @@ struct pool_item {
 struct pool_head {
   struct pool_item item;
   int avail;
+  int spare;
   void (*move)(struct pool_item *item, int new_base);
   void (*move_up)(struct pool_item *item, int new_limit);
 };
@@ -21,6 +22,7 @@ void pool_init(
   struct pool_head *head,
   int base,
   int limit,
+  int spare,
   void (*move)(struct pool_item *item, int new_base),
   void (*move_up)(struct pool_item *item, int new_limit)
 );
index 4f488e7..13e5a93 100644 (file)
@@ -1,16 +1,8 @@
-#include <assert.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-
-#  define rassert(expr)                                                        \
-  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({                    \
-      if (expr)                                                                \
-        ; /* empty */                                                  \
-      else                                                             \
-        __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);  \
-    }))
+#include "rassert.h"
 
 int rand_int(int n) {
   return (int)((long long)rand() * n / (RAND_MAX + 1LL));
index e712541..8105127 100644 (file)
@@ -1,21 +1,9 @@
-#include <assert.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "pool.h"
-
-#  define rassert(expr)                                                        \
-  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({                    \
-      if (expr)                                                                \
-        ; /* empty */                                                  \
-      else                                                             \
-        __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);  \
-    }))
-
-int rand_int(int n) {
-  return (int)((long long)rand() * n / (RAND_MAX + 1LL));
-}
+#include "rassert.h"
 
 int *mem;
 
@@ -29,7 +17,7 @@ void move(struct pool_item *item, int new_base) {
     new_base,
     new_base + size
   );
-  rassert(new_base < base || new_base >= base + size);
+  assert(new_base < base || new_base >= base + size);
   for (int i = 0; i < size; ++i) {
     mem[new_base + i] = mem[base + i];
     mem[base + i] = 0xaaaaaaaa;
@@ -47,7 +35,7 @@ void move_up(struct pool_item *item, int new_limit) {
     new_limit + neg_size,
     new_limit
   );
-  rassert(new_limit > limit || new_limit <= limit + neg_size);
+  assert(new_limit > limit || new_limit <= limit + neg_size);
   for (int i = -1; i >= neg_size; --i) {
     mem[new_limit + i] = mem[limit + i];
     mem[limit + i] = 0xaaaaaaaa;
@@ -55,16 +43,17 @@ void move_up(struct pool_item *item, int new_limit) {
 }
 
 int main(int argc, char **argv) {
-  if (argc < 3) {
-    printf("usage: %s n_items pool_size [moveable]\n", argv[0]);
+  if (argc < 4) {
+    printf("usage: %s n_items pool_size spare [moveable]\n", argv[0]);
     exit(EXIT_FAILURE);
   }
   int n_items = atoi(argv[1]);
   int pool_size = atoi(argv[2]);
-  bool moveable = argc >= 4 ? (bool)atoi(argv[3]) : false;
+  int spare = atoi(argv[3]);
+  bool moveable = argc >= 5 ? (bool)atoi(argv[4]) : false;
 
   struct pool_head head;
-  pool_init(&head, 0, pool_size, move, move_up);
+  pool_init(&head, 0, pool_size, spare, move, move_up);
 
   struct pool_item *items = malloc(n_items * sizeof(struct pool_item));
   rassert(items);
@@ -74,15 +63,7 @@ int main(int argc, char **argv) {
   memset(mem, 0xaa, pool_size * sizeof(int));
 
   while (true) {
-    int avail = pool_size;
-    for (struct pool_item *p = head.item.next; p != &head.item; p = p->next) {
-      rassert(p->prev->next == p && p->next->prev == p);
-      rassert(p->base >= p->prev->limit && p->limit <= p->next->base);
-      avail += p->base - p->limit;
-    }
- printf("avail %d\n", avail);
-    rassert(avail == head.avail);
-
+ //printf("avail %d\n", head.avail);
     char buf[256];
     switch (scanf("%s", buf)) {
     case -1:
@@ -98,7 +79,7 @@ int main(int argc, char **argv) {
       rassert(item >= 0 && item < n_items);
       rassert(size >= 0 && size < pool_size);
       rassert(success >= 0 && success < 2);
-      rassert(items[item].prev == NULL && items[item].next == NULL);
+      rassert(items[item].prev == NULL);
       bool result =
         moveable ?
           pool_alloc_moveable(&head, items + item, size) :
@@ -116,6 +97,7 @@ int main(int argc, char **argv) {
         if (!success) {
           printf("undo\n");
           pool_free(&head, items + item);
+          items[item].prev = NULL;
         }
         else {
           int base = items[item].base;
@@ -138,7 +120,7 @@ int main(int argc, char **argv) {
       rassert(old_size >= 0 && old_size < pool_size);
       rassert(size >= 0 && size < pool_size);
       rassert(success >= 0 && success < 2);
-      if (items[item].prev == NULL && items[item].next == NULL)
+      if (items[item].prev == NULL)
         printf(
           "realloc %d %d %d %d: not allocated, ignore\n",
           item,
@@ -197,17 +179,17 @@ int main(int argc, char **argv) {
       rassert(scanf("%d %d", &item, &old_size) == 2);
       rassert(item >= 0 && item < n_items);
       rassert(old_size >= 0 && old_size < pool_size);
-      if (items[item].prev == NULL && items[item].next == NULL)
+      if (items[item].prev == NULL)
         printf(
           "free %d %d: not allocated, ignore\n",
           item,
           old_size
         );
       else {
- printf("old region [%d,%d)\n", items[item].base, items[item].limit);
         int actual_old_size = items[item].limit - items[item].base;
         rassert(actual_old_size <= old_size);
         pool_free(&head, items + item);
+        items[item].prev = NULL;
         printf(
           "free %d %d(%d): ok\n",
           item,
@@ -215,6 +197,7 @@ int main(int argc, char **argv) {
           actual_old_size
         );
         int base = items[item].base;
+ printf("old region [%d,%d)\n", base, base + actual_old_size);
         for (int i = 0; i < actual_old_size; ++i) {
           long long hash = item * 17 + i * 29;
           hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
@@ -231,7 +214,7 @@ int main(int argc, char **argv) {
 done:
   printf("final state:\n");
   for (int i = 0; i < n_items; ++i) {
-    if (items[i].prev == NULL && items[i].next == NULL)
+    if (items[i].prev == NULL)
       printf("item %d: not allocated\n", i);
     else {
       int base = items[i].base;
diff --git a/process.c b/process.c
new file mode 100644 (file)
index 0000000..f18be80
--- /dev/null
+++ b/process.c
@@ -0,0 +1,313 @@
+#include <stdio.h> // temporary
+#include <stdlib.h>
+#include "core.h"
+#include "process.h"
+#include "rassert.h"
+#include "swap.h"
+
+struct process *processes;
+int n_processes;
+
+int process_avail, process_spare;
+
+struct process_item process_head_core, process_head_swap;
+
+#if 1
+static void check_invariants() {
+  int avail =
+    core_head.item.base -
+    core_head.item.limit -
+    core_head.spare +
+    swap_head.item.base -
+    swap_head.item.limit -
+    swap_head.spare -
+    process_spare;
+  for (int i = 0; i < n_processes; ++i)
+    if (processes[i].item.prev && processes[i].item.next) {
+      assert(
+        processes[i].size ==
+          (
+            processes[i].core_item.prev ?
+              processes[i].core_item.limit - processes[i].core_item.base :
+              0
+          ) +
+          (
+            processes[i].swap_item.prev ?
+              processes[i].swap_item.limit - processes[i].swap_item.base :
+              0
+          )
+      );
+      avail -= processes[i].size;
+    }
+  assert(avail == process_avail);
+
+  // in-core LRU list processes:
+  // must have core, can't have swap except the last
+  for (
+    struct process *p = (struct process *)process_head_core.next;
+    (struct process_item *)p != &process_head_core;
+    p = (struct process *)p->item.next
+  ) {
+    assert(p->item.prev->next == &p->item && p->item.next->prev == &p->item);
+    assert(p->core_item.prev);
+    assert(p->swap_item.prev == NULL || p->item.next == &process_head_core);
+  }
+
+  // in-swap LRU list processes:
+  // may have swap, can't have core
+  for (
+    struct process *p = (struct process *)process_head_swap.next;
+    (struct process_item *)p != &process_head_swap;
+    p = (struct process *)p->item.next
+  ) {
+    assert(p->item.prev->next == &p->item && p->item.next->prev == &p->item);
+    assert(p->core_item.prev == NULL);
+  }
+}
+#endif
+
+void process_init(int n, int spare) {
+  processes = calloc(n, sizeof(struct process));
+  rassert(processes);
+  n_processes = n;
+
+  process_avail = core_head.avail + swap_head.avail - spare;
+  process_spare = spare;
+
+  process_head_core.prev = &process_head_core;
+  process_head_core.next = &process_head_core;
+
+  process_head_swap.prev = &process_head_swap;
+  process_head_swap.next = &process_head_swap;
+ check_invariants();
+}
+
+static void do_swap_out(int swap_out) {
+  int size;
+  struct process *victim;
+
+  // while can still swap out, choose victim and swap some
+  for (; swap_out > 0; swap_out -= size) {
+    // choose victim and calculate how much can swap out,
+    // getting rid of victims that are completely swapped
+    goto loop_entry;
+    do {
+ printf("victimized %d\n", (int)(victim - processes));
+      // remove from tail of in-core LRU list
+      victim->item.prev->next = victim->item.next;
+      victim->item.next->prev = victim->item.prev;
+
+      // insert at head of in-swap LRU list
+      victim->item.prev = &process_head_swap;
+      victim->item.next = process_head_swap.next;
+      victim->item.prev->next = &victim->item;
+      victim->item.next->prev = &victim->item;
+
+      // remove from core pool for efficiency
+      // note: if zero length it's neither in core nor swap pool
+      pool_free(&core_head, &victim->core_item);
+      victim->core_item.prev = NULL;
+
+    loop_entry:
+      // there must be a victim, based on calculations above
+      victim = (struct process *)process_head_core.prev;
+      assert((struct process_item *)victim != &process_head_core);
+      size = victim->core_item.limit - victim->core_item.base;
+    } while (size == 0);
+
+    // if removed from swap pool for efficiency, put back in
+    if (victim->swap_item.prev == NULL)
+      rassert(pool_alloc_moveable(&swap_head, &victim->swap_item, 0));
+
+    // calculate amount to swap
+    if (size > swap_out)
+      size = swap_out;
+ printf("victim %d size %d\n", (int)(victim - processes), size);
+
+    // increase swap allocation
+    rassert(
+      pool_realloc_moveable(
+        &swap_head,
+        &victim->swap_item,
+        size + victim->swap_item.limit - victim->swap_item.base
+      )
+    );
+
+    // transfer data to swap
+    swap_push(
+      victim->core_item.limit,
+      victim->swap_item.limit - size,
+      size
+    );
+
+    // reduce core allocation
+    rassert(
+      pool_realloc_moveable(
+        &core_head,
+        &victim->core_item,
+        victim->core_item.limit - victim->core_item.base - size
+      )
+    );
+  }
+}
+
+bool process_alloc(struct process *process, int size) {
+  int swap_out;
+
+  // must not be already allocated
+  assert(process->item.prev == NULL && process->item.next == NULL);
+
+  // check size, disregarding fragmentation
+  if (process_avail < size)
+    return false;
+
+  // free up as much core as we need to
+  swap_out = size - core_head.avail;
+  assert(swap_out <= swap_head.avail);
+  do_swap_out(swap_out);
+
+  // allocate core, can't fail
+  rassert(pool_alloc_moveable(&core_head, &process->core_item, size));
+
+  // not allowed to be in swap
+  process->swap_item.prev = NULL;
+
+  // insert at head of in-core LRU list
+  process->item.prev = &process_head_core;
+  process->item.next = process_head_core.next;
+  process->item.prev->next = &process->item;
+  process->item.next->prev = &process->item;
+
+  // track total allocation
+  process->size = size;
+  process_avail -= size;
+ check_invariants();
+  return true;
+}
+
+bool process_realloc(struct process *process, int size) {
+  int swap_out;
+
+  // must be already allocated
+  assert(process->item.prev && process->item.next);
+
+  // must be fully in core
+  assert(process->swap_item.prev == NULL);
+
+  // check size, disregarding fragmentation
+  int size_change = size - process->size;
+  if (process_avail < size_change)
+    return false;
+
+  // free up as much core as we need to
+  swap_out = size_change - core_head.avail;
+  assert(swap_out <= swap_head.avail);
+  do_swap_out(swap_out);
+
+  // reallocate core, can't fail
+  rassert(pool_realloc_moveable(&core_head, &process->core_item, size));
+  process->size = size;
+
+  // track total allocation
+  process->size = size;
+  process_avail -= size_change;
+ check_invariants();
+  return true;
+}
+
+void process_run(struct process *process) {
+  int swap_in, swap_out;
+
+  // must be already allocated
+  assert(process->item.prev && process->item.next);
+
+  // remove from whichever LRU list it's in
+  process->item.prev->next = process->item.next;
+  process->item.next->prev = process->item.prev;
+
+  // insert at head of in-core LRU list
+  process->item.prev = &process_head_core;
+  process->item.next = process_head_core.next;
+  process->item.prev->next = &process->item;
+  process->item.next->prev = &process->item;
+  // if removed from core pool for efficiency, put back in
+  // note: this can only happen if it came from in-swap LRU list
+  if (process->core_item.prev == NULL)
+    rassert(pool_alloc_moveable(&core_head, &process->core_item, 0));
+
+  // if not in swap pool, then it must be fully in core
+  if (process->swap_item.prev == NULL)
+    return;
+
+  // while not fully in core, make space and bring some in
+  while (
+    (
+      swap_in =
+        process->size +
+        process->core_item.base -
+        process->core_item.limit
+    ) != 0
+  ) {
+    // free up as much core as we can, for this iteration
+    swap_out = swap_in - core_head.avail;
+    if (swap_out > swap_head.avail) {
+      swap_in += swap_head.avail - swap_out;
+      swap_out = swap_head.avail;
+    }
+    do_swap_out(swap_out);
+
+    // increase core allocation
+    rassert(
+      pool_realloc_moveable(
+        &core_head,
+        &process->core_item,
+        swap_in + process->core_item.limit - process->core_item.base
+      )
+    );
+
+    // transfer data to core
+    swap_pop(
+      process->swap_item.limit,
+      process->core_item.limit - swap_in,
+      swap_in
+    );
+
+    // reduce swap allocation
+    rassert(
+      pool_realloc_moveable(
+        &swap_head,
+        &process->swap_item,
+        process->swap_item.limit - process->swap_item.base - swap_in
+      )
+    );
+  }
+
+  // remove from swap pool for efficiency
+  pool_free(&swap_head, &process->swap_item);
+  process->swap_item.prev = NULL;
+ check_invariants();
+}
+
+void process_free(struct process *process) {
+  // must be already allocated
+  assert(process->item.prev && process->item.next);
+
+  // remove from whichever LRU list it's in
+  process->item.prev->next = process->item.next;
+  process->item.next->prev = process->item.prev;
+#ifndef NDEBUG
+  process->item.prev = NULL;
+  process->item.next = NULL;
+#endif
+
+  // free from core and/or swap pools if there
+  if (process->core_item.prev)
+    pool_free(&core_head, &process->core_item);
+  if (process->swap_item.prev)
+    pool_free(&swap_head, &process->swap_item);
+
+  // track total allocation
+  process_avail += process->size;
+ check_invariants();
+}
diff --git a/process.h b/process.h
new file mode 100644 (file)
index 0000000..6782aaa
--- /dev/null
+++ b/process.h
@@ -0,0 +1,29 @@
+#ifndef _PROCESS_H
+#define _PROCESS_H 1
+
+#include "pool.h"
+
+struct process_item {
+  struct process_item *prev;
+  struct process_item *next;
+};
+
+extern struct process {
+  struct process_item item; // must be first
+  struct pool_item core_item;
+  struct pool_item swap_item;
+  int size; // brk level
+} *processes;
+int n_processes;
+
+extern int process_avail, process_spare;
+
+extern struct process_item process_head_core, process_head_swap;
+
+void process_init(int n, int spare);
+bool process_alloc(struct process *process, int size);
+bool process_realloc(struct process *process, int size);
+void process_run(struct process *process);
+void process_free(struct process *process);
+#endif
diff --git a/process_test_gen.c b/process_test_gen.c
new file mode 100644 (file)
index 0000000..db8075b
--- /dev/null
@@ -0,0 +1,60 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "rassert.h"
+
+int rand_int(int n) {
+  return (int)((long long)rand() * n / (RAND_MAX + 1LL));
+}
+
+int main(int argc, char **argv) {
+  if (argc < 5) {
+    printf("usage: %s n_processes pool_size n_events process_size [seed]\n", argv[0]);
+    exit(EXIT_FAILURE);
+  }
+  int n_processes = atoi(argv[1]);
+  int pool_size = atoi(argv[2]);
+  int n_events = atoi(argv[3]);
+  int process_size = atoi(argv[4]);
+  int seed = argc >= 6 ? atoi(argv[5]) : 1;
+
+  int *processes = malloc(n_processes * sizeof(int));
+  rassert(processes);
+  memset(processes, -1, n_processes * sizeof(int));
+
+  srand(seed);
+  int pool_used = 0;
+  for (int i = 0; i < n_events; ++i) {
+    int process = rand_int(n_processes);
+    int old_size = processes[process];
+    if (old_size == -1) {
+      int size = rand_int(process_size);
+      bool success = pool_used + size <= pool_size;
+      printf("alloc %d %d %d\n", process, size, success);
+      if (success) {
+        processes[process] = size;
+        pool_used += size;
+      }
+    }
+    else if (rand_int(64)) {
+      printf("run %d\n", process);
+      if (rand_int(8) == 0) {
+        int size = rand_int(process_size);
+        bool success = pool_used + size - old_size <= pool_size;
+        printf("realloc %d %d %d %d\n", process, old_size, size, success);
+        if (success) {
+          processes[process] = size;
+          pool_used += size - old_size;
+        }
+      }
+    }
+    else {
+      printf("free %d %d\n", process, old_size);
+      processes[process] = -1;
+      pool_used -= old_size;
+    }
+  }
+
+  return 0;
+}
diff --git a/process_test_run.c b/process_test_run.c
new file mode 100644 (file)
index 0000000..847c488
--- /dev/null
@@ -0,0 +1,225 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "core.h"
+#include "process.h"
+#include "rassert.h"
+#include "swap.h"
+
+int main(int argc, char **argv) {
+  if (argc < 7) {
+    printf("usage: %s n_processes core_size core_spare swap_size swap_spare spare\n", argv[0]);
+    exit(EXIT_FAILURE);
+  }
+  int n_processes = atoi(argv[1]);
+  int core_size = atoi(argv[2]);
+  int core_spare = atoi(argv[3]);
+  int swap_size = atoi(argv[4]);
+  int swap_spare = atoi(argv[5]);
+  int spare = atoi(argv[6]);
+
+  core_init(core_size, core_spare);
+  swap_init(swap_size, swap_spare);
+  process_init(n_processes, spare);
+
+  while (true) {
+ //printf("avail %d %d %d\n", process_avail, core_head.avail, swap_head.avail);
+    char buf[256];
+    switch (scanf("%s", buf)) {
+    case -1:
+      goto done;
+    case 1:
+      break;
+    default:
+      rassert(false);
+    }
+    if (strcmp(buf, "alloc") == 0) {
+      int process, size, success;
+      rassert(scanf("%d %d %d", &process, &size, &success) == 3);
+      rassert(process >= 0 && process < n_processes);
+      rassert(size >= 0 && size < core_size);
+      rassert(success >= 0 && success < 2);
+      rassert(processes[process].item.prev == NULL);
+      bool result = process_alloc(processes + process, size);
+      printf(
+        "alloc %d %d %d: %s\n",
+        process,
+        size,
+        success,
+        result == (bool)success ?
+          "ok" :
+          result ? "succeeded, should fail" : "failed, should succeed"
+      );
+      if (result) {
+        if (!success) {
+          printf("undo\n");
+          process_free(processes + process);
+          processes[process].item.prev = NULL;
+        }
+        else {
+          int base = processes[process].core_item.base;
+ printf("new core [%d,%d)\n", base, base + size);
+ rassert(processes[process].core_item.limit == base + size); 
+          for (int i = 0; i < size; ++i) {
+            rassert(core_mem[base + i] == 0xaaaaaaaa);
+            long long hash = process * 17 + i * 29;
+            hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+            hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+            core_mem[base + i] = (int)hash;
+          }
+        }
+      }
+    }
+    else if (strcmp(buf, "realloc") == 0) {
+      int process, old_size, size, success;
+      rassert(scanf("%d %d %d %d", &process, &old_size, &size, &success) == 4);
+      rassert(process >= 0 && process < n_processes);
+      rassert(old_size >= 0 && old_size < core_size);
+      rassert(size >= 0 && size < core_size);
+      rassert(success >= 0 && success < 2);
+      if (processes[process].item.prev == NULL)
+        printf(
+          "realloc %d %d %d %d: not allocated, ignore\n",
+          process,
+          old_size,
+          size,
+          success
+        );
+      else {
+ printf("old core [%d,%d)\n", processes[process].core_item.base, processes[process].core_item.limit);
+        int actual_old_size = processes[process].size;
+        rassert(actual_old_size <= old_size);
+        bool result = process_realloc(processes + process, size);
+        printf(
+          "realloc %d %d(%d) %d %d: %s\n",
+          process,
+          old_size,
+          actual_old_size,
+          size,
+          success,
+          result == (bool)success ?
+            "ok" :
+            result ? "succeeded, should fail" : "failed, should succeed"
+        );
+        if (result) {
+          if (!success) {
+            printf("undo\n");
+            rassert(process_realloc(processes + process, actual_old_size));
+          }
+          else {
+            int base = processes[process].core_item.base;
+ printf("new core [%d,%d)\n", base, base + size);
+ rassert(processes[process].core_item.limit == base + size); 
+            for (int i = actual_old_size; i < size; ++i) {
+              rassert(core_mem[base + i] == 0xaaaaaaaa);
+              long long hash = process * 17 + i * 29;
+              hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+              hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+              core_mem[base + i] = (int)hash;
+            }
+            for (int i = size; i < actual_old_size; ++i) {
+              long long hash = process * 17 + i * 29;
+              hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+              hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+              rassert(core_mem[base + i] == (int)hash);
+              core_mem[base + i] = 0xaaaaaaaa;
+            }
+          }
+        }
+      }
+    }
+    else if (strcmp(buf, "run") == 0) {
+      int process;
+      rassert(scanf("%d", &process) == 1);
+      rassert(process >= 0 && process < n_processes);
+      if (processes[process].item.prev == NULL)
+        printf("run %d: not allocated, ignore\n", process);
+      else {
+        process_run(processes + process);
+        printf("run %d\n", process);
+      }
+    } 
+    else if (strcmp(buf, "free") == 0) {
+      int process, old_size;
+      rassert(scanf("%d %d", &process, &old_size) == 2);
+      rassert(process >= 0 && process < n_processes);
+      rassert(old_size >= 0 && old_size < core_size);
+      if (processes[process].item.prev == NULL)
+        printf(
+          "free %d %d: not allocated, ignore\n",
+          process,
+          old_size
+        );
+      else {
+        bool in_core = processes[process].core_item.prev != NULL;
+        bool in_swap = processes[process].swap_item.prev != NULL;
+        rassert(processes[process].size <= old_size);
+        process_free(processes + process);
+        processes[process].item.prev = NULL;
+        printf(
+          "free %d %d(%d): ok\n",
+          process,
+          old_size,
+          processes[process].size
+        );
+        int core_base = in_core ? processes[process].core_item.base : -1;
+        int core_size = in_core ? processes[process].core_item.limit - core_base : 0;
+        int swap_base = in_swap ? processes[process].swap_item.base : -1;
+ int swap_size = in_swap ? processes[process].swap_item.limit - swap_base : 0;
+ printf("old core [%d,%d) swap [%d,%d)\n", core_base, core_base + core_size, swap_base, swap_base + swap_size);
+        for (int i = 0; i < processes[process].size; ++i) {
+          long long hash = process * 17 + i * 29;
+          hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+          hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+          if (i < core_size) {
+            rassert(core_mem[core_base + i] == (int)hash);
+            core_mem[core_base + i] = 0xaaaaaaaa;
+          }
+          else {
+            rassert(swap_mem[swap_base + processes[process].size + ~i] == (int)hash);
+            swap_mem[swap_base + processes[process].size + ~i] = 0xaaaaaaaa;
+          }
+        }
+      }
+    }
+    else
+      rassert(false);
+  }
+
+done:
+  printf("final state:\n");
+  for (int i = 0; i < n_processes; ++i) {
+    if (processes[i].item.prev == NULL)
+      printf("process %d: not allocated\n", i);
+    else {
+      bool in_core = processes[i].core_item.prev != NULL;
+      int core_base = in_core ? processes[i].core_item.base : -1;
+      int core_size = in_core ? processes[i].core_item.limit - core_base : 0;
+      bool in_swap = processes[i].swap_item.prev != NULL;
+      int swap_base = in_swap ? processes[i].swap_item.base : -1;
+      int swap_size = in_swap ? processes[i].swap_item.limit - swap_base : 0;
+      printf("process %d: core [%d,%d) swap [%d,%d)\n", i, core_base, core_base + core_size, swap_base, swap_base + swap_size);
+      for (int j = 0; j < processes[i].size; ++j) {
+        long long hash = i * 17 + j * 29;
+        hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+        hash = (hash & 0xffffffffffffffffLL) + (hash >> 32);
+        if (j < core_size) {
+          rassert(core_mem[core_base + j] == (int)hash);
+          core_mem[core_base + j] = 0xaaaaaaaa;
+        }
+        else {
+          rassert(swap_mem[swap_base + processes[i].size + ~j] == (int)hash);
+          swap_mem[swap_base + processes[i].size + ~j] = 0xaaaaaaaa;
+        }
+      }
+    }
+  }
+
+  for (int i = 0; i < core_size; ++i)
+    rassert(core_mem[i] == 0xaaaaaaaa);
+  for (int i = 0; i < swap_size; ++i)
+    rassert(swap_mem[i] == 0xaaaaaaaa);
+
+  return 0;
+}
diff --git a/rassert.h b/rassert.h
new file mode 100644 (file)
index 0000000..39a41f4
--- /dev/null
+++ b/rassert.h
@@ -0,0 +1,14 @@
+#ifndef _RASSERT_H
+#define _RASSERT_H 1
+
+#include <assert.h>
+
+#  define rassert(expr)                                                        \
+  ((void) sizeof ((expr) ? 1 : 0), __extension__ ({                    \
+      if (expr)                                                                \
+        ; /* empty */                                                  \
+      else                                                             \
+        __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION);  \
+    }))
+
+#endif
diff --git a/swap.c b/swap.c
new file mode 100644 (file)
index 0000000..591e583
--- /dev/null
+++ b/swap.c
@@ -0,0 +1,70 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "core.h"
+#include "swap.h"
+#include "rassert.h"
+
+struct pool_head swap_head;
+int *swap_mem;
+
+static void swap_move(struct pool_item *item, int new_base) {
+  int base = item->base;
+  int size = item->limit - base;
+  printf(
+    "swap_move [%d,%d) to [%d,%d)\n",
+    base,
+    base + size,
+    new_base,
+    new_base + size
+  );
+  assert(new_base < base || new_base >= base + size);
+  for (int i = 0; i < size; ++i) {
+    swap_mem[new_base + i] = swap_mem[base + i];
+    swap_mem[base + i] = 0xaaaaaaaa;
+  }
+}
+
+static void swap_move_up(struct pool_item *item, int new_limit) {
+  int limit = item->limit;
+  assert(limit != new_limit);
+  int neg_size = item->base - limit;
+  printf(
+    "swap_move_up [%d,%d) to [%d,%d)\n",
+    limit + neg_size,
+    limit,
+    new_limit + neg_size,
+    new_limit
+  );
+  assert(new_limit > limit || new_limit <= limit + neg_size);
+  for (int i = -1; i >= neg_size; --i) {
+    swap_mem[new_limit + i] = swap_mem[limit + i];
+    swap_mem[limit + i] = 0xaaaaaaaa;
+  }
+}
+
+void swap_init(int swap, int spare) {
+  pool_init(&swap_head, 0, swap, spare, swap_move, swap_move_up);
+
+  swap_mem = malloc(swap * sizeof(int));
+  memset(swap_mem, 0xaa, swap * sizeof(int));
+}
+
+// read forward from swap_base and write backward from core_limit
+void swap_pop(int swap_limit, int core_base, int size) {
+ printf("swap_pop swap [%d,%d) to core [%d,%d)\n", swap_limit - size, swap_limit, core_base, core_base + size);
+  for (int i = 0; i < size; ++i) {
+    assert(core_mem[core_base + i] == 0xaaaaaaaa);
+    core_mem[core_base + i] = swap_mem[swap_limit + ~i];
+    swap_mem[swap_limit + ~i] = 0xaaaaaaaa;
+  }
+}
+
+void swap_push(int core_limit, int swap_base, int size) {
+ printf("swap_push core [%d,%d) to swap [%d,%d)\n", core_limit - size, core_limit, swap_base, swap_base + size);
+  for (int i = 0; i < size; ++i) {
+    assert(swap_mem[swap_base + i] == 0xaaaaaaaa);
+    swap_mem[swap_base + i] = core_mem[core_limit + ~i];
+    core_mem[core_limit + ~i] = 0xaaaaaaaa;
+  }
+}
diff --git a/swap.h b/swap.h
new file mode 100644 (file)
index 0000000..ffdb52d
--- /dev/null
+++ b/swap.h
@@ -0,0 +1,13 @@
+#ifndef _SWAP_H
+#define _SWAP_H 1
+
+#include "pool.h"
+
+extern struct pool_head swap_head;
+extern int *swap_mem;
+
+void swap_init(int swap, int spare);
+void swap_pop(int swap_limit, int core_base, int size);
+void swap_push(int core_limit, int swap_base, int size);
+
+#endif