diff --git a/doc/dev/channels.md b/doc/dev/channels.md new file mode 100644 index 0000000..61b9a89 --- /dev/null +++ b/doc/dev/channels.md @@ -0,0 +1,63 @@ +# Channels + +As the emulation progresses, information is written in the PRV trace to record +the new states. The emulator has specific mechanism to handle the output of new +states in the PRV trace via channels. A channel stores a value that represents +an state at a given point in time and typically corresponds to the value that +will be observed in the Paraver timeline. + +!!! Note + + The emulator receives input *events* and performs *state* + transitions which are written into the Paraver trace. + +Channels become dirty when a new value is written to them. No other +modifications are allowed by default until the channel is flushed by calling +`chan_flush()`. This behavior can be controlled with the `CHAN_DIRTY_WRITE` +property. By default, a duplicated value cannot be written into a channel, +unless the `CHAN_DUPLICATES` property is set. + +A channel has an unique name used to identify the channel when debugging and +also to create derived channels. The name can be set following the printf style +when calling `chan_init()`. + +## Types + +There are two types of channels, **single** and **stack**. The type single only +stores one value which is updated by `chan_set()`. The stack type allows the +channel to record multiple values in a stack by using `chan_push()` and +`chan_pop()`. The value of the channels is the topmost in the stack (the last +pushed). + +Notice that the `chan_pop()` function uses the same value being pop()'ed as +argument. The function checks that the stack contains the expected value, +forcing the emulator to always receive a matching pair of push and pop values. + +After the channel initialization, the value of the channels is null. A channel +with an empty stack also returns the value null when being read. + +## Data value + +The data type of a channel is handled by the `value` structure. This structure +can store a null value, a 64 bit integer or a double. Any data type can be +written to a channel and multiple data types can be stored in the stack. + +!!! Note + + For now only null and int64 types are allowed when the channel is connected + to a Paraver trace. + +## Properties + +Channels have properties that can be set by `chan_prop_set()`. Setting the +`CHAN_DIRTY_WRITE` property to true allows a channel to modify its value while +being in the dirty state. The `CHAN_DUPLICATES` property determines is a channel +can write the same value to the channel twice. + +## Callback + +A unique function can be set to each channel which will be called once a channel +becomes dirty with `chan_set_dirty_cb()`. This callback will be called before +`chan_set()`, `chan_push()` or `chan_pop()` returns. The [patch +bay](../patchbay) uses this callback to detect when a channel is modified an run +other callbacks. diff --git a/doc/dev/extend.md b/doc/dev/extend.md new file mode 100644 index 0000000..c0a8dd8 --- /dev/null +++ b/doc/dev/extend.md @@ -0,0 +1,26 @@ +# Extend + +The extend mechanism allows a structure to be extended by a model. It works by +placing an array of pointers indexed by the model identifier (char) inside the +structure. Most of the structures of the emulator have a `ext` member, allowing +them to be extended. + +Models are forbidden to access information from other models than their own. + +The function `void extend_set(struct extend *ext, int id, void *ctx)` stores the +ctx pointer inside the extend structure for the model with the given id. + +Use `void *extend_get(struct extend *ext, int id)` to retrieve its value. + +A helper macro `EXT(st, m)` directly attempts to find the member `ext` in the +structure st, and return the pointer of the model `m`. + +Here is an example where Nanos6 stores its own CPU information: + + struct cpu *syscpu; + struct nanos6_cpu *cpu; + + extend_set(&syscpu->ext, '6', cpu); + + cpu = extend_get(&syscpu->ext, '6'); + cpu = EXT(syscpu, '6'); /* same */ diff --git a/doc/dev/fig/bay.jpg b/doc/dev/fig/bay.jpg new file mode 100644 index 0000000..8ac47ca Binary files /dev/null and b/doc/dev/fig/bay.jpg differ diff --git a/doc/dev/fig/mux.pdf b/doc/dev/fig/mux.pdf new file mode 100644 index 0000000..8a6e88b Binary files /dev/null and b/doc/dev/fig/mux.pdf differ diff --git a/doc/dev/fig/mux.svg b/doc/dev/fig/mux.svg new file mode 100644 index 0000000..8f272f7 --- /dev/null +++ b/doc/dev/fig/mux.svg @@ -0,0 +1,634 @@ + + + + + + + + + + + + + + + + + + + nanos6.thread0.subsystem + + thread0.state + mux0 + + nanos6.thread0.subsystem.run + + + + + + + + cpu0.th_running + + mux1 + cpu0 + cpu1 + + + + + cpu2 + cpu3 + + + + thread0 + thread1 + + thread2 + thread3 + + CPU: Nanos6 subsystem of the RUNNING thread + Thread: Nanos6 subsystem of the ACTIVE thread + + + thread0.state + mux2 + nanos6.thread0.subsystem.act + + + thread0 + thread1 + + thread2 + thread3 + + Thread: Thread state + + thread0.state + thread1.state + + + + + + + + thread2.state + thread3.state + + + + + nanos6.cpu0.subsystem.run + + diff --git a/doc/developer-guide/index.md b/doc/dev/index.md similarity index 99% rename from doc/developer-guide/index.md rename to doc/dev/index.md index ea8a890..1ff6519 100644 --- a/doc/developer-guide/index.md +++ b/doc/dev/index.md @@ -1,4 +1,4 @@ -# Developer guide +# Developer guidelines To contribute to the ovni project, please read carefully this guide and follow the guidelines to have your contribution accepted in the project. diff --git a/doc/dev/model.md b/doc/dev/model.md new file mode 100644 index 0000000..5d7ccc2 --- /dev/null +++ b/doc/dev/model.md @@ -0,0 +1,48 @@ +# Model + +The emulator is designed to support multiple models simultaneously. Each model +must be implemented by following the `model_spec` interface, which defines the +functions that will be called by the emulation when that model is enabled. + +Each model must have a unique character that identifies the model, and will be +used to filter the events in the MCV (model, category, value) tuple of the +input event. + +Unless otherwise stated, all model functions return 0 on success or -1 on error. + +## Probe + +The probe function determines if the model should be enabled or not, based on +the information read from the metadata of the traces. No events are available +yet. Returns -1 on error, 0 if the model must be enabled or 1 if not. + +If the model is not enabled, no other function will be called. + +## Create + +The create function is called for each enabled model to allow them to allocate +all the required structures to perform the emulation using the +[extend](../extend) mechanism. All the required channels must be created and +registered in the patch bay in this function, so other models can found them in +the next stage. + +## Connect + +In the connect function, the channels and multiplexers are connected, as all the +channels of all models have already been registered in the patch bay. The +channels must also be connected to the output traces to record the states. + +## Event + +The event function is called only if the processed input event matches the model +character. The function must return 0 on success or -1 on error. If an error +is returned, the emulator will print some information about the current event and +proceed to call the last stage so the traces can be closed and flushed to disk +before stopping the emulation process. + +## Finish + +This function is called when there are no more events to be processed or when +the emulator has encountered a problem processing on event and needs to abort +the emulation process. The output traces must be closed to write all the buffers +into disk. Additional allocated structures may now be freed. diff --git a/doc/dev/mux.md b/doc/dev/mux.md new file mode 100644 index 0000000..28847f8 --- /dev/null +++ b/doc/dev/mux.md @@ -0,0 +1,47 @@ +# Mux + +The emulator provides a mechanism to interconnect [channels](../channels) in a +similar way as an [analog +multiplexer](https://en.wikipedia.org/wiki/Multiplexer) by using the `mux` +module. + +Multiplexers or muxers have one output channel, one select channel and a +variable number of input channels (including zero). Each input channel is +registered in the multiplexer by using a key with a given value. In the normal +operation of a mux, the value of the select channel is used to find an input +with a matching key. When there is no match, the output value is set to null. + +Multiplexers can be configured to execute a custom select function, which will +take the value of the select channel and determine which input should be +selected. This allows a multiplexer to act as a filter too. + +## Tracking + +The typical use of multiplexers is to implement the tracking modes of channels. +As an example, the following diagram shows three multiplexers used to implement +the subsystem view of [Nanos6](../nanos6): + +![Patch bay](fig/mux.svg) + +The first mux0 uses the thread state channel to filter the value of the +subsystem channel and only pass it to the output when the thread is in the +Running state (green). Then the filtered subsystem channel is connected to an +input of a second mux (mux1) which selects the current input of the thread +running in the CPU0. The output *nanos6.cpu.subsystem.run* is then connected to +the Paraver timeline in the row corresponding to the CPU0, which shows the +subsystem of the currently running thread. + +The Nanos6 subsystem channel is also connected to the mux2, which forwards the +value to the output only when the thread state is Active. The output is directly +connected to the Paraver row assigned to that thread. This channels shows the +subsystem of the thread by only when is active (not paused nor dead). You can +see that the subsystem is shown in the thread0 at the current time (red dotted +line) when in the CPU0 the subsystem has been hidden when the thread stops being +in the Running state (yellow). + +Multiplexers allow models to interact with each other in a controlled way. In +the example, the blue channel (*nanos6.thread0.subsystem*) is directly modified by +the Nanos6 model when a new event is received. While the red channels are +controled by the ovni model. The rest of the channels are automatically updated +in the propagation phase of the [bay](../patchbay) allowing the ovni model to +modify the Nanos6 Paraver view of the subsystems. diff --git a/doc/dev/paraver.md b/doc/dev/paraver.md new file mode 100644 index 0000000..fc0f7ca --- /dev/null +++ b/doc/dev/paraver.md @@ -0,0 +1,23 @@ +# Paraver trace + +Paraver traces are handled by the top level module `pvt` (Paraver trace), which +in turn uses the `prv`, `pcf` and `prf` to control the trace events, type +definitions and row names (in that order). + +Traces must be initialized with the number of rows and a name by calling +`pvt_open()`. + +The emulation time must be updated with `pvt_advance()` prior to emitting any +event, so they get the update clock in the trace. + +## Connecting channels + +A channel can be connected to each row in a trace with `prv_register()`, so the +new values of the channel get written in the trace. Only null and int64 data +values are supported for now. + +Duplicated values cause an error by default, unless the channel is registered +with the flag `PRV_DUP`. + +The emission phase is controlled by the [patch bay](../patchbay) and runs all +the emit callbacks at once for all dirty channels. diff --git a/doc/dev/patchbay.md b/doc/dev/patchbay.md new file mode 100644 index 0000000..dc44d93 --- /dev/null +++ b/doc/dev/patchbay.md @@ -0,0 +1,39 @@ +# Patch bay + +The patch bay (or simply bay) allows [channels](../channels/) to be registered +with their name so they are visible to all parts of the emulator and provides a +way to run callbacks when the channels update their values. + +The name follows from the [patch bay](https://en.wikipedia.org/wiki/Patch_panel) +used to connect audio channels: + +![Patch bay](fig/bay.jpg) + +## Registering channels + +Channels are registered with `bay_register()`, using the channel name as a +unique identifier. It is an error to register multiple channels with the same +name. + +The register step sets the channel callback function, so the bay can detect +which channel become dirty and act upon them. + +## Callbacks + +After registering a channel in the bay, multiple callbacks can be registered +with `bay_add_cb()` and will be called in same order they were added. + +The callbacks are executed in the propagation phase, when `bay_propagate()` is +called. There are two types of callbacks: *dirty* and *emit*. + +All dirty callbacks are called first, for all the channels that are dirty. +Executing the dirty callbacks may cause other channels to become dirty too, +which will be added to the list of dirty channels. Channels that are already +dirty cannot be modified, so we prevent an infinite updating loop. + +Then the emit callbacks are called for each dirty channel in the same way. The +emit callbacks are generally used to write the values of the channels in the +output traces. These callbacks cannot cause any new channel to become dirty. + +Finally, all channels are flushed by calling `chan_flush()`, clearing the dirty +flag and leaving them ready for the next input event. diff --git a/doc/emulation/channels.md b/doc/emulation/channels.md deleted file mode 100644 index b6a29f0..0000000 --- a/doc/emulation/channels.md +++ /dev/null @@ -1,170 +0,0 @@ -# Channels - -As the emulation progresses, information is written in the PRV trace to -record the new states. The emulator has specific mechanism to handle the -output of new states in the PRV trace via channels. A channel stores an -integer that represents an state at a given point in time and -corresponds to the value that will be observed in the Paraver timeline. - -!!! Note - - In general, the emulator receives events, then performs a state - transition and the new state (or states) are written into the - PRV file. - -There are two classes of channels: CPU and thread channels. Both CPU and -threads have the same fixed number of channels, given by the enumeration -`enum chan`. - -For example the `CHAN_OVNI_STATE` of the thread stores the execution -state of the thread (running, paused ...). Whereas, the CPU channel -`CHAN_OVNI_NRTHREADS` records how many running threads a given CPU has. - -The channels are used in the following way: - -- In the "pre" phase, the emulator modifies the state of the emulator - based on the new event. The channels are then updated accordingly in - this phase, for example when a thread goes from running to paused it - must update the `CHAN_OVNI_STATE` channel of the thread by also the - `CHAN_OVNI_NRTHREADS` channel of the CPU. - -- In the "emit" phase, the emulator calls the `chan_emit()` method on - those channels that have been modified. Those have the dirty attribute - set to 1. - -- The optional "post" phase is used to perform some operations before - the next event is loaded, but is not commonly used. - -Then the emulator then loads the next event and repeats the process -again. - -## Disabling and enabling channels - -Some channels provide information that only makes sense in some -conditions. For example, the CPU channel `CHAN_OVNI_TID` tracks the TID -of the thread currently running in the CPU. When there is no thread -running or there are multiple threads running in the same CPU, this -channel cannot output valid information. - -For those cases, the channels can be enabled or disabled as to only -provide information when it is necessary. When a channel is disabled, it -will emit the value stored in `badst` which by default is set to 0. - -Notice that if a channel was in a given state A, and was disabled, it -must emit the new state is 0. When the channel is enabled again, it will -emit again the state A. - -## Thread tracking channels - -Regarding thread channels, there are two common conditions that cause -the channels to become disabled. When the thread is no longer running, -and then the thread is not active. - -For those cases, the thread channels can be configured to automatically -be enabled or disabled, following the execution state of the thread. The -tracking mode specifies how the tracking must be done: - -- `CHAN_TRACK_NONE`: nothing to track -- `CHAN_TRACK_RUNNING_TH`: enable the channel only if the thread is - running -- `CHAN_TRACK_ACTIVE_TH`: enable the channel only if the thread is - running, cooling or warming. - -This mechanism removes the complexity of detecting when a thread stops -running, to update a channel of a given module. As the thread state -changes as handled by the `emu_ovni.c` module only. - -## CPU tracking channels - -Similarly, CPU channels can also be configured to track the execution -state of the threads. They become disabled when the tracking condition -is not met, but also copy the state of the tracking thread channel. - -They share the same tracking modes, but their behavior is slightly -different: - -In the case of tracking the running thread, if the CPU has more than one -thread running, the channel will always output the error state -`ST_TOO_MANY_TH`. - -If is has no threads running, will be disabled and emit a 0 state by -default. - -Otherwise, it will emit the same value as the running thread. If the -thread channel is disabled, it will emit a `ST_BAD` error state. - -Regarding the active thread tracking mode, the CPU channels behave -similarly, but with the active threads instead of running ones. - -The CPU tracking mechanism simplify the process of updating CPU -channels, as the modules don't need to worry about the execution model. -Only the channels need to be configured to follow the proper execution -state. - -## Channel state modes - -The channels can be updated in three ways: - -1. A fixed state can be set to the channel using `chan_set()`, which -overrides the previous state. - -2. The new state can be stored in a stack with `chan_push()` and -`chan_pop()`, to remember the history of the previous states. The -emitted event will be the one on the top. - -3. Using a punctual event. - -Setting the channel state is commonly used to track quantities such as -the number of threads running per CPU. While the stack mode is commonly -used to track functions or sections of code delimited with enter and -exit events, which can call an return to the previous state. - -An example program may be instrumented like this: - - int bar() { - instr("Xb["); - ... - instr("Xb]"); - } - - int foo() { - instr("Xf["); - bar(); - instr("Xf]"); - } - -Then, in the emulator, when processing the events `Xf[` and `Xf]`, we -could track of the state as follows: - - int hook_pre_foo(struct ovni_chan *chan, int value) { - switch(value) { - case '[': chan_push(chan, 2); break; - case ']': chan_pop(chan, 2); break; - default: break; - } - } - - int hook_pre_bar(struct ovni_chan *chan, int value) { - switch(value) { - case '[': chan_push(chan, 1); break; - case ']': chan_pop(chan, 1); break; - default: break; - } - } - -The channel will emit the following sequence of states: 0, 1, 2, 1, 0. - -Notice that the `chan_pop()` function uses the same state being pop()'ed -as argument. The function checks that the stack contains the expected -state, forcing the emulator to always receive a matching pair of enter -and exit events. - -## Punctual events - -There are some conditions that are better mapped to events rather than -to state transitions. For those cases, the channels provide punctual -events which are emitted as a state than only has 1 ns of duration. - -When a channel is configured to emit a punctual event with `chan_ev()`, -it will first output the new state at the current time minus 1 ns, then -restore the previous channel state and emit it at the current time. diff --git a/doc/emulation/index.md b/doc/emulation/index.md index 3023741..203e377 100644 --- a/doc/emulation/index.md +++ b/doc/emulation/index.md @@ -10,7 +10,7 @@ happened on the hardware. It consists of CPUs which can execute multiple threads at the same time. The emulator uses several models to identify how the resources are being -used. The following diagram despicts the resource, process and task +used. The following diagram depicts the resource, process and task model. [![Model](fig/model.svg)](fig/model.svg) @@ -34,11 +34,11 @@ the problem. No effort is made to let the emulator recover from an inconsistency. The emulator critical path is kept as simple as possible, so the -processing of events can keep the disk writting as the bottleneck. +processing of events can keep the disk writing as the bottleneck. -The linter mode enables more tests which are disabled from the default +The lint mode enables more tests which are disabled from the default mode to prevent costly operations running in the emulator by default. -The linter tests are enabled when running the ovni testsuite. +The lint tests are enabled when running the ovni testsuite. ## Emulation models diff --git a/doc/emulation/nanos6.md b/doc/emulation/nanos6.md index 2cbae1f..a8ac0b9 100644 --- a/doc/emulation/nanos6.md +++ b/doc/emulation/nanos6.md @@ -202,4 +202,4 @@ multiple times by varying the arguments with the task for clause, which breaks the emulation model. The task for is currently only shown in the subsystem view, but it doesn't -appear as running any task in the the other views. +appear as running any task in the other views. diff --git a/mkdocs.yml b/mkdocs.yml index 3bf3204..897236a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,6 +32,11 @@ nav: - emulation/nosv.md - emulation/nanos6.md - emulation/events.md - - emulation/channels.md - 'Developer guide': - - developer-guide/index.md + - dev/index.md + - dev/channels.md + - dev/patchbay.md + - dev/mux.md + - dev/paraver.md + - dev/model.md + - dev/extend.md