diff --git a/arch/x86/boot/setup32.S b/arch/x86/boot/setup32.S
index bd578e0..0663137 100644
--- a/arch/x86/boot/setup32.S
+++ b/arch/x86/boot/setup32.S
@@ -3,6 +3,7 @@
 #include <asm/common.h>
 
 #include <arch/page.h>
+#include <arch/sched.h>
 #include <arch/vmparam.h>
 
 #include <gay/config.h>
@@ -173,6 +174,7 @@ ASM_ENTRY(_setup_highmem)
 	/* set up the initial stack frame */
 	mov	$stack_top, %ebp
 	mov	%ebp, %esp
+	pushl	$0 /* cpu number, see smp_cpuid() in arch/smp.h */
 
 	/* reset EFLAGS */
 	pushl	$0
@@ -199,8 +201,9 @@ ASM_ENTRY(_setup_highmem)
 ASM_END(_setup_highmem)
 
 	.section .bootstrap_stack, "aw", @nobits
+	.align KERN_STACK_SIZE
 stack_bottom:
-	.skip 16384 /* 16 KiB for the stack should be plenty for now */
+	.skip KERN_STACK_SIZE
 stack_top:
 
 /*
diff --git a/arch/x86/include/arch/sched.h b/arch/x86/include/arch/sched.h
index c62d204..0148888 100644
--- a/arch/x86/include/arch/sched.h
+++ b/arch/x86/include/arch/sched.h
@@ -1,29 +1,24 @@
 /* See the end of this file for copyright and license terms. */
 
 #pragma once
+#define _ARCH_SCHED_H_
+
+#include <arch/page.h>
+
+#define KERN_STACK_PAGES 2
+#define KERN_STACK_SHIFT (PAGE_SHIFT + KERN_STACK_PAGES - 1)
+#define KERN_STACK_SIZE (1 << KERN_STACK_SHIFT)
+
+#ifndef _ASM_SOURCE
 
 #include <gay/cdefs.h>
 #include <gay/types.h>
 
-/**
- * @brief In-kernel context save for the x86.
- * This precise structure layout is hardcoded in assembly, so don't forget to
- * update `arch/x86/sys/switch.S` if you need to change it for whatever reason.
- */
-struct x86_context {
-	/**
-	 * The register itself is %esp, which points to the %eip that was pushed
-	 * by the function calling `arch_switch_to()` as per the x86 SysV ABI
-	 */
-	union {
-		register_t esp;
-		register_t *eip;
-	};
-	register_t esi;
-	register_t edi;
-	register_t ebx;
-	register_t ebp;
-} __packed;
+#ifdef __x86_64__
+#include <amd64/sched.h>
+#else
+#include <i386/sched.h>
+#endif
 
 /**
  * @brief Arch dependent Task Control Block (x86 version).
@@ -41,6 +36,8 @@ typedef struct x86_context tcb_t;
  */
 extern void arch_switch_to(tcb_t *new, tcb_t *old);
 
+#endif /* not _ASM_SOURCE */
+
 /*
  * This file is part of GayBSD.
  * Copyright (c) 2021 fef <owo@fef.moe>.
diff --git a/arch/x86/include/arch/smp.h b/arch/x86/include/arch/smp.h
new file mode 100644
index 0000000..7040c0a
--- /dev/null
+++ b/arch/x86/include/arch/smp.h
@@ -0,0 +1,50 @@
+/* See the end of this file for copyright and license terms. */
+
+#pragma once
+
+#include <arch/page.h>
+#include <arch/sched.h>
+
+#include <gay/cdefs.h>
+#include <gay/config.h>
+#include <gay/types.h>
+
+static inline int smp_cpuid(void)
+{
+#if CFG_SMP
+	/*
+	 * The x86 has a dedicated CPUID instruction that can be used to query
+	 * pretty much anything except literally the cpu id.  Therefore, we make
+	 * use of the fact that kernel stacks are always aligned to their size.
+	 * The cpu number is the topmost item on the stack; for cpu 0 this gets
+	 * pushed in the assembly routine in startup{32,64}.S and for all others
+	 * ... there is no implementation yet.
+	 */
+	u_register_t sp;
+
+#ifdef __x86_64__
+	__asm__ volatile("movq %%rsp, %0" : "=r"(sp));
+#else
+	__asm__ volatile("movl %%esp, %0" : "=r"(sp));
+#endif
+
+	sp &= ~(KERN_STACK_SIZE - 1);
+	return ((int *)sp)[KERN_STACK_SIZE / sizeof(int) - 1];
+#else /* !CFG_SMP */
+	return 0;
+#endif /* !CFG_SMP */
+}
+
+/*
+ * This file is part of GayBSD.
+ * Copyright (c) 2021 fef <owo@fef.moe>.
+ *
+ * GayBSD is nonviolent software: you may only use, redistribute, and/or
+ * modify it under the terms of the Cooperative Nonviolent Public License
+ * (CNPL) as found in the LICENSE file in the source code root directory
+ * or at <https://git.pixie.town/thufie/npl-builder>; either version 7
+ * of the license, or (at your option) any later version.
+ *
+ * GayBSD comes with ABSOLUTELY NO WARRANTY, to the extent
+ * permitted by applicable law.  See the CNPL for details.
+ */
diff --git a/arch/x86/include/i386/sched.h b/arch/x86/include/i386/sched.h
new file mode 100644
index 0000000..1c95dea
--- /dev/null
+++ b/arch/x86/include/i386/sched.h
@@ -0,0 +1,41 @@
+/* See the end of this file for copyright and license terms. */
+
+#pragma once
+
+#ifndef _ARCH_SCHED_H_
+#error "This file is not meant to be included directly, use <arch/page.h>"
+#endif
+
+/**
+ * @brief In-kernel context save for the x86.
+ * This precise structure layout is hardcoded in assembly, so don't forget to
+ * update `arch/x86/sys/switch.S` if you need to change it for whatever reason.
+ */
+struct x86_context {
+	/**
+	 * The register itself is %esp, which points to the %eip that was pushed
+	 * by the function calling `arch_switch_to()` as per the x86 SysV ABI
+	 */
+	union {
+		register_t esp;
+		register_t *eip;
+	};
+	register_t esi;
+	register_t edi;
+	register_t ebx;
+	register_t ebp;
+} __packed;
+
+/*
+ * This file is part of GayBSD.
+ * Copyright (c) 2021 fef <owo@fef.moe>.
+ *
+ * GayBSD is nonviolent software: you may only use, redistribute, and/or
+ * modify it under the terms of the Cooperative Nonviolent Public License
+ * (CNPL) as found in the LICENSE file in the source code root directory
+ * or at <https://git.pixie.town/thufie/npl-builder>; either version 7
+ * of the license, or (at your option) any later version.
+ *
+ * GayBSD comes with ABSOLUTELY NO WARRANTY, to the extent
+ * permitted by applicable law.  See the CNPL for details.
+ */
diff --git a/cmake/config.cmake b/cmake/config.cmake
index 5bbcc95..0fc1af3 100644
--- a/cmake/config.cmake
+++ b/cmake/config.cmake
@@ -14,6 +14,10 @@ option(CFG_POISON_PAGES "Poison pages after allocate and free" ON)
 
 option(CFG_POISON_HEAP "Poison heap memory after kmalloc() and kfree()" ON)
 
+option(CFG_SMP "Enable Symmetric Multiprocessing" ON)
+
+set(CFG_MAX_CPU "64" CACHE STRING "Maximum number of logical processors")
+
 option(CFG_DEBUG_IRQ "Debug IRQs" ${DEBUG})
 
 option(CFG_DEBUG_CLIST "Sanitize clist operations" ${DEBUG})
diff --git a/include/gay/config.h.in b/include/gay/config.h.in
index 4efd91b..c75b4b9 100644
--- a/include/gay/config.h.in
+++ b/include/gay/config.h.in
@@ -31,6 +31,12 @@
 /** @brief Poison heap areas after `kmalloc()` and `kfree()` */
 #cmakedefine01 CFG_POISON_HEAP
 
+/** @brief Enable Symmetric Multiprocessing */
+#cmakedefine01 CFG_SMP
+
+/** @brief Maximum number of logical processors */
+#define CFG_MAX_CPU @CFG_MAX_CPU@
+
 /** @brief Debug IRQs */
 #cmakedefine01 CFG_DEBUG_IRQ