I am trying to build software in JavaScript that emulates an operating system. For all intents and purposes, it is an operating system, even though it is JavaScript and we are running in the Node.js or the browser basically. But that shouldn't matter, the fact that it is JavaScript. It could be in any language or platform for that matter.
What I'm stuck at is the beginning. I am used to writing code in higher level languages that are functioning on top of the operating system, so the process architecture is already built, so I've never had to really deal with it.
The broad question is: how do I create a process architecture?
The specific question is: at a high level, how do I create securely isolated processes in theory?
I essentially have this:
var all_processes = []
var specific_process = -1
function set_process(index) {
if (all_processes[index]) {
throw new Error('Process taken')
} else {
specific_process = index
all_processes[index] = {}
}
}
function call_function(name, ...args) {
all_processes[specific_process][name](...args)
}
But I am imagining doing async and multithreading, etc. All the fancy features of processes. I would like to know how you can keep processes isolated basically, in a secure way. How does the OS do it? Because you need to at some point create and manage the processes, but then the processes themselves can't communicate (unless through a specific protocol perhaps). So it feels like on one level, I need to pass the specific process around to each function call, but then you lose the clean API of not having the process in every parameter. But then if you do that, then what's to say you don't grab another process and pass that around (i.e. how do you maintain security of only accessing that one process?). Also, checking the process on every call would add performance overhead.
Essentially how to create secure processes, at least at a high level what I should be searching/looking for, if not a description of how to do it.
Wikipedia says:
Process isolation can be implemented with virtual address space, where process A's address space is different from process B's address space – preventing A from writing onto B.
I don't understand what this means though, what to do exactly. What does this make my code look like in theory?
Perhaps sandboxes offer some insight, but Wikipedia doesn't offer much, delegating to the operating system without details:
A sandbox is implemented by executing the software in a restricted operating system environment, thus controlling the resources (for example, file descriptors, memory, file system space, etc.) that a process may use.
Privileges also doesn't reveal much.
This paper says the following:
Most operating systems use a CPU’s memory management hardware to provide process isolation, using two mechanisms. First, processes are only allowed access to certain pages of physical memory. Second, privilege levels prevent untrusted code from manipulating the system resources that implement processes, for example, the memory management unit (MMU) or interrupt controllers. These mechanisms’ non-trivial performance costs are largely hidden, since there is no widely used alternative approach to compare them to. Mapping from virtual to physical addresses can incur overheads up to 10–30% due to exception handling, inline TLB lookup, TLB reloads, and maintenance of kernel data structures such as page tables [29]. In addition, virtual memory and privilege levels increase the cost of inter-process communication.
As a solution they present software isolated processes (SIPs), and say:
The design and implementation of a system based on SIPs is a major contribution of this work. A software isolated process is a collection of memory pages and a language safety mechanism that ensures that code in a process cannot access another process’s pages. A SIP replaces hardware memory protection with static verification of program safety. Singularity uses language safety and a fast communication mechanism built on channels [15] to enforce a system-wide invariant that neither the kernel nor any other process contains a reference into a given process’s object space. Because different process’ object spaces always reside on disjoint memory pages, memory reclamation is straightforward when processes terminate.
I don't understand how this could work at all. Though this is helpful:
Moreover, the system maintains the invariant that there exists at most one pointer to an item in the exchange heap. When a process sends a message, it loses its reference to the message, which is transferred to the receiving process (analogous to sending a letter by postal mail). Therefore, processes cannot use this heap as shared memory, and messages can be exchanged very efficiently through pointer passing, not copying.