Wow, I go to sleep for the night and there are 7 replies when I wake up
Here we go:
@candy, concerning the 'advancing to the next operation': it could be easier to deal with considering that there should be only *calls* here (and thus presumabely only a few opcodes like CALL <near, absolute>). Any other opcode can be handled like a 'real' fault as they fail to observe the protocol for IPC invokation.
I was figuring that this would only be valid for CALL instructions, and that data would not be shared between processes. This is for style reasons (violating encapsulation, like exposing a class's data members in an OO language), and for simplicity, because the MOV instruction and all its variants would be nearly impossible to support (as Brendan said)
You clobber the address space (as you say yourself) where you can define your own with for example syscall, using eax as the function number. This allows you 4G functions without clobbering a byte of address space.
You're right, this is a disadvantage of this technique. But, I figure that most programs would only have a few hundred to a few thousand (at most) imports, so this would only take up one or two pages. Thus, it shouldn't be much of a problem.
Using the "book" document class (thus introducing 'chapter X' on a new page while a single \section would have been enough makes the document larger than it needs to be ...
Actually, it's the "report" class, but yeah, I'll switch to "article" in future versions of the document.
Pushing the result(s) to the stack of the calling process is awkward, since you'd need a special wrapper for that kind of access, you're messing with the stack where the C compiler doesn't expect it.
How do you pass arguments to a page fault? I can only think of pushing them on the stack, but that's just as hackish as trying to pop the result off the stack.
How about this: In an export, the function lists how many bytes the arguments take up. This could be calculated from the function definitions by the same tool that writes the imports headers and whatnot. When it's called, the kernel initializes the process' stack, copying that data into the top of the stack. Upon return, the kernel copies the data from the start of the stack up to the callee's %esp (the return value), back to the caller's stack(overwriting the arguments) and increments the caller's %esp accordingly. Hackish, yes, but it should work just fine.
The issue of calling conventions arises here, but as long as the two ends can agree on a convention (or maybe, specify that all functions must use one), and the arguments are passed on the stack (no registers!), this shouldn't a problem.
@gnome: despite the mechanism is interresting, you might wish to reconsider the concept of "exporting functions" to the world. Try to model the use of IPC: you may have process that expose "objects" to the world (like a window, a network connection, a file handle or things alike), but you usually want to avoid a remote process to tell you where in your own memory things stands, which is the great difference between RPC and LPC ...
Hmm... that's a valid point about exporting objects vs. exporting functions.
About the remote process telling you what's in your own memory, I don't follow. The address that you call for any specific function is determined by the offset of that function in the process' import table. In the export table, a process lists the actual address of the function in its own address space, but that is only exposed to the kernel.
Could you clarify?
As a concluding thing, the asynchronous syscalls is a good idea I think. You might want to write a paper on that, since it's a thing I haven't heard about up to now. It is very close to my unreleased tcall API, although tcall is intended for user-level no-wait functions.
;D
Although it's a few years away, I'll have the option of writing a thesis in the 4th year of my CS program, and I might do it on this whole system. Failing that, the asynchronous calls might work too. We'll see what happens.
To avoid the re-entrancy issues you could put the call into a buffer or queue, so that the called function does one call at a time. When it completes one call it'd try to get the next call from the buffer/queue, and it could block until another call is made when no calls are in the buffer/queue.
Good idea. I suppose I could add another flag in the export list to control this behaviour. If a process wants to allow reentrancy, it should be allowed to. By default though, they're non-reentrant.
Thanks for the input everyone