diff --git a/build.zig b/build.zig
index e74d19a..3827878 100644
--- a/build.zig
+++ b/build.zig
@@ -78,10 +78,22 @@ pub fn build(b: *Builder) !void {
     var ramdisk_files_al = ArrayList([]const u8).init(b.allocator);
     defer ramdisk_files_al.deinit();
 
-    // Add some test files for the ramdisk runtime tests
     if (test_mode == .Initialisation) {
+        // Add some test files for the ramdisk runtime tests
         try ramdisk_files_al.append("test/ramdisk_test1.txt");
         try ramdisk_files_al.append("test/ramdisk_test2.txt");
+    } else if (test_mode == .Scheduler) {
+        // Add some test files for the user mode runtime tests
+        const user_program = b.addAssemble("user_program", "test/user_program.s");
+        user_program.setOutputDir(b.cache_root);
+        user_program.setTarget(target);
+        user_program.setBuildMode(build_mode);
+        user_program.strip = true;
+
+        const copy_user_program = b.addSystemCommand(&[_][]const u8{ "objcopy", "-O", "binary", "zig-cache/user_program.o", "zig-cache/user_program" });
+        copy_user_program.step.dependOn(&user_program.step);
+        try ramdisk_files_al.append("zig-cache/user_program");
+        exec.step.dependOn(&copy_user_program.step);
     }
 
     const ramdisk_step = RamdiskStep.create(b, target, ramdisk_files_al.toOwnedSlice(), ramdisk_path);
diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig
index fea6290..aad9cbf 100644
--- a/src/kernel/arch/x86/arch.zig
+++ b/src/kernel/arch/x86/arch.zig
@@ -24,6 +24,7 @@ const Serial = @import("../../serial.zig").Serial;
 const panic = @import("../../panic.zig").panic;
 const TTY = @import("../../tty.zig").TTY;
 const Keyboard = @import("../../keyboard.zig").Keyboard;
+const Task = @import("../../task.zig").Task;
 const MemProfile = mem.MemProfile;
 
 /// The type of a device.
@@ -53,8 +54,9 @@ extern var KERNEL_STACK_END: *u32;
 /// The interrupt context that is given to a interrupt handler. It contains most of the registers
 /// and the interrupt number and error code (if there is one).
 pub const CpuState = packed struct {
+    // Page directory
+    cr3: usize,
     // Extra segments
-    ss: u32,
     gs: u32,
     fs: u32,
     es: u32,
@@ -102,9 +104,6 @@ pub const VMM_MAPPER: vmm.Mapper(VmmPayload) = vmm.Mapper(VmmPayload){ .mapFn =
 /// The size of each allocatable block of memory, normally set to the page size.
 pub const MEMORY_BLOCK_SIZE: usize = paging.PAGE_SIZE_4KB;
 
-/// The default stack size of a task. Currently this is set to a page size.
-pub const STACK_SIZE: u32 = MEMORY_BLOCK_SIZE / @sizeOf(u32);
-
 ///
 /// Assembly that reads data from a given port and returns its value.
 ///
@@ -499,50 +498,71 @@ pub fn initKeyboard(allocator: *Allocator) Allocator.Error!*Keyboard {
 }
 
 ///
-/// Initialise a 32bit kernel stack used for creating a task.
+/// Initialise a stack used for creating a task.
 /// Currently only support fn () noreturn functions for the entry point.
 ///
 /// Arguments:
+///     IN task: *Task           - The task to be initialised. The function will only modify whatever
+///                                is required by the architecture. In the case of x86, it will put
+///                                the initial CpuState on the kernel stack.
 ///     IN entry_point: usize    - The pointer to the entry point of the function. Functions only
 ///                                supported is fn () noreturn
 ///     IN allocator: *Allocator - The allocator use for allocating a stack.
 ///
-/// Return: struct { stack: []u32, pointer: usize }
-///     The stack and stack pointer with the stack initialised as a 32bit kernel stack.
-///
 /// Error: Allocator.Error
 ///     OutOfMemory - Unable to allocate space for the stack.
 ///
-pub fn initTaskStack(entry_point: usize, allocator: *Allocator) Allocator.Error!struct { stack: []u32, pointer: usize } {
+pub fn initTask(task: *Task, entry_point: usize, allocator: *Allocator) Allocator.Error!void {
+    const data_offset = if (task.kernel) gdt.KERNEL_DATA_OFFSET else gdt.USER_DATA_OFFSET | 0b11;
+    // Setting the bottom two bits of the code offset designates that this is a ring 3 task
+    const code_offset = if (task.kernel) gdt.KERNEL_CODE_OFFSET else gdt.USER_CODE_OFFSET | 0b11;
+    // Ring switches push and pop two extra values on interrupt: user_esp and user_ss
+    const kernel_stack_bottom = if (task.kernel) task.kernel_stack.len - 18 else task.kernel_stack.len - 20;
+
+    var stack = &task.kernel_stack;
+
     // TODO Will need to add the exit point
     // Set up everything as a kernel task
-    var stack = try allocator.alloc(u32, STACK_SIZE);
-    stack[STACK_SIZE - 18] = gdt.KERNEL_DATA_OFFSET; // ss
-    stack[STACK_SIZE - 17] = gdt.KERNEL_DATA_OFFSET; // gs
-    stack[STACK_SIZE - 16] = gdt.KERNEL_DATA_OFFSET; // fs
-    stack[STACK_SIZE - 15] = gdt.KERNEL_DATA_OFFSET; // es
-    stack[STACK_SIZE - 14] = gdt.KERNEL_DATA_OFFSET; // ds
+    stack.*[kernel_stack_bottom] = mem.virtToPhys(@ptrToInt(&paging.kernel_directory));
+    stack.*[kernel_stack_bottom + 1] = data_offset; // gs
+    stack.*[kernel_stack_bottom + 2] = data_offset; // fs
+    stack.*[kernel_stack_bottom + 3] = data_offset; // es
+    stack.*[kernel_stack_bottom + 4] = data_offset; // ds
 
-    stack[STACK_SIZE - 13] = 0; // edi
-    stack[STACK_SIZE - 12] = 0; // esi
+    stack.*[kernel_stack_bottom + 5] = 0; // edi
+    stack.*[kernel_stack_bottom + 6] = 0; // esi
     // End of the stack
-    stack[STACK_SIZE - 11] = @ptrToInt(&stack[STACK_SIZE - 1]); // ebp
-    stack[STACK_SIZE - 10] = 0; // esp (temp) this won't be popped by popa bc intel is dump XD
+    stack.*[kernel_stack_bottom + 7] = @ptrToInt(&stack.*[stack.len - 1]); // ebp
+    stack.*[kernel_stack_bottom + 8] = 0; // esp (temp) this won't be popped by popa bc intel is dump XD
 
-    stack[STACK_SIZE - 9] = 0; // ebx
-    stack[STACK_SIZE - 8] = 0; // edx
-    stack[STACK_SIZE - 7] = 0; // ecx
-    stack[STACK_SIZE - 6] = 0; // eax
+    stack.*[kernel_stack_bottom + 9] = 0; // ebx
+    stack.*[kernel_stack_bottom + 10] = 0; // edx
+    stack.*[kernel_stack_bottom + 11] = 0; // ecx
+    stack.*[kernel_stack_bottom + 12] = 0; // eax
 
-    stack[STACK_SIZE - 5] = 0; // int_num
-    stack[STACK_SIZE - 4] = 0; // error_code
+    stack.*[kernel_stack_bottom + 13] = 0; // int_num
+    stack.*[kernel_stack_bottom + 14] = 0; // error_code
 
-    stack[STACK_SIZE - 3] = entry_point; // eip
-    stack[STACK_SIZE - 2] = gdt.KERNEL_CODE_OFFSET; // cs
-    stack[STACK_SIZE - 1] = 0x202; // eflags
+    stack.*[kernel_stack_bottom + 15] = entry_point; // eip
+    stack.*[kernel_stack_bottom + 16] = code_offset; // cs
+    stack.*[kernel_stack_bottom + 17] = 0x202; // eflags
 
-    const ret = .{ .stack = stack, .pointer = @ptrToInt(&stack[STACK_SIZE - 18]) };
-    return ret;
+    if (!task.kernel) {
+        // Put the extra values on the kernel stack needed when chaning privilege levels
+        stack.*[kernel_stack_bottom + 18] = @ptrToInt(&task.user_stack[task.user_stack.len - 1]); // user_esp
+        stack.*[kernel_stack_bottom + 19] = data_offset; // user_ss
+
+        if (!builtin.is_test) {
+            // Create a new page directory for the user task by mirroring the kernel directory
+            // We need kernel mem mapped so we don't get a page fault when entering kernel code from an interrupt
+            task.vmm.payload = &(try allocator.allocAdvanced(paging.Directory, paging.PAGE_SIZE_4KB, 1, .exact))[0];
+            task.vmm.payload.* = paging.kernel_directory.copy();
+            stack.*[kernel_stack_bottom] = vmm.kernel_vmm.virtToPhys(@ptrToInt(task.vmm.payload)) catch |e| {
+                panic(@errorReturnTrace(), "Failed to get the physical address of the user task's page directory: {}\n", .{e});
+            };
+        }
+    }
+    task.stack_pointer = @ptrToInt(&stack.*[kernel_stack_bottom]);
 }
 
 pub fn getDevices(allocator: *Allocator) Allocator.Error![]Device {
@@ -577,6 +597,19 @@ pub fn init(mem_profile: *const MemProfile) void {
     tty.init();
 }
 
+///
+/// Check the state of the user task used for runtime testing for the expected values. These should mirror those in test/user_program.s
+///
+/// Arguments:
+///     IN ctx: *const CpuState - The task's saved state
+///
+/// Return: bool
+///     True if the expected values were found, else false
+///
+pub fn runtimeTestCheckUserTaskState(ctx: *const CpuState) bool {
+    return ctx.eax == 0xCAFE and ctx.ebx == 0xBEEF;
+}
+
 test "" {
     std.testing.refAllDecls(@This());
 }
diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig
index f69d83f..6156097 100644
--- a/src/kernel/arch/x86/gdt.zig
+++ b/src/kernel/arch/x86/gdt.zig
@@ -358,7 +358,7 @@ var gdt_ptr: GdtPtr = GdtPtr{
 };
 
 /// The main task state segment entry.
-var main_tss_entry: Tss = init: {
+pub var main_tss_entry: Tss = init: {
     var tss_temp = std.mem.zeroes(Tss);
     tss_temp.ss0 = KERNEL_DATA_OFFSET;
     tss_temp.io_permissions_base_offset = @sizeOf(Tss);
diff --git a/src/kernel/arch/x86/interrupts.zig b/src/kernel/arch/x86/interrupts.zig
index 8cb2c92..a5c1929 100644
--- a/src/kernel/arch/x86/interrupts.zig
+++ b/src/kernel/arch/x86/interrupts.zig
@@ -32,7 +32,8 @@ export fn commonStub() callconv(.Naked) void {
         \\push  %%es
         \\push  %%fs
         \\push  %%gs
-        \\push  %%ss
+        \\mov %%cr3, %%eax
+        \\push %%eax
         \\mov   $0x10, %%ax
         \\mov   %%ax, %%ds
         \\mov   %%ax, %%es
@@ -42,13 +43,30 @@ export fn commonStub() callconv(.Naked) void {
         \\push  %%eax
         \\call  handler
         \\mov   %%eax, %%esp
-        \\pop   %%ss
+    );
+
+    // Pop off the new cr3 then check if it's the same as the previous cr3
+    // If so don't change cr3 to avoid a TLB flush
+    asm volatile (
+        \\pop   %%eax
+        \\mov   %%cr3, %%ebx
+        \\cmp   %%eax, %%ebx
+        \\je    same_cr3
+        \\mov   %%eax, %%cr3
+        \\same_cr3:
         \\pop   %%gs
         \\pop   %%fs
         \\pop   %%es
         \\pop   %%ds
         \\popa
-        \\add   $0x8, %%esp
+    );
+    // The Tss.esp0 value is the stack pointer used when an interrupt occurs. This should be the current process' stack pointer
+    // So skip the rest of the CpuState, set Tss.esp0 then un-skip the last few fields of the CpuState
+    asm volatile (
+        \\add   $0x1C, %%esp
+        \\.extern main_tss_entry
+        \\mov   %%esp, (main_tss_entry + 4)
+        \\sub   $0x14, %%esp
         \\iret
     );
 }
diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig
index d77bd26..a18ac5b 100644
--- a/src/kernel/arch/x86/paging.zig
+++ b/src/kernel/arch/x86/paging.zig
@@ -42,6 +42,19 @@ pub const Directory = packed struct {
             }
         }
     }
+
+    ///
+    /// Copy the page directory. Changes to one copy will not affect the other
+    ///
+    /// Arguments:
+    ///     IN self: *const Directory - The directory to copy
+    ///
+    /// Return: Directory
+    ///     The copy
+    ///
+    pub fn copy(self: *const Directory) Directory {
+        return self.*;
+    }
 };
 
 /// An array of table entries. Forms the second level of paging and covers a 4MB memory space.
@@ -598,6 +611,26 @@ test "map and unmap" {
     }
 }
 
+test "copy" {
+    // Create a dummy page dir
+    var dir: Directory = Directory{ .entries = [_]DirectoryEntry{0} ** ENTRIES_PER_DIRECTORY, .tables = [_]?*Table{null} ** ENTRIES_PER_DIRECTORY, .allocator = std.testing.allocator };
+    dir.entries[0] = 123;
+    dir.entries[56] = 794;
+    var table0 = Table{ .entries = [_]TableEntry{654} ** ENTRIES_PER_TABLE };
+    var table56 = Table{ .entries = [_]TableEntry{987} ** ENTRIES_PER_TABLE };
+    dir.tables[0] = &table0;
+    dir.tables[56] = &table56;
+    var dir2 = dir.copy();
+    const dir_slice = @ptrCast([*]const u8, &dir)[0..@sizeOf(Directory)];
+    const dir2_slice = @ptrCast([*]const u8, &dir2)[0..@sizeOf(Directory)];
+    testing.expectEqualSlices(u8, dir_slice, dir2_slice);
+
+    // Changes to one should not affect the other
+    dir2.tables[1] = &table0;
+    dir.tables[0] = &table56;
+    testing.expect(!std.mem.eql(u8, dir_slice, dir2_slice));
+}
+
 // The labels to jump to after attempting to cause a page fault. This is needed as we don't want to cause an
 // infinite loop by jumping to the same instruction that caused the fault.
 extern var rt_fault_callback: *u32;
diff --git a/src/kernel/bitmap.zig b/src/kernel/bitmap.zig
index e4c31cb..f135253 100644
--- a/src/kernel/bitmap.zig
+++ b/src/kernel/bitmap.zig
@@ -239,6 +239,29 @@ pub fn Bitmap(comptime BitmapType: type) type {
             return self;
         }
 
+        ///
+        /// Clone this bitmap.
+        ///
+        /// Arguments:
+        ///     IN self: *Self - The bitmap to clone.
+        ///
+        /// Return: Self
+        ///     The cloned bitmap
+        ///
+        /// Error: std.mem.Allocator.Error
+        ///     OutOfMemory: There isn't enough memory available to allocate the required number of BitmapType.
+        ///
+        pub fn clone(self: *const Self) std.mem.Allocator.Error!Self {
+            var copy = try init(self.num_entries, self.allocator);
+            var i: usize = 0;
+            while (i < copy.num_entries) : (i += 1) {
+                if (self.isSet(i) catch unreachable) {
+                    copy.setEntry(i) catch unreachable;
+                }
+            }
+            return copy;
+        }
+
         ///
         /// Free the memory occupied by this bitmap's internal state. It will become unusable afterwards.
         ///
diff --git a/src/kernel/filesystem/vfs.zig b/src/kernel/filesystem/vfs.zig
index f4cafc1..aab4e3b 100644
--- a/src/kernel/filesystem/vfs.zig
+++ b/src/kernel/filesystem/vfs.zig
@@ -753,7 +753,6 @@ test "traversePath" {
     testing.expectEqual(child4_linked, child3);
     var child5 = try traversePath("/child4", false, .CREATE_SYMLINK, .{ .symlink_target = "/child2" });
     var child5_linked = try traversePath("/child4/child3.txt", true, .NO_CREATION, .{});
-    std.debug.warn("child5_linked {}, child4_linked {}\n", .{ child5_linked, child4_linked });
     testing.expectEqual(child5_linked, child4_linked);
     child4_linked.File.close();
     child5_linked.File.close();
diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig
index 1bf08a0..9b99789 100644
--- a/src/kernel/kmain.zig
+++ b/src/kernel/kmain.zig
@@ -83,6 +83,12 @@ export fn kmain(boot_payload: arch.BootPayload) void {
     arch.init(&mem_profile);
     logger.info("Arch init done\n", .{});
 
+    // The VMM runtime tests can't happen until the architecture has initialised itself
+    switch (build_options.test_mode) {
+        .Initialisation => vmm.runtimeTests(arch.VmmPayload, kernel_vmm, &mem_profile),
+        else => {},
+    }
+
     // Give the kernel heap 10% of the available memory. This can be fine-tuned as time goes on.
     var heap_size = mem_profile.mem_kb / 10 * 1024;
     // The heap size must be a power of two so find the power of two smaller than or equal to the heap_size
@@ -101,10 +107,6 @@ export fn kmain(boot_payload: arch.BootPayload) void {
         keyboard.addKeyboard(kb) catch |e| panic_root.panic(@errorReturnTrace(), "Failed to add architecture keyboard: {}\n", .{e});
     }
 
-    scheduler.init(&kernel_heap.allocator) catch |e| {
-        panic_root.panic(@errorReturnTrace(), "Failed to initialise scheduler: {}\n", .{e});
-    };
-
     // Get the ramdisk module
     const rd_module = for (mem_profile.modules) |module| {
         if (std.mem.eql(u8, module.name, "initrd.ramdisk")) {
@@ -120,7 +122,6 @@ export fn kmain(boot_payload: arch.BootPayload) void {
         var ramdisk_filesystem = initrd.InitrdFS.init(&initrd_stream, &kernel_heap.allocator) catch |e| {
             panic_root.panic(@errorReturnTrace(), "Failed to initialise ramdisk: {}\n", .{e});
         };
-        defer ramdisk_filesystem.deinit();
 
         // Can now free the module as new memory is allocated for the ramdisk filesystem
         kernel_vmm.free(module.region.start) catch |e| {
@@ -129,10 +130,12 @@ export fn kmain(boot_payload: arch.BootPayload) void {
 
         // Need to init the vfs after the ramdisk as we need the root node from the ramdisk filesystem
         vfs.setRoot(ramdisk_filesystem.root_node);
-
-        // Load all files here
     }
 
+    scheduler.init(&kernel_heap.allocator, &mem_profile) catch |e| {
+        panic_root.panic(@errorReturnTrace(), "Failed to initialise scheduler: {}\n", .{e});
+    };
+
     // Initialisation is finished, now does other stuff
     logger.info("Init\n", .{});
 
@@ -142,10 +145,10 @@ export fn kmain(boot_payload: arch.BootPayload) void {
     logger.info("Creating init2\n", .{});
 
     // Create a init2 task
-    var idle_task = task.Task.create(initStage2, &kernel_heap.allocator) catch |e| {
+    var stage2_task = task.Task.create(@ptrToInt(initStage2), true, kernel_vmm, &kernel_heap.allocator) catch |e| {
         panic_root.panic(@errorReturnTrace(), "Failed to create init stage 2 task: {}\n", .{e});
     };
-    scheduler.scheduleTask(idle_task, &kernel_heap.allocator) catch |e| {
+    scheduler.scheduleTask(stage2_task, &kernel_heap.allocator) catch |e| {
         panic_root.panic(@errorReturnTrace(), "Failed to schedule init stage 2 task: {}\n", .{e});
     };
 
diff --git a/src/kernel/scheduler.zig b/src/kernel/scheduler.zig
index 8601a25..00f9203 100644
--- a/src/kernel/scheduler.zig
+++ b/src/kernel/scheduler.zig
@@ -10,18 +10,20 @@ const mock_path = build_options.mock_path;
 const arch = @import("arch.zig").internals;
 const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic;
 const task = if (is_test) @import(mock_path ++ "task_mock.zig") else @import("task.zig");
+const vmm = if (is_test) @import(mock_path ++ "vmm_mock.zig") else @import("vmm.zig");
+const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig");
+const fs = @import("filesystem/vfs.zig");
 const Task = task.Task;
+const EntryPoint = task.EntryPoint;
 const Allocator = std.mem.Allocator;
 const TailQueue = std.TailQueue;
 
-/// The function type for the entry point.
-const EntryPointFn = fn () void;
-
 /// The default stack size of a task. Currently this is set to a page size.
 const STACK_SIZE: u32 = arch.MEMORY_BLOCK_SIZE / @sizeOf(usize);
 
 /// Pointer to the start of the main kernel stack
 extern var KERNEL_STACK_START: []u32;
+extern var KERNEL_STACK_END: []u32;
 
 /// The current task running
 var current_task: *Task = undefined;
@@ -58,6 +60,14 @@ pub fn taskSwitching(enabled: bool) void {
 ///     The new stack pointer to the next stack of the next task.
 ///
 pub fn pickNextTask(ctx: *arch.CpuState) usize {
+    switch (build_options.test_mode) {
+        .Scheduler => if (!current_task.kernel) {
+            if (!arch.runtimeTestCheckUserTaskState(ctx)) {
+                panic(null, "User task state check failed\n", .{});
+            }
+        },
+        else => {},
+    }
     // Save the stack pointer from old task
     current_task.stack_pointer = @ptrToInt(ctx);
 
@@ -92,7 +102,7 @@ pub fn pickNextTask(ctx: *arch.CpuState) usize {
 /// Create a new task and add it to the scheduling queue. No locking.
 ///
 /// Arguments:
-///     IN entry_point: EntryPointFn - The entry point into the task. This must be a function.
+///     IN entry_point: EntryPoint - The entry point into the task. This must be a function.
 ///
 /// Error: Allocator.Error
 ///     OutOfMemory - If there isn't enough memory for the a task/stack. Any memory allocated will
@@ -112,11 +122,12 @@ pub fn scheduleTask(new_task: *Task, allocator: *Allocator) Allocator.Error!void
 ///
 /// Arguments:
 ///     IN allocator: *Allocator - The allocator to use when needing to allocate memory.
+///     IN mem_profile: *const mem.MemProfile - The system's memory profile used for runtime testing.
 ///
 /// Error: Allocator.Error
 ///     OutOfMemory - There is no more memory. Any memory allocated will be freed on return.
 ///
-pub fn init(allocator: *Allocator) Allocator.Error!void {
+pub fn init(allocator: *Allocator, mem_profile: *const mem.MemProfile) Allocator.Error!void {
     // TODO: Maybe move the task init here?
     log.info("Init\n", .{});
     defer log.info("Done\n", .{});
@@ -129,17 +140,20 @@ pub fn init(allocator: *Allocator) Allocator.Error!void {
     errdefer allocator.destroy(current_task);
     // PID 0
     current_task.pid = 0;
-    current_task.stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096];
+    const kernel_stack_size = @ptrToInt(&KERNEL_STACK_END) - @ptrToInt(&KERNEL_STACK_START);
+    current_task.kernel_stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..kernel_stack_size];
+    current_task.user_stack = &[_]usize{};
+    current_task.kernel = true;
     // ESP will be saved on next schedule
 
     // Run the runtime tests here
     switch (build_options.test_mode) {
-        .Scheduler => runtimeTests(allocator),
+        .Scheduler => runtimeTests(allocator, mem_profile),
         else => {},
     }
 
     // Create the idle task when there are no more tasks left
-    var idle_task = try Task.create(idle, allocator);
+    var idle_task = try Task.create(@ptrToInt(idle), true, &vmm.kernel_vmm, allocator);
     errdefer idle_task.destroy(allocator);
 
     try scheduleTask(idle_task, allocator);
@@ -154,20 +168,20 @@ fn test_fn2() void {}
 
 var test_pid_counter: u7 = 1;
 
-fn task_create(entry_point: EntryPointFn, allocator: *Allocator) Allocator.Error!*Task {
+fn createTestTask(entry_point: EntryPoint, allocator: *Allocator, kernel: bool, task_vmm: *vmm.VirtualMemoryManager(u8)) Allocator.Error!*Task {
     var t = try allocator.create(Task);
     errdefer allocator.destroy(t);
     t.pid = test_pid_counter;
     // Just alloc something
-    t.stack = try allocator.alloc(u32, 1);
+    t.kernel_stack = try allocator.alloc(u32, 1);
     t.stack_pointer = 0;
     test_pid_counter += 1;
     return t;
 }
 
-fn task_destroy(self: *Task, allocator: *Allocator) void {
-    if (@ptrToInt(self.stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) {
-        allocator.free(self.stack);
+fn destroyTestTask(self: *Task, allocator: *Allocator) void {
+    if (@ptrToInt(self.kernel_stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) {
+        allocator.free(self.kernel_stack);
     }
     allocator.destroy(self);
 }
@@ -176,9 +190,9 @@ test "pickNextTask" {
     task.initTest();
     defer task.freeTest();
 
-    task.addConsumeFunction("Task.create", task_create);
-    task.addConsumeFunction("Task.create", task_create);
-    task.addRepeatFunction("Task.destroy", task_destroy);
+    task.addConsumeFunction("Task.create", createTestTask);
+    task.addConsumeFunction("Task.create", createTestTask);
+    task.addRepeatFunction("Task.destroy", destroyTestTask);
 
     var ctx: arch.CpuState = std.mem.zeroes(arch.CpuState);
 
@@ -189,15 +203,15 @@ test "pickNextTask" {
     current_task = try allocator.create(Task);
     defer allocator.destroy(current_task);
     current_task.pid = 0;
-    current_task.stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096];
+    current_task.kernel_stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096];
     current_task.stack_pointer = @ptrToInt(&KERNEL_STACK_START);
 
     // Create two tasks and schedule them
-    var test_fn1_task = try Task.create(test_fn1, allocator);
+    var test_fn1_task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator);
     defer test_fn1_task.destroy(allocator);
     try scheduleTask(test_fn1_task, allocator);
 
-    var test_fn2_task = try Task.create(test_fn2, allocator);
+    var test_fn2_task = try Task.create(@ptrToInt(test_fn2), true, undefined, allocator);
     defer test_fn2_task.destroy(allocator);
     try scheduleTask(test_fn2_task, allocator);
 
@@ -239,8 +253,8 @@ test "createNewTask add new task" {
     task.initTest();
     defer task.freeTest();
 
-    task.addConsumeFunction("Task.create", task_create);
-    task.addConsumeFunction("Task.destroy", task_destroy);
+    task.addConsumeFunction("Task.create", createTestTask);
+    task.addConsumeFunction("Task.destroy", destroyTestTask);
 
     // Set the global allocator
     var allocator = std.testing.allocator;
@@ -248,7 +262,7 @@ test "createNewTask add new task" {
     // Init the task list
     tasks = TailQueue(*Task){};
 
-    var test_fn1_task = try Task.create(test_fn1, allocator);
+    var test_fn1_task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator);
     defer test_fn1_task.destroy(allocator);
     try scheduleTask(test_fn1_task, allocator);
 
@@ -262,15 +276,15 @@ test "init" {
     task.initTest();
     defer task.freeTest();
 
-    task.addConsumeFunction("Task.create", task_create);
-    task.addRepeatFunction("Task.destroy", task_destroy);
+    task.addConsumeFunction("Task.create", createTestTask);
+    task.addRepeatFunction("Task.destroy", destroyTestTask);
 
     var allocator = std.testing.allocator;
 
-    try init(allocator);
+    try init(allocator, undefined);
 
     expectEqual(current_task.pid, 0);
-    expectEqual(current_task.stack, @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096]);
+    expectEqual(current_task.kernel_stack, @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0 .. @ptrToInt(&KERNEL_STACK_END) - @ptrToInt(&KERNEL_STACK_START)]);
 
     expectEqual(tasks.len, 1);
 
@@ -308,7 +322,7 @@ fn rt_variable_preserved(allocator: *Allocator) void {
     defer allocator.destroy(is_set);
     is_set.* = true;
 
-    var test_task = Task.create(task_function, allocator) catch unreachable;
+    var test_task = Task.create(@ptrToInt(task_function), true, undefined, allocator) catch unreachable;
     scheduleTask(test_task, allocator) catch unreachable;
     // TODO: Need to add the ability to remove tasks
 
@@ -348,14 +362,57 @@ fn rt_variable_preserved(allocator: *Allocator) void {
     log.info("SUCCESS: Scheduler variables preserved\n", .{});
 }
 
+///
+/// Test the initialisation and running of a task running in user mode
+///
+/// Arguments:
+///     IN allocator: *std.mem.Allocator - The allocator to use when intialising the task
+///     IN mem_profile: mem.MemProfile - The system's memory profile. Determines the end address of the user task's VMM.
+///
+fn rt_user_task(allocator: *Allocator, mem_profile: *const mem.MemProfile) void {
+    // 1. Create user VMM
+    var task_vmm = allocator.create(vmm.VirtualMemoryManager(arch.VmmPayload)) catch |e| {
+        panic(@errorReturnTrace(), "Failed to allocate user task VMM: {}\n", .{e});
+    };
+    task_vmm.* = vmm.VirtualMemoryManager(arch.VmmPayload).init(0, @ptrToInt(mem_profile.vaddr_start), allocator, arch.VMM_MAPPER, undefined) catch unreachable;
+    // 2. Create user task. The code will be loaded at address 0
+    var user_task = task.Task.create(0, false, task_vmm, allocator) catch |e| {
+        panic(@errorReturnTrace(), "Failed to create user task: {}\n", .{e});
+    };
+    // 3. Read the user program file from the filesystem
+    const user_program_file = fs.openFile("/user_program", .NO_CREATION) catch |e| {
+        panic(@errorReturnTrace(), "Failed to open /user_program: {}\n", .{e});
+    };
+    defer user_program_file.close();
+    var code: [1024]u8 = undefined;
+    const code_len = user_program_file.read(code[0..1024]) catch |e| {
+        panic(@errorReturnTrace(), "Failed to read user program file: {}\n", .{e});
+    };
+    // 4. Allocate space in the vmm for the user_program
+    const code_start = task_vmm.alloc(std.mem.alignForward(code_len, vmm.BLOCK_SIZE) / vmm.BLOCK_SIZE, .{ .kernel = false, .writable = true, .cachable = true }) catch |e| {
+        panic(@errorReturnTrace(), "Failed to allocate VMM memory for user program code: {}\n", .{e});
+    } orelse panic(null, "User task VMM didn't allocate space for the user program\n", .{});
+    if (code_start != 0) panic(null, "User program start address was {} instead of 0\n", .{code_start});
+    // 5. Copy user_program code over
+    vmm.kernel_vmm.copyDataToVMM(task_vmm, code[0..code_len], code_start) catch |e| {
+        panic(@errorReturnTrace(), "Failed to copy user code: {}\n", .{e});
+    };
+    // 6. Schedule it
+    scheduleTask(user_task, allocator) catch |e| {
+        panic(@errorReturnTrace(), "Failed to schedule the user task: {}\n", .{e});
+    };
+}
+
 ///
 /// The scheduler runtime tests that will test the scheduling functionality.
 ///
 /// Arguments:
 ///     IN allocator: *Allocator - The allocator to use when needing to allocate memory.
+///     IN mem_profile: *const mem.MemProfile - The system's memory profile. Used to set up user task VMMs.
 ///
-fn runtimeTests(allocator: *Allocator) void {
+fn runtimeTests(allocator: *Allocator, mem_profile: *const mem.MemProfile) void {
     arch.enableInterrupts();
+    rt_user_task(allocator, mem_profile);
     rt_variable_preserved(allocator);
     while (true) {}
 }
diff --git a/src/kernel/task.zig b/src/kernel/task.zig
index d4f06c7..638ecd8 100644
--- a/src/kernel/task.zig
+++ b/src/kernel/task.zig
@@ -8,6 +8,7 @@ const mock_path = build_options.mock_path;
 const arch = @import("arch.zig").internals;
 const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic;
 const ComptimeBitmap = @import("bitmap.zig").ComptimeBitmap;
+const vmm = @import("vmm.zig");
 const Allocator = std.mem.Allocator;
 
 /// The kernels main stack start as this is used to check for if the task being destroyed is this stack
@@ -15,7 +16,7 @@ const Allocator = std.mem.Allocator;
 extern var KERNEL_STACK_START: *u32;
 
 /// The function type for the entry point.
-const EntryPointFn = fn () void;
+pub const EntryPoint = usize;
 
 /// The bitmap type for the PIDs
 const PidBitmap = if (is_test) ComptimeBitmap(u128) else ComptimeBitmap(u1024);
@@ -28,6 +29,9 @@ var all_pids: PidBitmap = brk: {
     break :brk pids;
 };
 
+/// The default stack size of a task. Currently this is set to a page size.
+pub const STACK_SIZE: u32 = arch.MEMORY_BLOCK_SIZE / @sizeOf(u32);
+
 /// The task control block for storing all the information needed to save and restore a task.
 pub const Task = struct {
     const Self = @This();
@@ -35,19 +39,30 @@ pub const Task = struct {
     /// The unique task identifier
     pid: PidBitmap.IndexType,
 
-    /// Pointer to the stack for the task. This will be allocated on initialisation.
-    stack: []u32,
+    /// Pointer to the kernel stack for the task. This will be allocated on initialisation.
+    kernel_stack: []usize,
+
+    /// Pointer to the user stack for the task. This will be allocated on initialisation and will be empty if it's a kernel task
+    user_stack: []usize,
 
     /// The current stack pointer into the stack.
     stack_pointer: usize,
 
+    /// Whether the process is a kernel process or not
+    kernel: bool,
+
+    /// The virtual memory manager belonging to the task
+    vmm: *vmm.VirtualMemoryManager(arch.VmmPayload),
+
     ///
     /// Create a task. This will allocate a PID and the stack. The stack will be set up as a
     /// kernel task. As this is a new task, the stack will need to be initialised with the CPU
     /// state as described in arch.CpuState struct.
     ///
     /// Arguments:
-    ///     IN entry_point: EntryPointFn - The entry point into the task. This must be a function.
+    ///     IN entry_point: EntryPoint - The entry point into the task. This must be a function.
+    ///     IN kernel: bool              - Whether the task has kernel or user privileges.
+    ///     IN task_vmm: *VirtualMemoryManager - The virtual memory manager associated with the task.
     ///     IN allocator: *Allocator     - The allocator for allocating memory for a task.
     ///
     /// Return: *Task
@@ -57,16 +72,29 @@ pub const Task = struct {
     ///     OutOfMemory - If there is no more memory to allocate. Any memory or PID allocated will
     ///                   be freed on return.
     ///
-    pub fn create(entry_point: EntryPointFn, allocator: *Allocator) Allocator.Error!*Task {
+    pub fn create(entry_point: EntryPoint, kernel: bool, task_vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), allocator: *Allocator) Allocator.Error!*Task {
         var task = try allocator.create(Task);
         errdefer allocator.destroy(task);
 
-        task.pid = allocatePid();
-        errdefer freePid(task.pid);
+        const pid = allocatePid();
+        errdefer freePid(pid);
 
-        const task_stack = try arch.initTaskStack(@ptrToInt(entry_point), allocator);
-        task.stack = task_stack.stack;
-        task.stack_pointer = task_stack.pointer;
+        var k_stack = try allocator.alloc(usize, STACK_SIZE);
+        errdefer allocator.free(k_stack);
+
+        var u_stack = if (kernel) &[_]usize{} else try allocator.alloc(usize, STACK_SIZE);
+        errdefer if (!kernel) allocator.free(u_stack);
+
+        task.* = .{
+            .pid = pid,
+            .kernel_stack = k_stack,
+            .user_stack = u_stack,
+            .stack_pointer = @ptrToInt(&k_stack[STACK_SIZE - 1]),
+            .kernel = kernel,
+            .vmm = task_vmm,
+        };
+
+        try arch.initTask(task, entry_point, allocator);
 
         return task;
     }
@@ -81,8 +109,11 @@ pub const Task = struct {
         freePid(self.pid);
         // We need to check that the the stack has been allocated as task 0 (init) won't have a
         // stack allocated as this in the linker script
-        if (@ptrToInt(self.stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) {
-            allocator.free(self.stack);
+        if (@ptrToInt(self.kernel_stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) {
+            allocator.free(self.kernel_stack);
+        }
+        if (!self.kernel) {
+            allocator.free(self.user_stack);
         }
         allocator.destroy(self);
     }
@@ -122,7 +153,8 @@ test "create out of memory for task" {
     // Set the global allocator
     var fa = FailingAllocator.init(testing_allocator, 0);
 
-    expectError(error.OutOfMemory, Task.create(test_fn1, &fa.allocator));
+    expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), true, undefined, &fa.allocator));
+    expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, &fa.allocator));
 
     // Make sure any memory allocated is freed
     expectEqual(fa.allocated_bytes, fa.freed_bytes);
@@ -135,7 +167,8 @@ test "create out of memory for stack" {
     // Set the global allocator
     var fa = FailingAllocator.init(testing_allocator, 1);
 
-    expectError(error.OutOfMemory, Task.create(test_fn1, &fa.allocator));
+    expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), true, undefined, &fa.allocator));
+    expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, &fa.allocator));
 
     // Make sure any memory allocated is freed
     expectEqual(fa.allocated_bytes, fa.freed_bytes);
@@ -145,32 +178,39 @@ test "create out of memory for stack" {
 }
 
 test "create expected setup" {
-    var task = try Task.create(test_fn1, std.testing.allocator);
+    var task = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator);
     defer task.destroy(std.testing.allocator);
 
     // Will allocate the first PID 1, 0 will always be allocated
     expectEqual(task.pid, 1);
+    expectEqual(task.kernel_stack.len, STACK_SIZE);
+    expectEqual(task.user_stack.len, 0);
+
+    var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator);
+    defer user_task.destroy(std.testing.allocator);
+    expectEqual(user_task.pid, 2);
+    expectEqual(user_task.user_stack.len, STACK_SIZE);
+    expectEqual(user_task.kernel_stack.len, STACK_SIZE);
 }
 
 test "destroy cleans up" {
     // This used the leak detector allocator in testing
     // So if any alloc were not freed, this will fail the test
-    var fa = FailingAllocator.init(testing_allocator, 2);
+    var allocator = std.testing.allocator;
 
-    var task = try Task.create(test_fn1, &fa.allocator);
+    var task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator);
+    var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, allocator);
 
-    task.destroy(&fa.allocator);
-
-    // Make sure any memory allocated is freed
-    expectEqual(fa.allocated_bytes, fa.freed_bytes);
+    task.destroy(allocator);
+    user_task.destroy(allocator);
 
     // All PIDs were freed
     expectEqual(all_pids.bitmap, 1);
 }
 
 test "Multiple create" {
-    var task1 = try Task.create(test_fn1, std.testing.allocator);
-    var task2 = try Task.create(test_fn1, std.testing.allocator);
+    var task1 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator);
+    var task2 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator);
 
     expectEqual(task1.pid, 1);
     expectEqual(task2.pid, 2);
@@ -180,13 +220,21 @@ test "Multiple create" {
 
     expectEqual(all_pids.bitmap, 5);
 
-    var task3 = try Task.create(test_fn1, std.testing.allocator);
+    var task3 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator);
 
     expectEqual(task3.pid, 1);
     expectEqual(all_pids.bitmap, 7);
 
     task2.destroy(std.testing.allocator);
     task3.destroy(std.testing.allocator);
+
+    var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator);
+
+    expectEqual(user_task.pid, 1);
+    expectEqual(all_pids.bitmap, 3);
+
+    user_task.destroy(std.testing.allocator);
+    expectEqual(all_pids.bitmap, 1);
 }
 
 test "allocatePid and freePid" {
diff --git a/src/kernel/vmm.zig b/src/kernel/vmm.zig
index e2f7987..2d316ef 100644
--- a/src/kernel/vmm.zig
+++ b/src/kernel/vmm.zig
@@ -107,6 +107,9 @@ pub const VmmError = error{
 
     /// Physical addresses are invalid
     InvalidPhysAddresses,
+
+    /// Not enough virtual space in the VMM
+    OutOfMemory,
 };
 
 /// The boot-time offset that the virtual addresses are from the physical addresses
@@ -180,6 +183,39 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
             };
         }
 
+        ///
+        /// Copy this VMM. Changes to one copy will not affect the other
+        ///
+        /// Arguments:
+        ///     IN self: *Self - The VMM to copy
+        ///
+        /// Error: Allocator.Error
+        ///     OutOfMemory - There wasn't enough memory for copying
+        ///
+        /// Return: Self
+        ///     The copy
+        ///
+        pub fn copy(self: *const Self) Allocator.Error!Self {
+            var clone = Self{
+                .bmp = try self.bmp.clone(),
+                .start = self.start,
+                .end = self.end,
+                .allocator = self.allocator,
+                .allocations = std.hash_map.AutoHashMap(usize, Allocation).init(self.allocator),
+                .mapper = self.mapper,
+                .payload = self.payload,
+            };
+            var it = self.allocations.iterator();
+            while (it.next()) |entry| {
+                var list = std.ArrayList(usize).init(self.allocator);
+                for (entry.value.physical.items) |block| {
+                    _ = try list.append(block);
+                }
+                _ = try clone.allocations.put(entry.key, Allocation{ .physical = list });
+            }
+            return clone;
+        }
+
         ///
         /// Free the internal state of the VMM. It is unusable afterwards
         ///
@@ -384,6 +420,84 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
             return null;
         }
 
+        ///
+        /// Copy data from an address in a virtual memory manager to an address in another virtual memory manager
+        ///
+        /// Arguments:
+        ///     IN self: *Self - The virtual memory that owns the data being copied. This must be the VMM currently in use
+        ///     IN to: *const Self - The virtual memory manager that owns the address that the data is being copied to
+        ///     IN data: []const u8 - The data being copied. Must be within memory mapped in `self`
+        ///     IN dest: usize - The address within `to` to copy the data to. The space covered by `dest` and `dest` + `dest.len` must be mapped in `to`
+        ///
+        /// Error: VmmError || pmm.PmmError || Allocator.Error
+        ///     VmmError.NotAllocated - Some or all of the destination isn't mapped
+        ///     VmmError.OutOfMemory - There wasn't enough space in the VMM to use for temporary mapping
+        ///     Bitmap(u32).Error.OutOfBounds - The address given is outside of the memory managed
+        ///     Allocator.Error.OutOfMemory - There wasn't enough memory available to fulfill the request
+        ///
+        pub fn copyDataToVMM(self: *Self, to: *const Self, data: []const u8, dest: usize) (bitmap.Bitmap(usize).BitmapError || VmmError || Allocator.Error)!void {
+            if (data.len == 0) {
+                return;
+            }
+            const start_addr = std.mem.alignBackward(dest, BLOCK_SIZE);
+            const end_addr = std.mem.alignForward(dest + data.len, BLOCK_SIZE);
+            if (end_addr >= to.end or start_addr < to.start)
+                return bitmap.Bitmap(usize).BitmapError.OutOfBounds;
+            // Find physical blocks for `dest`
+            var blocks = std.ArrayList(usize).init(self.allocator);
+            defer blocks.deinit();
+            var it = to.allocations.iterator();
+            while (it.next()) |allocation| {
+                const virtual = allocation.key;
+                const physical = allocation.value.physical.items;
+                if (start_addr >= virtual and virtual + physical.len * BLOCK_SIZE >= end_addr) {
+                    const first_block_idx = (start_addr - virtual) / BLOCK_SIZE;
+                    const last_block_idx = (end_addr - virtual) / BLOCK_SIZE;
+
+                    try blocks.appendSlice(physical[first_block_idx..last_block_idx]);
+                }
+            }
+            // Make sure the address is actually mapped in the destination VMM
+            if (blocks.items.len == 0) {
+                return VmmError.NotAllocated;
+            }
+
+            // Map them into `self` for some vaddr so they can be accessed from this VMM
+            if (self.bmp.setContiguous(blocks.items.len)) |entry| {
+                const v_start = entry * BLOCK_SIZE + self.start;
+                defer {
+                    // Unmap virtual blocks from `self` so they can be used in the future
+                    var v = v_start;
+                    while (v < v_start + blocks.items.len * BLOCK_SIZE) : (v += BLOCK_SIZE) {
+                        // Cannot be out of bounds as it has been set above
+                        self.bmp.clearEntry((v - self.start) / BLOCK_SIZE) catch unreachable;
+                    }
+                }
+                for (blocks.items) |block, i| {
+                    const v = v_start + i * BLOCK_SIZE;
+                    const v_end = v + BLOCK_SIZE;
+                    const p = block;
+                    const p_end = p + BLOCK_SIZE;
+                    self.mapper.mapFn(v, v_end, p, p_end, .{ .kernel = true, .writable = true, .cachable = true }, self.allocator, self.payload) catch |e| {
+                        // If we fail to map one of the blocks then attempt to free all previously mapped
+                        if (i > 0) {
+                            self.mapper.unmapFn(v_start, v_end, self.payload) catch |e2| {
+                                // If we can't unmap then just panic
+                                panic(@errorReturnTrace(), "Failed to unmap region 0x{X} -> 0x{X}: {}\n", .{ v_start, v_end, e2 });
+                            };
+                        }
+                        panic(@errorReturnTrace(), "Failed to map vrutal region 0x{X} -> 0x{X} to 0x{X} -> 0x{X}: {}\n", .{ v, v_end, p, p_end, e });
+                    };
+                }
+                // Copy to vaddr from above
+                const align_offset = dest - start_addr;
+                var data_copy = @intToPtr([*]u8, v_start + align_offset)[0..data.len];
+                std.mem.copy(u8, data_copy, data);
+            } else {
+                return VmmError.OutOfMemory;
+            }
+        }
+
         ///
         /// Free a previous allocation
         ///
@@ -463,10 +577,6 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *Allocator) Allocator
         };
     }
 
-    switch (build_options.test_mode) {
-        .Initialisation => runtimeTests(arch.VmmPayload, kernel_vmm, mem_profile),
-        else => {},
-    }
     return &kernel_vmm;
 }
 
@@ -475,7 +585,7 @@ test "virtToPhys" {
     var vmm = try testInit(num_entries);
     defer testDeinit(&vmm);
 
-    const vstart = test_vaddr_start + BLOCK_SIZE;
+    const vstart = vmm.start + BLOCK_SIZE;
     const vend = vstart + BLOCK_SIZE * 3;
     const pstart = BLOCK_SIZE * 20;
     const pend = BLOCK_SIZE * 23;
@@ -498,7 +608,7 @@ test "physToVirt" {
     var vmm = try testInit(num_entries);
     defer testDeinit(&vmm);
 
-    const vstart = test_vaddr_start + BLOCK_SIZE;
+    const vstart = vmm.start + BLOCK_SIZE;
     const vend = vstart + BLOCK_SIZE * 3;
     const pstart = BLOCK_SIZE * 20;
     const pend = BLOCK_SIZE * 23;
@@ -628,9 +738,79 @@ test "set" {
     }
 }
 
-var test_allocations: ?bitmap.Bitmap(u64) = null;
+test "copy" {
+    const num_entries = 512;
+    var vmm = try testInit(num_entries);
+    defer testDeinit(&vmm);
+
+    const attrs = .{ .kernel = true, .cachable = true, .writable = true };
+    const alloc0 = (try vmm.alloc(24, attrs)).?;
+
+    var mirrored = try vmm.copy();
+    defer mirrored.deinit();
+    std.testing.expectEqual(vmm.bmp.num_free_entries, mirrored.bmp.num_free_entries);
+    std.testing.expectEqual(vmm.start, mirrored.start);
+    std.testing.expectEqual(vmm.end, mirrored.end);
+    std.testing.expectEqual(vmm.allocations.count(), mirrored.allocations.count());
+    var it = vmm.allocations.iterator();
+    while (it.next()) |next| {
+        for (mirrored.allocations.get(next.key).?.physical.items) |block, i| {
+            std.testing.expectEqual(block, vmm.allocations.get(next.key).?.physical.items[i]);
+        }
+    }
+    std.testing.expectEqual(vmm.mapper, mirrored.mapper);
+    std.testing.expectEqual(vmm.payload, mirrored.payload);
+
+    // Allocating in the new VMM shouldn't allocate in the mirrored one
+    const alloc1 = (try mirrored.alloc(3, attrs)).?;
+    std.testing.expectEqual(vmm.allocations.count() + 1, mirrored.allocations.count());
+    std.testing.expectEqual(vmm.bmp.num_free_entries - 3, mirrored.bmp.num_free_entries);
+    std.testing.expectError(VmmError.NotAllocated, vmm.virtToPhys(alloc1));
+
+    // And vice-versa
+    const alloc2 = (try vmm.alloc(3, attrs)).?;
+    const alloc3 = (try vmm.alloc(1, attrs)).?;
+    const alloc4 = (try vmm.alloc(1, attrs)).?;
+    std.testing.expectEqual(vmm.allocations.count() - 2, mirrored.allocations.count());
+    std.testing.expectEqual(vmm.bmp.num_free_entries + 2, mirrored.bmp.num_free_entries);
+    std.testing.expectError(VmmError.NotAllocated, mirrored.virtToPhys(alloc3));
+    std.testing.expectError(VmmError.NotAllocated, mirrored.virtToPhys(alloc4));
+}
+
+test "copyDataToVMM" {
+    var vmm = try testInit(100);
+    defer testDeinit(&vmm);
+    const alloc1_blocks = 1;
+    const alloc = (try vmm.alloc(alloc1_blocks, .{ .kernel = true, .writable = true, .cachable = true })) orelse unreachable;
+    var vmm2 = try VirtualMemoryManager(u8).init(vmm.start, vmm.end, std.testing.allocator, test_mapper, 39);
+    defer vmm2.deinit();
+    var vmm_free_entries = vmm.bmp.num_free_entries;
+    var vmm2_free_entries = vmm2.bmp.num_free_entries;
+
+    const buff: []const u8 = &[_]u8{ 10, 11, 12, 13 };
+    try vmm2.copyDataToVMM(&vmm, buff, alloc);
+
+    // Make sure they are the same
+    const buff2 = @intToPtr([*]u8, alloc)[0..buff.len];
+    std.testing.expectEqualSlices(u8, buff, buff2);
+    std.testing.expectEqual(vmm_free_entries, vmm.bmp.num_free_entries);
+    std.testing.expectEqual(vmm2_free_entries, vmm2.bmp.num_free_entries);
+
+    // Test NotAllocated
+    std.testing.expectError(VmmError.NotAllocated, vmm2.copyDataToVMM(&vmm, buff, alloc + alloc1_blocks * BLOCK_SIZE));
+    std.testing.expectEqual(vmm_free_entries, vmm.bmp.num_free_entries);
+    std.testing.expectEqual(vmm2_free_entries, vmm2.bmp.num_free_entries);
+
+    // Test Bitmap.Error.OutOfBounds
+    std.testing.expectError(bitmap.Bitmap(usize).BitmapError.OutOfBounds, vmm2.copyDataToVMM(&vmm, buff, vmm.end));
+    std.testing.expectError(bitmap.Bitmap(usize).BitmapError.OutOfBounds, vmm.copyDataToVMM(&vmm2, buff, vmm2.end));
+    std.testing.expectEqual(vmm_free_entries, vmm.bmp.num_free_entries);
+    std.testing.expectEqual(vmm2_free_entries, vmm2.bmp.num_free_entries);
+}
+
+var test_allocations: ?*bitmap.Bitmap(u64) = null;
 var test_mapper = Mapper(u8){ .mapFn = testMap, .unmapFn = testUnmap };
-const test_vaddr_start: usize = 0xC0000000;
+var test_vmm: VirtualMemoryManager(u8) = undefined;
 
 ///
 /// Initialise a virtual memory manager used for testing
@@ -646,7 +826,8 @@ const test_vaddr_start: usize = 0xC0000000;
 ///
 fn testInit(num_entries: u32) Allocator.Error!VirtualMemoryManager(u8) {
     if (test_allocations == null) {
-        test_allocations = try bitmap.Bitmap(u64).init(num_entries, std.testing.allocator);
+        test_allocations = try std.testing.allocator.create(bitmap.Bitmap(u64));
+        test_allocations.?.* = try bitmap.Bitmap(u64).init(num_entries, std.testing.allocator);
     } else |allocations| {
         var entry: u32 = 0;
         while (entry < allocations.num_entries) : (entry += 1) {
@@ -666,16 +847,21 @@ fn testInit(num_entries: u32) Allocator.Error!VirtualMemoryManager(u8) {
         .modules = &[_]mem.Module{},
     };
     pmm.init(&mem_profile, std.testing.allocator);
-    return VirtualMemoryManager(u8).init(test_vaddr_start, test_vaddr_start + num_entries * BLOCK_SIZE, std.testing.allocator, test_mapper, 39);
+    const test_vaddr_start = @ptrToInt(&(try std.testing.allocator.alloc(u8, num_entries * BLOCK_SIZE))[0]);
+    test_vmm = try VirtualMemoryManager(u8).init(test_vaddr_start, test_vaddr_start + num_entries * BLOCK_SIZE, std.testing.allocator, test_mapper, 39);
+    return test_vmm;
 }
 
 fn testDeinit(vmm: *VirtualMemoryManager(u8)) void {
-    defer vmm.deinit();
-    defer {
-        test_allocations.?.deinit();
+    vmm.deinit();
+    const space = @intToPtr([*]u8, vmm.start)[0 .. vmm.end - vmm.start];
+    vmm.allocator.free(space);
+    if (test_allocations) |allocs| {
+        allocs.deinit();
+        std.testing.allocator.destroy(allocs);
         test_allocations = null;
     }
-    defer pmm.deinit();
+    pmm.deinit();
 }
 
 ///
@@ -693,8 +879,9 @@ fn testDeinit(vmm: *VirtualMemoryManager(u8)) void {
 fn testMap(vstart: usize, vend: usize, pstart: usize, pend: usize, attrs: Attributes, allocator: *Allocator, payload: u8) (Allocator.Error || MapperError)!void {
     std.testing.expectEqual(@as(u8, 39), payload);
     var vaddr = vstart;
+    var allocations = test_allocations.?;
     while (vaddr < vend) : (vaddr += BLOCK_SIZE) {
-        (test_allocations.?).setEntry((vaddr - test_vaddr_start) / BLOCK_SIZE) catch unreachable;
+        allocations.setEntry((vaddr - test_vmm.start) / BLOCK_SIZE) catch unreachable;
     }
 }
 
@@ -709,9 +896,10 @@ fn testMap(vstart: usize, vend: usize, pstart: usize, pend: usize, attrs: Attrib
 fn testUnmap(vstart: usize, vend: usize, payload: u8) (Allocator.Error || MapperError)!void {
     std.testing.expectEqual(@as(u8, 39), payload);
     var vaddr = vstart;
+    var allocations = test_allocations.?;
     while (vaddr < vend) : (vaddr += BLOCK_SIZE) {
-        if ((test_allocations.?).isSet((vaddr - test_vaddr_start) / BLOCK_SIZE) catch unreachable) {
-            (test_allocations.?).clearEntry((vaddr - test_vaddr_start) / BLOCK_SIZE) catch unreachable;
+        if (allocations.isSet((vaddr - test_vmm.start) / BLOCK_SIZE) catch unreachable) {
+            allocations.clearEntry((vaddr - test_vmm.start) / BLOCK_SIZE) catch unreachable;
         } else {
             return MapperError.NotMapped;
         }
@@ -727,7 +915,19 @@ fn testUnmap(vstart: usize, vend: usize, payload: u8) (Allocator.Error || Mapper
 ///     IN mem_profile: *const mem.MemProfile - The mem profile with details about all the memory regions that should be reserved
 ///     IN mb_info: *multiboot.multiboot_info_t - The multiboot info struct that should also be reserved
 ///
-fn runtimeTests(comptime Payload: type, vmm: VirtualMemoryManager(Payload), mem_profile: *const mem.MemProfile) void {
+pub fn runtimeTests(comptime Payload: type, vmm: *VirtualMemoryManager(Payload), mem_profile: *const mem.MemProfile) void {
+    rt_correctMapping(Payload, vmm, mem_profile);
+    rt_copyDataToVMM(vmm);
+}
+
+///
+/// Test that the correct mappings have been made in the VMM
+///
+/// Arguments:
+///     IN vmm: VirtualMemoryManager(Payload) - The virtual memory manager to test
+///     IN mem_profile: *const mem.MemProfile - The mem profile with details about all the memory regions that should be reserved
+///
+fn rt_correctMapping(comptime Payload: type, vmm: *VirtualMemoryManager(Payload), mem_profile: *const mem.MemProfile) void {
     const v_start = std.mem.alignBackward(@ptrToInt(mem_profile.vaddr_start), BLOCK_SIZE);
     const v_end = std.mem.alignForward(@ptrToInt(mem_profile.vaddr_end), BLOCK_SIZE);
 
@@ -760,6 +960,51 @@ fn runtimeTests(comptime Payload: type, vmm: VirtualMemoryManager(Payload), mem_
             panic(@errorReturnTrace(), "An address was not set in the VMM when it should have been: 0x{x}\n", .{vaddr});
         }
     }
-
     log.info("Tested allocations\n", .{});
 }
+
+///
+/// Test copying data to another VMM
+///
+/// Arguments:
+///     IN vmm: *VirtualMemoryManager() - The VMM to copy from
+///
+fn rt_copyDataToVMM(vmm: *VirtualMemoryManager(arch.VmmPayload)) void {
+    const expected_free_entries = vmm.bmp.num_free_entries;
+    // Mirror the VMM
+    var vmm2 = vmm.copy() catch |e| {
+        panic(@errorReturnTrace(), "Failed to mirror VMM: {}\n", .{e});
+    };
+    // Allocate within secondary VMM
+    const addr = vmm2.alloc(1, .{ .kernel = true, .cachable = true, .writable = true }) catch |e| {
+        panic(@errorReturnTrace(), "Failed to allocate within the secondary VMM in rt_copyDataToVMM: {}\n", .{e});
+    } orelse panic(@errorReturnTrace(), "Failed to get an allocation within the secondary VMM in rt_copyDataToVMM\n", .{});
+    const expected_free_entries2 = vmm2.bmp.num_free_entries;
+    const expected_free_pmm_entries = pmm.blocksFree();
+    // Copy an arbitrary buffer into the allocation
+    const buff = &[_]u8{ 4, 5, 9, 123, 90, 67 };
+    vmm.copyDataToVMM(&vmm2, buff, addr) catch |e| {
+        panic(@errorReturnTrace(), "Failed to copy data to secondary VMM in rt_copyDataToVMM: {}\n", .{e});
+    };
+    // Make sure the function cleaned up
+    if (vmm.bmp.num_free_entries != expected_free_entries) {
+        panic(@errorReturnTrace(), "Expected {} free entries in VMM, but there were {}\n", .{ expected_free_entries, vmm.bmp.num_free_entries });
+    }
+    if (vmm2.bmp.num_free_entries != expected_free_entries2) {
+        panic(@errorReturnTrace(), "Expected {} free entries in the secondary VMM, but there were {}\n", .{ expected_free_entries2, vmm2.bmp.num_free_entries });
+    }
+    if (pmm.blocksFree() != expected_free_pmm_entries) {
+        panic(@errorReturnTrace(), "Expected {} free entries in PMM, but there were {}\n", .{ expected_free_pmm_entries, pmm.blocksFree() });
+    }
+
+    // Make sure that the data at the allocated address is correct
+    // Since vmm2 is a mirror of vmm, this address should be mapped by the CPU's MMU
+    const buff2 = @intToPtr([*]u8, addr)[0..buff.len];
+    if (!std.mem.eql(u8, buff, buff2)) {
+        panic(@errorReturnTrace(), "buff2 is not the same as buff in rt_copyDataToVMM\n", .{});
+    }
+    // Free the secondary VMM
+    vmm2.free(addr) catch |e| {
+        panic(@errorReturnTrace(), "Failed to free the allocation in secondary VMM: {}\n", .{e});
+    };
+}
diff --git a/test/gen_types.zig b/test/gen_types.zig
index 62fecb6..8044f04 100644
--- a/test/gen_types.zig
+++ b/test/gen_types.zig
@@ -49,6 +49,7 @@ const types = .{
     .{ "*const IdtPtr", "PTR_CONST_IDTPTR", "idt_mock", "", "IdtPtr" },
     .{ "*Task", "PTR_TASK", "task_mock", "", "Task" },
     .{ "*Allocator", "PTR_ALLOCATOR", "", "std.mem", "Allocator" },
+    .{ "*VirtualMemoryManager(u8)", "PTR_VMM", "vmm_mock", "", "VirtualMemoryManager" },
 
     .{ "IdtError!void", "ERROR_IDTERROR_RET_VOID", "idt_mock", "", "IdtError" },
     .{ "Allocator.Error!*Task", "ERROR_ALLOCATOR_RET_PTRTASK", "", "", "" },
@@ -82,6 +83,7 @@ const types = .{
     .{ "fn (*Task, usize) void", "FN_IPTRTASK_IUSIZE_OVOID", "", "", "" },
     .{ "fn (*Task, *Allocator) void", "FN_IPTRTASK_IPTRALLOCATOR_OVOID", "", "", "" },
     .{ "fn (fn () void, *Allocator) Allocator.Error!*Task", "FN_IFNOVOID_IPTRALLOCATOR_EALLOCATOR_OPTRTASK", "", "", "" },
+    .{ "fn (usize, *Allocator, bool, *VirtualMemoryManager(u8)) Allocator.Error!*Task", "FN_IUSIZE_IPTRALLOCATOR_IBOOL_IVMM_EALLOCATOR_OVOID", "", "", "" },
 
     .{ "fn (StatusRegister, u8, bool) void", "FN_ISTATUSREGISTER_IU8_IBOOL_OVOID", "", "", "" },
 };
diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig
index a4fcd72..7eb805f 100644
--- a/test/mock/kernel/arch_mock.zig
+++ b/test/mock/kernel/arch_mock.zig
@@ -11,7 +11,7 @@ const Serial = @import("../../../src/kernel/serial.zig").Serial;
 const TTY = @import("../../../src/kernel/tty.zig").TTY;
 const Keyboard = @import("../../../src/kernel/keyboard.zig").Keyboard;
 
-pub const task = @import("task_mock.zig");
+pub const task = @import("../../../src/kernel/task.zig");
 
 pub const Device = pci.PciDeviceInfo;
 
@@ -141,10 +141,7 @@ pub fn initMem(payload: BootPayload) Allocator.Error!mem.MemProfile {
     };
 }
 
-pub fn initTaskStack(entry_point: usize, allocator: *Allocator) Allocator.Error!struct { stack: []u32, pointer: usize } {
-    const ret = .{ .stack = &([_]u32{}), .pointer = 0 };
-    return ret;
-}
+pub fn initTask(t: *Task, entry_point: usize, allocator: *Allocator) Allocator.Error!void {}
 
 pub fn initKeyboard(allocator: *Allocator) Allocator.Error!?*Keyboard {
     return null;
diff --git a/test/mock/kernel/mock_framework_template.zig b/test/mock/kernel/mock_framework_template.zig
index 5506430..7da3182 100644
--- a/test/mock/kernel/mock_framework_template.zig
+++ b/test/mock/kernel/mock_framework_template.zig
@@ -146,6 +146,7 @@ fn Mock() type {
                 1 => fn (fields[0].field_type) RetType,
                 2 => fn (fields[0].field_type, fields[1].field_type) RetType,
                 3 => fn (fields[0].field_type, fields[1].field_type, fields[2].field_type) RetType,
+                4 => fn (fields[0].field_type, fields[1].field_type, fields[2].field_type, fields[3].field_type) RetType,
                 else => @compileError("More than 3 parameters not supported"),
             };
         }
@@ -167,6 +168,7 @@ fn Mock() type {
                 1 => function_type(params[0]),
                 2 => function_type(params[0], params[1]),
                 3 => function_type(params[0], params[1], params[2]),
+                4 => function_type(params[0], params[1], params[2], params[3]),
                 // Should get to this as `getFunctionType` will catch this
                 else => @compileError("More than 3 parameters not supported"),
             };
diff --git a/test/mock/kernel/task_mock.zig b/test/mock/kernel/task_mock.zig
index 390bfad..ce207a1 100644
--- a/test/mock/kernel/task_mock.zig
+++ b/test/mock/kernel/task_mock.zig
@@ -1,4 +1,6 @@
 const std = @import("std");
+const vmm = @import("vmm_mock.zig");
+const arch = @import("arch_mock.zig");
 const Allocator = std.mem.Allocator;
 
 const mock_framework = @import("mock_framework.zig");
@@ -8,17 +10,20 @@ pub const addTestParams = mock_framework.addTestParams;
 pub const addConsumeFunction = mock_framework.addConsumeFunction;
 pub const addRepeatFunction = mock_framework.addRepeatFunction;
 
-const EntryPointFn = fn () void;
+pub const EntryPoint = usize;
 
 pub const Task = struct {
     const Self = @This();
 
     pid: u32,
-    stack: []u32,
+    kernel_stack: []u32,
+    user_stack: []u32,
     stack_pointer: usize,
+    kernel: bool,
+    vmm: vmm.VirtualMemoryManager(arch.VmmPayload),
 
-    pub fn create(entry_point: EntryPointFn, allocator: *Allocator) Allocator.Error!*Task {
-        return mock_framework.performAction("Task.create", Allocator.Error!*Task, .{ entry_point, allocator });
+    pub fn create(entry_point: EntryPoint, kernel: bool, task_vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), allocator: *Allocator) Allocator.Error!*Task {
+        return mock_framework.performAction("Task.create", Allocator.Error!*Task, .{ entry_point, allocator, kernel, task_vmm });
     }
 
     pub fn destroy(self: *Self, allocator: *Allocator) void {
diff --git a/test/mock/kernel/vmm_mock.zig b/test/mock/kernel/vmm_mock.zig
index 0812d84..6020155 100644
--- a/test/mock/kernel/vmm_mock.zig
+++ b/test/mock/kernel/vmm_mock.zig
@@ -3,6 +3,7 @@ const bitmap = @import("../../../src/kernel/bitmap.zig");
 const vmm = @import("../../../src/kernel/vmm.zig");
 const arch = @import("arch_mock.zig");
 const std = @import("std");
+const Allocator = std.mem.Allocator;
 
 pub const VmmError = error{
     /// A memory region expected to be allocated wasn't
@@ -45,9 +46,11 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
         pub fn free(self: *Self, vaddr: usize) (bitmap.Bitmap(u32).BitmapError || VmmError)!void {
             return VmmError.NotAllocated;
         }
+
+        pub fn copyDataToVMM(self: *Self, to: *const Self, data: []const u8, dest: usize) (bitmap.Bitmap(usize).BitmapError || VmmError || Allocator.Error)!void {}
     };
 }
 
-pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) std.mem.Allocator.Error!VirtualMemoryManager(arch.VmmPayload) {
+pub fn init(mem_profile: *const mem.MemProfile, allocator: *Allocator) Allocator.Error!*VirtualMemoryManager(arch.VmmPayload) {
     return std.mem.Allocator.Error.OutOfMemory;
 }
diff --git a/test/user_program.s b/test/user_program.s
new file mode 100644
index 0000000..e3c0d2f
--- /dev/null
+++ b/test/user_program.s
@@ -0,0 +1,6 @@
+entry:
+    mov $0xCAFE, %eax
+    mov $0xBEEF, %ebx
+loop:
+    jmp loop
+