├── .gitattributes ├── README.md ├── _config.yml ├── boot.html ├── boot.js ├── boot.min.js ├── index.md ├── liblkl.js ├── liblkl.min.js └── pthread-main.js /.gitattributes: -------------------------------------------------------------------------------- 1 | boot.js filter=lfs diff=lfs merge=lfs -text 2 | liblkl.js filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lkl.js 2 | 3 | Run Linux kernel in your browser *directly*. 4 | 5 | This repository includes pre-compiled LKL.js files. 6 | If you want to build LKL.js on your environment, 7 | please read [lkl-js.txt](https://github.com/retrage/linux/blob/retrage/em-v2/Documentation/lkl-js.txt). 8 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: LKL.js 2 | description: Running Linux Kernel on JavaScript *Directly* 3 | theme: jekyll-theme-cayman 4 | -------------------------------------------------------------------------------- /boot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Emscripten-Generated Code 7 | 103 | 104 | 105 | 106 | image/svg+xml 1196 | 1197 | 1198 |
1199 |
Downloading...
1200 | 1201 | 1202 | Resize canvas 1203 | Lock/hide mouse pointer     1204 | 1206 | 1207 | 1208 | 1209 |
1210 | 1211 |
1212 | 1213 | 1214 |
1215 | 1216 |
1217 | 1218 | 1219 | 1300 | 1301 | 1302 | 1303 | -------------------------------------------------------------------------------- /boot.js: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:791888e97f2b8927874edaa40a3f585ddbf65d951ca7b53f390c88ac5f240dd1 3 | size 103337468 4 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | # LKL.js: Running Linux Kernel on JavaScript *Directly* 2 | 3 | [LKL.js](https://github.com/retrage/lkl-js) is 4 | [Linux Kernel Library](https://github.com/lkl/linux) ported to JavaScript 5 | using [Emscripten](https://github.com/kripken/emscripten). 6 | Unlike [JSLinux](https://bellard.org/jslinux/), 7 | LKL.js includes a Linux kernel fully written in JavaScript and runs without 8 | emulators. 9 | For more details, 10 | read my [blog post](https://retrage.github.io/2018/07/25/lkl-js-en.html) 11 | 12 | LKL.js just boots Linux kernel and still completely useless. 13 | It shows how Emscripten is powerful and how Linux kernel is flexible. 14 | 15 | ## Demo 16 | 17 | LKL.js requires SharedArrayBuffer for pthread support. 18 | SharedArrayBuffer is disabled by default due to 19 | [Spectre](https://meltdownattack.com/) mitigation in Mozilla Firefox. 20 | Please enable it. (`javascript.options.shared_memory` in Mozilla Firefox) 21 | The link below is `boot.js` demo site. 22 | 23 | * [https://retrage.github.io/lkl-js/boot.html](https://retrage.github.io/lkl-js/boot.html) 24 | 25 | ## Downloads 26 | 27 | `liblkl.js` is a JavaScript version of `liblkl.a` and 28 | `liblkl.min.js` is a compressed version of `liblkl.js`. 29 | 30 | `boot.js` is a JavaScript version of `tools/lkl/tests/boot` and 31 | `boot.min.js` is a compressed version of `boot.js` 32 | 33 | `*.min.js` is generated by using 34 | [Google Closure Compiler](https://github.com/google/closure-compiler). 35 | 36 | * [liblkl.js](https://github.com/retrage/lkl-js/blob/master/liblkl.js) 37 | * [liblkl.min.js](https://github.com/retrage/lkl-js/blob/master/liblkl.min.js) 38 | * [boot.js](https://github.com/retrage/lkl-js/blob/master/boot.js) 39 | * [boot.min.js](https://github.com/retrage/lkl-js/blob/master/boot.min.js) 40 | 41 | ## Source code 42 | 43 | The source code is available at 44 | [retrage/linux](https://github.com/retrage/linux) 45 | branch [retrage/em-v2](https://github.com/retrage/linux/tree/retrage/em-v2). 46 | -------------------------------------------------------------------------------- /liblkl.js: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ef2054c2eed5278e5bc79abbc6ef4d6e7dbeaf32651016237acd52f90c02bcbc 3 | size 103529440 4 | -------------------------------------------------------------------------------- /pthread-main.js: -------------------------------------------------------------------------------- 1 | // Pthread Web Worker startup routine: 2 | // This is the entry point file that is loaded first by each Web Worker 3 | // that executes pthreads on the Emscripten application. 4 | 5 | // Thread-local: 6 | var threadInfoStruct = 0; // Info area for this thread in Emscripten HEAP (shared). If zero, this worker is not currently hosting an executing pthread. 7 | var selfThreadId = 0; // The ID of this thread. 0 if not hosting a pthread. 8 | var parentThreadId = 0; // The ID of the parent pthread that launched this thread. 9 | var tempDoublePtr = 0; // A temporary memory area for global float and double marshalling operations. 10 | 11 | // Thread-local: Each thread has its own allocated stack space. 12 | var STACK_BASE = 0; 13 | var STACKTOP = 0; 14 | var STACK_MAX = 0; 15 | 16 | // These are system-wide memory area parameters that are set at main runtime startup in main thread, and stay constant throughout the application. 17 | var buffer; // All pthreads share the same Emscripten HEAP as SharedArrayBuffer with the main execution thread. 18 | var DYNAMICTOP_PTR = 0; 19 | var TOTAL_MEMORY = 0; 20 | var STATICTOP = 0; 21 | var staticSealed = true; // When threads are being initialized, the static memory area has been already sealed a long time ago. 22 | var DYNAMIC_BASE = 0; 23 | 24 | var ENVIRONMENT_IS_PTHREAD = true; 25 | 26 | // performance.now() is specced to return a wallclock time in msecs since that Web Worker/main thread launched. However for pthreads this can cause 27 | // subtle problems in emscripten_get_now() as this essentially would measure time from pthread_create(), meaning that the clocks between each threads 28 | // would be wildly out of sync. Therefore sync all pthreads to the clock on the main browser thread, so that different threads see a somewhat 29 | // coherent clock across each of them (+/- 0.1msecs in testing) 30 | var __performance_now_clock_drift = 0; 31 | 32 | // Cannot use console.log or console.error in a web worker, since that would risk a browser deadlock! https://bugzilla.mozilla.org/show_bug.cgi?id=1049091 33 | // Therefore implement custom logging facility for threads running in a worker, which queue the messages to main thread to print. 34 | var Module = {}; 35 | 36 | // When error objects propagate from Web Worker to main thread, they lose helpful call stack and thread ID information, so print out errors early here, 37 | // before that happens. 38 | this.addEventListener('error', function(e) { 39 | if (e.message.indexOf('SimulateInfiniteLoop') != -1) return e.preventDefault(); 40 | 41 | var errorSource = ' in ' + e.filename + ':' + e.lineno + ':' + e.colno; 42 | console.error('Pthread ' + selfThreadId + ' uncaught exception' + (e.filename || e.lineno || e.colno ? errorSource : '') + ': ' + e.message + '. Error object:'); 43 | console.error(e.error); 44 | }); 45 | 46 | function threadPrint() { 47 | var text = Array.prototype.slice.call(arguments).join(' '); 48 | console.log(text); 49 | } 50 | function threadPrintErr() { 51 | var text = Array.prototype.slice.call(arguments).join(' '); 52 | console.error(text); 53 | console.error(new Error().stack); 54 | } 55 | function threadAlert() { 56 | var text = Array.prototype.slice.call(arguments).join(' '); 57 | postMessage({cmd: 'alert', text: text, threadId: selfThreadId}); 58 | } 59 | Module['print'] = threadPrint; 60 | Module['printErr'] = threadPrintErr; 61 | this.alert = threadAlert; 62 | 63 | // #if WASM 64 | Module['instantiateWasm'] = function(info, receiveInstance) { 65 | // Instantiate from the module posted from the main thread. 66 | // We can just use sync instantiation in the worker. 67 | instance = new WebAssembly.Instance(Module['wasmModule'], info); 68 | // We don't need the module anymore; new threads will be spawned from the main thread. 69 | delete Module['wasmModule']; 70 | receiveInstance(instance); 71 | return instance.exports; 72 | } 73 | //#endif 74 | 75 | this.onmessage = function(e) { 76 | try { 77 | if (e.data.cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code. 78 | // Initialize the thread-local field(s): 79 | tempDoublePtr = e.data.tempDoublePtr; 80 | 81 | // Initialize the global "process"-wide fields: 82 | Module['TOTAL_MEMORY'] = TOTAL_MEMORY = e.data.TOTAL_MEMORY; 83 | STATICTOP = e.data.STATICTOP; 84 | DYNAMIC_BASE = e.data.DYNAMIC_BASE; 85 | DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR; 86 | 87 | 88 | //#if WASM 89 | if (e.data.wasmModule) { 90 | // Module and memory were sent from main thread 91 | Module['wasmModule'] = e.data.wasmModule; 92 | Module['wasmMemory'] = e.data.wasmMemory; 93 | buffer = Module['wasmMemory'].buffer; 94 | } else { 95 | //#else 96 | buffer = e.data.buffer; 97 | } 98 | //#endif 99 | 100 | PthreadWorkerInit = e.data.PthreadWorkerInit; 101 | if (typeof e.data.urlOrBlob === 'string') { 102 | importScripts(e.data.urlOrBlob); 103 | } else { 104 | var objectUrl = URL.createObjectURL(e.data.urlOrBlob); 105 | importScripts(objectUrl); 106 | URL.revokeObjectURL(objectUrl); 107 | } 108 | //#if !ASMFS 109 | if (typeof FS !== 'undefined' && typeof FS.createStandardStreams === 'function') FS.createStandardStreams(); 110 | //#endif 111 | postMessage({ cmd: 'loaded' }); 112 | } else if (e.data.cmd === 'objectTransfer') { 113 | PThread.receiveObjectTransfer(e.data); 114 | } else if (e.data.cmd === 'run') { // This worker was idle, and now should start executing its pthread entry point. 115 | __performance_now_clock_drift = performance.now() - e.data.time; // Sync up to the clock of the main thread. 116 | threadInfoStruct = e.data.threadInfoStruct; 117 | __register_pthread_ptr(threadInfoStruct, /*isMainBrowserThread=*/0, /*isMainRuntimeThread=*/0); // Pass the thread address inside the asm.js scope to store it for fast access that avoids the need for a FFI out. 118 | assert(threadInfoStruct); 119 | selfThreadId = e.data.selfThreadId; 120 | parentThreadId = e.data.parentThreadId; 121 | assert(selfThreadId); 122 | assert(parentThreadId); 123 | // TODO: Emscripten runtime has these variables twice(!), once outside the asm.js module, and a second time inside the asm.js module. 124 | // Review why that is? Can those get out of sync? 125 | STACK_BASE = STACKTOP = e.data.stackBase; 126 | STACK_MAX = STACK_BASE + e.data.stackSize; 127 | assert(STACK_BASE != 0); 128 | assert(STACK_MAX > STACK_BASE); 129 | establishStackSpace(e.data.stackBase, e.data.stackBase + e.data.stackSize); 130 | var result = 0; 131 | //#if STACK_OVERFLOW_CHECK 132 | if (typeof writeStackCookie === 'function') writeStackCookie(); 133 | //#endif 134 | 135 | PThread.receiveObjectTransfer(e.data); 136 | PThread.setThreadStatus(_pthread_self(), 1/*EM_THREAD_STATUS_RUNNING*/); 137 | 138 | try { 139 | // pthread entry points are always of signature 'void *ThreadMain(void *arg)' 140 | // Native codebases sometimes spawn threads with other thread entry point signatures, 141 | // such as void ThreadMain(void *arg), void *ThreadMain(), or void ThreadMain(). 142 | // That is not acceptable per C/C++ specification, but x86 compiler ABI extensions 143 | // enable that to work. If you find the following line to crash, either change the signature 144 | // to "proper" void *ThreadMain(void *arg) form, or try linking with the Emscripten linker 145 | // flag -s EMULATE_FUNCTION_POINTER_CASTS=1 to add in emulation for this x86 ABI extension. 146 | result = Module['asm'].dynCall_ii(e.data.start_routine, e.data.arg); 147 | 148 | //#if STACK_OVERFLOW_CHECK 149 | if (typeof checkStackCookie === 'function') checkStackCookie(); 150 | //#endif 151 | 152 | } catch(e) { 153 | if (e === 'Canceled!') { 154 | PThread.threadCancel(); 155 | return; 156 | } else if (e === 'SimulateInfiniteLoop') { 157 | return; 158 | } else { 159 | Atomics.store(HEAPU32, (threadInfoStruct + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, (e instanceof ExitStatus) ? e.status : -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/); 160 | Atomics.store(HEAPU32, (threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running. 161 | _emscripten_futex_wake(threadInfoStruct + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/, 0x7FFFFFFF/*INT_MAX*/); // Wake all threads waiting on this thread to finish. 162 | if (!(e instanceof ExitStatus)) throw e; 163 | } 164 | } 165 | // The thread might have finished without calling pthread_exit(). If so, then perform the exit operation ourselves. 166 | // (This is a no-op if explicit pthread_exit() had been called prior.) 167 | PThread.threadExit(result); 168 | } else if (e.data.cmd === 'cancel') { // Main thread is asking for a pthread_cancel() on this thread. 169 | if (threadInfoStruct && PThread.thisThreadCancelState == 0/*PTHREAD_CANCEL_ENABLE*/) { 170 | PThread.threadCancel(); 171 | } 172 | } else if (e.data.target === 'setimmediate') { 173 | // no-op 174 | } else if (e.data.cmd === 'processThreadQueue') { 175 | if (threadInfoStruct) { // If this thread is actually running? 176 | _emscripten_current_thread_process_queued_calls(); 177 | } 178 | } else { 179 | Module['printErr']('pthread-main.js received unknown command ' + e.data.cmd); 180 | console.error(e.data); 181 | } 182 | } catch(e) { 183 | console.error('pthread-main.js onmessage() captured an uncaught exception: ' + e); 184 | console.error(e.stack); 185 | throw e; 186 | } 187 | } 188 | --------------------------------------------------------------------------------