Page 1 of 1
Efficient IPC with Software Isolated Processes
Posted: Fri Oct 30, 2009 6:19 pm
by AndrewAPrice
I've taken a recent liking to the idea of software isolation over hardware, that is, only execute bytecode that is JITed into machine code at runtime and use the JITer to ensure 'unsafe' code (such as pointer manipulation) isn't allowed. The other benefits include cross architecture portability of most user applications, machine specific JIT optimizations, and the ability to remove the overhead of task and ring switching (a BIG plus in my view). I'm going with .Net since there is a wide range of languages out there targeting the platform.
IPC can potentially be as fast as calling a function in another program. You'd be able to pass strings as parameters which exist in another address space without having to copy, you can pass a reference to a very large array around (such as audio/video data), and with reference counting you need not worry if the sending process died because the memory will still be accessible until everything has released it. But above basic inbuilt types I'm stuck. I'm putting a heavy reliance on security through type safety, so I need to ensure that for nothing to overflow or leak, two process's must share the exact same type when they're trying to communicate.
At JIT time would I need to would need to see each process and what types it wishes to up/down cast to, a process which could be error prone in itself? Or ensure two processes can only communicate using types within a shared signed DLL? (which could be faked, although highly unlikely) I'm interested in how Cosmos, SharpOS, as well as the various Java and other language-based security systems work in this aspect.
More problems come in when I'm dealing with virtual functions. For example, if I share a type which has overloaded a function, but that function exists within another program, then that program dies. One solution is to store a list of every place referring to that function and when the process dies replace every instance with a reference to a null function that throws an exception.
Re: Efficient IPC with Software Isolated Processes
Posted: Fri Oct 30, 2009 7:31 pm
by gravaera
MessiahAndrw wrote:I've taken a recent liking to the idea of software isolation over hardware, that is, only execute bytecode that is JITed into machine code at runtime and use the JITer to ensure 'unsafe' code (such as pointer manipulation) isn't allowed.
I saw that, and thought it sounded very protect-the-programmer-from-actually-doing-his-job-and-programming-the-system like. And this level of absolution of abstraction enables the UniMonkey to thrive, and promotes the idea that anything low-level is innately not only risky (you can argue slightly successfully that it is), but also 'wrong', or 'not good practice'.
I personally would like to discourage you from filtering out any pointer arithmetic, or other 'unsafe practices' which are not by themselves able to place the system in an unsafe state (If it is possible to really filter bytecode that efficiently with a decent performance throughput).
Please take into consideration the purpose of an OS: To provide a consistent software environment so that applications of any type which the hardware platform is capable of supporting would be able to run without having to concern themselves with the state of the environment.
From the
application viewpoint, (notice the stress there; there are other viewpoints which would be simultaneously critical within the system) this is the sole purpose of an OS. An OS should therefore, not limit the flexibility of an application where it is possible not to do so. Every application should be allowed to stretch as far as it may conceptually need to, as long as it does not hamper
another application, or the physical (hardware) environment which other applications may be depending on.
Of course, this was just a small bit of idealism, and nothing but a suggestion; Nice idea, and all the best,
gravaera
Re: Efficient IPC with Software Isolated Processes
Posted: Fri Oct 30, 2009 8:04 pm
by AndrewAPrice
gravaera wrote:I personally would like to discourage you from filtering out any pointer arithmetic, or other 'unsafe practices' which are not by themselves able to place the system in an unsafe state
I am open for suggestions if you have an alternative idea. I want to eliminate the overheard of switching into the kernel and swapping address spaces just to do IPC. But in doing so, if applications were allowed to touch whatever part of the memory they'd like then a single crash would bring the system down.
gravaera wrote:(If it is possible to really filter bytecode that efficiently with a decent performance throughput).
JIT'ing adds overhead to launching an application when it compiles the bytecode to native machine code (during this stage it will catch out unsafe code), but once running it would launch be running natively on the hardware.
gravaera wrote:Please take into consideration the purpose of an OS: To provide a consistent software environment so that applications of any type which the hardware platform is capable of supporting would be able to run without having to concern themselves with the state of the environment.
Our OS's have different goals. I wish to create a high performance microkernel system without the traditional overhead in multitasking systems.
Re: Efficient IPC with Software Isolated Processes
Posted: Fri Oct 30, 2009 8:34 pm
by NickJohnson
MessiahAndrw wrote:You'd be able to pass strings as parameters which exist in another address space without having to copy, you can pass a reference to a very large array around (such as audio/video data), and with reference counting you need not worry if the sending process died because the memory will still be accessible until everything has released it. But above basic inbuilt types I'm stuck
I don't understand exactly what your problem is here. If you can already pass around arbitrarily large amounts of data as arrays, why do you need anything more than that? IPC in general should not rely on the implementation of the receiving program, and if data can be passed as an arbitrary class, then the sender and receiver become implicitly intertwined, (imo) ruining all true modularity. I would either stick with just arrays of basic data types as IPC, or have a common set of classes (I'm guessing this is what you mean by a shared DLL) that are the only types for IPC.
MessiahAndrw wrote:More problems come in when I'm dealing with virtual functions. For example, if I share a type which has overloaded a function, but that function exists within another program, then that program dies. One solution is to store a list of every place referring to that function and when the process dies replace every instance with a reference to a null function that throws an exception.
If you're already running everything in one system-wide virtual machine, especially if you can overload functions from other programs' classes, it seems like the lines between programs are quite blurred. So, why not just make functions as easily shared as data? Keep each one as an object and make them part of the garbage collection system, so an exiting process will not necessarily take its functions with it, if they are being used.
Re: Efficient IPC with Software Isolated Processes
Posted: Fri Oct 30, 2009 9:45 pm
by Colonel Kernel
Read the Singularity papers and do what they do.
More specifically:
- Don't use JIT if you don't plan to support dynamic loading. Disallowing dynamic loading is one of the ways you ensure that processes are "safe". Instead of JIT, compile the app to native code at install time.
- Singularity does not force processes to share the same garbage collection system. Each can have its own, and the kernel has its own.
- IPC messages in Singularity must be allocated from a special heap called the Exchange heap. Every value in the Exchange heap must be of an "exchangeable type", which is either a primitive type or a struct composed of fields of exchangeable types. It's kind of annoying because processes may need to do marshaling to and from the messages, depending on what they're trying to achieve. On the other hand, it's necessary to ensure that the garbage collectors of communicating processes don't need to co-ordinate with one another.
- Type safety of IPC is enforced through the use of strongly-typed "channel contracts" which dictate not only the types of messages that can be sent, but also the sequence in which they can be sent (a kind of simplified protocol state machine). The two processes on either end of a channel must share the same definition of the channel contract, and the kernel enforces this.
- "Overloaded" names can't really be confused in a .NET-based system... Every type visible to more than one process is identified by a "strong name" which includes the assembly-qualified type name, full version number, and public key token.
Hope this helps.
On the flip side of things, there are some reasons not to consider this kind of system, or to at least re-consider using software isolation exclusively:
- Software isolation doesn't protect you from memory corruption caused by hardware failures. MMUs aren't perfect either, but mitigate this risk better.
- IPC without switching to the kernel is possible, if it's cross-CPU IPC. Take a look at Barrelfish. In the not-so-distant future, we'll be running on machines with dozens or even hundreds of cores. That's more than enough parallelism to keep many of those cores dedicated to running some key processes most of the time. Context switching on one core could become a lot less frequent than sending messages to other cores. In this kind of system, having fast topology-aware user-level IPC will be much more important than single-core context switching speed.
@gravaera: UniMonkeys write unmanaged code too, and they do a lot more damage that way. I know because I've worked with some of them in years past.
Re: Efficient IPC with Software Isolated Processes
Posted: Sat Oct 31, 2009 12:16 am
by AndrewAPrice
Colonel Kernel wrote:- IPC messages in Singularity must be allocated from a special heap called the Exchange heap. Every value in the Exchange heap must be of an "exchangeable type", which is either a primitive type or a struct composed of fields of exchangeable types. It's kind of annoying because processes may need to do marshaling to and from the messages, depending on what they're trying to achieve. On the other hand, it's necessary to ensure that the garbage collectors of communicating processes don't need to co-ordinate with one another.
I was thinking of a L4-style approach in that rather that IPC is more like a function call than sending messages. A server would expose a DLL that shows an interface and calling a member of that interface would actually cause that process's thread to be executing code inside of another process.
For example:
unit GetTotalMemory();
would be extremely light weight, jumping into the kernel's code and return a value instantly.
Something slightly more heavy (C#):
Code: Select all
interface VirtualFileSystem
{
// no need to check if 'array' is big enough, because if an out of bounds exception occurs it occurs in the thread calling this (the application)
bool ReadFile(File file, uint offset, uint bytes, ref byte[] array);
}
The implementation of that interface (and ReadFile by extension) actually exists inside of the VFS server rather than the application calling it, and it returns when it is done. The VFS server's code will be responsible for blocking if it's busy doing something else. Asynchronous access would be the responsibility of the application (a wrapper API within the DLL the VFS provides that spawns a temporary thread?)
I'm not discluding traditional messaging IPC, which could be another server exposing an interface with functions like SendMessage, ReceiveMessage, PeekMessage. Again, the overhead would be minimal since it'll be equivalent to making a virtual function call.
Re: Efficient IPC with Software Isolated Processes
Posted: Sat Oct 31, 2009 9:29 am
by Colonel Kernel
Synchronous IPC doesn't scale, and it's all messages under the hood anyway because you've got to get the parameters from one process to another.
The reason why IPC in Singularity is fast and scales really well is that it's zero-copy and asynchronous (non-blocking send, blocking receive). In order to be zero-copy and still guarantee isolation, the system has to ensure that only one process can refer to a block of memory at a time. In your ReadFile example, how would you prevent one process from modifying the other process' buffer? Without that guarantee, you don't have software isolation.
Re: Efficient IPC with Software Isolated Processes
Posted: Sat Oct 31, 2009 3:50 pm
by AndrewAPrice
Colonel Kernel wrote:In your ReadFile example, how would you prevent one process from modifying the other process' buffer? Without that guarantee, you don't have software isolation.
Just like in Singularity, pointers aren't allowed in 'safe' code, only references, which suits servers and applications programs fine. A program is only allowed to modify another process's buffer if that buffer has been passed to that program via a shared interface.
Re: Efficient IPC with Software Isolated Processes
Posted: Sat Oct 31, 2009 9:38 pm
by Colonel Kernel
MessiahAndrw wrote:Colonel Kernel wrote:In your ReadFile example, how would you prevent one process from modifying the other process' buffer? Without that guarantee, you don't have software isolation.
Just like in Singularity, pointers aren't allowed in 'safe' code, only references, which suits servers and applications programs fine. A program is only allowed to modify another process's buffer if that buffer has been passed to that program via a shared interface.
This isn't a pointer-versus-reference issue. You're allowing shared memory to be modified by concurrently running processes. You can do it, but it's no longer a sealed process model (in a sealed model, no process can affect the state of any other except by sending it messages). My point is only that Singularity does not allow this. Each block in the Exchange heap is owned by exactly one process at a time, and ownership is transferred via IPC. Read the Singularity papers for more details on why they did it this way. To summarize: allowing any kind of shared memory makes it difficult to design a completely dependable OS, which is the goal of Singularity (not performance).