Efficient IPC with Software Isolated Processes
- AndrewAPrice
- Member
- Posts: 2299
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Efficient IPC with Software Isolated Processes
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.
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.
My OS is Perception.
- gravaera
- Member
- Posts: 737
- Joined: Tue Jun 02, 2009 4:35 pm
- Location: Supporting the cause: Use \tabs to indent code. NOT \x20 spaces.
Re: Efficient IPC with Software Isolated Processes
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'.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 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
17:56 < sortie> Paging is called paging because you need to draw it on pages in your notebook to succeed at it.
- AndrewAPrice
- Member
- Posts: 2299
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: Efficient IPC with Software Isolated Processes
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: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
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:(If it is possible to really filter bytecode that efficiently with a decent performance throughput).
Our OS's have different goals. I wish to create a high performance microkernel system without the traditional overhead in multitasking systems.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.
My OS is Perception.
- NickJohnson
- Member
- Posts: 1249
- Joined: Tue Mar 24, 2009 8:11 pm
- Location: Sunnyvale, California
Re: Efficient IPC with Software Isolated Processes
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: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
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.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.
- Colonel Kernel
- Member
- Posts: 1437
- Joined: Tue Oct 17, 2006 6:06 pm
- Location: Vancouver, BC, Canada
- Contact:
Re: Efficient IPC with Software Isolated Processes
Read the Singularity papers and do what they do.
More specifically:
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:
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.
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.
Top three reasons why my OS project died:
- Too much overtime at work
- Got married
- My brain got stuck in an infinite loop while trying to design the memory manager
- AndrewAPrice
- Member
- Posts: 2299
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: Efficient IPC with Software Isolated Processes
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.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.
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);
}
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.
My OS is Perception.
- Colonel Kernel
- Member
- Posts: 1437
- Joined: Tue Oct 17, 2006 6:06 pm
- Location: Vancouver, BC, Canada
- Contact:
Re: Efficient IPC with Software Isolated Processes
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.
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.
Top three reasons why my OS project died:
- Too much overtime at work
- Got married
- My brain got stuck in an infinite loop while trying to design the memory manager
- AndrewAPrice
- Member
- Posts: 2299
- Joined: Mon Jun 05, 2006 11:00 pm
- Location: USA (and Australia)
Re: Efficient IPC with Software Isolated Processes
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.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.
My OS is Perception.
- Colonel Kernel
- Member
- Posts: 1437
- Joined: Tue Oct 17, 2006 6:06 pm
- Location: Vancouver, BC, Canada
- Contact:
Re: Efficient IPC with Software Isolated Processes
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).MessiahAndrw wrote: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.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.
Top three reasons why my OS project died:
- Too much overtime at work
- Got married
- My brain got stuck in an infinite loop while trying to design the memory manager