diff --git a/py/builtin.h b/py/builtin.h
index a29844498f568796a84a6df5c8ca805028da72ea..bf65b48329eb2cd08cd2ee03c0805f5a276e5889 100644
--- a/py/builtin.h
+++ b/py/builtin.h
@@ -42,3 +42,4 @@ extern const mp_obj_module_t mp_module_io;
 extern const mp_obj_module_t mp_module_math;
 extern const mp_obj_module_t mp_module_micropython;
 extern const mp_obj_module_t mp_module_struct;
+extern const mp_obj_module_t mp_module_sys;
diff --git a/py/builtinimport.c b/py/builtinimport.c
index 697244878ff36ae6d2fb23b46e6796676916fd36..578e73feb699e71de28f50f6db83e1183ef748cc 100644
--- a/py/builtinimport.c
+++ b/py/builtinimport.c
@@ -28,8 +28,6 @@
 
 #define PATH_SEP_CHAR '/'
 
-mp_obj_t mp_sys_path;
-
 mp_import_stat_t stat_dir_or_file(vstr_t *path) {
     //printf("stat %s\n", vstr_str(path));
     mp_import_stat_t stat = mp_import_stat(vstr_str(path));
@@ -48,9 +46,7 @@ mp_import_stat_t find_file(const char *file_str, uint file_len, vstr_t *dest) {
     // extract the list of paths
     uint path_num = 0;
     mp_obj_t *path_items;
-    if (mp_sys_path != MP_OBJ_NULL) {
-        mp_obj_list_get(mp_sys_path, &path_num, &path_items);
-    }
+    mp_obj_list_get(mp_sys_path, &path_num, &path_items);
 
     if (path_num == 0) {
         // mp_sys_path is empty, so just use the given file name
diff --git a/py/builtintables.c b/py/builtintables.c
index e2007f3b41badcef9089c06a78e74e9ce8947d30..23c34eda1136739d419483252420b042993baad0 100644
--- a/py/builtintables.c
+++ b/py/builtintables.c
@@ -134,6 +134,7 @@ STATIC const mp_map_elem_t mp_builtin_module_table[] = {
 #if MICROPY_ENABLE_FLOAT
     { MP_OBJ_NEW_QSTR(MP_QSTR_math), (mp_obj_t)&mp_module_math },
 #endif
+    { MP_OBJ_NEW_QSTR(MP_QSTR_sys), (mp_obj_t)&mp_module_sys },
 
     // extra builtin modules as defined by a port
     MICROPY_EXTRA_BUILTIN_MODULES
diff --git a/py/modsys.c b/py/modsys.c
new file mode 100644
index 0000000000000000000000000000000000000000..7f8cd68a8d4cee38f14c3af0c065b215a50f77b0
--- /dev/null
+++ b/py/modsys.c
@@ -0,0 +1,48 @@
+#include "misc.h"
+#include "mpconfig.h"
+#include "qstr.h"
+#include "obj.h"
+#include "builtin.h"
+#include "runtime.h"
+#include "objlist.h"
+
+#if MICROPY_ENABLE_MOD_SYS
+
+// These should be implemented by ports, specific types don't matter,
+// only addresses.
+struct _dummy_t;
+extern struct _dummy_t mp_sys_stdin_obj;
+extern struct _dummy_t mp_sys_stdout_obj;
+extern struct _dummy_t mp_sys_stderr_obj;
+
+mp_obj_list_t mp_sys_path_obj;
+mp_obj_list_t mp_sys_argv_obj;
+
+STATIC const mp_map_elem_t mp_module_sys_globals_table[] = {
+    { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_sys) },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_path), (mp_obj_t)&mp_sys_path_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_argv), (mp_obj_t)&mp_sys_argv_obj },
+
+    { MP_OBJ_NEW_QSTR(MP_QSTR_stdin), (mp_obj_t)&mp_sys_stdin_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_stdout), (mp_obj_t)&mp_sys_stdout_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_stderr), (mp_obj_t)&mp_sys_stderr_obj },
+};
+
+STATIC const mp_obj_dict_t mp_module_sys_globals = {
+    .base = {&mp_type_dict},
+    .map = {
+        .all_keys_are_qstrs = 1,
+        .table_is_fixed_array = 1,
+        .used = sizeof(mp_module_sys_globals_table) / sizeof(mp_map_elem_t),
+        .alloc = sizeof(mp_module_sys_globals_table) / sizeof(mp_map_elem_t),
+        .table = (mp_map_elem_t*)mp_module_sys_globals_table,
+    },
+};
+
+const mp_obj_module_t mp_module_sys = {
+    .base = { &mp_type_module },
+    .name = MP_QSTR_sys,
+    .globals = (mp_obj_dict_t*)&mp_module_sys_globals,
+};
+
+#endif
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 2c118b4bba933e3ca4e5568a4c20b6aafc5c850e..119d0177dd526d56ada019d104f987bad37d8081 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -120,6 +120,11 @@ typedef double mp_float_t;
 #define MICROPY_ENABLE_MOD_STRUCT (1)
 #endif
 
+// Whether to provide "sys" module
+#ifndef MICROPY_ENABLE_MOD_SYS
+#define MICROPY_ENABLE_MOD_SYS (1)
+#endif
+
 // Whether to support slice object and correspondingly
 // slice subscript operators
 #ifndef MICROPY_ENABLE_SLICE
diff --git a/py/py.mk b/py/py.mk
index 23ba9ebe743325acb860fff2578bbc48ebbb4475..09b40566d58cf0c3117ca2605239138a7d1e01d6 100644
--- a/py/py.mk
+++ b/py/py.mk
@@ -82,6 +82,7 @@ PY_O_BASENAME = \
 	modmath.o \
 	modmicropython.o \
 	modstruct.o \
+	modsys.o \
 	vm.o \
 	showbc.o \
 	repl.o \
diff --git a/py/runtime.c b/py/runtime.c
index 053367e3f725d68a742fe8f39517394e4d90db51..ed011936287d1a147a921b6749eefbf55fda4594 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -53,17 +53,6 @@ void mp_init(void) {
 
     // locals = globals for outer module (see Objects/frameobject.c/PyFrame_New())
     dict_locals = dict_globals = &dict_main;
-
-#if MICROPY_CPYTHON_COMPAT
-    // Precreate sys module, so "import sys" didn't throw exceptions.
-    mp_obj_t m_sys = mp_obj_new_module(MP_QSTR_sys);
-    // Avoid warning of unused var
-    (void)m_sys;
-#endif
-    // init sys.path
-    // for efficiency, left to platform-specific startup code
-    //mp_sys_path = mp_obj_new_list(0, NULL);
-    //mp_store_attr(m_sys, MP_QSTR_path, mp_sys_path);
 }
 
 void mp_deinit(void) {
diff --git a/py/runtime.h b/py/runtime.h
index 867d633520e4cc5b06dfa11daafc62e73d121df8..bf3d3da3cae0e784792a95717e83f5f7c1ebc04a 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -60,8 +60,12 @@ mp_vm_return_kind_t mp_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t th
 
 mp_obj_t mp_make_raise_obj(mp_obj_t o);
 
-extern mp_obj_t mp_sys_path;
 mp_map_t *mp_loaded_modules_get(void);
 mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level);
 mp_obj_t mp_import_from(mp_obj_t module, qstr name);
 void mp_import_all(mp_obj_t module);
+
+extern struct _mp_obj_list_t mp_sys_path_obj;
+extern struct _mp_obj_list_t mp_sys_argv_obj;
+#define mp_sys_path ((mp_obj_t)&mp_sys_path_obj)
+#define mp_sys_argv ((mp_obj_t)&mp_sys_argv_obj)
diff --git a/unix/file.c b/unix/file.c
index fa7be791a4d431f0a8551126f965ba7ecb3c6eee..a0a865a2636bcb2e7f4f914b5f406661c45f2db7 100644
--- a/unix/file.c
+++ b/unix/file.c
@@ -146,9 +146,6 @@ mp_obj_t mp_builtin_open(uint n_args, const mp_obj_t *args) {
 }
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_open_obj, 1, 2, mp_builtin_open);
 
-void file_init() {
-    mp_obj_t m_sys = mp_obj_new_module(MP_QSTR_sys);
-    mp_store_attr(m_sys, MP_QSTR_stdin, fdfile_new(STDIN_FILENO));
-    mp_store_attr(m_sys, MP_QSTR_stdout, fdfile_new(STDOUT_FILENO));
-    mp_store_attr(m_sys, MP_QSTR_stderr, fdfile_new(STDERR_FILENO));
-}
+const mp_obj_fdfile_t mp_sys_stdin_obj  = { .base = {&rawfile_type}, .fd = STDIN_FILENO };
+const mp_obj_fdfile_t mp_sys_stdout_obj = { .base = {&rawfile_type}, .fd = STDOUT_FILENO };
+const mp_obj_fdfile_t mp_sys_stderr_obj = { .base = {&rawfile_type}, .fd = STDERR_FILENO };
diff --git a/unix/main.c b/unix/main.c
index f18e40a7fa90868a5c6434cf78316b28f1ea5428..e582244b394e8a5173b8d8f1f03a7d72977e5148 100644
--- a/unix/main.c
+++ b/unix/main.c
@@ -42,7 +42,6 @@ long heap_size = 128*1024 * (sizeof(machine_uint_t) / 4);
 // Stack top at the start of program
 void *stack_top;
 
-void file_init();
 void microsocket_init();
 void time_init();
 void ffi_init();
@@ -326,7 +325,7 @@ int main(int argc, char **argv) {
             p++;
         }
     }
-    mp_sys_path = mp_obj_new_list(path_num, NULL);
+    mp_obj_list_init(mp_sys_path, path_num);
     mp_obj_t *path_items;
     mp_obj_list_get(mp_sys_path, &path_num, &path_items);
     path_items[0] = MP_OBJ_NEW_QSTR(MP_QSTR_);
@@ -348,10 +347,7 @@ int main(int argc, char **argv) {
         p = p1 + 1;
     }
 
-    mp_obj_t m_sys = mp_obj_new_module(MP_QSTR_sys);
-    mp_store_attr(m_sys, MP_QSTR_path, mp_sys_path);
-    mp_obj_t py_argv = mp_obj_new_list(0, NULL);
-    mp_store_attr(m_sys, MP_QSTR_argv, py_argv);
+    mp_obj_list_init(mp_sys_argv, 0);
 
     mp_store_name(qstr_from_str("test"), test_obj_new(42));
     mp_store_name(qstr_from_str("mem_info"), mp_make_function_n(0, mem_info));
@@ -360,7 +356,6 @@ int main(int argc, char **argv) {
     mp_store_name(qstr_from_str("gc"), (mp_obj_t)&pyb_gc_obj);
 #endif
 
-    file_init();
     microsocket_init();
 #if MICROPY_MOD_TIME
     time_init();
@@ -418,7 +413,7 @@ int main(int argc, char **argv) {
             free(basedir);
 
             for (int i = a; i < argc; i++) {
-                mp_obj_list_append(py_argv, MP_OBJ_NEW_QSTR(qstr_from_str(argv[i])));
+                mp_obj_list_append(mp_sys_argv, MP_OBJ_NEW_QSTR(qstr_from_str(argv[i])));
             }
             do_file(argv[a]);
             executed = true;