NOVA User-Level Environment
Version testbox/changed-memory-timing-317-g320d8b5
|
This section documents various interfaces avaiable to NUL programs.
Futex-like semaphore is defined in semaphore.h as class Semaphore
.
Memory MAP of NUL programs is currently as follows:
Addresses | Content |
---|---|
0x1000 - 0x7fffffff | Free |
0x80000000 - __image_end | Elf image |
ALIGN(__image_end, 4MB) - 0xbfffdfff | Free |
0xbfffe000 - 0xbfffefff | UTCB |
0xbffff000 - 0xbfffffff | Client HIP |
0xc0000000 - 0xffffffff? | Kernel |
The "Elf image" area is backed up by a phycial memory with a copy of the program's binary. At the end of this area, there is a heap used in simplemalloc.cc. The size of the heap is determined at compile time by MEMSIZE value in SConscript.
Applications can inherit their class from ProgramConsole
class in order to have access to the console. The console is created by calling console_init()
. This also initializes the Logging facility.
Parent/service protocol is used for communication between clients and services (also called servers) with the assistance from parent. It offers the following properties:
The service protocol consists of several closely related protocols:
Class ParentProtocol
implements the lowest level of the protocol. The message is composed in UTCB (untyped items) and its format is defined as:
Word | Content |
---|---|
0 | Operation type |
1 | client identifier (typically one of the following: client ID, client pseudonym or session ID) |
other | operation dependent data |
Word | Content |
---|---|
0 | Return value |
other | operation dependent data |
IdentifyCap is a capability that is used to identify the sender in a parent. If the child calls its parent, the identity is the semaphore created by the parent in Sigma0::_start_config()
.
Although the parent protocol and the service protocol are logically different, there is some similarity between them. Namely, the parent also provides a service to clients. This service allows them to connect to other services. Because of that the nil can be treated as the pseudonym of the child, i.e. it is used when the parent is first contacted by the child. Children use this pseudonym to open sessions (nil) with parent. Then, the obtained parent-child session ID is used as a pseudonym for the other services contacted by the child.
Name of the constant (the name of the method, if it differs from the constant in anything but case and TYPE_ prefix).
This operation serves two distinct purposes.
When a client wants to access a service, it needs to get a "pseudonym" for that service. Is uses the TYPE_OPEN
operation to get the pseudonym from its parent. The parent can identify which client is asking for pseudonym by the id capability, which was delegated to him on startup. Depending on the identity and configuration, the parent either grants the client the access to the service by delegating him the "pseudonym" capability and returning 0 or it denies the access by not delegating the pseudonym and returning ERROR.
Returns the portal for the service previously requested by nil.
Generic protocol (class GenericProtocol
) is a client-side implementation of the whole parent/service protocol. Its method call_server()
takes care of setting up everything (including negotation with parent) as well as of sending the message to the service. Typically, service specific protocols are derived from this class.
The protocol operates on a contiguous range of capability selectors whose layout is shown in the table below:
Selector | Symbol | Meaning |
---|---|---|
base + 0 | CAP_PSEUDONYM | Identifies the client to the service (retrieved from parent). |
base + 1 | CAP_LOCK | TODO |
base + 2 | CAP_SERVER_SESSION | Identifies the session to the service (one client can open multiple sessions to the service) |
base + 3, ... | CAP_SERVER_PT | Per-CPU portals to talk to the service. If the service is available only on a single CPU, there might be only a single selector. |
The protocol is defined by the following rules:
EEXISTS
error.A typical communication sequence between a client and a service (and also a parent) looks like this:
EEXIST
).ECAP
.EEXIST
is returned (6a) and the client continues by step 7, otherwise the call is finished (6b).... the ID field is always the session ID. Generic protocol automatically opens the session if it is required, i.e. when the call returns EEXISTS
.
Ellipses represent IPC (nova_call
) and arrows represent action taken based on the return of the IPC.
Server side of the parent protocol is implemented in class s0_ParentProtocol.
Other services may be implemented either from scratch or by using several helper classes:
ClientDataStorage
can be used to store and search for client specific data (sessions).SService
and its derived classes encapsulate all the boilerplate code for the services.Note: this is obsolete.
Accessing the disk currently works as follows.
Sigma0 has a table called Sigma0::_disk_data with an entry for each possible client. The index to the table is determined from the portal capability selector which receives the disk request. Besides other things, the entry in this table determines which disks are accessible by the client as defined by sigma0::drive:X command line parameters.
A client that wants to access a disk creates a semaphore and a DiskConsumer object, which is a ring buffer for MessageDiskCommit messages. Then, it calls Sigma0 via Sigma0Base::request_disks_attach(), which delegates the semaphore to Sigma0 and passes it the address of the Consumer object. Sigma0 connects the producer in the appropriate entry in Sigma0::_disk_data with the consumer in the client.
Reading from the disk is performed by sending MessageDisk::DISK_READ message to Sigma0 via Sigma0Base::disk(). The message contains which disk to read, a tag (unique identifier of the request), starting sector, a pointer to the buffer in the client's address space and a DMA scatter-gatter list with offsets relative to the buffer.
Sigma0 translates the virtual addresses to physical ones and redirect the request to the proper disk driver. When the driver completes the request if sends a MessageDiskCommit to Sigma0 which again translates the message for the client and uses Producer to notify the client about completion of his request.
Sometimes it is useful to have central log of multiple applications. This can be achieved by using the tracebuffer service implemented in Sigma0. The tracebuffer is activated by default but if you want the to see the output from other applications than Sigma0, you need to put tracebuffer_verbose
in front of S0_DEFAULT
. If you do not use S0_DEFAULT
then you may use service_tracebuffer:32768,1
.
Redirecting the logging output (Logging::printf()
) of applications to the tracebuffer is achieved by running this code in the application(s):
_console_data.log = new LogProtocol(alloc_cap(LogProtocol::CAP_SERVER_PT + hip->cpu_count()));
When the second parameter in the command line is non-zero (1 in the example above), the traced outout also goes to the sigma0 output, i.e. to the VGA and/or serial line if those are enabled.
Admission service is responsible for creating scheduling contexts. No other protection domain (besides Sigma0) has the right to create scheduling contexts and hence those protection domains must contact admission service and ask to create scheduling contexts for them.
The following is the example how to initialize admission service and how to create a scheduling context for an ececution context.
The admission service is part of Sigma0 binary and is executed automatically without user intervention. You can configure the admission service by changing the content of base/apps/sigma0/cfg/admission.nulconfig
. For instnace, you can remove the top
parameter and add log
parameter to get some logging output from the service.
NUL does not support automatic calling of constructors of static objects.