diff --git a/core/src/sys/mem.zig b/core/src/sys/mem.zig
index 985298d..c39541e 100644
--- a/core/src/sys/mem.zig
+++ b/core/src/sys/mem.zig
@@ -40,7 +40,7 @@ pub fn lockFrame(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerr
 
 pub fn setAddressSpace(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
     const core = cpu.thisCore();
-    if (!sys.checkToken(core, system.kernel.Token.VirtualMemory)) return error.NotAuthorized;
+    if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
 
     const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
 
diff --git a/core/src/sys/sched.zig b/core/src/sys/sched.zig
index 244874c..e1b9f19 100644
--- a/core/src/sys/sched.zig
+++ b/core/src/sys/sched.zig
@@ -41,7 +41,7 @@ pub fn sleep(regs: *platform.Registers, args: *sys.Arguments, _: *isize) anyerro
 
 pub fn setEventQueue(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
     const core = cpu.thisCore();
-    if (!sys.checkToken(core, system.kernel.Token.EventQueue)) return error.NotAuthorized;
+    if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
     const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
 
     if (target.event_queue) |_| return error.ThreadQueueAlreadySet;
@@ -54,3 +54,55 @@ pub fn setEventQueue(_: *platform.Registers, args: *sys.Arguments, _: *isize) an
 
     target.event_queue = RingBuffer.init(data, platform.PAGE_SIZE, true);
 }
+
+pub fn createThread(_: *platform.Registers, _: *sys.Arguments, result: *isize) anyerror!void {
+    const core = cpu.thisCore();
+    if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
+
+    const allocator = pmm.lockGlobalAllocator();
+    defer pmm.unlockGlobalAllocator();
+
+    const child = try thread.createThreadControlBlock(allocator);
+    thread.arch.initUserRegisters(&child.regs);
+
+    result.* = @bitCast(child.id);
+}
+
+pub fn setThreadEntry(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
+    const core = cpu.thisCore();
+    if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
+    const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
+
+    thread.arch.setAddress(&target.regs, args.arg1);
+}
+
+pub fn setThreadArguments(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
+    const core = cpu.thisCore();
+    if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
+    const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
+
+    thread.arch.setArguments(&target.regs, args.arg1, args.arg2);
+}
+
+pub fn setThreadStack(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
+    const core = cpu.thisCore();
+    if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
+    const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
+
+    thread.arch.setStack(&target.regs, args.arg1);
+}
+
+pub fn startThread(_: *platform.Registers, args: *sys.Arguments, _: *isize) anyerror!void {
+    const core = cpu.thisCore();
+    if (!sys.checkToken(core, system.kernel.Token.CreateProcess)) return error.NotAuthorized;
+    const target = thread.lookupThreadById(args.arg0) orelse return error.NoSuchThread;
+
+    if (target.state != .Inactive) return;
+
+    thread.reviveThread(core, target);
+}
+
+pub fn getThreadId(_: *platform.Registers, _: *sys.Arguments, result: *isize) anyerror!void {
+    const core = cpu.thisCore();
+    result.* = @bitCast(core.current_thread.id);
+}
diff --git a/core/src/sys/syscall.zig b/core/src/sys/syscall.zig
index 4616530..2c309cf 100644
--- a/core/src/sys/syscall.zig
+++ b/core/src/sys/syscall.zig
@@ -29,6 +29,13 @@ const syscalls = [_]SystemCall{
     sched.sleep,
     sched.setEventQueue,
     tokens.setTokens,
+    mem.setAddressSpace,
+    sched.createThread,
+    sched.setThreadEntry,
+    sched.setThreadArguments,
+    sched.setThreadStack,
+    sched.startThread,
+    sched.getThreadId,
 };
 
 pub fn invokeSyscall(number: usize, frame: *platform.Registers, args: *Arguments, retval: *isize) void {
diff --git a/system/lib/kernel.zig b/system/lib/kernel.zig
index 42d478c..6f9ad0c 100644
--- a/system/lib/kernel.zig
+++ b/system/lib/kernel.zig
@@ -7,17 +7,22 @@ pub const SystemCall = enum(u64) {
     SetPriority, // requires Token.ThreadPriority
     GetPriority,
     Sleep,
-    SetEventQueue, // requires Token.EventQueue
+    SetEventQueue, // requires Token.CreateProcess
     SetTokens, // requires Token.Root
-    SetAddressSpace, // requires Token.VirtualMemory
+    SetAddressSpace, // requires Token.CreateProcess
+    CreateThread, // requires Token.CreateProcess
+    SetThreadEntry, // requires Token.CreateProcess
+    SetThreadArguments, // requires Token.CreateProcess
+    SetThreadStack, // requires Token.CreateProcess
+    StartThread, // requires Token.CreateProcess
+    GetThreadId,
 };
 
 pub const Token = enum(u64) {
     Root = 1 << 0,
     PhysicalMemory = 1 << 1,
     ThreadPriority = 1 << 2,
-    EventQueue = 1 << 3,
-    VirtualMemory = 1 << 4,
+    CreateProcess = 1 << 3,
 };
 
 pub const SystemError = error{
diff --git a/system/lib/syscalls.zig b/system/lib/syscalls.zig
index 6cc57d4..be5cf59 100644
--- a/system/lib/syscalls.zig
+++ b/system/lib/syscalls.zig
@@ -1,58 +1,94 @@
 const kernel = @import("kernel.zig");
 const target = @import("builtin").target;
 
-fn syscall(num: kernel.SystemCall, arg0: u64, arg1: u64) i64 {
+fn syscall(num: kernel.SystemCall, arg0: u64, arg1: u64, arg2: u64) i64 {
     return switch (target.cpu.arch) {
         .x86_64 => asm volatile ("int $66"
             : [result] "={rax}" (-> i64),
             : [num] "{rax}" (@intFromEnum(num)),
               [arg0] "{rdi}" (arg0),
               [arg1] "{rsi}" (arg1),
+              [arg2] "{rdx}" (arg2),
         ),
         else => @compileError("unsupported architecture"),
     };
 }
 
 pub fn print(arg: u64) void {
-    _ = syscall(.Print, arg, 0);
+    _ = syscall(.Print, arg, 0, 0);
 }
 
 pub fn allocFrame() !usize {
-    const retval = syscall(.AllocFrame, 0, 0);
+    const retval = syscall(.AllocFrame, 0, 0, 0);
     if (retval < 0) return error.OutOfMemory;
     return @bitCast(retval);
 }
 
 pub fn lockFrame(address: u64) void {
-    _ = syscall(.LockFrame, address, 0);
+    _ = syscall(.LockFrame, address, 0, 0);
 }
 
 pub fn freeFrame(address: u64) void {
-    _ = syscall(.FreeFrame, address, 0);
+    _ = syscall(.FreeFrame, address, 0, 0);
 }
 
 pub fn yield() void {
-    _ = syscall(.Yield, 0, 0);
+    _ = syscall(.Yield, 0, 0, 0);
 }
 
 pub fn setPriority(priority: u8) void {
-    _ = syscall(.SetPriority, priority, 0);
+    _ = syscall(.SetPriority, priority, 0, 0);
 }
 
 pub fn getPriority() u8 {
-    return @truncate(@as(u64, @bitCast(syscall(.GetPriority, 0, 0))));
+    return @truncate(@as(u64, @bitCast(syscall(.GetPriority, 0, 0, 0))));
 }
 
 pub fn sleep(ms: u64) void {
-    _ = syscall(.Sleep, ms, 0);
+    _ = syscall(.Sleep, ms, 0, 0);
 }
 
 pub fn setEventQueue(pid: u64, address: u64) !void {
-    const retval = syscall(.SetEventQueue, pid, address);
+    const retval = syscall(.SetEventQueue, pid, address, 0);
     if (retval < 0) return error.NoSuchThread;
 }
 
 pub fn setTokens(pid: u64, tokens: u64) !void {
-    const retval = syscall(.SetTokens, pid, tokens);
+    const retval = syscall(.SetTokens, pid, tokens, 0);
     if (retval < 0) return error.NoSuchThread;
 }
+
+pub fn setAddressSpace(pid: u64, address: u64) !void {
+    const retval = syscall(.SetAddressSpace, pid, address, 0);
+    if (retval < 0) return error.NoSuchThread;
+}
+
+pub fn createThread() !u64 {
+    const retval = syscall(.CreateThread, 0, 0, 0);
+    if (retval < 0) return error.NoSuchThread;
+    return @bitCast(retval);
+}
+
+pub fn setThreadEntry(pid: u64, entry: u64) !void {
+    const retval = syscall(.SetThreadEntry, pid, entry, 0);
+    if (retval < 0) return error.NoSuchThread;
+}
+
+pub fn setThreadArguments(pid: u64, arg0: u64, arg1: u64) !void {
+    const retval = syscall(.SetThreadArguments, pid, arg0, arg1);
+    if (retval < 0) return error.NoSuchThread;
+}
+
+pub fn setThreadStack(pid: u64, stack: u64) !void {
+    const retval = syscall(.SetThreadStack, pid, stack, 0);
+    if (retval < 0) return error.NoSuchThread;
+}
+
+pub fn startThread(pid: u64) !void {
+    const retval = syscall(.StartThread, pid, 0, 0);
+    if (retval < 0) return error.NoSuchThread;
+}
+
+pub fn getThreadId() u64 {
+    return @bitCast(syscall(.GetThreadId, 0, 0, 0));
+}