TODO: * Keep track of number of allocated capabilities or otherwise enable to check if a bucket is still in use!!! (so at least a per-client count is required). * Implement bucket-inhibit correctly. !!! Also see: * Decide if it is needed to allow capability allocation outside of RPCs (as return arguments). In that case, allocation must be blocked while it is checked if no extinct users exists - otherwise inhibiting RPCs is good enough. * Verify all the bucket/client stuff after rewrite. * Do we need to support soft references? * Extend table and hash interface to reclaim resources without reinitialization. !! * table.h/table.c should take alignment. An introduction to the capability system, server side. ------------------------------------------------------ A server provides services to its clients. In a capability system the right to access a given service is called a capability. This right is held by a client. It was previously granted to the client by the server. The client can then make use of this right, cease it, or transfer (copy) it to a new client. The capability system is object oriented. This means that the services granted by the server are represented by remote procedure calls (RPCs) invoked on server-side objects. A capability thus represents the right to invoke an (arbitrary) RPC on any given object. The Hurd capability system provides a flexible, yet efficient way, for servers to implement services for different types of objects to many users. The server-side objects (capability objects) are organized in capability classes. All capability objects in one class have the same basic type. Of course, the implementation of the server is free to make further distinctions among the objects in one class. But the implementation of the capability library makes certain assumptions about classes, and some operations always affect all objects in one class. Furthermore, all objects in one class share certain ressources. Thus, a capability class is an important organizational structure. For example, all open files in a file server can be implemented as one capability class. Another capability class could be used for all filesystem control capabilities. Capability Objects ------------------ Any server-side object you want to receive RPCs on is represented by a capability object. Clients are then granted access to capability objects, which means that they are allowed to invoke RPCs on such objects (see "Capabilities"). All capability objects have the same basic storage size, use the same constructors and destructors, and are cached in the same slab allocator. Capability objects have their own lock and reference counter. The lock and reference counter are embedded at the start of your own definition of what a capability object should contain: struct my_cap { struct hurd_cap_obj obj; int my_own_data; ... }; Capability objects are cached objects. They are cached in the slab allocator provided by libhurd-slab. This improves processor cache usage and allows pre-allocation, and makes it possible to keep some state even across object life time. The life-time of a capability object, and the meaning of the various constructors and destructors, can be seen in the following diagram: 1. Object is constructed in the cache OBJ_INIT 2.1. Object is instantiated and removed from the free list OBJ_ALLOC 2.2. Object is deallocated and put back on the free list OBJ_REINIT 3. Object is destroyed and removed from the cache OBJ_DESTROY Note that step 2 can occur several times, or not at all. This is the state diagram for each object: START ==(1.)== OBJ_INIT --(3.)--> OBJ_DESTROY = END | ^ | | (2.1.) (3.) | | v | OBJ_ALLOC -(2.2.)--> OBJ_REINIT ^ | | | +-------(2.1.)-------+ Capability objects are constructed and initialized in bursts whenever a new slab page is allocated for the cache. For this purpose, the OBJ_INIT callback is invoked. If the object is used, further per-instantiation initialization can be performed by OBJ_ALLOC. Each time this happens, the OBJ_REINIT callback is invoked when the object becomes deallocated and is returned to the cache. At the end of each objects lifetime, when the cache page is destroyed, for example due to memory pressure, or because the capability class is destroyed, the destructor callback OBJ_DESTROY is called. OBJ_ALLOC is provided because some resources may not be suitable for caching. In particular, OBJ_REINIT must not fail, so any resources that can not be safely (ie without errors) reverted to their initialized state are not suitable for caching and must be allocated with OBJ_ALLOC and destroyed with OBJ_REINIT. After having defined your own capability objects, you can create a capability class from these objects. A capability class is a complex structure that allows to grant access to capability objects to users, and process the incoming RPCs. Capability Classes ------------------ Capability classes require a capability object definition (via storage size and constructor/destructor callbacks), and a demuxer for incoming RPC messages on capability objects from this class. After creating a class, you will usually want to start a manager thread and call one of the RPC manage functions (multi-threaded or single-threaded). The manage function starts processing incoming RPCs immediately. You can provide a timeout or let it run indefinitely. If you specify a timeout, the manage function will exit if there are no active client connections for a certain time. You can also inhibit all RPCs on a class, from the outside or from an RPC handler. This will cancel all pending operations (except the calling one), and prevents any more messages from being processed. Incoming RPCs are blocked (FIXME: except interrupt_rpc). To prevent DoS attacks, only one RPC per client thread at any time is allowed. Clients ------- Each client gets a capability ID name space. In this name space, references for capability objects are kept. If a capability object gets revoked, the references become stale. Server Loop ----------- FIXME: Should inhibit class block the server manager thread, or only worker threads? The former is resource lighter, the other allows to process interrupt messages to interrupt the operation blocking the class. Worker Thread Operation ----------------------- 0. Lock cap_class for most of the following. If necessary, first block until class state is green. 1. Check cap_class->client_threads if that client is already in an RPC. If yes, drop the message. 2. Is this a proper RPC? If not, it might be an initial handshake request. If it is an initial handshake request, lookup the task ID in cap_class->clients_reverse, and if it doesn't exist, add it and return. Otherwise proceed. 3. Lookup the provided client id in the table cap_class->clients (range checked), adding a reference. If not found, drop the message. 4. Add yourself to cap_class->pending_rpcs. 5. Unlock the cap_class, and lock the client. If necessary, first block until client state is green. 6. Lookup the capability id, adding an internal reference. If not found, drop the message. 7. FIXME: Handle external references and reference container transactions here. Otherwise, proceed: 8. Add ourselves to client->pending_rpcs. Unlock the client, lock the capability object. If necessary, first block until cap obj state is green, 9. Add yourself to cap_obj->pending_rpcs. 10. Process RPC. 11. FIXME: Reverse the whole funky business.