summaryrefslogtreecommitdiff
path: root/kern
diff options
context:
space:
mode:
authorHanjie Wu <hanjiew@andrew.cmu.edu>2023-04-18 02:21:10 -0400
committerHanjie Wu <hanjiew@andrew.cmu.edu>2023-04-18 02:21:10 -0400
commitc71294cdd3581c3738d24ffd65414f8644a1094d (patch)
treec86245d50fad9eee4163dbba169bd1839edbf88f /kern
initial commit
Diffstat (limited to 'kern')
-rw-r--r--kern/asm_instr.S22
-rw-r--r--kern/common.c472
-rw-r--r--kern/console.c212
-rw-r--r--kern/inc/asm_instr.h25
-rw-r--r--kern/inc/common.h228
-rw-r--r--kern/inc/console.h162
-rw-r--r--kern/inc/interrupt.h27
-rw-r--r--kern/inc/kbd.h30
-rw-r--r--kern/inc/loader.h46
-rw-r--r--kern/inc/mm.h55
-rw-r--r--kern/inc/paging.h155
-rw-r--r--kern/inc/sched.h361
-rw-r--r--kern/inc/sync.h102
-rw-r--r--kern/inc/timer.h24
-rw-r--r--kern/inc/usermem.h50
-rw-r--r--kern/interrupt.c502
-rw-r--r--kern/interrupt_asm.S134
-rw-r--r--kern/kbd.c218
-rw-r--r--kern/kernel.c109
-rw-r--r--kern/loader.c58
-rw-r--r--kern/malloc_wrappers.c70
-rw-r--r--kern/mm.c318
-rw-r--r--kern/paging.c76
-rw-r--r--kern/sched.c762
-rw-r--r--kern/sched_asm.S67
-rw-r--r--kern/sync.c78
-rw-r--r--kern/sync_asm.S75
-rw-r--r--kern/syscall_asm.S61
-rw-r--r--kern/syscall_io.c151
-rw-r--r--kern/syscall_memory.c137
-rw-r--r--kern/syscall_misc.c169
-rw-r--r--kern/syscall_process.c397
-rw-r--r--kern/syscall_thread.c274
-rw-r--r--kern/timer.c74
-rw-r--r--kern/usermem.c153
-rw-r--r--kern/usermem_asm.S35
36 files changed, 5889 insertions, 0 deletions
diff --git a/kern/asm_instr.S b/kern/asm_instr.S
new file mode 100644
index 0000000..e096302
--- /dev/null
+++ b/kern/asm_instr.S
@@ -0,0 +1,22 @@
+/** @file asm_instr.c
+ *
+ * @brief assembly stubs.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+.text
+
+.global invlpg
+.type invlpg, %function
+invlpg:
+ mov 0x4(%esp), %eax
+ invlpg (%eax)
+ ret
+
+.global hlt
+.type hlt, %function
+hlt:
+ hlt
+ ret /* keep it for other platforms that do not respond to hlt */
diff --git a/kern/common.c b/kern/common.c
new file mode 100644
index 0000000..93edb3d
--- /dev/null
+++ b/kern/common.c
@@ -0,0 +1,472 @@
+/** @file common.c
+ *
+ * @brief common data structures.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+#include <common.h>
+
+#include <string.h>
+
+void queue_insert_head(queue_t** queue, queue_t* t) {
+ if (*queue == NULL) {
+ t->next = t->prev = t;
+ } else {
+ t->next = *queue;
+ t->prev = (*queue)->prev;
+ (*queue)->prev->next = t;
+ (*queue)->prev = t;
+ }
+ *queue = t;
+}
+
+void queue_insert_tail(queue_t** queue, queue_t* t) {
+ if (*queue == NULL) {
+ t->next = t->prev = t;
+ *queue = t;
+ } else {
+ t->next = *queue;
+ t->prev = (*queue)->prev;
+ (*queue)->prev->next = t;
+ (*queue)->prev = t;
+ }
+}
+
+queue_t* queue_remove_head(queue_t** queue) {
+ queue_t* t = *queue;
+ if (t->next == t) {
+ *queue = NULL;
+ } else {
+ t->next->prev = t->prev;
+ t->prev->next = t->next;
+ *queue = t->next;
+ }
+ return t;
+}
+
+queue_t* queue_remove_tail(queue_t** queue) {
+ queue_t* t = (*queue)->prev;
+ if (t->next == t) {
+ *queue = NULL;
+ } else {
+ t->next->prev = t->prev;
+ t->prev->next = t->next;
+ }
+ return t;
+}
+
+void queue_detach(queue_t** queue, queue_t* t) {
+ if (*queue == t) {
+ if (t->next == t) {
+ *queue = NULL;
+ } else {
+ *queue = t->next;
+ }
+ }
+ t->next->prev = t->prev;
+ t->prev->next = t->next;
+}
+
+int heap_init(heap_t* heap) {
+ return vector_init(heap, sizeof(heap_node_t), INITIAL_HEAP_SIZE);
+}
+
+int heap_insert(heap_t* heap, heap_node_t* node) {
+ if (vector_push(heap, node) != 0) {
+ return -1;
+ }
+ int cur = vector_size(heap) - 1;
+ while (cur != 0) {
+ int parent = ((cur - 1) >> 1);
+ heap_node_t* cn = (heap_node_t*)vector_at(heap, cur);
+ heap_node_t* pn = (heap_node_t*)vector_at(heap, parent);
+ if (cn->key >= pn->key) {
+ break;
+ }
+ heap_node_t t = *pn;
+ *pn = *cn;
+ *cn = t;
+ cur = parent;
+ }
+ return 0;
+}
+
+heap_node_t* heap_peak(heap_t* heap) {
+ return (vector_size(heap) == 0 ? NULL : (heap_node_t*)vector_at(heap, 0));
+}
+
+void heap_pop(heap_t* heap) {
+ int size = vector_size(heap);
+ if (size == 0) {
+ return;
+ }
+ *(heap_node_t*)vector_at(heap, 0) =
+ *(heap_node_t*)vector_at(heap, size - 1);
+ vector_pop(heap);
+ size--;
+ int cur = 0;
+ while (1) {
+ int lc = cur * 2 + 1;
+ int rc = lc + 1;
+ int small = cur;
+ heap_node_t* cn = (heap_node_t*)vector_at(heap, cur);
+ heap_node_t *ln, *rn, *sn = cn;
+ if (lc < size) {
+ ln = (heap_node_t*)vector_at(heap, lc);
+ if (ln->key < sn->key) {
+ small = lc;
+ sn = ln;
+ }
+ }
+ if (rc < size) {
+ rn = (heap_node_t*)vector_at(heap, rc);
+ if (rn->key < sn->key) {
+ small = rc;
+ sn = rn;
+ }
+ }
+ if (small == cur) {
+ break;
+ }
+ if (small == lc) {
+ heap_node_t t = *ln;
+ *ln = *cn;
+ *cn = t;
+ cur = lc;
+ } else {
+ heap_node_t t = *rn;
+ *rn = *cn;
+ *cn = t;
+ cur = rc;
+ }
+ }
+}
+
+rb_t rb_nil = {
+ .color = RB_BLACK,
+ .parent = &rb_nil,
+ .left = &rb_nil,
+ .right = &rb_nil,
+};
+
+rb_t* rb_find(rb_t* root, int key) {
+ rb_t* p = root;
+ while (p != &rb_nil) {
+ if (key == p->key) {
+ return p;
+ }
+ p = (key > p->key) ? p->right : p->left;
+ }
+ return NULL;
+}
+
+/**
+ * @brief left rotate on a node
+ * @param root rbtree root
+ * @param node node
+ */
+static void rb_rotate_left(rb_t** root, rb_t* node) {
+ rb_t* r = node->right;
+ node->right = r->left;
+ if (r->left != &rb_nil) {
+ r->left->parent = node;
+ }
+ r->parent = node->parent;
+ if (node->parent == &rb_nil) {
+ *root = r;
+ } else if (node == node->parent->left) {
+ node->parent->left = r;
+ } else {
+ node->parent->right = r;
+ }
+ r->left = node;
+ node->parent = r;
+}
+
+/**
+ * @brief right rotate on a node
+ * @param root rbtree root
+ * @param node node
+ */
+static void rb_rotate_right(rb_t** root, rb_t* node) {
+ rb_t* l = node->left;
+ node->left = l->right;
+ if (l->right != &rb_nil) {
+ l->right->parent = node;
+ }
+ l->parent = node->parent;
+ if (node->parent == &rb_nil) {
+ *root = l;
+ } else if (node == node->parent->left) {
+ node->parent->left = l;
+ } else {
+ node->parent->right = l;
+ }
+ l->right = node;
+ node->parent = l;
+}
+
+/**
+ * @brief fixup after insertion
+ * @param root rbtree root
+ * @param node node
+ */
+static void rb_fixup(rb_t** root, rb_t* node) {
+ while (node->parent->color == RB_RED) {
+ rb_t* grand = node->parent->parent;
+ if (node->parent == grand->left) {
+ rb_t* uncle = grand->right;
+ if (uncle->color == RB_RED) {
+ node->parent->color = RB_BLACK;
+ uncle->color = RB_BLACK;
+ node->parent->parent->color = RB_RED;
+ node = node->parent->parent;
+ } else {
+ if (node == node->parent->right) {
+ node = node->parent;
+ rb_rotate_left(root, node);
+ }
+ node->parent->color = RB_BLACK;
+ grand->color = RB_RED;
+ rb_rotate_right(root, grand);
+ }
+ } else {
+ rb_t* uncle = grand->left;
+ if (uncle->color == RB_RED) {
+ node->parent->color = RB_BLACK;
+ uncle->color = RB_BLACK;
+ node->parent->parent->color = RB_RED;
+ node = node->parent->parent;
+ } else {
+ if (node == node->parent->left) {
+ node = node->parent;
+ rb_rotate_right(root, node);
+ }
+ node->parent->color = RB_BLACK;
+ grand->color = RB_RED;
+ rb_rotate_left(root, grand);
+ }
+ }
+ }
+ (*root)->color = RB_BLACK;
+}
+
+void rb_insert(rb_t** root, rb_t* node) {
+ rb_t *p = *root, *parent = &rb_nil;
+ while (p != &rb_nil) {
+ parent = p;
+ p = (node->key > p->key) ? p->right : p->left;
+ }
+ node->parent = parent;
+ if (parent == &rb_nil) {
+ *root = node;
+ } else if (node->key > parent->key) {
+ parent->right = node;
+ } else {
+ parent->left = node;
+ }
+ node->left = node->right = &rb_nil;
+ node->color = RB_RED;
+ rb_fixup(root, node);
+}
+
+/**
+ * @brief move v to u's position
+ * @param root rbtree root
+ * @param u u
+ * @param v v
+ */
+static void rb_transplant(rb_t** root, rb_t* u, rb_t* v) {
+ if (u->parent == &rb_nil) {
+ *root = v;
+ } else if (u == u->parent->left) {
+ u->parent->left = v;
+ } else {
+ u->parent->right = v;
+ }
+ v->parent = u->parent;
+}
+
+rb_t* rb_min(rb_t* root) {
+ while (root->left != &rb_nil) {
+ root = root->left;
+ }
+ return root;
+}
+
+rb_t* rb_next(rb_t* node) {
+ if (node->right != &rb_nil) {
+ return rb_min(node->right);
+ }
+ rb_t* parent = node->parent;
+ while (parent != &rb_nil && node == parent->right) {
+ node = parent;
+ parent = parent->parent;
+ }
+ return parent;
+}
+
+/**
+ * @brief fixup after deletion
+ * @param root rbtree root
+ * @param node node
+ */
+static void rb_delete_fixup(rb_t** root, rb_t* node) {
+ while (node != *root && node->color == RB_BLACK) {
+ if (node == node->parent->left) {
+ rb_t* r = node->parent->right;
+ if (r->color == RB_RED) {
+ r->color = RB_BLACK;
+ node->parent->color = RB_RED;
+ rb_rotate_left(root, node->parent);
+ r = node->parent->right;
+ }
+ if (r->left->color == RB_BLACK && r->right->color == RB_BLACK) {
+ r->color = RB_RED;
+ node = node->parent;
+ } else {
+ if (r->right->color == RB_BLACK) {
+ r->left->color = RB_BLACK;
+ r->color = RB_RED;
+ rb_rotate_right(root, r);
+ r = node->parent->right;
+ }
+ r->color = node->parent->color;
+ node->parent->color = RB_BLACK;
+ r->right->color = RB_BLACK;
+ rb_rotate_left(root, node->parent);
+ node = *root;
+ }
+ } else {
+ rb_t* l = node->parent->left;
+ if (l->color == RB_RED) {
+ l->color = RB_BLACK;
+ node->parent->color = RB_RED;
+ rb_rotate_right(root, node->parent);
+ l = node->parent->left;
+ }
+ if (l->right->color == RB_BLACK && l->left->color == RB_BLACK) {
+ l->color = RB_RED;
+ node = node->parent;
+ } else {
+ if (l->left->color == RB_BLACK) {
+ l->right->color = RB_BLACK;
+ l->color = RB_RED;
+ rb_rotate_left(root, l);
+ l = node->parent->left;
+ }
+ l->color = node->parent->color;
+ node->parent->color = RB_BLACK;
+ l->left->color = RB_BLACK;
+ rb_rotate_right(root, node->parent);
+ node = *root;
+ }
+ }
+ }
+ node->color = RB_BLACK;
+}
+
+void rb_delete(rb_t** root, rb_t* node) {
+ rb_t *x, *y = node;
+ int orig_color = y->color;
+ if (node->left == &rb_nil) {
+ x = node->right;
+ rb_transplant(root, node, x);
+ } else if (node->right == &rb_nil) {
+ x = node->left;
+ rb_transplant(root, node, x);
+ } else {
+ y = rb_min(node->right);
+ orig_color = y->color;
+ x = y->right;
+ if (y->parent == node) {
+ x->parent = y;
+ } else {
+ rb_transplant(root, y, y->right);
+ y->right = node->right;
+ y->right->parent = y;
+ }
+ rb_transplant(root, node, y);
+ y->left = node->left;
+ y->left->parent = y;
+ y->color = node->color;
+ }
+ if (orig_color == RB_BLACK) {
+ rb_delete_fixup(root, x);
+ }
+}
+
+/** do not shink small vectors as this do not bring big benefit */
+#define VECTOR_MINIMUM_SIZE 8
+/** don't shink too fast so we divide by 3 */
+#define VECTOR_SHINK_RATIO 3
+
+int vector_init(vector_t* v, int elem_size, int init_size) {
+ v->elem_size = elem_size;
+ v->size = init_size;
+ v->array = (char*)smalloc(init_size * elem_size);
+ v->current = 0;
+ return (v->array == NULL ? -1 : 0);
+}
+
+int vector_size(vector_t* v) {
+ return v->current;
+}
+
+int vector_push(vector_t* v, void* elem) {
+ if (v->current == v->size) {
+ char* result = (char*)smalloc(v->size * v->elem_size * 2);
+ if (result == NULL) {
+ return -1;
+ }
+ memcpy(result, v->array, v->current * v->elem_size);
+ sfree(v->array, v->size * v->elem_size);
+ v->size <<= 1;
+ v->array = result;
+ }
+ memcpy(v->array + v->current * v->elem_size, elem, v->elem_size);
+ v->current++;
+ return 0;
+}
+
+/**
+ * @brief check if a vector should shrink
+ * @param v vector
+ */
+static void vector_try_shink(vector_t* v) {
+ if (v->current < v->size / VECTOR_SHINK_RATIO) {
+ if ((v->size / 2) < VECTOR_MINIMUM_SIZE) {
+ return;
+ }
+ char* result = (char*)smalloc((v->size / 2) * v->elem_size);
+ if (result == NULL) {
+ return;
+ }
+ memcpy(result, v->array, v->current * v->elem_size);
+ sfree(v->array, v->size * v->elem_size);
+ v->size >>= 1;
+ v->array = result;
+ }
+}
+
+void vector_pop(vector_t* v) {
+ v->current--;
+ vector_try_shink(v);
+}
+
+void vector_remove(vector_t* v, int index) {
+ memmove(v->array + index * v->elem_size,
+ v->array + (index + 1) * v->elem_size,
+ (v->current - index - 1) * v->elem_size);
+ v->current--;
+ vector_try_shink(v);
+}
+
+void* vector_at(vector_t* v, int index) {
+ return (void*)(v->array + index * v->elem_size);
+}
+
+void vector_free(vector_t* v) {
+ sfree(v->array, v->size * v->elem_size);
+}
diff --git a/kern/console.c b/kern/console.c
new file mode 100644
index 0000000..2b25fcc
--- /dev/null
+++ b/kern/console.c
@@ -0,0 +1,212 @@
+/** @file console.c
+ *
+ * @brief Console driver implementation.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <x86/asm.h>
+#include <x86/video_defines.h>
+
+#include <console.h>
+
+/** how many bits in a x86 byte */
+#define NUM_OF_BITS_X86 8
+/** Mask bits for a 8-bit byte */
+#define BYTE_MASK_X86 0xFF
+
+/** index of the register to start displaying cursor */
+#define CRTC_CURSOR_START 10
+/** bit to start displaying cursor */
+#define CURSOR_ENABLE_BIT 0x20
+
+/**
+ * Someone is not satisfied with the type name char_t so we use this
+ * to reperesent a char on screen so that nobody will mistake it with 'char'
+ */
+typedef struct a_char_on_screen_s {
+ uint8_t ch; /** char */
+ uint8_t color; /** color */
+} a_char_on_screen_t;
+
+/** pointer to the viedo memory at B800h */
+a_char_on_screen_t (*console_mem)[CONSOLE_WIDTH] =
+ (a_char_on_screen_t(*)[CONSOLE_WIDTH])CONSOLE_MEM_BASE;
+
+/** the char used to produce blank */
+#define BLANK_CH ' '
+/** initial color of the cursor */
+#define DEFAULT_COLOR (FGND_WHITE | BGND_BLACK)
+
+/** x position of the cursor */
+static int cur_x = 0;
+/** y position of the cursor */
+static int cur_y = 0;
+/** current color of the cursor */
+static char cur_color = DEFAULT_COLOR;
+/** should the cursor be displayed? */
+static int cur_shown = true;
+
+/** mutex for console operations */
+mutex_t console_lock = MUTEX_INIT;
+
+/**
+ * @brief Move the cursor to a position. Position is checked before calling this
+ * function
+ * @param x x
+ * @param y y
+ */
+static void move_cursor(int x, int y) {
+ cur_x = x;
+ cur_y = y;
+ int pos = y * CONSOLE_WIDTH + x;
+ outb(CRTC_IDX_REG, CRTC_CURSOR_LSB_IDX);
+ outb(CRTC_DATA_REG, pos & BYTE_MASK_X86);
+ outb(CRTC_IDX_REG, CRTC_CURSOR_MSB_IDX);
+ outb(CRTC_DATA_REG, pos >> NUM_OF_BITS_X86);
+}
+
+/**
+ * @brief Scroll the console by one line and clean the last line
+ */
+static void scroll_page() {
+ /* move all but last line forward */
+ memmove(console_mem[0], console_mem[1],
+ sizeof(a_char_on_screen_t) * CONSOLE_WIDTH * (CONSOLE_HEIGHT - 1));
+ int i;
+ for (i = 0; i < CONSOLE_WIDTH; i++) {
+ console_mem[CONSOLE_HEIGHT - 1][i] =
+ (a_char_on_screen_t){BLANK_CH, cur_color};
+ }
+}
+
+int putbyte(char ch) {
+ if (ch == '\n') {
+ if (cur_y < CONSOLE_HEIGHT - 1) {
+ move_cursor(0, cur_y + 1);
+ } else {
+ scroll_page();
+ move_cursor(0, cur_y);
+ }
+ return ch;
+ }
+ if (ch == '\r') {
+ move_cursor(0, cur_y);
+ return ch;
+ }
+ if (ch == '\b') {
+ if (cur_x > 0) {
+ move_cursor(cur_x - 1, cur_y);
+
+ } else if (cur_y > 0) {
+ move_cursor(CONSOLE_WIDTH - 1, cur_y - 1);
+ }
+ console_mem[cur_y][cur_x] = (a_char_on_screen_t){BLANK_CH, cur_color};
+ return ch;
+ }
+ /* save the position where we should show the char */
+ int prev_x = cur_x, prev_y = cur_y;
+ if (cur_x < CONSOLE_WIDTH - 1) {
+ /* move forward */
+ move_cursor(cur_x + 1, cur_y);
+ } else if (cur_y < CONSOLE_HEIGHT - 1) {
+ /* go to next line */
+ move_cursor(0, cur_y + 1);
+ } else {
+ /* scroll and go to beginning of the line */
+ scroll_page();
+ move_cursor(0, cur_y);
+ prev_y -= 1;
+ }
+ console_mem[prev_y][prev_x] = (a_char_on_screen_t){ch, cur_color};
+ return ch;
+}
+
+void putbytes(const char* s, int len) {
+ if (s == NULL || len <= 0) {
+ return;
+ }
+ int i;
+ for (i = 0; i < len; i++) {
+ putbyte(s[i]);
+ }
+}
+
+void draw_char(int row, int col, int ch, int color) {
+ if (row >= CONSOLE_HEIGHT || row < 0) {
+ return;
+ }
+ if (col >= CONSOLE_WIDTH || col < 0) {
+ return;
+ }
+ if (color > BYTE_MASK_X86 || color < 0) {
+ return;
+ }
+ console_mem[row][col] = (a_char_on_screen_t){ch, color};
+}
+
+int set_term_color(int color) {
+ if (color > BYTE_MASK_X86 || color < 0) {
+ return -1;
+ }
+ cur_color = (char)color;
+ return 0;
+}
+
+void get_term_color(int* color) {
+ *color = cur_color;
+}
+
+int set_cursor(int row, int col) {
+ if (row >= CONSOLE_HEIGHT || row < 0) {
+ return -1;
+ }
+ if (col >= CONSOLE_WIDTH || col < 0) {
+ return -1;
+ }
+ move_cursor(col, row);
+ return 0;
+}
+
+void get_cursor(int* row, int* col) {
+ *row = cur_y;
+ *col = cur_x;
+}
+
+void hide_cursor(void) {
+ cur_shown = 0;
+ outb(CRTC_IDX_REG, CRTC_CURSOR_START);
+ outb(CRTC_DATA_REG, inb(CRTC_DATA_REG) | CURSOR_ENABLE_BIT);
+}
+
+void show_cursor(void) {
+ cur_shown = 1;
+ outb(CRTC_IDX_REG, CRTC_CURSOR_START);
+ outb(CRTC_DATA_REG, inb(CRTC_DATA_REG) & ~CURSOR_ENABLE_BIT);
+}
+
+void clear_console(void) {
+ move_cursor(0, 0);
+ int i, j;
+ for (i = 0; i < CONSOLE_HEIGHT - 1; i++) {
+ for (j = 0; j < CONSOLE_WIDTH - 1; j++) {
+ console_mem[i][j] = (a_char_on_screen_t){BLANK_CH, cur_color};
+ }
+ }
+}
+
+char get_char(int row, int col) {
+ if (row >= CONSOLE_HEIGHT || row < 0) {
+ return BLANK_CH;
+ }
+ if (col >= CONSOLE_WIDTH || col < 0) {
+ return BLANK_CH;
+ }
+ return console_mem[row][col].ch;
+}
diff --git a/kern/inc/asm_instr.h b/kern/inc/asm_instr.h
new file mode 100644
index 0000000..f323b81
--- /dev/null
+++ b/kern/inc/asm_instr.h
@@ -0,0 +1,25 @@
+/** @file asm_instr.h
+ *
+ * @brief assembly stubs.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _ASM_INSTR_H_
+#define _ASM_INSTR_H_
+
+#include <paging.h>
+
+/**
+ * @brief runs invlpg on an address
+ * @param va addr to invalidate
+ */
+void invlpg(va_t va);
+
+/**
+ * @brief runs hlt
+ */
+void hlt();
+
+#endif
diff --git a/kern/inc/common.h b/kern/inc/common.h
new file mode 100644
index 0000000..dad473b
--- /dev/null
+++ b/kern/inc/common.h
@@ -0,0 +1,228 @@
+/** @file common.h
+ *
+ * @brief common data structure
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <stdlib.h>
+
+/**
+ * a queue node
+ */
+typedef struct queue_s {
+ struct queue_s* next;
+ struct queue_s* prev;
+} queue_t;
+
+/**
+ * @brief insert a node to the head of a queue
+ * @param queue queue
+ * @param t the node
+ */
+void queue_insert_head(queue_t** queue, queue_t* t);
+
+/**
+ * @brief insert a node to the tail of a queue
+ * @param queue queue
+ * @param t the node
+ */
+void queue_insert_tail(queue_t** queue, queue_t* t);
+
+/**
+ * @brief remove a node from the head of a queue
+ * @param queue queue
+ * @return the node removed
+ */
+queue_t* queue_remove_head(queue_t** queue);
+
+/**
+ * @brief remove a node from the tail of a queue
+ * @param queue queue
+ * @return the node removed
+ */
+queue_t* queue_remove_tail(queue_t** queue);
+
+/**
+ * @brief detach a node from the a queue
+ * @param queue queue
+ * @param t the node to be detached
+ */
+void queue_detach(queue_t** queue, queue_t* t);
+
+/** get the enclose object of a queue node */
+#define queue_data(queue, type, member) \
+ (type*)((char*)queue - offsetof(type, member))
+
+/**
+ * a rbtree node
+ */
+typedef struct rb_s {
+ int key;
+ int color;
+ struct rb_s* parent;
+ struct rb_s* left;
+ struct rb_s* right;
+} rb_t;
+
+/** this color can have childs of any color */
+#define RB_BLACK 0
+/** this color can only have childs of the color mentioned above */
+#define RB_RED 1
+
+/** used to represent NULL in rbtrees */
+extern rb_t rb_nil;
+
+/**
+ * @brief find a node with key
+ * @param root rbtree root
+ * @param key key to find
+ * @return rb_nil if not found, else the node
+ */
+rb_t* rb_find(rb_t* root, int key);
+
+/**
+ * @brief insert a node to rbtree
+ * @param root rbtree root
+ * @param node the node to insert
+ */
+void rb_insert(rb_t** root, rb_t* node);
+
+/**
+ * @brief delete a node from rbtree
+ * @param root rbtree root
+ * @param node the node to delete
+ */
+void rb_delete(rb_t** root, rb_t* node);
+
+/**
+ * @brief find the node with minimum key in rbtree
+ * @param root rbtree root
+ * @return rb_nil if rbtree is empty, otherwise the node
+ */
+rb_t* rb_min(rb_t* root);
+
+/**
+ * @brief find the node with the samllest key which is greater than node's key
+ * @param root rbtree root
+ * @param node the node to compare
+ * @return rb_nil if no such node exist, otherwise the node
+ */
+rb_t* rb_next(rb_t* node);
+
+/** get the enclose object of a rbtree node */
+#define rb_data(rb, type, member) (type*)((char*)rb - offsetof(type, member))
+
+/**
+ * a vector
+ */
+typedef struct vector_s {
+ int current;
+ int size;
+ int elem_size;
+ char* array;
+} vector_t;
+
+/**
+ * @brief initializa a vector
+ * @param v the vector
+ * @param elem_size size of element
+ * @param init_size initial size of vector
+ * @return -1 on failure, 0 on success
+ */
+int vector_init(vector_t* v, int elem_size, int init_size);
+
+/**
+ * @brief get the size of a vector
+ * @param v the vector
+ * @return size
+ */
+int vector_size(vector_t* v);
+
+/**
+ * @brief push an element to the back of v
+ * @param v vector
+ * @param elem element
+ * @return -1 on failure, 0 on success
+ */
+int vector_push(vector_t* v, void* elem);
+
+/**
+ * @brief remove an element from the back of v
+ * @param v vector
+ */
+void vector_pop(vector_t* v);
+
+/**
+ * @brief remove an element at index index
+ * @param v vector
+ * @param index index
+ */
+void vector_remove(vector_t* v, int index);
+
+/**
+ * @brief return a pointer to the element at index in v
+ * @param v vector
+ * @param index index
+ * @return the element
+ */
+void* vector_at(vector_t* v, int index);
+
+/**
+ * @brief free a vector's resource
+ * @param v vector
+ */
+void vector_free(vector_t* v);
+
+/**
+ * a node in heap
+ */
+typedef struct heap_node_s {
+ int key;
+ void* value;
+} heap_node_t;
+
+/**
+ * a minimum heap
+ */
+typedef vector_t heap_t;
+
+/**
+ * heap is only used for timer and we choose this size because 8 is a common
+ * number in computer science and no other size make more sense than this one
+ */
+#define INITIAL_HEAP_SIZE (8)
+
+/**
+ * @brief initializa a heap
+ * @param heap the heap
+ * @return -1 on failure, 0 on success
+ */
+int heap_init(heap_t* heap);
+
+/**
+ * @brief insert a node into heap
+ * @param heap the heap
+ * @param node the node
+ * @return -1 on failure, 0 on success
+ */
+int heap_insert(heap_t* heap, heap_node_t* node);
+
+/**
+ * @brief peak the top of a heap
+ * @param heap the heap
+ * @return top node of the heap or NULL if heap is empty
+ */
+heap_node_t* heap_peak(heap_t* heap);
+
+/**
+ * @brief remove the top node from heap
+ * @param heap the heap
+ */
+void heap_pop(heap_t* heap);
+
+#endif
diff --git a/kern/inc/console.h b/kern/inc/console.h
new file mode 100644
index 0000000..a3649de
--- /dev/null
+++ b/kern/inc/console.h
@@ -0,0 +1,162 @@
+/*
+ *
+ * # #
+ * ## # #### ##### # #### ######
+ * # # # # # # # # # #
+ * # # # # # # # # #####
+ * # # # # # # # # #
+ * # ## # # # # # # #
+ * # # #### # # #### ######
+ *
+ * Now that it's P3 instead of P1 you are allowed
+ * to edit this file if it suits you.
+ *
+ * Please delete this notice.
+ *
+ */
+
+/** @file console.h
+ *
+ * @brief console functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _CONSOLE_H
+#define _CONSOLE_H
+
+#include <video_defines.h>
+
+#include <sync.h>
+
+/** mutex for console operations */
+extern mutex_t console_lock;
+
+/** @brief Prints character ch at the current location
+ * of the cursor.
+ *
+ * If the character is a newline ('\n'), the cursor is
+ * be moved to the beginning of the next line (scrolling if necessary). If
+ * the character is a carriage return ('\r'), the cursor
+ * is immediately reset to the beginning of the current
+ * line, causing any future output to overwrite any existing
+ * output on the line. If backsapce ('\b') is encountered,
+ * the previous character is erased. See the main console.c description
+ * for more backspace behavior.
+ *
+ * @param ch the character to print
+ * @return The input character
+ */
+int putbyte(char ch);
+
+/** @brief Prints the string s, starting at the current
+ * location of the cursor.
+ *
+ * If the string is longer than the current line, the
+ * string fills up the current line and then
+ * continues on the next line. If the string exceeds
+ * available space on the entire console, the screen
+ * scrolls up one line, and then the string
+ * continues on the new line. If '\n', '\r', and '\b' are
+ * encountered within the string, they are handled
+ * as per putbyte. If len is not a positive integer or s
+ * is null, the function has no effect.
+ *
+ * @param s The string to be printed.
+ * @param len The length of the string s.
+ * @return Void.
+ */
+void putbytes(const char* s, int len);
+
+/** @brief Changes the foreground and background color
+ * of future characters printed on the console.
+ *
+ * If the color code is invalid, the function has no effect.
+ *
+ * @param color The new color code.
+ * @return 0 on success or integer error code less than 0 if
+ * color code is invalid.
+ */
+int set_term_color(int color);
+
+/** @brief Writes the current foreground and background
+ * color of characters printed on the console
+ * into the argument color.
+ * @param color The address to which the current color
+ * information will be written.
+ * @return Void.
+ */
+void get_term_color(int* color);
+
+/** @brief Sets the position of the cursor to the
+ * position (row, col).
+ *
+ * Subsequent calls to putbytes should cause the console
+ * output to begin at the new position. If the cursor is
+ * currently hidden, a call to set_cursor() does not show
+ * the cursor.
+ *
+ * @param row The new row for the cursor.
+ * @param col The new column for the cursor.
+ * @return 0 on success or integer error code less than 0 if
+ * cursor location is invalid.
+ */
+int set_cursor(int row, int col);
+
+/** @brief Writes the current position of the cursor
+ * into the arguments row and col.
+ * @param row The address to which the current cursor
+ * row will be written.
+ * @param col The address to which the current cursor
+ * column will be written.
+ * @return Void.
+ */
+void get_cursor(int* row, int* col);
+
+/** @brief Hides the cursor.
+ *
+ * Subsequent calls to putbytes do not cause the
+ * cursor to show again.
+ *
+ * @return Void.
+ */
+void hide_cursor(void);
+
+/** @brief Shows the cursor.
+ *
+ * If the cursor is already shown, the function has no effect.
+ *
+ * @return Void.
+ */
+void show_cursor(void);
+
+/** @brief Clears the entire console.
+ *
+ * The cursor is reset to the first row and column
+ *
+ * @return Void.
+ */
+void clear_console(void);
+
+/** @brief Prints character ch with the specified color
+ * at position (row, col).
+ *
+ * If any argument is invalid, the function has no effect.
+ *
+ * @param row The row in which to display the character.
+ * @param col The column in which to display the character.
+ * @param ch The character to display.
+ * @param color The color to use to display the character.
+ * @return Void.
+ */
+void draw_char(int row, int col, int ch, int color);
+
+/** @brief Returns the character displayed at position (row, col).
+ * @param row Row of the character.
+ * @param col Column of the character.
+ * @return The character at (row, col).
+ */
+char get_char(int row, int col);
+
+#endif /* _CONSOLE_H */
diff --git a/kern/inc/interrupt.h b/kern/inc/interrupt.h
new file mode 100644
index 0000000..7c15ccd
--- /dev/null
+++ b/kern/inc/interrupt.h
@@ -0,0 +1,27 @@
+/** @file interrupt.h
+ *
+ * @brief idt initializer.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _INTERRUPT_H_
+#define _INTERRUPT_H_
+
+/**
+ * IRQ # of timer
+ */
+#define TIMER_IRQ 0
+
+/**
+ * IRQ # of keyboard
+ */
+#define KBD_IRQ 1
+
+/**
+ * @brief initialize idt
+ */
+void idt_init();
+
+#endif
diff --git a/kern/inc/kbd.h b/kern/inc/kbd.h
new file mode 100644
index 0000000..a7350b6
--- /dev/null
+++ b/kern/inc/kbd.h
@@ -0,0 +1,30 @@
+/** @file kbd.h
+ *
+ * @brief keyboard functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _KBD_H_
+#define _KBD_H_
+
+#include <common.h>
+#include <sched.h>
+#include <sync.h>
+
+/**
+ * @brief read a line to userspace
+ * @param len max length to read
+ * @param buf buffer
+ * @return size on success, -1 on failure
+ */
+int do_readline(int len, va_t buf);
+
+/**
+ * @brief read a char
+ * @return char read
+ */
+int do_getchar();
+
+#endif
diff --git a/kern/inc/loader.h b/kern/inc/loader.h
new file mode 100644
index 0000000..504a29a
--- /dev/null
+++ b/kern/inc/loader.h
@@ -0,0 +1,46 @@
+/* The 15-410 kernel project
+ *
+ * loader.h
+ *
+ * Structure definitions, #defines, and function prototypes
+ * for the user process loader.
+ */
+
+#ifndef _LOADER_H
+#define _LOADER_H
+
+#include <exec2obj.h>
+
+/* --- Prototypes --- */
+
+/**
+ * @brief read size bytes from offset of filename to buf
+ * @param filename filename
+ * @param offser offset in file
+ * @param size size to read
+ * @param buf buffer
+ * @return returns the number of bytes copied on succes; -1 on failure
+ */
+int getbytes(const char* filename, int offset, int size, char* buf);
+
+/** shorter name for file entry */
+typedef const exec2obj_userapp_TOC_entry file_t;
+
+/**
+ * @brief read size bytes from offset of f to buf
+ * @param f file entry
+ * @param offser offset in file
+ * @param size size to read
+ * @param buf buffer
+ * @return returns the number of bytes copied on succes; -1 on failure
+ */
+int read_file(file_t* f, int offset, int size, char* buf);
+
+/**
+ * @brief find a file entry
+ * @param filename filename
+ * @return the file entry, NULL if not found
+ */
+file_t* find_file(const char* name);
+
+#endif /* _LOADER_H */
diff --git a/kern/inc/mm.h b/kern/inc/mm.h
new file mode 100644
index 0000000..64499d0
--- /dev/null
+++ b/kern/inc/mm.h
@@ -0,0 +1,55 @@
+/** @file mm.h
+ *
+ * @brief memory management functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _MM_H_
+#define _MM_H_
+
+#include <syscall_int.h>
+
+#include <paging.h>
+#include <sync.h>
+
+/** a invalid physical address when managing user memory */
+#define BAD_PA 0
+
+/** lock for kernel memory allocation */
+extern mutex_t malloc_lock;
+
+/** lock for user memory management */
+extern mutex_t mm_lock;
+
+/**
+ * @brief initialize user memory system
+ */
+void mm_init();
+
+/**
+ * @brief allocate physical pages
+ * @param num_pages number of pages to allocate
+ * @return physical address of pages, or BAD_PA on failure
+ */
+pa_t alloc_user_pages(int num_pages);
+
+/**
+ * @brief free physical pages
+ * @param pa physcial address of pages
+ * @param num_pages number of pages to allocate
+ */
+void free_user_pages(pa_t pa, int num_pages);
+
+/**
+ * @brief map a physical page to kernel memory, the mapping area will be reused
+ * after subsequent map_phys_page() calls
+ * @param pa physcial address of the page
+ * @param old_pa output argument to save old mapped page, can be NULL if no need
+ * to save
+ * @return virtual address of mapped page
+ */
+va_t map_phys_page(pa_t pa, pa_t* old_pa);
+
+#endif
diff --git a/kern/inc/paging.h b/kern/inc/paging.h
new file mode 100644
index 0000000..182f50f
--- /dev/null
+++ b/kern/inc/paging.h
@@ -0,0 +1,155 @@
+/** @file paging.h
+ *
+ * @brief paging management functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _PAGING_H_
+#define _PAGING_H_
+
+#include <x86/page.h>
+
+/* SOMEONE SUGGESTED THAT I SHOULD USE UINTPTR_T FOR PORTABILITY BUT I DON'T SEE
+ * ANY USE OF IT IN ANY PROVIDED CODE AND THIS COURSE HAVE ALREADY SAY WE ARE
+ * TARGETING X86 ARCHTECTURE SO I JUST USE A 'NON-PORTABLE' INT TYPE */
+
+/** an entry in page table */
+typedef unsigned long pte_t;
+/** an entry in page directory */
+typedef unsigned long pde_t;
+
+/** virtual address */
+typedef unsigned long va_t;
+
+/** physical address */
+typedef unsigned long pa_t;
+
+/** size of memory */
+typedef unsigned long va_size_t;
+
+/** a register */
+typedef unsigned int reg_t;
+
+#ifndef PAGE_OFFSET_MASK
+/** mask of page offset bits */
+#define PAGE_OFFSET_MASK (PAGE_SIZE - 1)
+#endif
+
+#ifndef PAGE_BASE_MASK
+/** mask of page base bits */
+#define PAGE_BASE_MASK (~PAGE_OFFSET_MASK)
+#endif
+
+/** index of entry in page directory where user memory starts */
+#define USER_PD_START (USER_MEM_START / PAGE_SIZE / NUM_PAGE_ENTRY)
+
+/** means a page is present */
+#define PTE_PRESENT 1
+/** means a page is writeable */
+#define PTE_RW 1
+/** means a page is not writeable */
+#define PTE_RO 0
+/** means a page can be accessed in ring 3 */
+#define PTE_USER 1
+/** means a page can only be accessed in ring 0 */
+#define PTE_SUPERVISOR 0
+#define PTE_PCD 1
+/** means a page's TLB entry should not be flushed on switching cr3 */
+#define PTE_G 1
+
+/** P bit's position */
+#define PTE_P_SHIFT 0
+/** RW/RO bit's position */
+#define PTE_RW_SHIFT 1
+/** USER/SUPERVISOR bit's position */
+#define PTE_US_SHIFT 2
+/** PCD bit's position */
+#define PTE_PCD_SHIFT 4
+/** G bit's position */
+#define PTE_G_SHIFT 8
+
+/** an invalid page directory entry */
+#define BAD_PDE ((pde_t)0)
+/** an invalid page table entry */
+#define BAD_PTE ((pte_t)0)
+
+/**
+ * @brief create a page table entry
+ * @param base address of the page
+ * @param g g bit
+ * @param us user/supervisor bit
+ * @param rw rw/ro bit
+ * @param p present bit
+ * @return the page table entry
+ */
+static inline pte_t make_pte(pa_t base, int g, int us, int rw, int p) {
+ return ((base & PAGE_BASE_MASK) | (g << PTE_G_SHIFT) |
+ (us << PTE_US_SHIFT) | (rw << PTE_RW_SHIFT) | (p << PTE_P_SHIFT));
+}
+
+/**
+ * @brief create a page directory entry
+ * @param base address of the page
+ * @param us user/supervisor bit
+ * @param rw rw/ro bit
+ * @param p present bit
+ * @return the page directory entry
+ */
+static inline pde_t make_pde(pa_t base, int us, int rw, int p) {
+ return ((base & PAGE_BASE_MASK) | (us << PTE_US_SHIFT) |
+ (rw << PTE_RW_SHIFT) | (p << PTE_P_SHIFT));
+}
+
+#ifndef NUM_PAGE_ENTRY
+/** number of entries in a page table */
+#define NUM_PAGE_ENTRY (PAGE_SIZE / sizeof(pte_t))
+#endif
+
+/** size of memory a page table can manage */
+#define PT_SIZE (PAGE_SIZE * NUM_PAGE_ENTRY)
+
+/** page directory type */
+typedef pde_t page_directory_t[PAGE_SIZE / sizeof(pde_t)];
+/** page table type */
+typedef pte_t page_table_t[PAGE_SIZE / sizeof(pde_t)];
+
+/** get page table address from a pde */
+static inline pa_t get_page_table(pde_t pde) {
+ return (pa_t)(pde & PAGE_BASE_MASK);
+}
+
+/** get page base address from a pte */
+static inline pa_t get_page_base(pte_t pte) {
+ return (pa_t)(pte & PAGE_BASE_MASK);
+}
+
+/** get index in page directory of an address */
+static inline int get_pd_index(va_t va) {
+ return va / PAGE_SIZE / NUM_PAGE_ENTRY;
+}
+
+/** get index in page table of an address */
+static inline int get_pt_index(va_t va) {
+ return (va / PAGE_SIZE) % NUM_PAGE_ENTRY;
+}
+
+/** kernel's page directory which directly maps all kernel memory */
+extern page_directory_t* kernel_pd;
+/** area for mapping physical pages */
+extern va_t mapped_phys_pages;
+/** location of the page table entry of the mapping area */
+extern pte_t* mapped_phys_page_ptes;
+
+/**
+ * @brief initialize and enable paging
+ */
+void paging_init();
+
+/**
+ * @brief enable paging
+ */
+void paging_enable();
+
+#endif
diff --git a/kern/inc/sched.h b/kern/inc/sched.h
new file mode 100644
index 0000000..335f648
--- /dev/null
+++ b/kern/inc/sched.h
@@ -0,0 +1,361 @@
+/** @file sched.h
+ *
+ * @brief scheduling functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _SCHED_H_
+#define _SCHED_H_
+
+#include <common_kern.h>
+#include <elf/elf_410.h>
+
+#include <x86/eflags.h>
+#include <x86/seg.h>
+
+#include <common.h>
+#include <paging.h>
+#include <sync.h>
+
+/** name of idle process */
+#define IDLE_NAME "idle"
+/** name of init process */
+#define INIT_NAME "init"
+
+/** we use fs to find percpu area */
+#define SEGSEL_KERNEL_FS SEGSEL_SPARE0
+/** index of fs in gdt */
+#define SEGSEL_KERNEL_FS_IDX SEGSEL_SPARE0_IDX
+
+/** an allocated memory of a process */
+typedef struct region_s {
+ va_t addr;
+ va_size_t size; /* size in bytes */
+ pa_t paddr;
+ int is_rw;
+} region_t;
+
+/**
+ * default size for kernel thread, as long as kernel does not contain recursion
+ * and do not allocate big local variable, the kernel stack pointer will never
+ * overflow
+ */
+#define K_STACK_SIZE PAGE_SIZE
+
+/** initial eflags, interrupt enabled and IOPL=0 */
+#define DEFAULT_EFLAGS (EFL_IF | EFL_IOPL_RING0 | EFL_RESV1)
+
+/** frame used for kernel context switching */
+typedef struct yield_frame_s {
+ reg_t ebp;
+ reg_t ebx;
+ reg_t eflags;
+ reg_t raddr;
+} yield_frame_t;
+
+/** frame used for syscall and interrupts (not faults) */
+typedef struct stack_frame_s {
+ reg_t gs;
+ reg_t fs;
+ reg_t es;
+ reg_t ds;
+ reg_t edi;
+ reg_t esi;
+ reg_t ebp;
+ reg_t dummy_esp; /* gap of pusha */
+ reg_t ebx;
+ reg_t edx;
+ reg_t ecx;
+ reg_t eax;
+ reg_t eip;
+ reg_t cs;
+ reg_t eflags;
+ reg_t esp; /* not present if interrupted in kernel mode */
+ reg_t ss; /* not present if interrupted in kernel mode */
+} stack_frame_t;
+
+/** process control block */
+typedef struct process_s {
+ int pid;
+ int exit_value;
+ struct process_s* parent;
+ queue_t sible_link; /* in parent process' child queue */
+
+ mutex_t refcount_lock;
+ int refcount;
+ queue_t* threads;
+
+ int nchilds; /* live and dead but unclaimed childs */
+ queue_t* live_childs;
+ queue_t* dead_childs;
+
+ int nwaiters; /* number of threads calling wait() */
+ mutex_t wait_lock;
+ cv_t wait_cv;
+
+ pa_t cr3;
+ vector_t regions; /* record the vitural memories the user has mapped */
+ mutex_t mm_lock; /* lock when operating VM */
+} process_t;
+
+/** status of the thread, must lock ready_lock to change it, otherwise may be
+ * changed back to READY by timer interrupt */
+typedef enum thr_stat_e {
+ THREAD_RUNNING,
+ THREAD_READY,
+ THREAD_BLOCKED, /* blocked on a mutex/cv */
+ THREAD_SLEEPING,
+ THREAD_DESCHEDULED,
+ THREAD_DEAD
+} thr_stat_t;
+
+/** thread control block */
+typedef struct thread_s {
+ rb_t rb_node; /* rbtree node, key is tid */
+ thr_stat_t status;
+ spl_t status_lock;
+ queue_t sched_link; /* in ready queue or other queue */
+ int pending_exit; /* if a task_vanish is pending */
+
+ queue_t process_link; /* in process_t's threads queue */
+
+ process_t* process;
+
+ reg_t esp3;
+ reg_t eip3;
+ reg_t swexn_arg;
+ int df3; /* is a fault is currently being handled by swexn handler */
+
+ reg_t esp0; /* esp to use when switching from ring3 to ring0
+ (fault/interrupt) */
+ reg_t eip0; /* handler for faults happen in ring0 */
+
+ reg_t kernel_esp; /* saved kernel esp when swapped out */
+ char* stack; /* kernel stack */
+} thread_t;
+
+/**
+ * @brief get next available tid
+ * @return tid
+ */
+int alloc_tid();
+
+/** initial stack size for user program, 16 pages is a middle size that do not
+ * waste too much and big enough for arguments */
+#define DEFAULT_STACK_SIZE (65536)
+/** initial stack end for user program, skip the last page as it would cause
+ * round bugs */
+#define DEFAULT_STACK_END (0xffffe000)
+/** inital stack area start */
+#define DEFAULT_STACK_POS (DEFAULT_STACK_END - DEFAULT_STACK_SIZE)
+
+/** maximum arg length for exec(), a middle size */
+#define MAX_ARG_LEN 4096
+/** maximum number of args for exec(), a middle size */
+#define MAX_NUM_ARG 256
+/** maximum total arg length for exec(), save last page for argc, argv, stack_hi
+ * and stack_lo */
+#define MAX_TOTAL_ARG_LEN (DEFAULT_STACK_SIZE - PAGE_SIZE)
+
+/**
+ * @brief create an empty process with one thread and no user memory
+ * @return the thread created or NULL on failure
+ */
+thread_t* create_empty_process();
+
+/**
+ * @brief create a process and load the executable and push the arguments
+ * @param tid tid to use, or 0 if we want to allocate a new one
+ * @param exec name of executable to load
+ * @param argc number of arguments
+ * @param argv array of arguments
+ * @return the thread created or NULL on failure
+ */
+thread_t* create_process(int tid, char* exe, int argc, const char** argv);
+
+/**
+ * @brief destroy a thread which is never run
+ * @param t the thread to destroy
+ */
+void destroy_thread(thread_t* t);
+
+/**
+ * @brief add a memory region to a process
+ * @param p the process
+ * @param addr virtual address
+ * @param n_pages number of pages in this region
+ * @param pa physical address
+ * @param is_rw is this region writeable
+ * @return 0 for success, -1 for failure
+ */
+int add_region(process_t* p, va_t vaddr, int n_pages, pa_t pa, int is_rw);
+
+/**
+ * @brief find the page table for addr, or allocate one if not present
+ * @param p the process
+ * @param addr virtual address
+ * @return physical address of page table or BAD_PA on failure
+ */
+pa_t find_or_create_pt(process_t* p, va_t vaddr);
+
+/** CPU specific varibles */
+typedef struct percpu_s {
+ thread_t* current; /* current running thread */
+ thread_t* idle; /* idle thread */
+ thread_t* kthread; /* original kernel thread */
+ va_t mapped_phys_page; /* physical page mapping area */
+ pte_t* mapped_phys_page_pte; /* pte for the mapping area */
+} percpu_t;
+
+/**
+ * @brief initialize fs segment's base to percpu structure
+ * @param percpu address of percpu structure
+ */
+void setup_percpu(percpu_t* percpu);
+
+/**
+ * @brief setup the kernel thread's control structure, which needed when
+ * switching to othre processes when startup
+ * @param kthread kernel thread's thread control structure
+ * @param kprocess kernel thread's process control structure
+ */
+void setup_kth(thread_t* kthread, process_t* kprocess);
+
+/**
+ * @brief get current running thread
+ * @return current running thread
+ */
+thread_t* get_current();
+/**
+ * @brief set current running thread
+ * @param t current running thread
+ */
+void set_current(thread_t* t);
+/**
+ * @brief get idle thread
+ * @return idle thread
+ */
+thread_t* get_idle();
+/**
+ * @brief set idle thread
+ * @param t idle thread
+ */
+void set_idle(thread_t* t);
+/**
+ * @brief get kernel thread
+ * @return kernel thread
+ */
+thread_t* get_kthread();
+/**
+ * @brief set kernel thread
+ * @param t kernel thread
+ */
+void set_kthread(thread_t* t);
+/**
+ * @brief get physical page mapping area
+ * @return physical page mapping area
+ */
+va_t get_mapped_phys_page();
+/**
+ * @brief set physical page mapping area
+ * @param va physical page mapping area
+ */
+void set_mapped_phys_page(va_t va);
+/**
+ * @brief get pte for physical page mapping area
+ * @return pte for physical page mapping area
+ */
+pte_t* get_mapped_phys_page_pte();
+/**
+ * @brief set pte for physical page mapping area
+ * @param pte pte for physical page mapping area
+ */
+void set_mapped_phys_page_pte(pte_t* pte);
+
+/**
+ * @brief swap current thread's process and newt's process, newt must be a new
+ * thread with new process, current thread must be the only thread in a process
+ * @param newt thread to swap
+ */
+void swap_process_inplace(thread_t* newt);
+
+/** init process */
+extern process_t* init_process;
+
+/** lock for ready queue */
+extern spl_t ready_lock;
+/** queue of ready threads */
+extern queue_t* ready;
+
+/** rbtree of all threads */
+extern rb_t* threads;
+/** mutex for rbtree */
+extern mutex_t threads_lock;
+
+/**
+ * @brief insert a thread to the tail of ready queue, must lock ready_lock
+ * before calling this function
+ * @param t thread to insert
+ */
+void insert_ready_tail(thread_t* t);
+
+/**
+ * @brief insert a thread to the head of ready queue, must lock ready_lock
+ * before calling this function
+ * @param t thread to insert
+ */
+void insert_ready_head(thread_t* t);
+
+/**
+ * @brief select next ready thread
+ * @return the thread
+ */
+thread_t* select_next();
+
+/**
+ * @brief save current esp, load t's cr3, esp0 and return t's esp, must disable
+ * interrupt before calling
+ * @param t thread to load
+ * @param esp current esp
+ * @return new esp
+ */
+reg_t save_and_setup_env(thread_t* t, reg_t esp);
+
+/**
+ * @brief let current thread vanish
+ */
+void kill_current();
+
+/**
+ * @brief switch to another stack and cleanup t's thread control block and stack
+ */
+void switch_stack_cleanup(reg_t esp, thread_t* t);
+
+/**
+ * @brief check pending exit request and pop saved user registers and return to
+ * ring 3
+ */
+void return_to_user();
+
+/**
+ * @brief find a thread from rbtree, must lock threads_lock before calling
+ * @param tid tid of thread
+ * @return the thread or NULL if not found
+ */
+thread_t* find_thread(int tid);
+
+/**
+ * @brief add a thread to rbtree, will automatically lock threads_lock
+ * @param t thread to add
+ */
+void add_thread(thread_t* t);
+
+/**
+ * @brief remove a thread from rbtree, will automatically lock threads_lock
+ * @param t thread to remove
+ */
+void remove_thread(thread_t* t);
+
+#endif
diff --git a/kern/inc/sync.h b/kern/inc/sync.h
new file mode 100644
index 0000000..9bc53c8
--- /dev/null
+++ b/kern/inc/sync.h
@@ -0,0 +1,102 @@
+/** @file sync.h
+ *
+ * @brief locks.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _SYNC_H_
+#define _SYNC_H_
+
+#include <common.h>
+
+/** declaration of thread control block */
+struct thread_s;
+/** declaration of thread control block */
+typedef struct thread_s thread_t;
+
+/**
+ * @brief save eflags and disable interrupt
+ * @return eflags
+ */
+int save_clear_if();
+/**
+ * @brief restore eflags
+ * @param old_if eflags
+ */
+void restore_if(int old_if);
+
+/** spinlock structure, only needed for SMP */
+typedef struct spl_s {
+ int locked;
+} spl_t;
+
+/** initial value for spinlock */
+#define SPL_INIT (spl_t){.locked = 0};
+
+/**
+ * @brief lock the spinlock and disable interrupts
+ * @return original eflags
+ */
+int spl_lock(spl_t* spl);
+/**
+ * @brief unlock the spinlock and restore eflags
+ * @param old_if eflags
+ */
+void spl_unlock(spl_t* spl, int old_if);
+
+/**
+ * @brief atomically yield to another thread and unlock the spinlock, eflags
+ * will be restored when current thread become running again
+ * @param t thread to yield to
+ * @param spl spinlock
+ * @param old_if eflags
+ */
+void yield_to_spl_unlock(thread_t* t, spl_t* spl, int old_if);
+
+/** mutex structure */
+typedef struct mutex_s {
+ spl_t guard;
+ int locked;
+ queue_t* waiters;
+} mutex_t;
+
+/** initial value for mutex */
+#define MUTEX_INIT \
+ (mutex_t){.guard = {.locked = 0}, .locked = 0, .waiters = NULL};
+
+/**
+ * @brief lock the mutex, will be blocked if lock is held by other thread
+ * @param m mutex
+ */
+void mutex_lock(mutex_t* m);
+/**
+ * @brief unlock the mutex, will wake up one blocked thread if there is one
+ * @param m mutex
+ */
+void mutex_unlock(mutex_t* m);
+
+/** cv structure */
+typedef struct cv_s {
+ spl_t guard;
+ queue_t* waiters;
+} cv_t;
+
+/** initial value for cv */
+#define CV_INIT \
+ (cv_t) { .guard = {.locked = 0}, .waiters = NULL }
+
+/**
+ * @brief unlock the mutex m and wait for other threads to call cv_signal on cv
+ * @param cv cv
+ * @param m mutex
+ */
+void cv_wait(cv_t* cv, mutex_t* m);
+/**
+ * @brief signal a cv, will wake up one blocked thread if there is one
+ * @param cv cv
+ */
+void cv_signal(cv_t* cv);
+
+#endif
diff --git a/kern/inc/timer.h b/kern/inc/timer.h
new file mode 100644
index 0000000..8d6368e
--- /dev/null
+++ b/kern/inc/timer.h
@@ -0,0 +1,24 @@
+/** @file timer.h
+ *
+ * @brief timer functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _TIMER_H_
+#define _TIMER_H_
+
+/** ticks passed */
+extern unsigned int ticks;
+/** timer heap lock */
+extern spl_t timer_lock;
+/** timers of sleeping threads */
+extern heap_t timers;
+
+/**
+ * @brief initialize the timer
+ */
+void timer_init();
+
+#endif
diff --git a/kern/inc/usermem.h b/kern/inc/usermem.h
new file mode 100644
index 0000000..c5622e8
--- /dev/null
+++ b/kern/inc/usermem.h
@@ -0,0 +1,50 @@
+/** @file usermem.h
+ *
+ * @brief userspace r/w functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#ifndef _USERMEM_H_
+#define _USERMEM_H_
+
+#include <stdint.h>
+
+#include <common_kern.h>
+
+#include <paging.h>
+
+/**
+ * @brief copy bytes from userspace
+ * @param addr address of user memory
+ * @param size number of bytes
+ * @param buf buffer
+ * @return 0 on success, -1 on failure
+ */
+int copy_from_user(va_t addr, int size, void* buf);
+/**
+ * @brief copy bytes to userspace
+ * @param addr address of user memory
+ * @param size number of bytes
+ * @param buf buffer
+ * @return 0 on success, -1 on failure
+ */
+int copy_to_user(va_t addr, int size, void* buf);
+
+/**
+ * @brief try to copy a string from userspace
+ * @param addr address of user memory
+ * @param maxlen maximum length of the string including the null byte
+ * @return NULL on failure, a string allocated by malloc() on success
+ */
+char* copy_string_from_user(va_t addr, int maxlen);
+/**
+ * @brief try to print a buffer from userspace
+ * @param addr address of user memory
+ * @param maxlen maximum length of to print
+ * @return 0 on success, -1 on failure
+ */
+int print_buf_from_user(va_t addr, int maxlen);
+
+#endif
diff --git a/kern/interrupt.c b/kern/interrupt.c
new file mode 100644
index 0000000..ad48e5f
--- /dev/null
+++ b/kern/interrupt.c
@@ -0,0 +1,502 @@
+/** @file interrupt.c
+ *
+ * @brief idt initializer and fault/interrupt handlers.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <x86/asm.h>
+#include <x86/eflags.h>
+#include <x86/idt.h>
+#include <x86/interrupt_defines.h>
+#include <x86/seg.h>
+#include <x86/timer_defines.h>
+
+#include <keyhelp.h>
+#include <simics.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall_int.h>
+#include <ureg.h>
+
+#include <asm_instr.h>
+#include <interrupt.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <sync.h>
+#include <timer.h>
+#include <usermem.h>
+
+/** means this entry requires ring 0 privilige */
+#define IDT_DPL_KERNEL 0
+/** means this entry requires ring 3 privilige */
+#define IDT_DPL_USER 3
+/** means this entry is valid */
+#define IDT_P 1
+/** means this entry is a 32 bit interrupt */
+#define IDT_TYPE_I32 0xe
+/** means this entry is a 32 bit trap */
+#define IDT_TYPE_T32 0xf
+
+/** P bit's position */
+#define IDT_P_SHIFT (47 - 32)
+/** DPL's position */
+#define IDT_DPL_SHIFT (45 - 32)
+/** TYPE's position */
+#define IDT_TYPE_SHIFT (40 - 32)
+/** CS's position */
+#define IDT_CS_SHIFT 16
+
+/** EIP's high part's bits */
+#define IDT_EIP_HI_MASK 0xffff0000
+/** EIP's low part's bits */
+#define IDT_EIP_LO_MASK 0x0000ffff
+
+/**
+ * @brief an idt entry
+ */
+typedef struct idt_s {
+ unsigned long lo;
+ unsigned long hi;
+} idt_t;
+
+/**
+ * @brief create an idt entry
+ * @param eip eip
+ * @param type type
+ * @param dpl dpl
+ * @return the idt entry
+ */
+static inline idt_t make_idt(va_t eip, int type, int dpl) {
+ idt_t idt;
+ idt.hi = ((eip & IDT_EIP_HI_MASK) | (IDT_P << IDT_P_SHIFT) |
+ (dpl << IDT_DPL_SHIFT) | (type << IDT_TYPE_SHIFT));
+ idt.lo = ((SEGSEL_KERNEL_CS << IDT_CS_SHIFT) | (eip & IDT_EIP_LO_MASK));
+ return idt;
+}
+
+/**
+ * @brief DE handler entry
+ */
+int de_handler();
+/**
+ * @brief DB handler entry
+ */
+int db_handler();
+/**
+ * @brief NMI handler entry
+ */
+int nmi_handler();
+/**
+ * @brief BP handler entry
+ */
+int bp_handler();
+/**
+ * @brief OF handler entry
+ */
+int of_handler();
+/**
+ * @brief BR handler entry
+ */
+int br_handler();
+/**
+ * @brief UD handler entry
+ */
+int ud_handler();
+/**
+ * @brief NM handler entry
+ */
+int nm_handler();
+/**
+ * @brief DF handler entry
+ */
+int df_handler();
+/**
+ * @brief CSO handler entry
+ */
+int cso_handler();
+/**
+ * @brief TS handler entry
+ */
+int ts_handler();
+/**
+ * @brief NP handler entry
+ */
+int np_handler();
+/**
+ * @brief SS handler entry
+ */
+int ss_handler();
+/**
+ * @brief GP handler entry
+ */
+int gp_handler();
+/**
+ * @brief PF handler entry
+ */
+int pf_handler();
+/**
+ * @brief MF handler entry
+ */
+int mf_handler();
+/**
+ * @brief AC handler entry
+ */
+int ac_handler();
+/**
+ * @brief MC handler entry
+ */
+int mc_handler();
+/**
+ * @brief XF handler entry
+ */
+int xf_handler();
+
+/**
+ * @brief default handler entry
+ */
+int default_handler();
+
+/**
+ * @brief timer handler entry
+ */
+void timer_handler();
+/**
+ * @brief keyboard handler entry
+ */
+void kbd_handler();
+
+/**
+ * @brief fork() syscall entry
+ */
+void sys_fork();
+/**
+ * @brief exec() syscall entry
+ */
+void sys_exec();
+/**
+ * @brief wait() syscall entry
+ */
+void sys_wait();
+/**
+ * @brief yield() syscall entry
+ */
+void sys_yield();
+/**
+ * @brief deschedule() syscall entry
+ */
+void sys_deschedule();
+/**
+ * @brief make_runnable() syscall entry
+ */
+void sys_make_runnable();
+/**
+ * @brief gettid() syscall entry
+ */
+void sys_gettid();
+/**
+ * @brief new_pages() syscall entry
+ */
+void sys_new_pages();
+/**
+ * @brief remove_pages() syscall entry
+ */
+void sys_remove_pages();
+/**
+ * @brief sleep() syscall entry
+ */
+void sys_sleep();
+/**
+ * @brief getchar() syscall entry
+ */
+void sys_getchar();
+/**
+ * @brief readline() syscall entry
+ */
+void sys_readline();
+/**
+ * @brief print() syscall entry
+ */
+void sys_print();
+/**
+ * @brief set_term_color() syscall entry
+ */
+void sys_set_term_color();
+/**
+ * @brief set_cursor_pos() syscall entry
+ */
+void sys_set_cursor_pos();
+/**
+ * @brief get_cursor() syscall entry
+ */
+void sys_get_cursor_pos();
+/**
+ * @brief thread() syscall entry
+ */
+void sys_thread_fork();
+/**
+ * @brief get_ticks() syscall entry
+ */
+void sys_get_ticks();
+/**
+ * @brief misbehave() syscall entry
+ */
+void sys_misbehave();
+/**
+ * @brief halt() syscall entry
+ */
+void sys_halt();
+/**
+ * @brief task_vanish() syscall entry
+ */
+void sys_task_vanish();
+/**
+ * @brief set_status() syscall entry
+ */
+void sys_set_status();
+/**
+ * @brief vanish() syscall entry
+ */
+void sys_vanish();
+/**
+ * @brief readfile() syscall entry
+ */
+void sys_readfile();
+/**
+ * @brief swexn() syscall entry
+ */
+void sys_swexn();
+
+/**
+ * @brief all other syscall entries
+ */
+void sys_enosys();
+
+/** index of first syscall */
+#define IDT_SYSCALL_START (X86_PIC_MASTER_IRQ_BASE + 16)
+
+void idt_init() {
+ idt_t* idt = (idt_t*)idt_base();
+
+ int i;
+ for (i = 0; i < IDT_SYSCALL_START; i++) {
+ idt[i] = make_idt((va_t)default_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ }
+
+ idt[IDT_DE] = make_idt((va_t)de_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_DB] = make_idt((va_t)db_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_NMI] = make_idt((va_t)nmi_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_BP] = make_idt((va_t)bp_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_OF] = make_idt((va_t)of_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_BR] = make_idt((va_t)br_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_UD] = make_idt((va_t)ud_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_NM] = make_idt((va_t)nm_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_DF] = make_idt((va_t)df_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_CSO] = make_idt((va_t)cso_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_TS] = make_idt((va_t)ts_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_NP] = make_idt((va_t)np_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_SS] = make_idt((va_t)ss_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_GP] = make_idt((va_t)gp_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_PF] = make_idt((va_t)pf_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_MF] = make_idt((va_t)mf_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_AC] = make_idt((va_t)ac_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_MC] = make_idt((va_t)mc_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[IDT_XF] = make_idt((va_t)xf_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+
+ idt[TIMER_IDT_ENTRY] =
+ make_idt((va_t)timer_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+ idt[KEY_IDT_ENTRY] =
+ make_idt((va_t)kbd_handler, IDT_TYPE_I32, IDT_DPL_KERNEL);
+
+ for (i = IDT_SYSCALL_START; i < IDT_ENTS; i++) {
+ idt[i] = make_idt((va_t)sys_enosys, IDT_TYPE_T32, IDT_DPL_USER);
+ }
+
+ idt[FORK_INT] = make_idt((va_t)sys_fork, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[EXEC_INT] = make_idt((va_t)sys_exec, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[WAIT_INT] = make_idt((va_t)sys_wait, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[YIELD_INT] = make_idt((va_t)sys_yield, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[DESCHEDULE_INT] =
+ make_idt((va_t)sys_deschedule, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[MAKE_RUNNABLE_INT] =
+ make_idt((va_t)sys_make_runnable, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[GETTID_INT] = make_idt((va_t)sys_gettid, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[NEW_PAGES_INT] =
+ make_idt((va_t)sys_new_pages, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[REMOVE_PAGES_INT] =
+ make_idt((va_t)sys_remove_pages, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[SLEEP_INT] = make_idt((va_t)sys_sleep, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[GETCHAR_INT] = make_idt((va_t)sys_getchar, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[READLINE_INT] =
+ make_idt((va_t)sys_readline, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[PRINT_INT] = make_idt((va_t)sys_print, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[SET_TERM_COLOR_INT] =
+ make_idt((va_t)sys_set_term_color, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[SET_CURSOR_POS_INT] =
+ make_idt((va_t)sys_set_cursor_pos, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[GET_CURSOR_POS_INT] =
+ make_idt((va_t)sys_get_cursor_pos, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[THREAD_FORK_INT] =
+ make_idt((va_t)sys_thread_fork, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[GET_TICKS_INT] =
+ make_idt((va_t)sys_get_ticks, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[MISBEHAVE_INT] =
+ make_idt((va_t)sys_misbehave, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[HALT_INT] = make_idt((va_t)sys_halt, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[TASK_VANISH_INT] =
+ make_idt((va_t)sys_task_vanish, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[SET_STATUS_INT] =
+ make_idt((va_t)sys_set_status, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[VANISH_INT] = make_idt((va_t)sys_vanish, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[READFILE_INT] =
+ make_idt((va_t)sys_readfile, IDT_TYPE_T32, IDT_DPL_USER);
+ idt[SWEXN_INT] = make_idt((va_t)sys_swexn, IDT_TYPE_T32, IDT_DPL_USER);
+}
+
+/** string explanation of fault */
+const static char* reasons[] = {"Division Error",
+ "Debug",
+ "Non-maskable Interrupt",
+ "Breakpoint",
+ "Overflow",
+ "Bound Range Exceeded",
+ "Invalid Opcode",
+ "Device Not Available",
+ "Double Fault",
+ "Coprocessor Segment Overrun",
+ "Invalid TSS",
+ "Segment Not Present",
+ "Stack-Segment Fault",
+ "General Protection Fault",
+ "Page Fault",
+ "Reserved",
+ "x87 Floating-Point Exception",
+ "Alignment Check",
+ "Machine Check",
+ "SIMD Floating-Point Exception",
+ "Virtualization Exception",
+ "Control Protection Exception",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Hypervisor Injection Exception",
+ "VMM Communication Exception",
+ "Security Exception",
+ "Reserved"};
+
+/**
+ * @brief dump register information
+ * @param frame ureg registers
+ */
+static void dump_fault(ureg_t* frame);
+
+/**
+ * @brief handle a fault
+ * @param frame ureg registers
+ */
+void handle_fault(ureg_t* frame) {
+ thread_t* current = get_current();
+ if (frame->cause == SWEXN_CAUSE_PAGEFAULT && frame->cr2 >= USER_MEM_START) {
+ /* check ZFOD */
+ pa_t old_pa;
+ page_directory_t* pd =
+ (page_directory_t*)map_phys_page(current->process->cr3, &old_pa);
+ pde_t* pde = &(*pd)[get_pd_index(frame->cr2)];
+ if (*pde != BAD_PDE) {
+ pa_t pt_pa = get_page_table(*pde);
+ page_table_t* pt = (page_table_t*)map_phys_page(pt_pa, NULL);
+ pte_t* pte = &(*pt)[get_pt_index(frame->cr2)];
+ if (*pte != BAD_PTE &&
+ ((*pte & (PTE_PRESENT << PTE_P_SHIFT)) == 0)) {
+ *pte = (*pte | (PTE_PRESENT << PTE_P_SHIFT));
+ invlpg(frame->cr2);
+ memset((void*)(frame->cr2 & PAGE_BASE_MASK), 0, PAGE_SIZE);
+ map_phys_page(old_pa, NULL);
+ return;
+ }
+ }
+ map_phys_page(old_pa, NULL);
+ }
+ if (frame->cs == SEGSEL_KERNEL_CS) {
+ /* recover from accessing user memory */
+ if ((frame->cause == SWEXN_CAUSE_PAGEFAULT ||
+ frame->cause == SWEXN_CAUSE_PROTFAULT) &&
+ current->eip0 != 0) {
+ frame->eip = current->eip0;
+ return;
+ }
+ /* all other non recoverable fault */
+ dump_fault(frame);
+ while (1) {
+ }
+ }
+ /* send to user swexn */
+ if (current->eip3 != 0 && current->df3 == 0) {
+ va_t esp3 = (va_t)current->esp3;
+ esp3 -= sizeof(ureg_t);
+ esp3 = (esp3 & (~(sizeof(va_t) - 1)));
+ /* push ureg */
+ if (copy_to_user(esp3, sizeof(ureg_t), frame) != 0) {
+ goto kill_thread;
+ }
+ /* push swexn args (return address, swexn arg, ureg)
+ * swexn handler must call swexn to jump back, we use 0 address to cause
+ * a GP on function return
+ */
+ unsigned long stack[3] = {0, current->swexn_arg, (unsigned long)esp3};
+ unsigned long* new_esp = (unsigned long*)esp3;
+ new_esp -= 3;
+ if (copy_to_user((va_t)new_esp, 3 * sizeof(unsigned long), stack) !=
+ 0) {
+ goto kill_thread;
+ }
+ frame->edi = 0;
+ frame->esi = 0;
+ frame->ebp = 0;
+ frame->zero = 0;
+ frame->ebx = 0;
+ frame->edx = 0;
+ frame->ecx = 0;
+ frame->eax = 0;
+ frame->eip = current->eip3;
+ frame->eflags = DEFAULT_EFLAGS;
+ frame->esp = (unsigned int)new_esp;
+ current->df3 = 1;
+ current->eip3 = 0;
+ return;
+ }
+ /* no swexn handler or fault inside swexn */
+kill_thread:
+ sim_printf("LWP %d killed: %s", current->rb_node.key,
+ reasons[frame->cause]);
+ printf("LWP %d killed: %s\n", current->rb_node.key, reasons[frame->cause]);
+ if (current->process->refcount == 1) {
+ current->process->exit_value = -2;
+ }
+ kill_current();
+}
+
+static void dump_fault(ureg_t* frame) {
+ sim_printf("Fault: %s Error Code: %08x", reasons[frame->cause],
+ frame->error_code);
+ sim_printf("CS:EIP=%04x:%08x EFLAGS=%08x", frame->cs, frame->eip,
+ frame->eflags);
+ sim_printf("EAX=%08x EBX=%08x ECX=%08x EDX=%08x", frame->eax, frame->ebx,
+ frame->ecx, frame->edx);
+ sim_printf("ESI=%08x EDI=%08x ESP=%08x EBP=%08x", frame->esi, frame->edi,
+ frame->esp, frame->ebp);
+ printf("=========================\n");
+ printf("Fault: %s Error Code: %08x\n", reasons[frame->cause],
+ frame->error_code);
+ printf("CS:EIP=%04x:%08x EFLAGS=%08x\n", frame->cs, frame->eip,
+ frame->eflags);
+ printf("EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n", frame->eax, frame->ebx,
+ frame->ecx, frame->edx);
+ printf("ESI=%08x EDI=%08x ESP=%08x EBP=%08x\n", frame->esi, frame->edi,
+ frame->esp, frame->ebp);
+}
diff --git a/kern/interrupt_asm.S b/kern/interrupt_asm.S
new file mode 100644
index 0000000..99176e4
--- /dev/null
+++ b/kern/interrupt_asm.S
@@ -0,0 +1,134 @@
+/** @file interrupt_asm.S
+ *
+ * @brief fault and interrupt handlers.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <x86/seg.h>
+#include <x86/interrupt_defines.h>
+
+#define SEGSEL_KERNEL_FS SEGSEL_SPARE0
+
+.global default_handler
+default_handler:
+ iret
+
+.macro FAULT_HANDLER_ERR name cause
+.global \name\()_handler
+.type \name\()_handler, %function
+\name\()_handler:
+ pusha /* save a ureg_t structure */
+ push %gs
+ push %fs
+ push %es
+ push %ds
+ mov %cr2, %eax
+ push %eax
+ push $\cause
+ mov $SEGSEL_KERNEL_DS, %eax
+ mov %ax, %ds
+ mov %ax, %es
+ mov $SEGSEL_KERNEL_FS, %eax
+ mov %ax, %fs
+ cld
+ push %esp
+ call handle_fault
+ add $0xc, %esp /* skip cr2, cause and argument */
+ pop %ds
+ pop %es
+ pop %fs
+ pop %gs
+ popa
+ add $0x4, %esp /* skip error code */
+ iret
+.endm
+
+.macro FAULT_HANDLER_NOERR name cause
+.global \name\()_handler
+.type \name\()_handler, %function
+\name\()_handler:
+ sub $0x4, %esp /* dummy error code */
+ pusha
+ push %gs
+ push %fs
+ push %es
+ push %ds
+ mov %cr2, %eax
+ push %eax
+ push $\cause
+ mov $SEGSEL_KERNEL_DS, %eax
+ mov %ax, %ds
+ mov %ax, %es
+ mov $SEGSEL_KERNEL_FS, %eax
+ mov %ax, %fs
+ cld
+ push %esp
+ call handle_fault
+ add $0xc, %esp /* skip cr2, cause and argument */
+ pop %ds
+ pop %es
+ pop %fs
+ pop %gs
+ popa
+ add $0x4, %esp /* skip error code */
+ iret
+.endm
+
+FAULT_HANDLER_NOERR de 0x0
+FAULT_HANDLER_NOERR db 0x1
+FAULT_HANDLER_NOERR nmi 0x2
+FAULT_HANDLER_NOERR bp 0x3
+FAULT_HANDLER_NOERR of 0x4
+FAULT_HANDLER_NOERR br 0x5
+FAULT_HANDLER_NOERR ud 0x6
+FAULT_HANDLER_NOERR nm 0x7
+FAULT_HANDLER_ERR df 0x8
+FAULT_HANDLER_NOERR cso 0x9
+FAULT_HANDLER_ERR ts 0xa
+FAULT_HANDLER_ERR np 0xb
+FAULT_HANDLER_ERR ss 0xc
+FAULT_HANDLER_ERR gp 0xd
+FAULT_HANDLER_ERR pf 0xe
+FAULT_HANDLER_NOERR mf 0x10
+FAULT_HANDLER_ERR ac 0x11
+FAULT_HANDLER_NOERR mc 0x12
+FAULT_HANDLER_NOERR xf 0x13
+
+#define SEGSEL_KERNEL_FS SEGSEL_SPARE0
+
+.global timer_handler
+.type timer_handler, %function
+timer_handler:
+ pusha /* save a stack_frame_t structure */
+ push %ds
+ push %es
+ push %fs
+ push %gs
+ mov $SEGSEL_KERNEL_DS, %eax
+ mov %ax, %ds
+ mov %ax, %es
+ mov $SEGSEL_KERNEL_FS, %eax
+ mov %ax, %fs
+ cld
+ call timer_handler_real
+ jmp return_to_user /* check pending exit before iret */
+
+
+.global kbd_handler
+.type kbd_handler, %function
+kbd_handler:
+ pusha /* save a stack_frame_t structure */
+ push %ds
+ push %es
+ push %fs
+ push %gs
+ mov $SEGSEL_KERNEL_DS, %eax
+ mov %ax, %ds
+ mov %ax, %es
+ mov $SEGSEL_KERNEL_FS, %eax
+ mov %ax, %fs
+ cld
+ call kbd_handler_real
+ jmp return_to_user /* check pending exit before iret */
diff --git a/kern/kbd.c b/kern/kbd.c
new file mode 100644
index 0000000..5ba1bcb
--- /dev/null
+++ b/kern/kbd.c
@@ -0,0 +1,218 @@
+/** @file kbd.c
+ *
+ * @brief Keyboard handler implementation.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <x86/asm.h>
+#include <x86/interrupt_defines.h>
+#include <x86/keyhelp.h>
+
+#include <console.h>
+#include <interrupt.h>
+#include <kbd.h>
+#include <paging.h>
+#include <sync.h>
+#include <usermem.h>
+
+/** a node in queue of threads requesting keyboard access */
+typedef struct kbd_request_s {
+ thread_t* t;
+ queue_t node;
+} kbd_request_t;
+
+/** size of scancode ring buffer, a big buffer enough for someone pressing
+ * keyboard fast */
+#define SC_RING_SIZE PAGE_SIZE
+/** size of character ring buffer, a big buffer enough for someone pressing
+ * keyboard fast */
+#define CHR_RING_SIZE PAGE_SIZE
+/** maximum readline size */
+#define MAX_READLINE (CHR_RING_SIZE - 1)
+
+/** queue of threads requesting keyboard access */
+static queue_t* inputs = NULL;
+
+/** mutex for keyboard request queue */
+static mutex_t kbd_request_lock = MUTEX_INIT;
+/** signal for a thread leaving request queue */
+static cv_t kbd_request_cv = CV_INIT;
+
+/** mutex for new scancode input */
+static mutex_t input_lock = MUTEX_INIT;
+/** signal for new scancode input */
+static cv_t input_cv = CV_INIT;
+/** need pic acknowlege */
+static int need_ack = 0;
+
+/** scancode ring buffer */
+static unsigned char sc_ring[SC_RING_SIZE];
+/** read pointer of scancode ring buffer */
+static int sc_r_pos = 0;
+/** write pointer of scancode ring buffer */
+static int sc_w_pos = 0;
+
+/** character ring buffer */
+static char chr_ring[CHR_RING_SIZE];
+/** read pointer of character ring buffer */
+static int chr_r_pos = 0;
+/** write pointer of character ring buffer */
+static int chr_w_pos = 0;
+
+/**
+ * @brief keyboard inturrupt handler
+ */
+void kbd_handler_real() {
+ unsigned char sc = inb(KEYBOARD_PORT);
+ int next_w_pos = (sc_w_pos + 1) % SC_RING_SIZE;
+ if (next_w_pos != sc_r_pos) {
+ sc_ring[sc_w_pos] = sc;
+ sc_w_pos = next_w_pos;
+ pic_acknowledge(KBD_IRQ);
+ } else {
+ /* let processing thread ack after processing scancode */
+ need_ack = 1;
+ }
+ cv_signal(&input_cv);
+}
+
+/**
+ * @brief process scancode until a char is produced
+ * @return char produced
+ */
+static int sc_process();
+
+/**
+ * @brief flush a line to user buffer
+ * @param len length of buffer
+ * @param buf buffer
+ * @return size of chars flushed, -1: buf invalid
+ */
+static int flush_line(int len, va_t buf);
+
+int do_readline(int len, va_t buf) {
+ if (len < 0 || len > MAX_READLINE) {
+ return -1;
+ }
+ if (len == 0) {
+ return 0;
+ }
+ kbd_request_t req;
+ req.t = get_current();
+ /* wait until our turn */
+ mutex_lock(&kbd_request_lock);
+ queue_insert_tail(&inputs, &req.node);
+ while (inputs != &req.node) {
+ cv_wait(&kbd_request_cv, &kbd_request_lock);
+ }
+ mutex_unlock(&kbd_request_lock);
+ int should_flush = 0;
+ if (chr_r_pos != chr_w_pos) {
+ int i;
+ for (i = chr_r_pos; i != chr_w_pos; i = (i + 1) % CHR_RING_SIZE) {
+ if (chr_ring[i] == '\n') {
+ should_flush = 1;
+ break;
+ }
+ }
+ /* check if ring is full */
+ if (chr_r_pos == (chr_w_pos + 1) % CHR_RING_SIZE) {
+ should_flush = 1;
+ }
+ }
+ while (should_flush == 0) {
+ /* wait for more chars */
+ char c = sc_process();
+ if (c == '\b') {
+ if (chr_w_pos != chr_r_pos) {
+ putbyte(c);
+ chr_w_pos = (chr_w_pos + CHR_RING_SIZE - 1) % CHR_RING_SIZE;
+ }
+ } else {
+ chr_ring[chr_w_pos] = c;
+ chr_w_pos = (chr_w_pos + 1) % CHR_RING_SIZE;
+ putbyte(c);
+ should_flush =
+ (c == '\n') || (chr_r_pos == (chr_w_pos + 1) % CHR_RING_SIZE);
+ }
+ }
+ int result = flush_line(len, buf);
+ mutex_lock(&kbd_request_lock);
+ queue_detach(&inputs, &req.node);
+ cv_signal(&kbd_request_cv);
+ mutex_unlock(&kbd_request_lock);
+ return result;
+}
+
+int do_getchar() {
+ kbd_request_t req;
+ req.t = get_current();
+ mutex_lock(&kbd_request_lock);
+ queue_insert_tail(&inputs, &req.node);
+ while (inputs != &req.node) {
+ cv_wait(&kbd_request_cv, &kbd_request_lock);
+ }
+ mutex_unlock(&kbd_request_lock);
+ int result;
+ if (chr_r_pos != chr_w_pos) {
+ result = chr_ring[chr_r_pos];
+ chr_r_pos = (chr_r_pos + 1) % CHR_RING_SIZE;
+ } else {
+ result = sc_process();
+ }
+ mutex_lock(&kbd_request_lock);
+ queue_detach(&inputs, &req.node);
+ cv_signal(&kbd_request_cv);
+ mutex_unlock(&kbd_request_lock);
+ return result;
+}
+
+static int flush_line(int len, va_t buf) {
+ int size = 0, i;
+ for (i = chr_r_pos; i != chr_w_pos; i = (i + 1) % CHR_RING_SIZE) {
+ size++;
+ if (chr_ring[i] == '\n') {
+ break;
+ }
+ }
+ if (size > len) {
+ size = len;
+ }
+ for (i = 0; i < size; i++) {
+ if (copy_to_user(buf + i, 1,
+ &chr_ring[(chr_r_pos + i) % CHR_RING_SIZE]) != 0) {
+ /* failed to flush, do not discard anything */
+ return -1;
+ }
+ }
+ chr_r_pos = (chr_r_pos + i) % CHR_RING_SIZE;
+ return size;
+}
+
+static int sc_process() {
+ while (1) {
+ /* wait for more scancode */
+ mutex_lock(&input_lock);
+ while (sc_r_pos == sc_w_pos) {
+ cv_wait(&input_cv, &input_lock);
+ }
+ int sc = sc_ring[sc_r_pos];
+ sc_r_pos = (sc_r_pos + 1) % SC_RING_SIZE;
+ mutex_unlock(&input_lock);
+ if (need_ack == 1) {
+ need_ack = 0;
+ pic_acknowledge(KBD_IRQ);
+ }
+ kh_type kh = process_scancode(sc);
+ if (KH_HASDATA(kh) && KH_ISMAKE(kh)) {
+ return KH_GETCHAR(kh);
+ }
+ }
+}
diff --git a/kern/kernel.c b/kern/kernel.c
new file mode 100644
index 0000000..e473022
--- /dev/null
+++ b/kern/kernel.c
@@ -0,0 +1,109 @@
+/** @file kernel.c
+ *
+ * @brief kernel entry.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <common_kern.h>
+
+/* libc includes. */
+#include <simics.h> /* lprintf() */
+#include <stdio.h>
+#include <string.h>
+
+/* multiboot header file */
+#include <mptable.h>
+#include <multiboot.h> /* boot_info */
+#include <smp.h>
+
+/* x86 specific includes */
+#include <x86/asm.h> /* enable_interrupts() */
+#include <x86/cr.h>
+
+#include <assert.h>
+#include <common.h>
+#include <console.h>
+#include <interrupt.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <timer.h>
+
+/**
+ * @brief entry for a cpu core
+ * @param cpuid cpuid
+ */
+static void kernel_smp_entry(int cpuid);
+
+/**
+ * @brief main function for a cpu core
+ */
+static void kernel_smp_main();
+
+/** @brief Kernel entrypoint.
+ *
+ * This is the entrypoint for the kernel.
+ *
+ * @return Does not return
+ */
+int kernel_main(mbinfo_t* mbinfo, int argc, char** argv, char** envp) {
+ int smp_good = (smp_init(mbinfo) == 0);
+ paging_init();
+ percpu_t percpu;
+ setup_percpu(&percpu);
+ thread_t kthread;
+ process_t kprocess;
+ setup_kth(&kthread, &kprocess);
+ set_mapped_phys_page(mapped_phys_pages);
+ set_mapped_phys_page_pte(mapped_phys_page_ptes);
+
+ idt_init();
+ mm_init();
+ timer_init();
+ clear_console();
+
+ const char* init_args[] = {INIT_NAME};
+ thread_t* init = create_process(0, INIT_NAME, 1, init_args);
+ init_process = init->process;
+ add_thread(init);
+ insert_ready_tail(init);
+
+ if (smp_good && smp_num_cpus() > 1 && 0) {
+ /* SMP not enabled */
+ smp_boot(kernel_smp_entry);
+ }
+
+ kernel_smp_main();
+ return -1;
+}
+
+static void kernel_smp_entry(int cpuid) {
+ paging_enable();
+ percpu_t percpu;
+ setup_percpu(&percpu);
+ thread_t kthread;
+ process_t kprocess;
+ setup_kth(&kthread, &kprocess);
+ set_mapped_phys_page(mapped_phys_pages + cpuid * PAGE_SIZE);
+ set_mapped_phys_page_pte(mapped_phys_page_ptes + cpuid);
+ kernel_smp_main();
+}
+
+static void kernel_smp_main() {
+ const char* idle_args[] = {IDLE_NAME};
+ thread_t* idle = create_process(0, IDLE_NAME, 1, idle_args);
+ add_thread(idle);
+ set_idle(idle);
+
+ while (1) {
+ int old_if = spl_lock(&ready_lock);
+ thread_t* t = select_next();
+ yield_to_spl_unlock(t, &ready_lock, old_if);
+ /* when a thread borrow kthread's stack and free itself, it will return
+ * here and we continue yielding
+ */
+ }
+ panic("kthread should not return");
+}
diff --git a/kern/loader.c b/kern/loader.c
new file mode 100644
index 0000000..2dfd8e3
--- /dev/null
+++ b/kern/loader.c
@@ -0,0 +1,58 @@
+/**
+ * The 15-410 kernel project.
+ * @name loader.c
+ *
+ * Functions for the loading
+ * of user programs from binary
+ * files should be written in
+ * this file. The function
+ * elf_load_helper() is provided
+ * for your use.
+ */
+
+/* --- Includes --- */
+#include <elf_410.h>
+#include <exec2obj.h>
+#include <loader.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <string.h>
+
+/* --- Local function prototypes --- */
+
+/**
+ * Copies data from a file into a buffer.
+ *
+ * @param filename the name of the file to copy data from
+ * @param offset the location in the file to begin copying from
+ * @param size the number of bytes to be copied
+ * @param buf the buffer to copy the data into
+ *
+ * @return returns the number of bytes copied on succes; -1 on failure
+ */
+int getbytes(const char* filename, int offset, int size, char* buf) {
+ file_t* f = find_file(filename);
+ if (f == NULL || offset > f->execlen) {
+ return -1;
+ }
+ return read_file(f, offset, size, buf);
+}
+
+int read_file(file_t* f, int offset, int size, char* buf) {
+ int src_size = size;
+ if (f->execlen - offset < size) {
+ src_size = f->execlen - offset;
+ }
+ memcpy(buf, f->execbytes + offset, src_size);
+ return src_size;
+}
+
+file_t* find_file(const char* name) {
+ int i;
+ for (i = 0; i < exec2obj_userapp_count; i++) {
+ if (strcmp(name, exec2obj_userapp_TOC[i].execname) == 0) {
+ return &exec2obj_userapp_TOC[i];
+ }
+ }
+ return NULL;
+}
diff --git a/kern/malloc_wrappers.c b/kern/malloc_wrappers.c
new file mode 100644
index 0000000..6b4d525
--- /dev/null
+++ b/kern/malloc_wrappers.c
@@ -0,0 +1,70 @@
+/** @file malloc_wrappers.c
+ *
+ * @brief locked malloc functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <malloc.h>
+#include <malloc_internal.h>
+#include <stddef.h>
+
+#include <sync.h>
+
+mutex_t malloc_lock = MUTEX_INIT;
+
+/* safe versions of malloc functions */
+void* malloc(size_t size) {
+ mutex_lock(&malloc_lock);
+ void* mem = _malloc(size);
+ mutex_unlock(&malloc_lock);
+ return mem;
+}
+
+void* memalign(size_t alignment, size_t size) {
+ mutex_lock(&malloc_lock);
+ void* mem = _memalign(alignment, size);
+ mutex_unlock(&malloc_lock);
+ return mem;
+}
+
+void* calloc(size_t nelt, size_t eltsize) {
+ mutex_lock(&malloc_lock);
+ void* mem = _calloc(nelt, eltsize);
+ mutex_unlock(&malloc_lock);
+ return mem;
+}
+
+void* realloc(void* buf, size_t new_size) {
+ mutex_lock(&malloc_lock);
+ void* mem = _realloc(buf, new_size);
+ mutex_unlock(&malloc_lock);
+ return mem;
+}
+
+void free(void* buf) {
+ mutex_lock(&malloc_lock);
+ _free(buf);
+ mutex_unlock(&malloc_lock);
+}
+
+void* smalloc(size_t size) {
+ mutex_lock(&malloc_lock);
+ void* mem = _smalloc(size);
+ mutex_unlock(&malloc_lock);
+ return mem;
+}
+
+void* smemalign(size_t alignment, size_t size) {
+ mutex_lock(&malloc_lock);
+ void* mem = _smemalign(alignment, size);
+ mutex_unlock(&malloc_lock);
+ return mem;
+}
+
+void sfree(void* buf, size_t size) {
+ mutex_lock(&malloc_lock);
+ _sfree(buf, size);
+ mutex_unlock(&malloc_lock);
+}
diff --git a/kern/mm.c b/kern/mm.c
new file mode 100644
index 0000000..2c40af8
--- /dev/null
+++ b/kern/mm.c
@@ -0,0 +1,318 @@
+/** @file mm.c
+ *
+ * @brief memory management functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <assert.h>
+#include <common_kern.h>
+#include <x86/asm.h>
+
+#include <asm_instr.h>
+#include <common.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <sync.h>
+
+/** header of a free page */
+typedef struct page_head_s {
+ int size;
+ pa_t prev;
+ pa_t next;
+} page_head_t;
+
+/** footer of a free page */
+typedef struct page_foot_s {
+ char padding[PAGE_SIZE - sizeof(int)];
+ int size;
+} page_foot_t;
+
+/** total user pages */
+static int num_user_pages;
+
+/** bitmap to track page usage */
+static unsigned char* user_page_bitmap;
+
+/** bins from 4k to 1m, which is normal request size */
+#define NUM_BINS 9
+/** minimum size of last bin */
+#define LAST_BIN_SIZE (1 << (NUM_BINS - 1))
+/** bins for free chunks */
+static pa_t bins[9];
+
+mutex_t mm_lock = MUTEX_INIT;
+
+/**
+ * @brief is a page inuse
+ * @param pn physical page number
+ * @return 1 inuse, 0 free
+ */
+static int is_page_inuse(int pn) {
+ return (user_page_bitmap[(pn + 1) / 8] & (1 << ((pn + 1) % 8))) != 0;
+}
+
+/**
+ * @brief mark a page inuse
+ * @param pn physical page number
+ */
+static void set_page_inuse(int pn) {
+ user_page_bitmap[(pn + 1) / 8] |= (1 << ((pn + 1) % 8));
+}
+
+/**
+ * @brief mark a page free
+ * @param pn physical page number
+ */
+static void set_page_free(int pn) {
+ user_page_bitmap[(pn + 1) / 8] &= (~(1 << ((pn + 1) % 8)));
+}
+
+/**
+ * @brief convert physical address to page number
+ * @param pa physical address
+ * @return physical page number
+ */
+static int pa_to_pn(pa_t pa) {
+ return (pa - USER_MEM_START) / PAGE_SIZE;
+}
+
+/**
+ * @brief convert page number to physical address
+ * @param pn physical page number
+ * @return physical address
+ */
+static int pn_to_pa(int pn) {
+ return pn * PAGE_SIZE + USER_MEM_START;
+}
+
+/**
+ * @brief find which bin a free chunk belongs to
+ * @param size size of chunk
+ * @return bin
+ */
+static int find_bin(int size) {
+ if (size >= LAST_BIN_SIZE) {
+ return NUM_BINS - 1;
+ }
+ int i;
+ for (i = NUM_BINS - 2; i > 0; i--) {
+ if ((size & (1 << i)) != 0) {
+ return i;
+ }
+ }
+ return 0;
+}
+
+/**
+ * @brief add a chunk to bins
+ * @param pn physical page number
+ * @param size size of chunk
+ */
+static void add_to_bins(int pn, int size);
+
+/**
+ * @brief remove a chunk from bins
+ * @param pn physical page number
+ */
+static void remove_from_bins(int pn);
+
+/**
+ * @brief allocate a memory from a bin
+ * @param bin bin
+ * @param size size of chunk
+ * @return address of chunk, BAD_PA on failure
+ */
+static pa_t alloc_from_bin(pa_t* bin, int size);
+
+void mm_init() {
+ num_user_pages = machine_phys_frames() - (USER_MEM_START / PAGE_SIZE);
+ assert(num_user_pages > 0);
+ user_page_bitmap = smalloc((num_user_pages + 2) / 8);
+ assert(user_page_bitmap != NULL);
+ memset(user_page_bitmap, 0, (num_user_pages + 2) / 8);
+ memset(bins, 0, sizeof(bins));
+
+ set_page_inuse(-1);
+ set_page_inuse(num_user_pages);
+ add_to_bins(pa_to_pn(USER_MEM_START), num_user_pages);
+}
+
+pa_t alloc_user_pages(int num_pages) {
+ mutex_lock(&mm_lock);
+ int bn = find_bin(num_pages);
+ while (bn < NUM_BINS) {
+ pa_t result = alloc_from_bin(&bins[bn], num_pages);
+ if (result != BAD_PA) {
+ mutex_unlock(&mm_lock);
+ return result;
+ }
+ bn++;
+ }
+ mutex_unlock(&mm_lock);
+ return BAD_PA;
+}
+
+void free_user_pages(pa_t pa, int num_pages) {
+ mutex_lock(&mm_lock);
+ int pn = pa_to_pn(pa);
+ int final_pn = pn, final_size = num_pages;
+
+ /* merge with prev */
+ int prev_inuse = is_page_inuse(pn - 1);
+ if (prev_inuse == 0) {
+ int old_if = save_clear_if();
+ page_foot_t* prev_foot =
+ (page_foot_t*)map_phys_page(pn_to_pa(pn - 1), NULL);
+ int prev_size = prev_foot->size;
+ restore_if(old_if);
+ int prev_pn = pn - prev_size;
+ remove_from_bins(prev_pn);
+ final_pn = prev_pn;
+ final_size += prev_size;
+ }
+
+ /* merge with next */
+ int next_pn = pn + num_pages;
+ int next_inuse = is_page_inuse(next_pn);
+ if (next_inuse == 0) {
+ int old_if = save_clear_if();
+ page_head_t* next_head =
+ (page_head_t*)map_phys_page(pn_to_pa(next_pn), NULL);
+ int next_size = next_head->size;
+ restore_if(old_if);
+ remove_from_bins(next_pn);
+ final_size += next_size;
+ }
+
+ add_to_bins(final_pn, final_size);
+ mutex_unlock(&mm_lock);
+}
+
+va_t map_phys_page(pa_t pa, pa_t* old_pa) {
+ pte_t* pte = get_mapped_phys_page_pte();
+ if (old_pa != NULL) {
+ *old_pa = get_page_base(*pte);
+ }
+ *pte = make_pte(pa, 0, PTE_SUPERVISOR, PTE_RW, PTE_PRESENT);
+ va_t va = get_mapped_phys_page();
+ invlpg(va);
+ return va;
+}
+
+/**
+ * @brief insert a chunk to a bin
+ * @param bin bin
+ * @param this_chunk chunk
+ * @param size size of chunk
+ */
+static void bin_insert(pa_t* bin, pa_t this_chunk, int size) {
+ int old_if = save_clear_if();
+ if (*bin == BAD_PA) {
+ page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
+ this_head->next = this_head->prev = this_chunk;
+ this_head->size = size;
+ } else {
+ pa_t head_chunk = *bin;
+ page_head_t* head_head = (page_head_t*)map_phys_page(head_chunk, NULL);
+ pa_t tail_chunk = head_head->prev;
+ head_head->prev = this_chunk;
+ page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
+ this_head->next = head_chunk;
+ this_head->prev = tail_chunk;
+ this_head->size = size;
+ page_head_t* tail_head = (page_head_t*)map_phys_page(tail_chunk, NULL);
+ tail_head->next = this_chunk;
+ }
+ restore_if(old_if);
+ *bin = this_chunk;
+}
+
+/**
+ * @brief remove a chunk from a bin
+ * @param bin bin
+ * @param this_chunk chunk
+ */
+static void bin_delete(pa_t* bin, pa_t this_chunk) {
+ int old_if = save_clear_if();
+ page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
+ pa_t next_chunk = this_head->next;
+ if (*bin == this_chunk) {
+ *bin = (next_chunk == this_chunk ? BAD_PA : next_chunk);
+ }
+ if (next_chunk != this_chunk) {
+ pa_t prev_chunk = this_head->prev;
+ page_head_t* next_head = (page_head_t*)map_phys_page(next_chunk, NULL);
+ next_head->prev = prev_chunk;
+ page_head_t* prev_head = (page_head_t*)map_phys_page(prev_chunk, NULL);
+ prev_head->next = next_chunk;
+ }
+ restore_if(old_if);
+}
+
+static void add_to_bins(int pn, int size) {
+ set_page_free(pn);
+ set_page_free(pn + (size - 1));
+ pa_t this_chunk = pn_to_pa(pn);
+ pa_t* bin = &bins[find_bin(size)];
+
+ bin_insert(bin, this_chunk, size);
+
+ int old_if = save_clear_if();
+ page_foot_t* this_foot =
+ (page_foot_t*)map_phys_page(pn_to_pa(pn + (size - 1)), NULL);
+ this_foot->size = size;
+ restore_if(old_if);
+}
+
+static void remove_from_bins(int pn) {
+ pa_t this_chunk = pn_to_pa(pn);
+ int old_if = save_clear_if();
+ page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
+ int size = this_head->size;
+ restore_if(old_if);
+ pa_t* bin = &bins[find_bin(size)];
+ bin_delete(bin, this_chunk);
+}
+
+static pa_t alloc_from_bin(pa_t* bin, int size) {
+ if (*bin == BAD_PA) {
+ return BAD_PA;
+ }
+ pa_t r_pa = BAD_PA;
+ pa_t r_pn = 0;
+ pa_t this_chunk = *bin, end = *bin;
+ do {
+ int old_if = save_clear_if();
+ page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
+ int this_size = this_head->size;
+ pa_t next_chunk = this_head->next;
+ restore_if(old_if);
+ if (this_size > size) {
+ bin_delete(bin, this_chunk);
+ r_pa = this_chunk;
+ r_pn = pa_to_pn(r_pa);
+ add_to_bins(r_pn + size, this_size - size);
+ break;
+ } else if (this_size == size) {
+ bin_delete(bin, this_chunk);
+ r_pa = this_chunk;
+ r_pn = pa_to_pn(r_pa);
+ break;
+ }
+ this_chunk = next_chunk;
+ } while (this_chunk != end);
+ if (r_pa == BAD_PA) {
+ return BAD_PA;
+ }
+ set_page_inuse(r_pn);
+ set_page_inuse(r_pn + (size - 1));
+ return r_pa;
+}
diff --git a/kern/paging.c b/kern/paging.c
new file mode 100644
index 0000000..568db84
--- /dev/null
+++ b/kern/paging.c
@@ -0,0 +1,76 @@
+/** @file paging.c
+ *
+ * @brief paging management functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <common_kern.h>
+#include <malloc_internal.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <apic.h>
+#include <mptable.h>
+#include <smp.h>
+#include <x86/asm.h>
+#include <x86/cr.h>
+
+#include <asm_instr.h>
+#include <paging.h>
+#include <sched.h>
+
+/** number of page tables for kernel memory */
+#define NUM_KERNEL_PT (USER_MEM_START / PAGE_SIZE / NUM_PAGE_ENTRY)
+
+page_directory_t* kernel_pd;
+/** page tables for kernel memory */
+static page_table_t* kernel_pt;
+
+va_t mapped_phys_pages;
+pte_t* mapped_phys_page_ptes;
+
+void paging_init() {
+ kernel_pd = (page_directory_t*)_smemalign(PAGE_SIZE, PAGE_SIZE);
+ kernel_pt = (page_table_t*)_smemalign(PAGE_SIZE, PAGE_SIZE * NUM_KERNEL_PT);
+
+ int num_cpus = smp_num_cpus();
+ mapped_phys_pages = (va_t)_smemalign(PAGE_SIZE, num_cpus * PAGE_SIZE);
+ mapped_phys_page_ptes = (pte_t*)kernel_pt + (mapped_phys_pages / PAGE_SIZE);
+
+ if (kernel_pd == NULL || kernel_pt == NULL || mapped_phys_pages == 0 ||
+ mapped_phys_page_ptes == NULL) {
+ panic("no space for kernel page table");
+ }
+
+ int i, j;
+ pa_t base = 0;
+ memset(kernel_pd, 0, sizeof(page_directory_t));
+ for (i = 0; i < (USER_MEM_START / PT_SIZE); i++) {
+ (*kernel_pd)[i] =
+ make_pde((pa_t)&kernel_pt[i], PTE_SUPERVISOR, PTE_RW, PTE_PRESENT);
+ for (j = 0; j < NUM_PAGE_ENTRY; j++) {
+ kernel_pt[i][j] =
+ make_pte(base, PTE_G, PTE_SUPERVISOR, PTE_RW, PTE_PRESENT);
+ base += PAGE_SIZE;
+ }
+ }
+
+ pa_t lapic_pa = (pa_t)smp_lapic_base();
+ pte_t* lapic_pte = (pte_t*)kernel_pt + (LAPIC_VIRT_BASE / PAGE_SIZE);
+ *lapic_pte =
+ (make_pte(lapic_pa, PTE_G, PTE_SUPERVISOR, PTE_RW, PTE_PRESENT) |
+ (PTE_PCD << PTE_PCD_SHIFT));
+ invlpg(LAPIC_VIRT_BASE);
+
+ set_cr4(get_cr4() | CR4_PSE | CR4_PGE);
+ set_cr3((uint32_t)kernel_pd);
+ set_cr0(get_cr0() | CR0_PE | CR0_PG);
+}
+
+void paging_enable() {
+ set_cr4(get_cr4() | CR4_PSE | CR4_PGE);
+ set_cr3((uint32_t)kernel_pd);
+ set_cr0(get_cr0() | CR0_PE | CR0_PG);
+}
diff --git a/kern/sched.c b/kern/sched.c
new file mode 100644
index 0000000..eb8d334
--- /dev/null
+++ b/kern/sched.c
@@ -0,0 +1,762 @@
+/** @file sched.c
+ *
+ * @brief scheduling functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <malloc_internal.h>
+#include <string.h>
+#include <x86/asm.h>
+#include <x86/cr.h>
+#include <x86/eflags.h>
+#include <x86/seg.h>
+
+#include <asm_instr.h>
+#include <loader.h>
+#include <malloc.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <sync.h>
+
+/* .text, .rodata, .data+bss, stack and a heap and one for future new_pages */
+#define INIT_NUM_REGIONS 6
+
+/**
+ * @brief load a elf to memory space
+ * @param p process
+ * @param elf elf header
+ * @param exe elf file name
+ * @return 0 on success, -1 on failure
+ */
+static int process_load_elf(process_t* p, simple_elf_t* elf, char* exe);
+/**
+ * @brief load a elf segment to memory space
+ * @param p process
+ * @param f elf file entry, NULL for empty segments
+ * @param offset offset in elf file
+ * @param f_len length of data in elf file
+ * @param vaddr addr to load to
+ * @param m_len length of segment
+ * @param is_rw is segment writable
+ * @return 0 on success, -1 on failure
+ */
+static int load_segment(process_t* p,
+ file_t* f,
+ int offset,
+ int f_len,
+ va_t vaddr,
+ int m_len,
+ int is_rw);
+
+process_t* init_process;
+
+spl_t ready_lock = SPL_INIT;
+queue_t* ready = NULL;
+
+rb_t* threads = &rb_nil;
+mutex_t threads_lock = MUTEX_INIT;
+
+/* G means global segment */
+#define GDT_G_BIT 0x0080000000000000ull
+/* FLAG is control flags for segment */
+#define GDT_FLAG_MASK 0x00f0ff0000000000ull
+/* high part of base address */
+#define GDT_BASE_MASK_HI 0xff000000ull
+/* low part of base address */
+#define GDT_BASE_MASK_LO 0x00ffffffull
+/* position of high part of base address */
+#define GDT_BASE_SHIFT_HI (64 - 32)
+/* position of low part of base address */
+#define GDT_BASE_SHIFT_LO (40 - 24)
+/* high part of limit */
+#define GDT_LIMIT_MASK_HI 0xf0000ull
+/* low part of limit */
+#define GDT_LIMIT_MASK_LO 0x0ffffull
+/* position of high part of limit */
+#define GDT_LIMIT_SHIFT_HI (52 - 20)
+/* position of low part of limit */
+#define GDT_LIMIT_SHIFT_LO (16 - 16)
+
+/**
+ * @brief set fs
+ * @param fs fs
+ */
+void set_fs(reg_t fs);
+
+void setup_percpu(percpu_t* percpu) {
+ uint64_t* gdt = (uint64_t*)gdt_base();
+ uint64_t ds = gdt[SEGSEL_KERNEL_DS_IDX];
+ /* copy ds's flags so we do not need to create one */
+ uint64_t ds_flags = (ds & GDT_FLAG_MASK);
+ uint64_t base = (uint64_t)(uint32_t)percpu;
+ uint64_t limit = base + sizeof(percpu_t) - 1;
+ uint64_t fs = (((base & GDT_BASE_MASK_HI) << GDT_BASE_SHIFT_HI) |
+ ((base & GDT_BASE_MASK_LO) << GDT_BASE_SHIFT_LO) |
+ ((limit & GDT_LIMIT_MASK_HI) << GDT_LIMIT_SHIFT_HI) |
+ ((limit & GDT_LIMIT_MASK_LO) << GDT_LIMIT_SHIFT_LO) |
+ (ds_flags & (~GDT_G_BIT)));
+ gdt[SEGSEL_KERNEL_FS_IDX] = fs;
+ lgdt(gdt, GDT_SEGS * sizeof(uint64_t) - 1);
+ set_fs(SEGSEL_KERNEL_FS);
+}
+
+void setup_kth(thread_t* kthread, process_t* kprocess) {
+ memset(kthread, 0, sizeof(thread_t));
+ memset(kprocess, 0, sizeof(process_t));
+ kprocess->refcount = 1;
+ kprocess->cr3 = (pa_t)kernel_pd;
+ kthread->process = kprocess;
+ set_current(kthread);
+ set_kthread(kthread);
+}
+
+/* exit value when all tasks vanish and status is never set, no religious
+ * meaning
+ */
+#define DEFAULT_EXIT_VALUE 666
+
+thread_t* create_empty_process() {
+ process_t* p = smalloc(sizeof(process_t));
+ if (p == NULL) {
+ goto alloc_pcb_fail;
+ }
+ thread_t* t = smalloc(sizeof(thread_t));
+ if (t == NULL) {
+ goto alloc_tcb_fail;
+ }
+ t->stack = smalloc(K_STACK_SIZE);
+ if (t->stack == NULL) {
+ goto alloc_thread_stack_fail;
+ }
+ p->exit_value = DEFAULT_EXIT_VALUE;
+ p->parent = NULL;
+ p->refcount_lock = MUTEX_INIT;
+ p->refcount = 1;
+ p->threads = NULL;
+ p->nchilds = 0;
+ p->live_childs = NULL;
+ p->dead_childs = NULL;
+ p->nwaiters = 0;
+ p->wait_lock = MUTEX_INIT;
+ p->wait_cv = CV_INIT;
+ if (vector_init(&p->regions, sizeof(region_t), INIT_NUM_REGIONS) != 0) {
+ goto alloc_region_fail;
+ }
+
+ p->cr3 = alloc_user_pages(1);
+ if (p->cr3 == 0) {
+ goto alloc_pd_fail;
+ }
+ int old_if = save_clear_if();
+ page_directory_t* pd = (page_directory_t*)map_phys_page(p->cr3, NULL);
+ memset(pd, 0, PAGE_SIZE);
+ int i;
+ /* copy kernel direct mapping */
+ for (i = 0; i < USER_PD_START; i++) {
+ (*pd)[i] = (*kernel_pd)[i];
+ }
+ restore_if(old_if);
+ p->mm_lock = MUTEX_INIT;
+
+ t->status = THREAD_DEAD;
+ t->status_lock = SPL_INIT;
+ t->pending_exit = 0;
+ queue_insert_head(&p->threads, &t->process_link);
+ t->rb_node.parent = NULL; /* mark that the thread is not added to rbtree */
+ t->process = p;
+ t->eip3 = 0;
+ t->esp0 = t->kernel_esp = (reg_t)&t->stack[K_STACK_SIZE];
+ t->eip0 = 0;
+ t->df3 = 0;
+ return t;
+
+alloc_pd_fail:
+ vector_free(&p->regions);
+alloc_region_fail:
+ sfree(t->stack, K_STACK_SIZE);
+alloc_thread_stack_fail:
+ sfree(t, sizeof(thread_t));
+alloc_tcb_fail:
+ sfree(p, sizeof(process_t));
+alloc_pcb_fail:
+ return NULL;
+}
+
+static int process_load_elf(process_t* p, simple_elf_t* elf, char* exe) {
+ file_t* f = find_file(exe);
+
+ if (elf->e_txtlen != 0 &&
+ load_segment(p, f, elf->e_txtoff, elf->e_txtlen, elf->e_txtstart,
+ elf->e_txtlen, 0) != 0) {
+ goto load_segment_fail;
+ }
+ if (elf->e_rodatlen != 0 &&
+ load_segment(p, f, elf->e_rodatoff, elf->e_rodatlen, elf->e_rodatstart,
+ elf->e_rodatlen, 0) != 0) {
+ goto load_segment_fail;
+ }
+ if (elf->e_datlen != 0 &&
+ load_segment(p, f, elf->e_datoff, elf->e_datlen, elf->e_datstart,
+ elf->e_datlen, 1) != 0) {
+ goto load_segment_fail;
+ }
+ /* if data segment is loaded, try to load bss after data */
+ if (elf->e_datlen != 0) {
+ if (elf->e_bsslen != 0) {
+ /* allow bss to start from unaligned/overlapped address */
+ va_t bss_start = (elf->e_bssstart & PAGE_BASE_MASK);
+ va_t bss_end = ((elf->e_bssstart + elf->e_bsslen + PAGE_SIZE) &
+ PAGE_BASE_MASK);
+ va_t data_start = (elf->e_datstart & PAGE_BASE_MASK);
+ va_t data_end = ((elf->e_datstart + elf->e_datlen + PAGE_SIZE) &
+ PAGE_BASE_MASK);
+ if (bss_start >= data_start && bss_start < data_end) {
+ /* append bss after data */
+ bss_start = data_end;
+ }
+ /* let add_region handle overlap/overflow problem */
+ if ((bss_end > bss_start) &&
+ load_segment(p, NULL, 0, 0, bss_start, bss_end - bss_start,
+ 1) != 0) {
+ goto load_segment_fail;
+ };
+ }
+ } else {
+ if (elf->e_bsslen != 0 && load_segment(p, NULL, 0, 0, elf->e_bssstart,
+ elf->e_bsslen, 1) != 0) {
+ goto load_segment_fail;
+ }
+ }
+
+ if (load_segment(p, NULL, 0, 0, DEFAULT_STACK_POS, DEFAULT_STACK_SIZE, 1)) {
+ goto load_segment_fail;
+ }
+ return 0;
+
+load_segment_fail:
+ return -1;
+}
+
+thread_t* create_process(int tid, char* exe, int argc, const char** argv) {
+ thread_t* t = create_empty_process();
+ if (t == NULL) {
+ goto alloc_tcb_fail;
+ }
+ t->process->pid = t->rb_node.key = ((tid == 0) ? alloc_tid() : tid);
+
+ /* temporarily use new process's cr3 to load elf */
+ reg_t old_cr3 = get_current()->process->cr3;
+ get_current()->process->cr3 = t->process->cr3;
+ set_cr3(t->process->cr3);
+
+ simple_elf_t elf;
+ if (elf_load_helper(&elf, exe) != ELF_SUCCESS ||
+ process_load_elf(t->process, &elf, exe) != 0) {
+ goto load_elf_fail;
+ }
+
+ unsigned long* argv_addrs = smalloc(argc * sizeof(unsigned long));
+ if (argv_addrs == NULL) {
+ goto alloc_argc_addr_fail;
+ }
+ char* esp = (char*)(DEFAULT_STACK_END);
+ int i, len, total = 0;
+ for (i = argc - 1; i >= 0; i--) {
+ len = strlen(argv[i]);
+ if (len + total > MAX_TOTAL_ARG_LEN) {
+ goto load_elf_fail;
+ }
+ esp -= (len + 1);
+ memcpy(esp, argv[i], len + 1);
+ argv_addrs[i] = (unsigned long)esp;
+ }
+ unsigned long* new_esp =
+ (unsigned long*)(((va_t)esp) & (~(sizeof(va_t) - 1)));
+ --new_esp;
+ *new_esp = 0;
+ for (i = argc - 1; i >= 0; i--) {
+ --new_esp;
+ *new_esp = argv_addrs[i];
+ }
+ unsigned long new_argv = (unsigned long)new_esp;
+ sfree(argv_addrs, argc * sizeof(unsigned long));
+
+ new_esp -= 5;
+ new_esp[0] = 0; /* return address */
+ new_esp[1] = argc; /* argc */
+ new_esp[2] = new_argv; /* argv */
+ new_esp[3] = DEFAULT_STACK_END; /* stack_hi */
+ new_esp[4] = DEFAULT_STACK_POS; /* stack_lo */
+
+ get_current()->process->cr3 = old_cr3;
+ set_cr3(old_cr3);
+
+ t->kernel_esp -= sizeof(stack_frame_t);
+ stack_frame_t* frame = (stack_frame_t*)t->kernel_esp;
+ frame->eip = (reg_t)elf.e_entry;
+ frame->cs = SEGSEL_USER_CS;
+ frame->eflags = DEFAULT_EFLAGS;
+ frame->esp = (reg_t)new_esp;
+ frame->ss = SEGSEL_USER_DS;
+ frame->eax = 0;
+ frame->ecx = 0;
+ frame->edx = 0;
+ frame->ebx = 0;
+ frame->ebp = 0;
+ frame->esi = 0;
+ frame->edi = 0;
+ frame->ds = SEGSEL_USER_DS;
+ frame->es = SEGSEL_USER_DS;
+ frame->fs = SEGSEL_USER_DS;
+ frame->gs = SEGSEL_USER_DS;
+
+ t->kernel_esp -= sizeof(yield_frame_t);
+ yield_frame_t* yf = (yield_frame_t*)t->kernel_esp;
+ yf->eflags = DEFAULT_EFLAGS;
+ yf->raddr = (reg_t)return_to_user;
+ return t;
+
+alloc_argc_addr_fail:
+load_elf_fail:
+ get_current()->process->cr3 = old_cr3;
+ set_cr3(old_cr3);
+ destroy_thread(t);
+alloc_tcb_fail:
+ return NULL;
+}
+
+void destroy_thread(thread_t* t) {
+ process_t* p = t->process;
+ sfree(t->stack, K_STACK_SIZE);
+ sfree(t, sizeof(thread_t));
+ mutex_lock(&p->refcount_lock);
+ queue_detach(&p->threads, &t->process_link);
+ p->refcount--;
+ if (p->refcount != 0) {
+ mutex_unlock(&p->refcount_lock);
+ return;
+ }
+ mutex_unlock(&p->refcount_lock);
+ int i, n = vector_size(&p->regions);
+ for (i = 0; i < n; i++) {
+ region_t* r = (region_t*)vector_at(&p->regions, i);
+ free_user_pages(r->paddr, r->size / PAGE_SIZE);
+ }
+ vector_free(&p->regions);
+
+ int old_if = save_clear_if();
+ for (i = USER_PD_START; i < NUM_PAGE_ENTRY; i++) {
+ page_directory_t* pd = (page_directory_t*)map_phys_page(p->cr3, NULL);
+ if ((*pd)[i] != BAD_PDE) {
+ pa_t pt = get_page_table((*pd)[i]);
+ free_user_pages(pt, 1);
+ }
+ }
+ restore_if(old_if);
+ free_user_pages(p->cr3, 1);
+ sfree(p, sizeof(process_t));
+}
+
+int alloc_tid() {
+ static int tid = 1;
+ static int next_check = (1 << 31);
+ if (tid == next_check) {
+ /* allocated all pids */
+ mutex_lock(&threads_lock);
+ rb_t *p, *q;
+ p = rb_min(threads);
+ if (p == &rb_nil) {
+ /* no pid inuse */
+ tid = 1;
+ next_check = (1 << 31);
+ } else {
+ while (1) {
+ q = rb_next(p);
+ if (q == &rb_nil) {
+ /* available tids are p+1 ~ 0x7fffffff */
+ if (p->key + 1 == (1 << 31)) {
+ /* all pids inuse */
+ panic("too many threads");
+ }
+ tid = p->key + 1;
+ next_check = (1 << 31);
+ break;
+ }
+ /* check until q-p>1, which gives us some free tids */
+ if (q->key == p->key + 1) {
+ p = q;
+ continue;
+ }
+ tid = p->key + 1;
+ next_check = q->key;
+ break;
+ }
+ }
+ mutex_unlock(&threads_lock);
+ }
+ return tid++;
+}
+
+static int load_segment(process_t* p,
+ file_t* f,
+ int f_off,
+ int f_len,
+ va_t m_off,
+ int m_len,
+ int is_rw) {
+ va_t m_start = (m_off & PAGE_BASE_MASK);
+ va_t m_end = ((m_off + m_len + (PAGE_SIZE - 1)) & PAGE_BASE_MASK);
+ int n_pages = (m_end - m_start) / PAGE_SIZE;
+ pa_t paddr = alloc_user_pages(n_pages);
+ if (paddr == 0) {
+ goto alloc_segment_fail;
+ }
+ /* handle address check and overlapping issues */
+ if (add_region(p, m_start, n_pages, paddr, is_rw) != 0) {
+ goto add_region_fail;
+ }
+
+ int rw = (is_rw ? PTE_RW : PTE_RO);
+ int i = 0;
+ int offset = 0;
+ /* file page table one entry per loop */
+ pa_t pt_pa = find_or_create_pt(p, m_start);
+ if (pt_pa == BAD_PA) {
+ goto add_pt_fail;
+ }
+ do {
+ offset = i * PAGE_SIZE;
+ int pt_index = get_pt_index(m_start + offset);
+ if (pt_index == 0) {
+ /* step to next page table */
+ pt_pa = find_or_create_pt(p, m_start + offset);
+ if (pt_pa == BAD_PA) {
+ goto add_pt_fail;
+ }
+ }
+
+ int old_if = save_clear_if();
+ page_table_t* pt = (page_table_t*)map_phys_page(pt_pa, NULL);
+ /* do not set P bit for ZFOD */
+ (*pt)[pt_index] = make_pte(paddr + offset, 0, PTE_USER, rw, 0);
+ restore_if(old_if);
+ invlpg(m_start + offset);
+ } while (++i < n_pages);
+
+ if (f != NULL) {
+ read_file(f, f_off, f_len, (char*)m_off);
+ }
+ return 0;
+
+add_pt_fail:
+ /* page tables are reachable from page directory so no need to free them */
+ vector_pop(&p->regions);
+add_region_fail:
+ free_user_pages(paddr, n_pages);
+alloc_segment_fail:
+ return -1;
+}
+
+pa_t find_or_create_pt(process_t* p, va_t vaddr) {
+ int old_if = save_clear_if();
+ page_directory_t* pd = (page_directory_t*)map_phys_page(p->cr3, NULL);
+ pde_t* pde = &(*pd)[get_pd_index(vaddr)];
+ if (*pde == BAD_PDE) {
+ /* add a page table */
+ pa_t pt = alloc_user_pages(1);
+ if (pt == 0) {
+ return 0;
+ }
+ va_t pt_addr = map_phys_page(pt, NULL);
+ memset((void*)pt_addr, 0, PAGE_SIZE);
+
+ map_phys_page(p->cr3, NULL);
+ *pde = make_pde(pt, PTE_USER, PTE_RW, PTE_PRESENT);
+ invlpg(vaddr);
+ restore_if(old_if);
+ return pt;
+ }
+ pa_t result = get_page_table(*pde);
+ restore_if(old_if);
+ return result;
+}
+
+int add_region(process_t* p, va_t start, int n_pages, pa_t pa, int is_rw) {
+ if (start > DEFAULT_STACK_END || start < USER_MEM_START) {
+ return -1;
+ }
+ va_t end = start + n_pages * PAGE_SIZE;
+ if (end < start) {
+ return -1;
+ }
+ int i, n = vector_size(&p->regions);
+ for (i = 0; i < n; i++) {
+ /* check overlapping */
+ region_t* r = (region_t*)vector_at(&p->regions, i);
+ va_t this_start = r->addr;
+ va_t this_end = this_start + r->size;
+ if (start >= this_start && start < this_end) {
+ return -1;
+ }
+ if (end > this_start && end <= this_end) {
+ return -1;
+ }
+ if (this_start >= start && this_start < end) {
+ return -1;
+ }
+ if (this_end > start && this_end <= end) {
+ return -1;
+ }
+ }
+ region_t newr;
+ newr.addr = start;
+ newr.size = n_pages * PAGE_SIZE;
+ newr.paddr = pa;
+ newr.is_rw = is_rw;
+ return vector_push(&p->regions, &newr);
+}
+
+thread_t* select_next() {
+ thread_t* t;
+ if (ready != NULL) {
+ t = queue_data(queue_remove_head(&ready), thread_t, sched_link);
+ } else {
+ t = get_idle();
+ }
+ return t;
+}
+
+void insert_ready_tail(thread_t* t) {
+ t->status = THREAD_READY;
+ queue_insert_tail(&ready, &t->sched_link);
+}
+
+void insert_ready_head(thread_t* t) {
+ t->status = THREAD_READY;
+ queue_insert_head(&ready, &t->sched_link);
+}
+
+reg_t save_and_setup_env(thread_t* t, reg_t esp) {
+ get_current()->kernel_esp = esp;
+ set_current(t);
+ t->status = THREAD_RUNNING;
+ set_esp0(t->esp0);
+ set_cr3(t->process->cr3);
+ return t->kernel_esp;
+}
+
+void swap_process_inplace(thread_t* newt) {
+ disable_interrupts();
+ thread_t* oldt = get_current();
+ process_t* oldp = oldt->process;
+ process_t* newp = newt->process;
+ pa_t cr3 = oldp->cr3;
+ vector_t regions = oldp->regions;
+ queue_t* p_threads = oldp->threads;
+ oldp->cr3 = newp->cr3;
+ oldp->regions = newp->regions;
+ oldp->threads = newp->threads;
+ newp->cr3 = cr3;
+ newp->regions = regions;
+ newp->threads = p_threads;
+ newt->process = oldp;
+ oldt->process = newp;
+ set_cr3(newp->cr3);
+ mutex_lock(&threads_lock);
+ /* move oldt's rbtree node to newt */
+ newt->rb_node = oldt->rb_node;
+ if (threads == &oldt->rb_node) {
+ threads = &newt->rb_node;
+ };
+ if (newt->rb_node.left != &rb_nil) {
+ newt->rb_node.left->parent = &newt->rb_node;
+ }
+ if (newt->rb_node.right != &rb_nil) {
+ newt->rb_node.right->parent = &newt->rb_node;
+ }
+ if (newt->rb_node.parent != &rb_nil) {
+ if (newt->rb_node.parent->left == &oldt->rb_node) {
+ newt->rb_node.parent->left = &newt->rb_node;
+ } else {
+ newt->rb_node.parent->right = &newt->rb_node;
+ }
+ }
+ oldt->rb_node.parent = NULL;
+ mutex_unlock(&threads_lock);
+ enable_interrupts();
+}
+
+void kill_current() {
+ thread_t* current = get_current();
+ process_t* p = current->process;
+ /* respawn important process */
+ if (current == get_idle()) {
+ if (p->refcount == 1) {
+ const char* idle_args[] = {IDLE_NAME};
+ thread_t* new_idle =
+ create_process(current->rb_node.key, IDLE_NAME, 1, idle_args);
+ if (new_idle == NULL) {
+ panic("no space to allocate idle process");
+ }
+ swap_process_inplace(new_idle);
+ set_idle(new_idle);
+ }
+ } else if (p == init_process) {
+ if (p->refcount == 1) {
+ mutex_lock(&p->wait_lock);
+ /* reclaim dead childs */
+ while (p->dead_childs != NULL) {
+ queue_t* node = p->dead_childs;
+ queue_detach(&p->dead_childs, node);
+ process_t* child_process =
+ queue_data(node, process_t, sible_link);
+ sfree(child_process, sizeof(process_t));
+ p->nchilds--;
+ }
+ mutex_unlock(&p->wait_lock);
+ const char* init_args[] = {INIT_NAME};
+ thread_t* new_init =
+ create_process(current->rb_node.key, INIT_NAME, 1, init_args);
+ if (new_init == NULL) {
+ panic("no space to allocate init process");
+ }
+ swap_process_inplace(new_init);
+ int old_if = spl_lock(&ready_lock);
+ insert_ready_tail(new_init);
+ spl_unlock(&ready_lock, old_if);
+ }
+ }
+
+ if (current->rb_node.parent != NULL) {
+ remove_thread(current);
+ }
+
+ p = current->process;
+ mutex_lock(&p->refcount_lock);
+ p->refcount--;
+ queue_detach(&p->threads, &current->process_link);
+ int is_last = (p->refcount == 0);
+ mutex_unlock(&p->refcount_lock);
+ if (is_last != 0) {
+ /* move childs to init process */
+ mutex_lock(&p->wait_lock);
+ mutex_lock(&init_process->wait_lock);
+ while (p->live_childs != NULL) {
+ queue_t* node = p->live_childs;
+ queue_detach(&p->live_childs, node);
+ queue_insert_tail(&init_process->live_childs, node);
+ process_t* child_process = queue_data(node, process_t, sible_link);
+ child_process->parent = init_process;
+ init_process->nchilds++;
+ }
+ while (p->dead_childs != NULL) {
+ queue_t* node = p->dead_childs;
+ queue_detach(&p->dead_childs, node);
+ queue_insert_tail(&init_process->dead_childs, node);
+ process_t* child_process = queue_data(node, process_t, sible_link);
+ child_process->parent = init_process;
+ init_process->nchilds++;
+ cv_signal(&init_process->wait_cv);
+ }
+ mutex_unlock(&init_process->wait_lock);
+ mutex_unlock(&p->wait_lock);
+
+ /* free userspace memory */
+ pa_t old_cr3 = p->cr3;
+ p->cr3 = (pa_t)kernel_pd;
+ set_cr3((pa_t)kernel_pd);
+ int i, n = vector_size(&p->regions);
+ for (i = 0; i < n; i++) {
+ region_t* r = (region_t*)vector_at(&p->regions, i);
+ free_user_pages(r->paddr, r->size / PAGE_SIZE);
+ }
+ vector_free(&p->regions);
+
+ int old_if = save_clear_if();
+ for (i = USER_PD_START; i < NUM_PAGE_ENTRY; i++) {
+ page_directory_t* pd =
+ (page_directory_t*)map_phys_page(old_cr3, NULL);
+ if ((*pd)[i] != BAD_PDE) {
+ pa_t pt = get_page_table((*pd)[i]);
+ free_user_pages(pt, 1);
+ }
+ }
+ restore_if(old_if);
+ free_user_pages(old_cr3, 1);
+ }
+
+ /* make sure we do not get swapped out before borrowing kthread's stack */
+ mutex_lock(&malloc_lock);
+ disable_interrupts();
+ current->status = THREAD_DEAD;
+ if (is_last != 0) {
+ /* we cannot switch back after p is reclaimed so we must put cv_signal
+ * after disable_interrupts
+ */
+ if (p->parent != NULL) {
+ mutex_lock(&p->parent->wait_lock);
+ queue_detach(&p->parent->live_childs, &p->sible_link);
+ queue_insert_tail(&p->parent->dead_childs, &p->sible_link);
+ cv_signal(&p->parent->wait_cv);
+ mutex_unlock(&p->parent->wait_lock);
+ } else {
+ _sfree(p, sizeof(process_t));
+ }
+ }
+ /* borrow kth's stack for freeing thread control block
+ * we can make sure kthread is not running since we are the running thread
+ * and kthread's IF is cleared so it will not be interrupted
+ */
+ thread_t* kth = get_kthread();
+ set_current(kth);
+ switch_stack_cleanup(kth->kernel_esp, current);
+}
+
+/**
+ * @brief clean thread control block and its stack
+ * @param t thread
+ */
+void cleanup_dead_thread(thread_t* t) {
+ _sfree(t->stack, K_STACK_SIZE);
+ _sfree(t, sizeof(thread_t));
+ mutex_unlock(&malloc_lock);
+ /* we will return to kthread's loop and it will yield
+ * cannot do yield here since it increases stack
+ */
+}
+
+/**
+ * @brief check if a task_vanish is pending and if so, do vanish
+ * @param f saved regs
+ */
+void check_pending_exit(stack_frame_t* f) {
+ /* only vanish before returning to userspace, otherwise some kernel
+ * structure may be jammed
+ */
+ if (get_current()->pending_exit != 0 && f->cs == SEGSEL_USER_CS) {
+ kill_current();
+ }
+}
+
+thread_t* find_thread(int tid) {
+ rb_t* result = rb_find(threads, tid);
+ if (result == NULL) {
+ return NULL;
+ }
+ return rb_data(result, thread_t, rb_node);
+}
+
+void add_thread(thread_t* t) {
+ mutex_lock(&threads_lock);
+ rb_insert(&threads, &t->rb_node);
+ mutex_unlock(&threads_lock);
+}
+
+void remove_thread(thread_t* t) {
+ mutex_lock(&threads_lock);
+ rb_delete(&threads, &t->rb_node);
+ mutex_unlock(&threads_lock);
+}
diff --git a/kern/sched_asm.S b/kern/sched_asm.S
new file mode 100644
index 0000000..747598e
--- /dev/null
+++ b/kern/sched_asm.S
@@ -0,0 +1,67 @@
+/** @file sched_asm.S
+ *
+ * @brief scheduling functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <x86/seg.h>
+
+.text
+
+.global set_fs
+.type set_fs, %function
+set_fs:
+ push 0x4(%esp)
+ pop %fs
+ ret
+
+.macro PERCPU_GETSET name offset
+.global get_\name
+.type get_\name, %function
+get_\name:
+ xor %eax, %eax
+ mov %fs:\offset(%eax), %eax
+ ret
+
+.global set_\name
+.type set_\name, %function
+set_\name:
+ xor %esi, %esi
+ mov 0x4(%esp), %eax
+ mov %eax, %fs:\offset(%esi)
+ ret
+.endm
+
+PERCPU_GETSET current 0x0
+PERCPU_GETSET idle 0x4
+PERCPU_GETSET kthread 0x8
+PERCPU_GETSET mapped_phys_page 0xc
+PERCPU_GETSET mapped_phys_page_pte 0x10
+
+.global return_to_user
+.type return_to_user, %function
+return_to_user:
+ push %esp
+ call check_pending_exit
+ add $0x4, %esp
+ pop %gs /* restore from stack_frame_t structure */
+ pop %fs
+ pop %es
+ pop %ds
+ popa
+ iret
+
+.global switch_stack_cleanup
+.type switch_stack_cleanup, %function
+switch_stack_cleanup:
+ mov 0x8(%esp), %eax
+ mov 0x4(%esp), %esp
+ push %eax
+ call cleanup_dead_thread
+ add $0x4, %esp
+ pop %ebp
+ pop %ebx
+ popf
+ ret
diff --git a/kern/sync.c b/kern/sync.c
new file mode 100644
index 0000000..4e4e6de
--- /dev/null
+++ b/kern/sync.c
@@ -0,0 +1,78 @@
+/** @file sync.c
+ *
+ * @brief locks.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <x86/asm.h>
+
+#include <interrupt.h>
+#include <sched.h>
+#include <sync.h>
+
+void mutex_lock(mutex_t* m) {
+ thread_t* current = get_current();
+ int old_if = spl_lock(&m->guard);
+ if (m->locked == 0) {
+ m->locked = 1;
+ spl_unlock(&m->guard, old_if);
+ return;
+ }
+ queue_insert_tail(&m->waiters, &current->sched_link);
+ current->status = THREAD_BLOCKED;
+ int old_if2 = spl_lock(&ready_lock);
+ thread_t* t = select_next();
+ spl_unlock(&ready_lock, old_if2);
+ /* release m->gaurd last, otherwise other thread may switch to us before
+ * yielding
+ */
+ yield_to_spl_unlock(t, &m->guard, old_if);
+}
+
+void mutex_unlock(mutex_t* m) {
+ int old_if = spl_lock(&m->guard);
+ if (m->waiters == NULL) {
+ m->locked = 0;
+ spl_unlock(&m->guard, old_if);
+ return;
+ }
+ /* transfer lock ownership to t */
+ thread_t* t =
+ queue_data(queue_remove_head(&m->waiters), thread_t, sched_link);
+ int old_if2 = spl_lock(&ready_lock);
+ insert_ready_head(t);
+ spl_unlock(&ready_lock, old_if2);
+ spl_unlock(&m->guard, old_if);
+}
+
+void cv_wait(cv_t* cv, mutex_t* m) {
+ thread_t* current = get_current();
+ int old_if = spl_lock(&cv->guard);
+ queue_insert_tail(&cv->waiters, &current->sched_link);
+ current->status = THREAD_BLOCKED;
+ mutex_unlock(m);
+ int old_if2 = spl_lock(&ready_lock);
+ thread_t* t = select_next();
+ spl_unlock(&ready_lock, old_if2);
+ yield_to_spl_unlock(t, &cv->guard, old_if);
+ /* some threads may jump in so a condition check in a while loop is required
+ * outside
+ */
+ mutex_lock(m);
+}
+
+void cv_signal(cv_t* cv) {
+ int old_if = spl_lock(&cv->guard);
+ if (cv->waiters == NULL) {
+ spl_unlock(&cv->guard, old_if);
+ return;
+ }
+ thread_t* t =
+ queue_data(queue_remove_head(&cv->waiters), thread_t, sched_link);
+ int old_if2 = spl_lock(&ready_lock);
+ insert_ready_head(t);
+ spl_unlock(&ready_lock, old_if2);
+ spl_unlock(&cv->guard, old_if);
+}
diff --git a/kern/sync_asm.S b/kern/sync_asm.S
new file mode 100644
index 0000000..b3fc852
--- /dev/null
+++ b/kern/sync_asm.S
@@ -0,0 +1,75 @@
+/** @file sync_asm.S
+ *
+ * @brief locks.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+.text
+
+#define EFL_IF 0x00000200
+
+.global save_clear_if
+.type save_clear_if, %function
+save_clear_if:
+ pushf
+ pop %eax
+ cli
+ ret
+
+.global restore_if
+.type restore_if, %function
+restore_if:
+ mov 0x4(%esp), %eax
+ push %eax
+ popf
+ ret
+
+.global spl_lock
+.type spl_lock, %function
+spl_lock:
+ pushf
+ pop %edx
+ cli
+ mov 0x4(%esp), %esi
+ mov $1, %edi
+.spl_lock_try:
+ xor %eax, %eax
+ lock cmpxchg %edi, (%esi)
+ test %eax, %eax
+ jnz .spl_lock_try
+ mov %edx, %eax
+ ret
+
+.global spl_unlock
+.type spl_unlock, %function
+spl_unlock:
+ mov 0x8(%esp), %eax
+ mov 0x4(%esp), %esi
+ xor %edx, %edx
+ mov %edx, (%esi)
+ push %eax
+ popf
+ ret
+
+.global yield_to_spl_unlock
+.type yield_to_spl_unlock, %function
+yield_to_spl_unlock:
+ mov 0xc(%esp), %edi
+ mov 0x8(%esp), %esi
+ mov 0x4(%esp), %eax
+ push %edi /* old_if, will be restored when switching back */
+ push %ebx
+ push %ebp
+ mov %esi, %ebx
+ push %esp
+ push %eax
+ call save_and_setup_env
+ mov %eax, %esp
+ xor %eax, %eax
+ mov %eax, (%ebx)
+ pop %ebp
+ pop %ebx
+ popf
+ ret
diff --git a/kern/syscall_asm.S b/kern/syscall_asm.S
new file mode 100644
index 0000000..050788a
--- /dev/null
+++ b/kern/syscall_asm.S
@@ -0,0 +1,61 @@
+/** @file syscall_asm.S
+ *
+ * @brief syscall entries.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <simics.h>
+#include <x86/seg.h>
+
+#define SEGSEL_KERNEL_FS SEGSEL_SPARE0
+
+.macro SYSCALL name
+.global sys_\name
+.type sys_\name, %function
+sys_\name:
+ pusha /* save a stack_frame_t structure */
+ push %ds
+ push %es
+ push %fs
+ push %gs
+ mov $SEGSEL_KERNEL_DS, %eax
+ mov %ax, %ds
+ mov %ax, %es
+ mov $SEGSEL_KERNEL_FS, %eax
+ mov %ax, %fs
+ cld
+ push %esp
+ call sys_\name\()_real
+ add $0x4, %esp
+ jmp return_to_user /* check pending exit before iret */
+.endm
+
+SYSCALL fork
+SYSCALL exec
+SYSCALL wait
+SYSCALL yield
+SYSCALL deschedule
+SYSCALL make_runnable
+SYSCALL gettid
+SYSCALL new_pages
+SYSCALL remove_pages
+SYSCALL sleep
+SYSCALL getchar
+SYSCALL readline
+SYSCALL print
+SYSCALL set_term_color
+SYSCALL set_cursor_pos
+SYSCALL get_cursor_pos
+SYSCALL thread_fork
+SYSCALL get_ticks
+SYSCALL misbehave
+SYSCALL halt
+SYSCALL task_vanish
+SYSCALL set_status
+SYSCALL vanish
+SYSCALL readfile
+SYSCALL swexn
+
+SYSCALL enosys
diff --git a/kern/syscall_io.c b/kern/syscall_io.c
new file mode 100644
index 0000000..3a9dc99
--- /dev/null
+++ b/kern/syscall_io.c
@@ -0,0 +1,151 @@
+/** @file syscall_io.c
+ *
+ * @brief IO syscalls.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <common_kern.h>
+#include <elf/elf_410.h>
+#include <string.h>
+#include <ureg.h>
+
+#include <x86/asm.h>
+#include <x86/cr.h>
+#include <x86/eflags.h>
+#include <x86/seg.h>
+
+#include <console.h>
+#include <kbd.h>
+#include <malloc.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <usermem.h>
+
+/**
+ * @brief print() syscall handler
+ * @param f saved regs
+ */
+void sys_print_real(stack_frame_t* f) {
+ reg_t esi = f->esi;
+ int len;
+ if (copy_from_user((va_t)esi, sizeof(int), &len) != 0) {
+ goto read_fail;
+ }
+ if (len < 0) {
+ goto bad_length;
+ }
+ va_t base;
+ if (copy_from_user((va_t)esi + sizeof(va_t), sizeof(va_t), &base) != 0) {
+ goto read_fail;
+ }
+ mutex_lock(&console_lock);
+ f->eax = (reg_t)print_buf_from_user(base, len);
+ mutex_unlock(&console_lock);
+ return;
+
+read_fail:
+ f->eax = (reg_t)-1;
+ return;
+
+bad_length:
+ f->eax = (reg_t)-2;
+}
+
+/**
+ * @brief set_term_color() syscall handler
+ * @param f saved regs
+ */
+void sys_set_term_color_real(stack_frame_t* f) {
+ reg_t esi = f->esi;
+ mutex_lock(&console_lock);
+ f->eax = (reg_t)set_term_color((int)esi);
+ mutex_unlock(&console_lock);
+}
+
+/**
+ * @brief set_cursor_pos() syscall handler
+ * @param f saved regs
+ */
+void sys_set_cursor_pos_real(stack_frame_t* f) {
+ reg_t esi = f->esi;
+ int row, col;
+ if (copy_from_user((va_t)esi, sizeof(int), &row) != 0) {
+ goto read_arg_fail;
+ }
+ if (copy_from_user((va_t)(esi + sizeof(va_t)), sizeof(int), &col) != 0) {
+ goto read_arg_fail;
+ }
+ mutex_lock(&console_lock);
+ f->eax = (reg_t)set_cursor(row, col);
+ mutex_unlock(&console_lock);
+ return;
+
+read_arg_fail:
+ f->eax = (reg_t)-1;
+ return;
+}
+
+/**
+ * @brief get_cursor_pos() syscall handler
+ * @param f saved regs
+ */
+void sys_get_cursor_pos_real(stack_frame_t* f) {
+ reg_t esi = f->esi;
+ va_t prow, pcol;
+ if (copy_from_user((va_t)esi, sizeof(va_t), &prow) != 0) {
+ goto read_arg_fail;
+ }
+ if (copy_from_user((va_t)(esi + sizeof(va_t)), sizeof(va_t), &pcol) != 0) {
+ goto read_arg_fail;
+ }
+ int row, col;
+ mutex_lock(&console_lock);
+ get_cursor(&row, &col);
+ mutex_unlock(&console_lock);
+ if (copy_to_user(prow, sizeof(int), &row) != 0) {
+ goto bad_arg;
+ }
+ if (copy_to_user(pcol, sizeof(int), &col) != 0) {
+ goto bad_arg;
+ }
+ f->eax = 0;
+ return;
+
+bad_arg:
+read_arg_fail:
+ f->eax = (reg_t)-1;
+ return;
+}
+
+/**
+ * @brief getchar() syscall handler
+ * @param f saved regs
+ */
+void sys_getchar_real(stack_frame_t* f) {
+ f->eax = (reg_t)do_getchar();
+}
+
+/**
+ * @brief readline() syscall handler
+ * @param f saved regs
+ */
+void sys_readline_real(stack_frame_t* f) {
+ int len;
+ va_t buf;
+ reg_t esi = f->esi;
+ if (copy_from_user((va_t)esi, sizeof(int), &len) != 0) {
+ goto read_arg_fail;
+ }
+ if (copy_from_user((va_t)(esi + sizeof(va_t)), sizeof(va_t), &buf) != 0) {
+ goto read_arg_fail;
+ }
+ f->eax = (reg_t)do_readline(len, buf);
+ return;
+
+read_arg_fail:
+ f->eax = (reg_t)-1;
+ return;
+}
diff --git a/kern/syscall_memory.c b/kern/syscall_memory.c
new file mode 100644
index 0000000..288d6df
--- /dev/null
+++ b/kern/syscall_memory.c
@@ -0,0 +1,137 @@
+/** @file syscall_memory.c
+ *
+ * @brief memory syscalls.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <string.h>
+#include <common_kern.h>
+#include <elf/elf_410.h>
+#include <ureg.h>
+
+#include <x86/asm.h>
+#include <x86/cr.h>
+#include <x86/eflags.h>
+#include <x86/seg.h>
+
+#include <asm_instr.h>
+#include <malloc.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <usermem.h>
+
+/**
+ * @brief new_pages() syscall handler
+ * @param f saved regs
+ */
+void sys_new_pages_real(stack_frame_t* f) {
+ reg_t esi = f->esi;
+ va_t base;
+ if (copy_from_user((va_t)esi, sizeof(va_t), &base) != 0) {
+ goto read_fail;
+ }
+ if ((base & PAGE_OFFSET_MASK) != 0) {
+ goto read_fail;
+ }
+ int len;
+ if (copy_from_user((va_t)esi + sizeof(va_t), sizeof(int), &len) != 0) {
+ goto read_fail;
+ }
+ if ((len & PAGE_OFFSET_MASK) != 0) {
+ goto read_fail;
+ }
+
+ process_t* p = get_current()->process;
+ mutex_lock(&p->mm_lock);
+ int n_pages = len / PAGE_SIZE;
+ pa_t paddr = alloc_user_pages(n_pages);
+ if (paddr == 0) {
+ goto alloc_segment_fail;
+ }
+ if (add_region(p, base, n_pages, paddr, 1) != 0) {
+ goto add_region_fail;
+ }
+
+ int i = 0;
+ int offset = 0;
+ /* file page table one entry per loop */
+ pa_t pt_pa = find_or_create_pt(p, base);
+ if (pt_pa == BAD_PA) {
+ goto add_pt_fail;
+ }
+ do {
+ offset = i * PAGE_SIZE;
+ /* step to next page table */
+ int pt_index = get_pt_index(base + offset);
+ if (pt_index == 0) {
+ pt_pa = find_or_create_pt(p, base + offset);
+ if (pt_pa == BAD_PA) {
+ goto add_pt_fail;
+ }
+ }
+
+ int old_if = save_clear_if();
+ page_table_t* pt = (page_table_t*)map_phys_page(pt_pa, NULL);
+ (*pt)[pt_index] = make_pte(paddr + offset, 0, PTE_USER, PTE_RW, 0);
+ restore_if(old_if);
+ invlpg(base + offset);
+ } while (++i < n_pages);
+ mutex_unlock(&p->mm_lock);
+ f->eax = 0;
+ return;
+
+add_pt_fail:
+ /* page tables are reachable from page directory so no need to free them */
+ vector_pop(&p->regions);
+add_region_fail:
+ free_user_pages(paddr, n_pages);
+alloc_segment_fail:
+ mutex_unlock(&p->mm_lock);
+read_fail:
+ f->eax = (reg_t)-1;
+}
+
+/**
+ * @brief remove_pages() syscall handler
+ * @param f saved regs
+ */
+void sys_remove_pages_real(stack_frame_t* f) {
+ process_t* p = get_current()->process;
+ mutex_lock(&p->mm_lock);
+ va_t base = (va_t)f->esi;
+ int i, j, n = vector_size(&p->regions);
+ for (i = 0; i < n; i++) {
+ region_t* r = (region_t*)vector_at(&p->regions, i);
+ if (base == r->addr) {
+ int n_pages = r->size / PAGE_SIZE;
+ j = 0;
+ int offset = 0;
+ pa_t pt_pa = find_or_create_pt(p, base);
+ do {
+ offset = j * PAGE_SIZE;
+ int pt_index = get_pt_index(base + offset);
+ if (pt_index == 0) {
+ pt_pa = find_or_create_pt(p, base + offset);
+ }
+
+ int old_if = save_clear_if();
+ page_table_t* pt = (page_table_t*)map_phys_page(pt_pa, NULL);
+ (*pt)[pt_index] = BAD_PDE;
+ restore_if(old_if);
+ invlpg(base + offset);
+ } while (++j < n_pages);
+
+ free_user_pages(r->paddr, r->size / PAGE_SIZE);
+ vector_remove(&p->regions, i);
+ mutex_unlock(&p->mm_lock);
+ f->eax = 0;
+ return;
+ }
+ }
+ /* no such region */
+ mutex_unlock(&p->mm_lock);
+ f->eax = (reg_t)-1;
+}
diff --git a/kern/syscall_misc.c b/kern/syscall_misc.c
new file mode 100644
index 0000000..4c94513
--- /dev/null
+++ b/kern/syscall_misc.c
@@ -0,0 +1,169 @@
+/** @file syscall_misc.c
+ *
+ * @brief miscellaneous syscalls.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <simics.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <common_kern.h>
+#include <elf/elf_410.h>
+#include <malloc_internal.h>
+#include <ureg.h>
+
+#include <x86/asm.h>
+#include <x86/cr.h>
+#include <x86/eflags.h>
+#include <x86/seg.h>
+
+#include <asm_instr.h>
+#include <assert.h>
+#include <loader.h>
+#include <malloc.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <sync.h>
+#include <timer.h>
+#include <usermem.h>
+
+/**
+ * @brief misbehave() syscall handler
+ * @param f saved regs
+ */
+void sys_misbehave_real(stack_frame_t* f) {
+ f->eax = 0;
+}
+
+/**
+ * @brief all other syscalls handler
+ * @param f saved regs
+ */
+void sys_enosys_real(stack_frame_t* f) {
+ f->eax = (reg_t)-1;
+}
+
+/**
+ * @brief halt() syscall handler
+ * @param f saved regs
+ */
+void sys_halt_real(stack_frame_t* f) {
+ sim_halt();
+ hlt();
+}
+
+/**
+ * @brief read the "." file to user buf
+ * @param buf buffer
+ * @param count size
+ * @param offset offset
+ * @return number of bytes read, or -1 on failure
+ */
+static reg_t read_dot_file(va_t buf, int count, int offset);
+
+/**
+ * @brief readfile() syscall handler
+ * @param f saved regs
+ */
+void sys_readfile_real(stack_frame_t* f) {
+ reg_t esi = f->esi;
+ va_t pfilename, buf;
+ int count, offset;
+ if (copy_from_user((va_t)esi, sizeof(va_t), &pfilename) != 0) {
+ goto read_arg_fail;
+ }
+ if (copy_from_user((va_t)(esi + sizeof(va_t)), sizeof(va_t), &buf) != 0) {
+ goto read_arg_fail;
+ }
+ if (copy_from_user((va_t)(esi + 2 * sizeof(va_t)), sizeof(int), &count) !=
+ 0) {
+ goto read_arg_fail;
+ }
+ if (copy_from_user((va_t)(esi + 3 * sizeof(va_t)), sizeof(int), &offset) !=
+ 0) {
+ goto read_arg_fail;
+ }
+ if (count < 0 || offset < 0) {
+ goto read_arg_fail;
+ }
+ char* filename = copy_string_from_user(pfilename, MAX_EXECNAME_LEN);
+ if (filename == NULL) {
+ goto read_arg_fail;
+ }
+ if (strcmp(filename, ".") == 0) {
+ free(filename);
+ f->eax = read_dot_file(buf, count, offset);
+ return;
+ }
+ file_t* fp = find_file(filename);
+ if (fp == NULL) {
+ goto no_such_file;
+ }
+ if (offset > fp->execlen) {
+ goto offset_too_big;
+ }
+ int size = fp->execlen - offset;
+ if (count < size) {
+ size = count;
+ }
+ if (copy_to_user(buf, size, (void*)(fp->execbytes + offset)) != 0) {
+ goto bad_buffer;
+ }
+ f->eax = (reg_t)size;
+ return;
+
+bad_buffer:
+offset_too_big:
+no_such_file:
+ free(filename);
+read_arg_fail:
+ f->eax = (reg_t)-1;
+ return;
+}
+
+static reg_t read_dot_file(va_t buf, int count, int offset) {
+ file_t* fp;
+ int i = 0;
+ int cur = 0;
+ int written = 0, left = count;
+ while (i < exec2obj_userapp_count && left > 0) {
+ fp = &exec2obj_userapp_TOC[i];
+ int len = strlen(fp->execname) + 1;
+ if (cur + len <= offset) {
+ /* before offset */
+ cur += len;
+ i++;
+ continue;
+ }
+ int size = len;
+ const char* start = fp->execname;
+ /* write this entry to buffer */
+ if (cur < offset) {
+ size -= (offset - cur);
+ start += (offset - cur);
+ }
+ if (left < size) {
+ size = left;
+ }
+ if (copy_to_user((va_t)buf + written, size, (void*)start) != 0) {
+ return (reg_t)-1;
+ }
+ cur += len;
+ written += size;
+ left -= size;
+ i++;
+ }
+ /* write extra null byte */
+ if (left != 0) {
+ char nul = '\0';
+ if (copy_to_user((va_t)buf + written, 1, &nul) != 0) {
+ return (reg_t)-1;
+ }
+ written++;
+ }
+ return (reg_t)written;
+}
diff --git a/kern/syscall_process.c b/kern/syscall_process.c
new file mode 100644
index 0000000..6007bcd
--- /dev/null
+++ b/kern/syscall_process.c
@@ -0,0 +1,397 @@
+/** @file syscall_process.c
+ *
+ * @brief process management syscalls.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <simics.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <common_kern.h>
+#include <elf/elf_410.h>
+#include <ureg.h>
+
+#include <x86/asm.h>
+#include <x86/cr.h>
+#include <x86/eflags.h>
+#include <x86/seg.h>
+
+#include <asm_instr.h>
+#include <assert.h>
+#include <loader.h>
+#include <malloc.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <sync.h>
+#include <timer.h>
+#include <usermem.h>
+
+/**
+ * @brief gettid() syscall handler
+ * @param f saved regs
+ */
+void sys_gettid_real(stack_frame_t* f) {
+ f->eax = (reg_t)get_current()->rb_node.key;
+}
+
+/**
+ * @brief copy a region of current process to a process
+ * @param p target process
+ * @param src source region
+ * @return 0 on success, or -1 on failure
+ */
+static int copy_region(process_t* p, region_t* src);
+
+/**
+ * @brief fork() syscall handler
+ * @param f saved regs
+ */
+void sys_fork_real(stack_frame_t* f) {
+ thread_t* current = get_current();
+ process_t* p = current->process;
+ mutex_lock(&p->refcount_lock);
+ if (p->refcount != 1) {
+ /* reject multithread fork() */
+ mutex_unlock(&p->refcount_lock);
+ goto fork_multiple_threads;
+ }
+ mutex_unlock(&p->refcount_lock);
+ thread_t* t = create_empty_process();
+ if (t == NULL) {
+ goto create_thread_fail;
+ }
+ int tid = alloc_tid();
+ t->process->pid = t->rb_node.key = tid;
+ int i, n = vector_size(&p->regions);
+ for (i = 0; i < n; i++) {
+ int result =
+ copy_region(t->process, (region_t*)vector_at(&p->regions, i));
+ if (result != 0) {
+ goto copy_region_fail;
+ }
+ }
+ t->esp3 = current->esp3;
+ t->eip3 = current->eip3;
+ t->swexn_arg = current->swexn_arg;
+ t->eip0 = current->eip0;
+
+ t->kernel_esp -= sizeof(stack_frame_t);
+ stack_frame_t* frame = (stack_frame_t*)t->kernel_esp;
+ *frame = *f;
+ frame->eax = 0; /* child process will get 0 from fork() */
+
+ t->kernel_esp -= sizeof(yield_frame_t);
+ yield_frame_t* yf = (yield_frame_t*)t->kernel_esp;
+ yf->eflags = DEFAULT_EFLAGS;
+ yf->raddr = (reg_t)return_to_user;
+
+ t->process->parent = p;
+ mutex_lock(&p->wait_lock);
+ queue_insert_head(&p->live_childs, &t->process->sible_link);
+ p->nchilds++;
+ mutex_unlock(&p->wait_lock);
+ add_thread(t);
+ int old_if = spl_lock(&ready_lock);
+ insert_ready_tail(t);
+ spl_unlock(&ready_lock, old_if);
+ f->eax = (reg_t)tid;
+ return;
+
+copy_region_fail:
+ destroy_thread(t);
+create_thread_fail:
+ f->eax = (reg_t)-1;
+ return;
+
+fork_multiple_threads:
+ f->eax = (reg_t)-2;
+ return;
+}
+
+static int copy_region(process_t* p, region_t* src) {
+ process_t* cur_p = get_current()->process;
+ int n_pages = src->size / PAGE_SIZE;
+ pa_t paddr = alloc_user_pages(n_pages);
+ if (paddr == 0) {
+ goto alloc_segment_fail;
+ }
+ if (add_region(p, src->addr, n_pages, paddr, src->is_rw) != 0) {
+ goto add_region_fail;
+ }
+
+ int rw = (src->is_rw ? PTE_RW : PTE_RO);
+ int i = 0;
+ int offset = 0;
+ /* file page table one entry per loop */
+ pa_t dst_pt_pa = find_or_create_pt(p, src->addr);
+ if (dst_pt_pa == BAD_PA) {
+ goto add_pt_fail;
+ }
+ pa_t src_pt_pa = find_or_create_pt(cur_p, src->addr);
+ do {
+ offset = i * PAGE_SIZE;
+ /* step to next page table */
+ int pt_index = get_pt_index(src->addr + offset);
+ if (pt_index == 0) {
+ dst_pt_pa = find_or_create_pt(p, src->addr + offset);
+ if (dst_pt_pa == BAD_PA) {
+ goto add_pt_fail;
+ }
+ src_pt_pa = find_or_create_pt(cur_p, src->addr);
+ }
+
+ int old_if = save_clear_if();
+ /* check if source page is never accessed */
+ page_table_t* src_pt = (page_table_t*)map_phys_page(src_pt_pa, NULL);
+ if (((*src_pt)[pt_index] & (PTE_PRESENT << PTE_P_SHIFT)) != 0) {
+ /* need to copy non zero page */
+ page_table_t* dst_pt =
+ (page_table_t*)map_phys_page(dst_pt_pa, NULL);
+ (*dst_pt)[pt_index] =
+ make_pte(paddr + offset, 0, PTE_USER, rw, PTE_PRESENT);
+ char* page = (char*)map_phys_page(paddr + offset, NULL);
+ memcpy(page, (char*)(src->addr + offset), PAGE_SIZE);
+ } else {
+ /* source page is waiting for ZFOD, so dest page will also ZFOD */
+ page_table_t* dst_pt =
+ (page_table_t*)map_phys_page(dst_pt_pa, NULL);
+ (*dst_pt)[pt_index] = make_pte(paddr + offset, 0, PTE_USER, rw, 0);
+ }
+ restore_if(old_if);
+ } while (++i < n_pages);
+ return 0;
+
+add_pt_fail:
+ /* page tables are reachable from page directory so no need to free them */
+ vector_pop(&p->regions);
+add_region_fail:
+ free_user_pages(paddr, n_pages);
+alloc_segment_fail:
+ return -1;
+}
+
+/**
+ * @brief task_vanish() syscall handler
+ * @param f saved regs
+ */
+void sys_task_vanish_real(stack_frame_t* f) {
+ process_t* p = get_current()->process;
+ p->exit_value = (int)f->esi;
+ mutex_lock(&p->refcount_lock);
+ queue_t *node = p->threads, *end = p->threads;
+ do {
+ /* send a pending exit to all other threads,
+ * blocked threads will exit when the thread blocking it exits
+ * sleeping threads will exit when timer is reached
+ * threads waiting for input will exit after getting input
+ */
+ thread_t* t = queue_data(node, thread_t, process_link);
+ t->pending_exit = 1;
+ int old_if = spl_lock(&t->status_lock);
+ /* wakeup descheduled threads */
+ if (t->status == THREAD_DESCHEDULED) {
+ int old_if2 = spl_lock(&ready_lock);
+ insert_ready_tail(t);
+ spl_unlock(&ready_lock, old_if2);
+ }
+ spl_unlock(&t->status_lock, old_if);
+ node = node->next;
+ } while (node != end);
+ mutex_unlock(&p->refcount_lock);
+ kill_current();
+}
+
+/**
+ * @brief set_status() syscall handler
+ * @param f saved regs
+ */
+void sys_set_status_real(stack_frame_t* f) {
+ get_current()->process->exit_value = (int)f->esi;
+}
+
+/**
+ * @brief exec() syscall handler
+ * @param f saved regs
+ */
+void sys_exec_real(stack_frame_t* f) {
+ thread_t* current = get_current();
+ process_t* p = current->process;
+ mutex_unlock(&p->refcount_lock);
+ if (p->refcount != 1) {
+ /* reject multithread exec() */
+ mutex_unlock(&p->refcount_lock);
+ goto exec_multiple_threads;
+ }
+ mutex_unlock(&p->refcount_lock);
+ reg_t esi = f->esi;
+ va_t pexe;
+ if (copy_from_user((va_t)esi, sizeof(va_t), &pexe) != 0) {
+ goto read_exe_fail;
+ }
+ char* exe = copy_string_from_user(pexe, MAX_EXECNAME_LEN);
+ if (exe == NULL) {
+ goto read_exe_fail;
+ }
+ va_t pargv;
+ if (copy_from_user((va_t)esi + sizeof(va_t), sizeof(va_t), &pargv) != 0) {
+ goto read_argv_fail;
+ }
+ int argc = 0;
+ while (1) {
+ if (argc > MAX_NUM_ARG) {
+ goto read_argv_fail;
+ }
+ va_t argv;
+ if (copy_from_user(pargv + argc * sizeof(va_t), sizeof(va_t), &argv) !=
+ 0) {
+ goto read_argv_fail;
+ }
+ if (argv == 0) {
+ break;
+ }
+ argc++;
+ }
+ char** argv_buf = smalloc(argc * sizeof(char*));
+ if (argv_buf == NULL) {
+ goto read_argv_fail;
+ }
+ int i;
+ for (i = 0; i < argc; i++) {
+ argv_buf[i] = copy_string_from_user(((va_t*)pargv)[i], MAX_ARG_LEN);
+ if (argv_buf[i] == NULL) {
+ goto read_argv_buf_fail;
+ }
+ }
+
+ /* we create a new process and swap to it so we can recover if process
+ * creation fails
+ */
+ thread_t* t =
+ create_process(current->rb_node.key, exe, argc, (const char**)argv_buf);
+ if (t == NULL) {
+ goto create_process_fail;
+ }
+ for (i = argc - 1; i >= 0; i--) {
+ free(argv_buf[i]);
+ }
+ sfree(argv_buf, argc * sizeof(char*));
+ free(exe);
+ swap_process_inplace(t);
+ int old_if = spl_lock(&ready_lock);
+ insert_ready_tail(t);
+ spl_unlock(&ready_lock, old_if);
+ kill_current();
+
+create_process_fail:
+read_argv_buf_fail:
+ for (i--; i >= 0; i--) {
+ free(argv_buf[i]);
+ }
+ sfree(argv_buf, argc * sizeof(char*));
+read_argv_fail:
+ free(exe);
+read_exe_fail:
+ f->eax = (reg_t)-1;
+ return;
+
+exec_multiple_threads:
+ f->eax = (reg_t)-2;
+ return;
+}
+
+/**
+ * @brief wait() syscall handler
+ * @param f saved regs
+ */
+void sys_wait_real(stack_frame_t* f) {
+ /* it may be possible that pstatus is not valid when calling wait() and then
+ * become valid after other thread allocates pages so we do not do check
+ * now
+ */
+ va_t pstatus = f->esi;
+ thread_t* current = get_current();
+ process_t* p = current->process;
+ /* it is possible that a process create multiple child processes and then
+ * create multiple threads and then call wait() in these threads, so we need
+ * to handle this case and count the waiting threads
+ */
+ mutex_lock(&p->wait_lock);
+ if (p->nchilds <= p->nwaiters) {
+ /* Pigeonhole principle prevents us to get a child in future even if
+ * there are some childs
+ */
+ mutex_unlock(&p->wait_lock);
+ goto no_child_to_wait;
+ }
+ p->nwaiters++;
+ while (p->dead_childs == NULL) {
+ cv_wait(&p->wait_cv, &p->wait_lock);
+ }
+ process_t* child = queue_data(p->dead_childs, process_t, sible_link);
+ /* copy the exit status first so we do not need to put the child back to the
+ * queue if copy fails
+ */
+ if (pstatus != 0 &&
+ copy_to_user(pstatus, sizeof(int), &child->exit_value) != 0) {
+ p->nwaiters--;
+ mutex_unlock(&p->wait_lock);
+ goto bad_pstatus;
+ }
+ queue_remove_head(&p->dead_childs);
+ p->nchilds--;
+ p->nwaiters--;
+ mutex_unlock(&p->wait_lock);
+ f->eax = (reg_t)child->pid;
+ sfree(child, sizeof(process_t));
+ return;
+
+bad_pstatus:
+ f->eax = (reg_t)-1;
+ return;
+
+no_child_to_wait:
+ f->eax = (reg_t)-2;
+ return;
+}
+
+/**
+ * @brief sleep() syscall handler
+ * @param f saved regs
+ */
+void sys_sleep_real(stack_frame_t* f) {
+ int dt = (int)f->esi;
+ if (dt <= 0) {
+ f->eax = (reg_t)dt;
+ return;
+ }
+ thread_t* current = get_current();
+ heap_node_t node;
+ node.key = ticks + dt;
+ node.value = (void*)current;
+ int old_if = spl_lock(&timer_lock);
+ if (heap_insert(&timers, &node) != 0) {
+ spl_unlock(&timer_lock, old_if);
+ f->eax = (reg_t)-2;
+ return;
+ }
+ int old_if2 = spl_lock(&ready_lock);
+ current->status = THREAD_SLEEPING;
+ thread_t* t = select_next();
+ spl_unlock(&ready_lock, old_if2);
+ yield_to_spl_unlock(t, &timer_lock, old_if);
+ f->eax = 0;
+ return;
+}
+
+/**
+ * @brief get_ticks() syscall handler
+ * @param f saved regs
+ */
+void sys_get_ticks_real(stack_frame_t* f) {
+ f->eax = (reg_t)ticks;
+ return;
+}
diff --git a/kern/syscall_thread.c b/kern/syscall_thread.c
new file mode 100644
index 0000000..d8984b0
--- /dev/null
+++ b/kern/syscall_thread.c
@@ -0,0 +1,274 @@
+/** @file syscall_thread.c
+ *
+ * @brief thread management syscalls.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <string.h>
+
+#include <common_kern.h>
+#include <elf/elf_410.h>
+#include <simics.h>
+#include <ureg.h>
+
+#include <x86/asm.h>
+#include <x86/cr.h>
+#include <x86/eflags.h>
+#include <x86/seg.h>
+
+#include <asm_instr.h>
+#include <assert.h>
+#include <malloc.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+#include <usermem.h>
+
+/**
+ * @brief thread_fork() syscall handler
+ * @param f saved regs
+ */
+void sys_thread_fork_real(stack_frame_t* f) {
+ thread_t* current = get_current();
+ process_t* p = current->process;
+ thread_t* t = smalloc(sizeof(thread_t));
+ if (t == NULL) {
+ goto alloc_tcb_fail;
+ }
+ t->stack = smemalign(PAGE_SIZE, K_STACK_SIZE);
+ if (t->stack == NULL) {
+ goto alloc_thread_stack_fail;
+ }
+ int tid = alloc_tid();
+ t->rb_node.key = tid;
+ t->status = THREAD_DEAD;
+ t->status_lock = SPL_INIT;
+ t->pending_exit = current->pending_exit;
+ t->esp3 = current->esp3;
+ t->eip3 = current->eip3;
+ t->df3 = current->df3;
+ t->swexn_arg = current->swexn_arg;
+ t->esp0 = t->kernel_esp = (reg_t)&t->stack[K_STACK_SIZE];
+ t->eip0 = current->eip0;
+
+ t->kernel_esp -= sizeof(stack_frame_t);
+ stack_frame_t* frame = (stack_frame_t*)t->kernel_esp;
+ *frame = *f;
+ frame->eax = 0;
+
+ t->kernel_esp -= sizeof(yield_frame_t);
+ yield_frame_t* yf = (yield_frame_t*)t->kernel_esp;
+ yf->eflags = DEFAULT_EFLAGS;
+ yf->raddr = (reg_t)return_to_user;
+
+ t->process = p;
+ mutex_lock(&p->refcount_lock);
+ p->refcount++;
+ queue_insert_tail(&p->threads, &t->process_link);
+ mutex_unlock(&p->refcount_lock);
+ add_thread(t);
+ int old_if = spl_lock(&ready_lock);
+ insert_ready_tail(t);
+ spl_unlock(&ready_lock, old_if);
+ f->eax = (reg_t)tid;
+ return;
+
+alloc_thread_stack_fail:
+ sfree(t, sizeof(thread_t));
+alloc_tcb_fail:
+ f->eax = (reg_t)-1;
+}
+
+/**
+ * @brief deschedule() syscall handler
+ * @param f saved regs
+ */
+void sys_deschedule_real(stack_frame_t* f) {
+ va_t preject = (va_t)f->esi;
+ int reject;
+ thread_t* current = get_current();
+ int old_if = spl_lock(&current->status_lock);
+ if (copy_from_user(preject, sizeof(int), &reject) != 0) {
+ spl_unlock(&current->status_lock, old_if);
+ f->eax = (reg_t)-1;
+ return;
+ }
+ if (reject != 0 || current->pending_exit != 0) {
+ spl_unlock(&current->status_lock, old_if);
+ f->eax = (reg_t)0;
+ return;
+ }
+ int old_if2 = spl_lock(&ready_lock);
+ current->status = THREAD_DESCHEDULED;
+ thread_t* t = select_next();
+ spl_unlock(&ready_lock, old_if2);
+ /* release status_lock last to make sure nobody make us runnable before
+ * deschedule
+ */
+ yield_to_spl_unlock(t, &current->status_lock, old_if);
+ f->eax = (reg_t)0;
+ return;
+}
+
+/**
+ * @brief make_runnable() syscall handler
+ * @param f saved regs
+ */
+void sys_make_runnable_real(stack_frame_t* f) {
+ int tid = (int)f->esi;
+ mutex_lock(&threads_lock);
+ thread_t* t = find_thread(tid);
+ if (t == NULL) {
+ mutex_unlock(&threads_lock);
+ f->eax = (reg_t)-2;
+ return;
+ }
+ int old_if = spl_lock(&t->status_lock);
+ /* we must hold threads_lock before making sure t is descheduled, otherwise
+ * if t is running/ready, t can be freed at any time
+ */
+ if (t->status != THREAD_DESCHEDULED) {
+ spl_unlock(&t->status_lock, old_if);
+ mutex_unlock(&threads_lock);
+ f->eax = (reg_t)-3;
+ return;
+ }
+ mutex_unlock(&threads_lock);
+ /* must lock ready lock before setting status to READY otherwise yield() may
+ * yield to it before we insert it into ready queue
+ */
+ int old_if2 = spl_lock(&ready_lock);
+ t->status = THREAD_READY;
+ insert_ready_tail(t);
+ spl_unlock(&ready_lock, old_if2);
+ spl_unlock(&t->status_lock, old_if);
+ f->eax = (reg_t)0;
+ return;
+}
+
+/**
+ * @brief vanish() syscall handler
+ * @param f saved regs
+ */
+void sys_vanish_real(stack_frame_t* f) {
+ kill_current();
+}
+
+/**
+ * @brief yield() syscall handler
+ * @param f saved regs
+ */
+void sys_yield_real(stack_frame_t* f) {
+ int tid = (int)f->esi;
+ if (tid == -1) {
+ int old_if = spl_lock(&ready_lock);
+ insert_ready_tail(get_current());
+ thread_t* t = select_next();
+ yield_to_spl_unlock(t, &ready_lock, old_if);
+ f->eax = (reg_t)0;
+ return;
+ }
+ mutex_lock(&threads_lock);
+ thread_t* t = find_thread(tid);
+ if (t == NULL) {
+ mutex_unlock(&threads_lock);
+ f->eax = (reg_t)-2;
+ return;
+ }
+ /* we must hold threads_lock and ready_lock before making sure t is ready
+ * if we do not have threads_lock, t may be freed
+ * if we do not have ready_lock, t may be turned from ready to running by
+ * other processors
+ */
+ int old_if = spl_lock(&ready_lock);
+ if (t->status == THREAD_RUNNING) {
+ /* t is running or other processor, no need to yield */
+ t = select_next();
+ } else if (t->status == THREAD_READY) {
+ queue_detach(&ready, &t->sched_link);
+ } else {
+ spl_unlock(&ready_lock, old_if);
+ mutex_unlock(&threads_lock);
+ f->eax = (reg_t)-1;
+ return;
+ }
+ /* if we are sure t is ready and remove it from ready queue, it will not
+ * suddenly run so we can make sure t will not be freed
+ */
+ mutex_unlock(&threads_lock);
+ insert_ready_tail(get_current());
+ yield_to_spl_unlock(t, &ready_lock, old_if);
+ f->eax = (reg_t)0;
+ return;
+}
+
+/* eflags fields that are allowed to be changed by user */
+#define EFLAGS_USER_MASK \
+ (EFL_CF | EFL_PF | EFL_AF | EFL_ZF | EFL_SF | EFL_TF | EFL_DF | EFL_OF | \
+ EFL_RF)
+
+/**
+ * @brief swexn() syscall handler
+ * @param f saved regs
+ */
+void sys_swexn_real(stack_frame_t* f) {
+ thread_t* current = get_current();
+ reg_t args[4]; /* esp3, eip3, arg, ureg */
+ reg_t esi = f->esi;
+ if (copy_from_user(esi, 4 * sizeof(reg_t), args) != 0) {
+ goto read_arg_fail;
+ }
+ reg_t esp3 = args[0], eip3 = args[1], pureg = args[3];
+ if (esp3 != 0 && eip3 != 0) {
+ /* for unlcear "stack end" definition */
+ esp3 = (esp3 & (~(sizeof(va_t) - 1)));
+ /* check first, will register after successful ureg copy */
+ if (eip3 < USER_MEM_START || eip3 >= DEFAULT_STACK_END ||
+ esp3 < USER_MEM_START || esp3 >= DEFAULT_STACK_END) {
+ goto bad_user_entry;
+ }
+ }
+ if (pureg != 0) {
+ ureg_t ureg;
+ if (copy_from_user((va_t)pureg, sizeof(ureg_t), &ureg) != 0) {
+ goto bad_user_entry;
+ }
+ /* segment regs are ignored since they cannot be changed */
+ /* only user fields in eflags are allowed to be changed */
+ if ((ureg.eflags & (~EFLAGS_USER_MASK)) != DEFAULT_EFLAGS) {
+ goto bad_user_entry;
+ }
+ f->eip = ureg.eip;
+ f->eflags = ureg.eflags;
+ f->esp = ureg.esp;
+ f->eax = ureg.eax;
+ f->ecx = ureg.ecx;
+ f->edx = ureg.edx;
+ f->ebx = ureg.ebx;
+ f->ebp = ureg.ebp;
+ f->esi = ureg.esi;
+ f->edi = ureg.edi;
+ } else {
+ f->eax = 0;
+ }
+ if (esp3 != 0 && eip3 != 0) {
+ current->esp3 = esp3;
+ current->eip3 = eip3;
+ current->swexn_arg = args[2];
+ } else {
+ current->eip3 = 0;
+ }
+ /* clear swexn flag after success swexn call */
+ current->df3 = 0;
+ return;
+
+bad_user_entry:
+ f->eax = (reg_t)-2;
+ return;
+
+read_arg_fail:
+ f->eax = (reg_t)-1;
+ return;
+}
diff --git a/kern/timer.c b/kern/timer.c
new file mode 100644
index 0000000..7f39f06
--- /dev/null
+++ b/kern/timer.c
@@ -0,0 +1,74 @@
+/** @file timer.c
+ *
+ * @brief timer functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <common_kern.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <assert.h>
+#include <x86/asm.h>
+#include <x86/interrupt_defines.h>
+#include <x86/timer_defines.h>
+
+#include <interrupt.h>
+#include <sched.h>
+#include <sync.h>
+#include <timer.h>
+
+unsigned int ticks = 0;
+
+spl_t timer_lock = SPL_INIT;
+
+heap_t timers;
+
+/* we want a 2ms thread switch but since sometimes interrupts are disabled, we
+ * set a higher rate to compensate this
+ */
+#define TIMER_FREQ 1000
+
+void timer_init() {
+ int counter = TIMER_RATE / TIMER_FREQ;
+ outb(TIMER_MODE_IO_PORT, TIMER_SQUARE_WAVE);
+ outb(TIMER_PERIOD_IO_PORT, counter & 0xFF);
+ outb(TIMER_PERIOD_IO_PORT, (counter >> 8) & 0xFF);
+ if (heap_init(&timers) != 0) {
+ panic("no space to initialize timer heap");
+ }
+}
+
+/**
+ * @brief check expired timers
+ */
+static void check_timers() {
+ int old_if = spl_lock(&timer_lock);
+ heap_node_t* node;
+ while ((node = heap_peak(&timers)) != NULL) {
+ if (node->key > ticks) {
+ break;
+ }
+ thread_t* t = (thread_t*)node->value;
+ heap_pop(&timers);
+ int old_if2 = spl_lock(&ready_lock);
+ insert_ready_tail(t);
+ spl_unlock(&ready_lock, old_if2);
+ }
+ spl_unlock(&timer_lock, old_if);
+}
+
+void timer_handler_real() {
+ pic_acknowledge(TIMER_IRQ);
+ ticks++;
+ check_timers();
+ int old_if = spl_lock(&ready_lock);
+ thread_t* current = get_current();
+ if (current != get_idle()) {
+ insert_ready_tail(current);
+ }
+ thread_t* t = select_next();
+ yield_to_spl_unlock(t, &ready_lock, old_if);
+}
diff --git a/kern/usermem.c b/kern/usermem.c
new file mode 100644
index 0000000..717cdbe
--- /dev/null
+++ b/kern/usermem.c
@@ -0,0 +1,153 @@
+/** @file usermem.c
+ *
+ * @brief userspace r/w functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <common_kern.h>
+#include <x86/asm.h>
+
+#include <console.h>
+#include <mm.h>
+#include <paging.h>
+#include <sched.h>
+
+/**
+ * @brief try to read a byte from userspace
+ * @param addr address
+ * @param output output byte if successful
+ * @return 0 on success, jump to usermem_fail on failure
+ */
+int try_read(va_t addr, char* output);
+/**
+ * @brief try to write a byte to userspace
+ * @param addr address
+ * @param c byte to write
+ * @return 0 on success, jump to usermem_fail on failure
+ */
+int try_write(va_t addr, char c);
+
+/**
+ * @brief tell the calling function the r/w failed
+ * @return -1
+ */
+int usermem_fail();
+
+/**
+ * @brief setup fault handler before r/w
+ * @return old fault handler
+ */
+static reg_t usermem_setup() {
+ thread_t* current = get_current();
+ reg_t old_eip0 = current->eip0;
+ current->eip0 = (reg_t)usermem_fail;
+ return old_eip0;
+}
+
+/**
+ * @brief restore old fault handler
+ * @param old_eip0 old fault handler
+ */
+static void usermem_finish(reg_t old_eip0) {
+ get_current()->eip0 = old_eip0;
+}
+
+int copy_from_user(va_t addr, int size, void* buf) {
+ va_t end = addr + size;
+ if (addr < USER_MEM_START || end < addr || end >= DEFAULT_STACK_END) {
+ return -1;
+ }
+ reg_t old_eip0 = usermem_setup();
+ int i, result = 0;
+ for (i = 0; i < size; i++) {
+ if (try_read(addr + i, (char*)buf + i) != 0) {
+ result = -1;
+ break;
+ }
+ }
+ usermem_finish(old_eip0);
+ return result;
+}
+
+int copy_to_user(va_t addr, int size, void* buf) {
+ va_t end = addr + size;
+ if (addr < USER_MEM_START || end < addr || end >= DEFAULT_STACK_END) {
+ return -1;
+ }
+ reg_t old_eip0 = usermem_setup();
+ int i, result = 0;
+ for (i = 0; i < size; i++) {
+ if (try_write(addr + i, *((char*)buf + i)) != 0) {
+ result = -1;
+ break;
+ }
+ }
+ usermem_finish(old_eip0);
+ return result;
+}
+
+char* copy_string_from_user(va_t addr, int maxlen) {
+ if (addr < USER_MEM_START || addr >= DEFAULT_STACK_END) {
+ return NULL;
+ }
+ if (DEFAULT_STACK_END - addr < maxlen) {
+ maxlen = DEFAULT_STACK_END - addr;
+ }
+ reg_t old_eip0 = usermem_setup();
+ int len = 0, buflen = 3 * sizeof(int);
+ char* buf = malloc(buflen);
+ char c;
+ if (buf == NULL) {
+ goto read_string_finished;
+ }
+ while (1) {
+ if (len >= maxlen || try_read(addr + len, &c) != 0) {
+ free(buf);
+ buf = NULL;
+ break;
+ }
+
+ buf[len++] = c;
+ if (c == '\0') {
+ break;
+ }
+
+ if (len >= buflen) {
+ buflen <<= 1;
+ char* result = realloc(buf, buflen);
+ if (result == NULL) {
+ free(buf);
+ buf = NULL;
+ break;
+ }
+ buf = result;
+ }
+ }
+read_string_finished:
+ usermem_finish(old_eip0);
+ return buf;
+}
+
+int print_buf_from_user(va_t addr, int len) {
+ va_t end = addr + len;
+ if (addr < USER_MEM_START || end < addr || end >= DEFAULT_STACK_END) {
+ return -1;
+ }
+ reg_t old_eip0 = usermem_setup();
+ int i, result = 0;
+ char c;
+ for (i = 0; i < len; i++) {
+ if (try_read(addr + i, &c) != 0) {
+ result = -1;
+ break;
+ }
+ putbyte(c);
+ }
+ usermem_finish(old_eip0);
+ return result;
+}
diff --git a/kern/usermem_asm.S b/kern/usermem_asm.S
new file mode 100644
index 0000000..0eee6f8
--- /dev/null
+++ b/kern/usermem_asm.S
@@ -0,0 +1,35 @@
+/** @file usermem_asm.S
+ *
+ * @brief userspace r/w functions.
+ *
+ * @author Hanjie Wu (hanjiew)
+ * @bug No functional bugs
+ */
+
+.text
+
+.global try_read
+.type try_read, %function
+try_read:
+ mov 0x4(%esp), %eax
+ mov 0x8(%esp), %esi
+ mov (%eax), %al
+ mov %al, (%esi)
+ xor %eax, %eax
+ ret
+
+.global try_write
+.type try_write, %function
+try_write:
+ mov 0x4(%esp), %eax
+ mov 0x8(%esp), %edx
+ mov %dl, (%eax)
+ xor %eax, %eax
+ ret
+
+/* unsuccessful r/w will jump to this function and we return 1 */
+.global usermem_fail
+.type usermem_fail, %function
+usermem_fail:
+ mov $0xffffffff, %eax
+ ret