Code: Select all
Wow... that's a tough one. There are probably a million ways. The most obvious to me is to pause the thread, modify it's EIP from the stack, copy the code segment over somewhere and resume.
Argh! that means that either (a) you have to assume that there's not already some code or a file mmap at that position in the destination address space or (b) ALL code is compiled position-independent.
Code: Select all
Well, threads are lighter weight than processes. Having one for each entry point can make sense, having them for event handling can make sense, or dedicating threads to different tasks that you want to do at the same time makes sense as long as you don't have to worry about security.
You have a point. Especially with my starvation talk I was assuming a scheduler like linux, which does not pre-empt threads. Obviously because you're making your own OS, you'd have thread pre-empting.
That sounds most dangerous. I'll assume it's not. Perchance you can explain how though. I don't see it.
I don't understand why you think it's dangerous, but I'll have a shot at an example here. Note I don't have my code with me (at work) so it may be a bit off.
Code: Select all
class C : RemoteProcedureCall
{
RPC_DEFINE1(0, int, C, myMethod, int); // The 0 should just be any unique integer
public:
C()
{
RPC_REGISTER(myMethod);
}
int myMethod(int param)
{
RPC_SYNC(myMethod, param);
// Do stuff
return 5;
}
};
// Some code somewhere else
C *myC = new myC();
myC->myMethod(3); // This will be synchronous and remote.
So, the RPC_REGISTER, DEFINE, SYNC calls are macros that look a little bit like:
Code: Select all
#define RPC_DEFINE1(idx, returnValue, className, func, param) \
const int __rpcconst__##func = idx; \
static returnValue __rpccall__##func(className *obj, param p1) \
{ \
return obj->func(p1); \
}
#define RPC_REGISTER(func) \
processManager.getProcess()->registerRpcHandler(__rpcconst__##func, __rpccall__##func);
#define RPC_SYNC(func, param) \
if (getpid() != targetPid) \
{ \
return rpcSynchronous(__rpcconst__##func, (void*)param); \
}
And the RemoteProcedureCall class looks like
Code: Select all
class RemoteProcedureCall
{
RemoteProcedureCall()
{
targetPid = getpid(); // The process that created us becomes our "owner" or target.
}
protected:
int targetPid;
template <BLAH LOTS OF TEMPLATE PARAMS>
R rpcSynchronous(Obj, Func, ..........)
{
.....
}
}
The rpcSynchronous call is heavily templated to make it integrate seamlessly. I couldnt remember/be bothered to work out what parameters go where.
It just fills out a RpcCall struct with the object, func ID and parameter (as void*), and passes it to the process manager, who adds it to the target process' queue.
Did that sort of explain my system?
It's gone through quite a few changes - originally I was using c++ pointer-to-member functions, but they do NOT play nicely across address spaces. More correctly, they do not play nicely when there are 2 different class definitions. (I would have one class definition with all member functions filled out etc, and another which would just be a declaration, exposing the interface of the class to others but not the implementation. Essentially every function in this class definition would just be
- it would assume you are calling from another address space, and didn't know the definition of the functions. (if you did, you wouldn't have to use IPC in the first place!).
Anyway, this didn't work because GCC created a different vtable for each class definition and that caused pointer-to-member functions to balls up big time. So I came up with this method. It works very well in my os, and it's pretty speedy, too.
Is that what you were imagining avarok?
EDIT: I should also mention that the vtable malarky is the reason every function has a static wrapper - so it can be referenced as a void* and not a pointer-to-member.
JamesM