GCC - Needs fork/exec support – through the compiling, assembling, and linking process multiple different binaries are executed, making this all run inside a single executable would be very difficult. Getting GCC to run was the primary motivation for implementing fork and exec support in NestedVM - GCC makes extensive use of the 64bit “long long” type. In order to support this type on a machine with 32-bit words a few instructions are used in obscure ways. Building gcc uncovered one new bug in the binary translator
 Runtime/Syscall interface - The nestedvm runtime provides all the functions a kernel normally provides in a traditional environment plus a few extra functions that can be done more efficiently in java - Translated binaries interact with the runtime using the SYSCALL instruction. The binary translator translates this instruction into a call to the syscall() method of the Runtime. When the syscall() method is invoked the runtime carries out whatever actions were requested (usually modifying the process’ memory or other state information in the process) and returns a value to the caller. - The NestedVM runtime is broken up into two parts, the standard runtime and the “Unix runtime”. The standard runtime only implements a minimal number of syscalls. Just enough to get simple ansi-c applications to run. Using this is sufficient if you have a binary that simply reads and writes from streams or memory. - Runtime syscalls o Open – The open syscall creates a file descriptor from a local file. After checking with an optional SecurityManager to see if access to the requested file is allowed the local file with the given name is opened and a file descriptor for that files is added to the file descriptor table for the process (simply an FD[] array ♣ Local files are opened using the RandomAccessFile class to allow reading, writing, and seeking o Read/write /seek syscalls – The read, write, and seek syscalls invoke the corresponding methods of the FileDescriptor objects they are called on. o Close – Simply closes the file descriptor and frees up the file descriptor table entry it uses o Fcntl – The fcntl syscall gets and sets various attributes of the file descriptor is is called on o Sbrk – the sbrk syscall extends the heap of the process. This is accomplished by adding more “pages” (int[] arrays) to the memory page table o Exit – The exit syscall records the exit status passed to it then sets the “state” instance variable to exited to indicate to subclasses that they should stop executing code. In most cases this will eventually cause control to return to the run() method in runtime where the exit status recorded earlier is returned o Pause – The pause syscall was previously used to implement a crude form of communication between the java caller and the mips binary. The pause syscall temporarily pauses execution of the mips binary and return control to java. At that point the caller in java can modify the state of the process and resume it. Using the call() method and _call_java() interfaces are preferable to this method though. o Memset and Memcpy – while these two functions can me implemented purely in user space, they can be done much more efficiently in java. Memcpy, for example, can usually be turned into a single call to System.arraycopy() which on most JVMs is executed in a very fast native loop. These two syscalls exist only for performance reasons - UnixRuntime syscalls o The unix runtime offers a more complete set of syscalls that are commonly found on unix systems. It also offers a more complex filesystem interface (including support for mount points and the /dev filesystem). The unix runtime also makes the host’s filesystem looks like a unix filesystem (“/” root directory, forward slash path separator, etc. Fork and exec are also implemented in the unix runtime and it allows for different unixruntime process’s to communicate with eachother o Filesystem interface ♣ UnixRuntime abstracts away all filesystem access to a filesystem class. This allows new filesystem to be added to NestedVM at any file. ♣ NestedVM included a filesystem class for accessing the Hosts’s filesystem and a special DevFS filesystem implementing some traditional /dev devices. ♣ New filesystem classes can be added to access any kind of file file store including Zip files, filesystem images, or even http servers. o Fork – the fork() creates a new instance of a UnixRuntime and begins executing it in a new thread. To make the actual copy of the currently running UnixRuntime instance java’s clone() function is used. Where appropriate state information is deeply copied. This includes, unfortunately, all writeable memory pages. Copy-on-write cannot be easily implemented in the JVM so this is necessary. After the new cloned process has been created it is added to the process table. This is stored in the “GlobalState” object. Multiple global states can exist in a single VM which allows for multiple independent collections of processes. Finally, the setCPUState() method is used on the new process to set the return value (register r2 to 0 and increment the program counter past the syscall instruction). At this point a new java thread is created and the new process is executed in the new thread o Waitpid – the waitpid function causes a process to block waiting for a child process to complete. Whenever a child exits it is added to its parents “exitedChildren” queue. If this queue is empty the parent blocks waiting for a chick to finish. This is done using java’s wait() method. Completed children add themselves to their parents exitedChildren queue and notify their parent with the notify() method o Exec - The exec() function is probably the most complicated one in NestedVM. It compiles mips binaries from the filesystem on the fly and executes them in place of the existing process ♣ Reading from the filesystem – the mips binary is read from the filesystem using the standard filesystem access methods. This allows binaries to be loaded from any source readable by the nestedvm runtime. ♣ The binary is then run through the binary to source translator (which is run in the same VM) where it is compiled to bytecode. Finally, the bytecode is loaded into the running VM using a custom classloader and it is instantiated. ♣ Next the new UnixRuntime instance is populated with the values of various attributes of the caller, including, the file descriptor table, current working directory, pid, ppid, etc. ♣ A special variable is set in the unix runtime class to indicate that an EXEC has taken place and control a similar process follows as does for EXIT (control is returned to the run() method in the runtime). At this point run() notices an exec() not an exits has taken place and runs the new unixruntime instance. This is used to prevent stack space from being used up for each exec() and to allow the old unused instance to be Gced o Pipe – In unix Pipes are commonly used for IPC. The pipe() syscall creates a unidirectional pipe just like in unix. Forked() process can then communicate though this pipe. o State/lstat/mkdir/unlink – These functions simple call the appropriate functions in the filesystem class. In the case of the HostFS filesystem the SecurityManager is checked before any actions are carried out on the hohst filesystem o Chdir – Since java has no concept of a “current working directory” and even if it did it wouldn’t be of much use since multiple binaries can be run in a single VM and the filesystem isn’t necessarily the hosts’s filesystem chdir() has to be fakes. This is accomplished by setting a variable in the unixruntime instance containing this process’ cwd as a string. This variable is consulted whenever a relative path is used by any filesystem accessing function. o Getdents – the getdents syscall is used to read directory entries. It works just like the traditional unix getdents syscall. It writes “struct dirent” structures to the buffer passed to it. o Opensocket – the open socket syscall is use to create a filedescriptor that maps to a tcp socket. A hostname and port is passed to this syscall which simply passes them on to javas Socket() constructor. If the connection is successful the socket’s input and outputstreams are connected to a file descriptor and returned to the caller o Listensocket – similar to open socket, creates a ServerSocket o Accept – calls the accept() method on the ServerSocket passed to it and creates a new FD for the accepted connection