Component creation

Each Genode component is made out of three basic ingredients:

PD

session representing the component's protection domain

ROM

session with the executable binary

CPU

session for creating the initial thread of the component

Figure 1 img/creation_initial
Starting point for creating a new component

It is the responsibility of the new component's parent to obtain those sessions. The initial situation of the parent is depicted in Figure 1. The parent's memory budget is represented by the parent's PD (Section Protection domains (PD)) session. The parent's virtual address space is represented by the region map contained in the parent's PD session. The parent's PD session was originally created at the parent's construction time. Along with the parent's CPU session, it forms the parent's so-called environment. The address space is populated with the parent's code (shown as red), the so-called stack area that hosts the stacks (shown as blue), and presumably several RAM dataspaces for the heap, the DATA segment, and the BSS segment. Those are shown as yellow.

Obtaining the child's ROM and PD sessions

The first step for creating a child component is obtaining the component's executable binary, e.g., by creating a session to a ROM service such as the one provided by core (Section Access to boot modules (ROM)). With the ROM session created, the parent can make the dataspace with the executable binary (i.e., an ELF binary) visible within its virtual address space by attaching the dataspace to its PD's region map. After this step, the parent is able to inspect the ELF header to determine the memory requirements for the binary's DATA and BSS segments.

The next step is the creation of the child's designated PD session, which holds the memory and capability budgets the child will have at its disposal. The freshly created PD session has no budget though. In order to make the PD session usable, the parent has to transfer a portion of its own RAM quota to the child's PD session. As explained in Section Resource assignment, the parent registers its own PD session as the reference account for the child's PD session in order to become able to transfer quota back and forth between both PD sessions. Figure 2 shows the situation.

Figure 2 img/creation_rom_pd
The parent creates the PD session of the new child and obtains the child's executable

Constructing the child's address space

With the child's PD session equipped with a memory, the parent can construct the address space for the new child and populate it with memory allocated from the child's budget (Figure 3). The address-space layout is represented as a region map that is part of each PD session (Section Protection domains (PD)). The first page of the address space is excluded such that any attempt by the child to de-reference a null pointer will cause a fault instead of silently corrupting memory. After its creation time, the child's region map is empty. It is up to the parent to populate the virtual address space with meaningful information by attaching dataspaces to the region map. The parent performs this procedure based on the information found in the ELF executable's header:

Figure 3 img/creation_pdsession
The parent creates and populates the virtual address space of the child using a new PD session (the parent's PD session is not depicted for brevity)
Read-only segments

For each read-only segment of the ELF binary, the parent attaches the corresponding portion of the ELF dataspace to the child's address space by invoking the attach operation on the child's region-map capability. By attaching a portion of the existing ELF dataspace to the new child's region map, no memory must be copied. If multiple instances of the same executable are created, the read-only segments of all instances refer to the same physical memory pages. If the segment contains the TEXT segment (the program code), the parent specifies a so-called executable flag to the attach operation. Core passes this flag to the respective kernel such that the corresponding page-table entries for the new components will be configured accordingly (by setting or clearing the non-executable bit in the page-table entries). Note that the propagation of this information (or the lack thereof) depends on the kernel used. Also note that not all hardware platforms distinguish executable from non-executable memory mappings.

Read-writable segments

In contrast to read-only segments, read-writable segments cannot be shared between components. Hence, each read-writable segment must be backed with a distinct copy of the segment data. The parent allocates the backing store for the copy from the child's PD session and thereby accounts the memory consumption on behalf of the child to the child's budget. For each segment, the parent performs the following steps:

  1. Allocation of a RAM dataspace from the child's PD session. The size of the dataspace corresponds to the segment's memory size. The memory size may be higher than the size of the segment in the ELF binary (named file size). In particular, if the segment contains a DATA section followed by a BSS section, the file size corresponds to the size of the DATA section whereby the memory size corresponds to the sum of both sections. Core's PD service ensures that each freshly allocated RAM dataspace is guaranteed to contain zeros. Core's PD service returns a RAM dataspace capability as the result of the allocation operation.

  2. Attachment of the RAM dataspace to the parent's virtual address space by invoking the attach operation on the parent's region map with the RAM dataspace capability as argument.

  3. Copying of the segment content from the ELF binary's dataspace to the freshly allocated RAM dataspace. If the memory size of the segment is larger than the file size, no special precautions are needed as the remainder of the RAM dataspace is known to be initialized with zeros.

  4. After filling the content of the segment dataspace, the parent no longer needs to access it. It can remove it from its virtual address space by invoking the detach operation on its own region map.

  5. Based on the virtual segment address as found in the ELF header, the parent attaches the RAM dataspace to the child's virtual address space by invoking the attach operation on the child PD's region map with the RAM dataspace as argument.

This procedure is repeated for each segment. Note that although the above description refers to ELF executables, the underlying mechanisms used to load the executable binary are file-format agnostic.

Creating the initial thread

Figure 4 img/creation_thread
Creation of the child's initial thread

With the virtual address space of the child configured, it is time to create the component's initial thread. Analogously to the child's PD session, the parent creates a CPU session (Section Processing-time allocation (CPU)) for the child. The parent may use session arguments to constrain the scheduling parameters (i.e., the priority) and the CPU affinity of the new child. Whichever session arguments are specified, the child's abilities will never exceed the parent's abilities. I.e., the child's priority is subjected to the parent's priority constraints. Once constructed, the CPU session can be used to create new threads by invoking the session's create-thread operation with the thread's designated PD as argument. Based on this association of the thread with its PD, core is able to respond to page faults triggered by the thread. The invocation of this operation results in a thread capability, which can be used to control the execution of the thread. Immediately after its creation, the thread remains inactive. In order to be executable, it first needs to be configured.

As described in Section Component ownership, each PD has initially a single capability installed, which allows the child to communicate with its parent. Right after the creation of the PD for a new child, the parent can register a capability to a locally implemented RPC object as parent capability for the PD session. Now that the child's PD is equipped with an initial thread and a communication channel to its parent, it is the right time to kick off the execution of the thread by invoking the start operation on its thread capability. The start operation takes the initial program counter as argument, which corresponds to the program's entry-point address as found in the ELF header of the child's executable binary. Figure 4 illustrates the relationship between the PD session, the CPU session, and the parent capability. Note that neither the ROM dataspace containing the ELF binary nor the RAM dataspaces allocated during the ELF loading are visible in the parent's virtual address space any longer. After the initial loading of the ELF binary, the parent has detached those dataspaces from its own region map.

The child starts its execution at the virtual address defined by the ELF entrypoint. It points to a short assembly routine that sets up the initial stack and calls the low-level C++ startup code. This code, in turn, initializes the C++ runtime (such as the exception handling) along with the component's local Genode environment. The environment is constructed by successively requesting the component's CPU and PD sessions from its parent. With the Genode environment in place, the startup code initializes the stack area, sets up the real stack for the initial thread within the stack area, and returns to the assembly startup code. The assembly code, in turn, switches the stack from the initial stack to the real stack and calls the program-specific C++ startup code. This code initializes the component's initial entrypoint and executes all global constructors before calling the component's construct function. Section Component-local startup code and linker scripts describes the component-local startup procedure in detail.