-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Polymorphic interfaces for resource creation and usage #377
Comments
Thank you for creating this issue and the information included is great! I've a few things to talk about, and I'm going to read this again and again and think if we need anything else for abstractions or API.
I'd think that this API is for both resource management and as a scheduling interface, so I'd modify the API slightly:
Similar confusion here,
My main problem with this API is that there is no "shared" key or namespace identifying the two communicating parties like we have I think this is a great start for a discussion, and I'm willing to meet any day to talk about these. We could perhaps set up a meeting for Tuesday (tomorrow?) or sometime this week to discuss this, maybe use the Wednesday (5-6) timeslot and talk? This is a good discussion to have especially because I think this could form a good foundation to the |
The main concerns about this design is a lack of coherency across abstractions.
We have two synchronization primitives: blockpoints and rcv capabilities. Can these be unified?
The event notification mechanisms are focused on channels. Can they accommodate locks?
Do we require a pairing between channels and threads? If not, how can rcvs be integrated? If so, then how do we allow events to span multiple channels? Does this require intermediate threads and multiplexing?
How does this all integrate with IPI rate-limiting and polling?
What is the synchronization hierarchy? Should locks be implemented with channels? A few options ("->" is a dependency, and "(a, b)" means that both "a" and "b" are parallel -- not dependent on each other):
Extra comments:
6.Decoupling memory allocation and creation might be a better design in this API. This simplifies the API.
This is a great issue to discuss I think, and we can meet to talk about it tomorrow(Tuesday). |
I added some thoughts above about the types of channels we need to support. The hardest part of this is going to be coordination between shared memory allocation and channels. Inspired by rust's channel's API, I think it would be good to separate sender's data-structure from receiver. I also now see it as inevitable that we'll have to separate between channels that are guaranteed-local to a component, and those that are not. Something like: typedef word_t chan_snd_t;
typedef word_t chan_rcv_t;
typedef word_t chan_t; // polymorphic representation of either of the previous
typedef enum {
CHAN_FLAG_SND = 1,
CHAN_FLAG_RCV = 2,
CHAN_FLAG_CHAN = 4, // channel to send channels
CHAN_FLAG_LOCAL = 8, // channel only accessed locally within this component
CHAN_FLAG_MP = 16 // possible multiple producers
} chan_flags_t;
int chan_create(unsigned int objsz, unsigned int nobjs, chan_flags_t flags, chan_snd_t *snd, chan_rcv_t *rcv);
chan_snd_t chan_snd_dup(chan_snd_t snd); // multisender channels
void chan_rcv_close(chan_rcv_t c);
void chan_snd_close(chan_snd_t c);
int chan_snd(chan_snd_t snd, void *obj);
int chan_rcv(chan_rcv_t rcv, void *obj);
// non-blocking variants
int chan_snd_nb(chan_snd_t snd, void *obj);
int chan_rcv_nb(chan_rcv_t rcv, void *obj);
typedef word_t chanchan_snd_t;
typedef word_t chanchan_rcv_t;
// flags determine if send or receive channels are sent through this channel for channels
int chanchan_create(unsigned int nchan, chan_flags_t flags, chanchan_snd_t *snd, chanchan_rcv_t *rcv);
int chanchan_snd(chanchan_snd_t c, chan_t to_snd);
int chanchan_rcv(chanchan_rcv_t c, chan_t *to_rcv);
void chanchan_snd_close(chan_snd_t snd);
void chanchan_rcv_close(chan_rcv_t rcv);
// A version of reinterpret_cast...does type checking an only casts if the proper flags are set.
int chan_to_snd(chan_t c, chan_snd_t *snd); // cast the received channel to a snd (if the types match)
int chan_to_rcv(chan_t c, chan_rcv_t *rcv);
int chan_to_chan_snd(chan_t c, chanchan_snd_t *snd);
int chan_to_chan_rcv(chan_t c, chanchan_rcv_t *rcv); Instead of the |
This is a work in progress, and mainly meant to start a thought process.
The low-level APIs of the system should be polymorphic so that they can be implemented in one of N ways, selected by the context (see #353). Thus, we need a set of APIs that work in each of the contexts. This issue is meant to start the discussion about which resources we're talking about, which contexts they should span across, and the shape of those context's APIs.
The resources include at least:
Implementations
We'd like to support at least a few different implementations of each of these including:
This is a challenge as the API must be equally applicable to scenarios that include multiple components as well as library-based solutions. Correspondingly, the main challenges involve namespacing, and polymorphic representation.
Threads
Straightforward thread manipulation functions:
A subtle problem with this API is that it both creates the thread, and executes it in a single function. This might be OK in many cases, but in others, it is not appropriate. For example, perhaps we don't want to run the thread until its parameters are set. Perhaps we want to create a clone of a thread for a checkpoint, and don't want to run it yet (see the component API below).
Synchronization
A lower-level API for coordinating synchronization between threads is blockpoints. These simply enable threads to block waiting on a specific blockpoint, and to wakeup those threads that have previously blocked (either one, or all).
I believe this API enables at least three separate libraries for locking and synchronization. First, a blockpoint abstraction to go with the low-level support that abstracts away the
epoch
.Of course, locks. Likely need to rethink this and make additions in light of multicore.
Condition variables, though not that desirable, are common. For backwards compatibility (should be updated to mimic POSIX):
Channels
We want a channel interface to enable controlled asynchronous communication between different components. @phanikishoreg has been using this in Chaos to communicate between subsystems. Each channel has two major aspects:
rcv
/asnd
pair in each component.Ideally, they would be uni-directional, and not enable information to pass the opposite direction. However, this uni-directional information flow isn't possible if we have shared memory due to cache timing channels. Thus, the interface here must allow shared memory, or invocations to another component to mediate data-transfer.
Further, it should allow channels to be created and used within a single component, and created across multiple other components.
In addition to communication of binary data, channels can be used to pass system objects, such as other channels between component end-points. The following example shows an expanded API that isn't very inspired. Additional examples might be used to pass other resources (components, memory, threads, etc...).
We'll likely need modifiers on channels to allow synchronous communication with amortization of interaction costs (like
call
andreply_and_wait
in L4), and the ability to decouple the channel's shared memory from the event notification.Some notes:
unsigned int
s here so that they have a consistent size if we have to go through asinv
(as opposed tosize_t
, for example).chan_t
as an opaque type of word size so that it can be a pointer, a capability, or an opaque id, depending on if it is provided by a library, a specificsinv
capability, or an offset into another component's abstraction's namespace.chan_create
implicitly creates arcv
andsnd
, and the receive is processed by a new thread running functionf
. Are we OK with a callback API instead where the function is called whenever an event triggers, and we don't define in which thread context it is invoked?chan_*_retrieve
enables a component to get all of the "initial" channels (both send and receive) gifted to it on bootup.chan_init_snder
returns both the channel identifier, and the object size, andchan_init_rcver
creates a thread, and passes itdata
andobjsz
.chan_poll
returns if there is data available to read off of the channel.Some problems and further work TODO:
rcv
from a channel should only be performed in the thread created for that channel.Event Notification
The ability to get a notification if any of the channels of interest become available (non-empty, to
snd
to, or if they have data in them torcv
from). This API centers aroundevtgrp_wait
.Concerns and TODO
The main concerns about this design is a lack of coherency across abstractions.
rcv
capabilities. Can these be unified?rcv
s be integrated? If so, then how do we allow events to span multiple channels? Does this require intermediate threads and multiplexing?Memory Allocation and Sharing
I'm not sure what the API should be here. On the one hand, we have the
cbuf
API that allows very efficient dynamic memory sharing, and on the other, we have eos which has a static region of shared memory, tracked efficiently with the equivalent of DMA ring buffers. For the former, see thecbuf
API.EOS-like channels. The API for this can be either slightly modified versions of channels, or hidden completely by the channel API, if we add some protection information (who should be able to read which memory).
However, we need to have an API for decoupling the async properties of the default implementation from the shared memory (channel-like) properties. This leads me to believe we want channels that provide synchronous and bi-directional communication, likely with
call
andreply_and_wait
amortizing calls.General shared memory. Independent of communication, we want the ability to map pages. This might be in response to page-faults, to create components, or simply to alias text segments. The core question for this is one of access control: which components should be allowed to map their pages into other components. At the lowest-level, we, of course, have the resource-table-based nested access control. We want the API to apply in that scenario, but we'd also like the API to be able to backed by invocations to the
capmgr
.Device memory. How can we integrate device memory into this system? How can the system management abstractions be portable across x86 and arm?
Notes on the co-design of channels, event notification, memory management, and scheduling
Channels are the API where the entire world of resource management comes to play. They can span from self-contained, simple and fast (spsc, async-only, pre-defined size message passing) to complex, bringing in most system mechanisms and policies (zero-copy, possibly-synchronous, with event notification, without redundant events). Given that we're taking the cost of scheduling to zero, we need to very intentionally plan all of this to maintain end-to-end performance.
This section is here only to start a discussion about this.
I believe that all channels should share a number of properties:
A few different channel implementations that we'd like to accommodate:
head
/tail
, we can use two bits in the message. In the naive case, this expands the message by a word. More scalable, but makes memory layout assumptions.Note that some of these are quite composable. The rings in the last can be implemented using the first.
Components
Of course, the management of components is foremost in the system as the isolation properties are a significant portion of our system's value. We'll likely want to expand on this, but to start:
This should be able to support
fork
semantics (paired with a thread function to modify thread register state), component creation as we currently define it, and checkpoint-restore as in EOS. It should have implementation-specific options for library-based efficient implementations, and cross-library, process-manager implementations.The text was updated successfully, but these errors were encountered: