Page 1 of 1

Ah the lovely amount of objects

Posted: Sun May 14, 2006 4:17 pm
by mystran
So here we go, I'm settle on the following for ipc handle management for now:

There are objects, that can be sent message, which are really sent to thread's messagequeue, and object pointer becomes part of the message, allowing dispatch. You are supposed to have an object like this:

Code: Select all

typedef struct myObj_S myObj;
struct myObj_S {
   void dispatch(myObj*, MWORD op, MWORD w1, MWORD w2);
   /* add below whatever other data you need */
};
If you don't use DispatchMessage() which is part of the messaging API, then it's just a userdata pointer for you.

Since sending a message involves an object, and a thread, the latter of which might die (taking the former with it, ofcourse), there are proxies, that are used to resolve the object and thread, and fail if the thread has died.

I don't want to keep dead thread in memory though, neither I want to keep a list of proxies pointing a particular thread. So there's a "lifestone" for each thread. This is a small object, with the sole purpose of being left behind after thread death, until the last proxy pointing to that thread is closed.

Finally, to find a proxy, userspace thread sends an handleID, which is simply an index into a per-process handle-table, which contains pointers to proxies. Since there can be several handles to single object, proxy is necessary to allow notifying the owning thread when the last handle is destroyed (allowing it to manage it's own resources like memory).

Ignoring the actual objects (which are not really involved in low-level messaging) I have:

- lifestones (one created per thread)
- threads (which have message queues)
- proxies (which actually represent certain objects)
- handles (which are used to check access)

This setup allows me to:

- Tell an object's owner when last handle is destroyed.
- Free the (rather large) thread structure when thread dies.
- Figure out if some thread has died.
- Not keep any reverse lookup tables, lists, or anything.
- Avoid security hazards from reusing thread numbers or such.

Limitations:

- If for some reason "object free" message can't be sent (say, full queue) then the object owner might accumulate garbage. Might try solving this another day.

- A small lifestone object stays in memory, until last handle is freed. Since at any given time there are finite number of threads (memory based, calculated limit), each process has at least one thread, and each process has finite number of handles (by design), there will only be finite number of lifestones in memory, but that limit can be quite large.

So to send a message, the system:

1) checks process table if given handleID is valid (fail if not)
2) follows the handle to find a proxy
3) adds the "object" from proxy to message
4) follows a pointer from proxy to a lifestone
5) checks if the stone's thread is alive (fail if not)
6) follows a pointer from lifestone to thread
7) put the message in thread's message queue

Simple, isn't it. :P

PS. Above ignores any handle/memory copies possibly made.

I'm not surprised at all that most systems try to get away with a security model more coarse-grained than a full object-capability system.

Re:Ah the lovely amount of objects

Posted: Sun May 14, 2006 6:05 pm
by mystran
Actually, the whole lifestone deal rules, because it allows another nice things:

I'm going to put a pointer to parent's lifestone too (or should I start calling it gravestone instead, i don't know).

So if thread's parent dies, I can go up the inheritance tree without having a list of children in kernel, or keeping the (rather large) parent thread structure around a zombie.

---

CS terminology is funny: "To avoid zombies, Neon keeps all the data needed after death in a separate gravestone."

:D

Re:Ah the lovely amount of objects

Posted: Mon May 15, 2006 4:53 am
by distantvoices
A "Lifestone" isn't equal to a Task Control Block, is it?

Just to make sure I get your idea right.

Re:Ah the lovely amount of objects

Posted: Mon May 15, 2006 5:30 am
by mystran
Lifestone is much less than you'd expect from a TCB.

There is a struct plainly called "thread" for each thread, which contains most of the information that one would have in a TCB. In fact, since thread's kernel stack is part of the object, it is currently allocated as about 8192bytes.

This thread-object is directly used for almost everything, and once it dies, very little information about the thread is relevant anymore. But it also contains a pointer to this "lifestone" struct, which is currently defined more or less as below:

Code: Select all

typedef struct lifestone_S lifestone;
struct lifestone_S {
    thread * owner; // owner of this stone, if one is alive
    lifestone * parent; // refer to parent's stone, 'cos parent might die first
    unsigned long refcount; // reference count, not including owner
};
When a thread dies, it sets owner=0 in it's lifestone (and frees the lifestone if it has no references, ofcourse). Otherwise it leaves the lifestone in memory, and when refcount is decremented to 0 (owner being 0), then we know the thread can be forgotten once and for all.

In short, lifestone contains only information that one might need long after the thread has died (mainly the fact that it's dead). It is itself destroyed when nobody remembers the thread anymore.

Re:Ah the lovely amount of objects

Posted: Wed May 17, 2006 5:58 pm
by mystran
Decided that it's better to get rid of proxies, and require explicit copying instead, so that if you send a handle to someone, you lose the handle (assuming send succeeds).

Every object should implement interface "unknown" which can easy contain "copy" in addition to "query" which it already has.

That way handles can contain <stone,object> directly, and I save one indirection, need less floating objects (handles are in tables), and make it much easier to handle replies, as they can be oneshot handles without having to allocate memory.

It seems small, but it removes quite a few special cases.