Untyped
This tutorial provides an introduction to physical memory management on seL4.
Prerequisites
Outcomes
By the end of this tutorial, you should be familiar with:
- The jargon untyped, device untyped, and bit size.
- Know how to create objects from untyped memory in seL4.
- Know how to reclaim objects.
Background
Physical memory
Apart from a small, static amount of kernel memory, all physical memory is managed by user level in an seL4 system. Capabilities to objects created by seL4 at boot, as well as the rest of the physical resources managed by seL4, are passed to the root task on start up.
Untyped memory and capabilities
Excluding the objects used to create the root task, capabilities to all available physical memory are passed to the root task as capabilities to untyped memory. Untyped memory is a block of contiguous physical memory with a specific size. Untyped capabilities are capabilities to untyped memory. Untyped capabilities can be retyped into kernel objects together with capabilities to them, or into further, usually smaller, untyped capabilities.
Untyped capabilities have a boolean property device which indicates whether the memory is writable by the kernel or not: it may not be backed by RAM but some other device, or it may be in an area of RAM not addressable by the kernel. Device untyped capabilities can only be retyped intro frame objects (physical memory frames, which can be mapped into virtual memory), and cannot be written to by the kernel.
Initial state
The seL4_BootInfo
structure provided to the root task describes all of the untyped capabilities, including their size,
if they are a device untyped, and the physical address of the untyped. The code example below
shows how to print out the initial untyped capabilities provided from seL4_BootInfo
.
printf(" CSlot \tPaddr \tSize\tType\n");
for (seL4_CPtr slot = info->untyped.start; slot != info->untyped.end; slot++) {
seL4_UntypedDesc *desc = &info->untypedList[slot - info->untyped.start];
printf("%8p\t%16p\t2^%d\t%s\n", (void *) slot, (void *) desc->paddr, desc->sizeBits, desc->isDevice ? "device untyped" : "untyped");
}
Retyping
Untyped capabilities have a single invocation: seL4_Untyped_Retype which is used to create a new capability (and potentially object) from an untyped capability. Specifically, the new capability created by a retype invocation provides access to a subset of the memory range of the original capability, either as a smaller untyped capability, or pointing to a new object with a specific type. New capabilities created by retyping an untyped capability are referred to as children of that untyped capability.
Untyped capabilities are retyped incrementally in a greedy fashion from the invoked untyped, which is important to understand in order to gain efficient memory utilisation in seL4 systems. Each untyped capability maintains a single watermark, with addresses before the watermark being unavailable (already retyped) and after the watermark being free (not retyped yet). On a retype operation, the watermark is moved first to the alignment of the object being created, and then to the end of the size of the object. For example, if we create first a 4KiB object and then a 16KiB object, the 12KiB between the end of the 4KiB object and the start of the 16KiB object are wasted due to alignment. The memory cannot be reclaimed until both children are revoked.
TL;DR: objects should be allocated in order of size, largest first, to avoid wasting memory.
error = seL4_Untyped_Retype(parent_untyped, // the untyped capability to retype
seL4_UntypedObject, // type
untyped_size_bits, //size
seL4_CapInitThreadCNode, // root
0, // node_index
0, // node_depth
child_untyped, // node_offset
1 // num_caps
);
The above code snippet shows an example of retyping an untyped into a smaller,
4KiB untyped capability (despite the name seL4_UntypedObject
no object is
created, the new untyped capability simply points to a subset of the untyped
memory of parent_untyped
. However, it is convenient to treat this memory like
an object for the purposes of this operation). We will refer to this snippet as
we discuss each parameter in the retype invocation. Recall that the first
parameter is the untyped capability being invoked to do the retype.
Types
Each object in seL4 has a specific type, and all of the type constants can be found in libsel4
. Some types
are architecture specific, while others are general across architectures. In the example above, we create a
new untyped capability, which can be further retyped. All other object type parameters create kernel objects and
capabilities to them, and the type of the created object (and capability) determines the invocations
that can be made on that capability. For example, were we to retype into a thread control block
(TCB – seL4_TCBObject
), we could then perform TCB invocations on the capability to the new TCB object, including seL4_TCB_SetPriority
.
Size
The size argument determines the size of the new object. The meaning of the argument depends on the object type that is being requested:
- most objects in seL4 are fixed size, and for these, the kernel will ignore the size argument;
- the types
seL4_UntypedObject
andseL4_SchedContextObject
allow a variable size, which is specified insize_bits
(more below); - the type
seL4_CapTableObject
is also of variable size, and the argument specifies the number of capability slots. The same mechanism as forsize_bits
is used, but for the number of slots, not the size in bytes.
In general in seL4, if sizes are measured in bits, they are powers
of two. “Bits” does not refer to the number of bits the object takes up, but
the bit-width that is needed to describe its contents. An object of bit size n
measures 2n
bytes. Also, generally in seL4, objects and memory
regions are aligned to their size, i.e. an n
-bit object is aligned to
2n
bytes, or, equivalently, the address of the object has 0 as its
n
bottom bits.
For retype
, it is enough to remember that the parameter size_bits
means the
object will measure 2size_bits
bytes, and for seL4_CapTableObject
that you are requesting 2size_bits
slots (you can compute the size
in bytes by taking 2size_bits + seL4_SlotBits
).
Root, node_index & node_depth
The root
, node_index
and node_depth
parameters are used to specify the CNode in which to place the new capabilities.
Depending on the depth
parameter, the CSlot to use is addressed by invocation or by direct addressing (see the capabilities tutorial for an explanation of those terms).
In the example above, node_depth
is set to 0, which means invocation addressing is used: the root
parameter is
looked up implicitly using the CSpace root of the current thread at a depth of seL4_WordBits
. So the example code
specifies the root task’s CNode (seL4_CapInitThreadCNode
). The node_index
parameter in this case is ignored.
If the node_depth
value is not set to 0, then direct addressing is used with the current thread’s CSpace root
as the root. Then the node_index
parameter is used to locate the CNode capability to place new capabilities in,
at the specified node_depth
. This is designed for managing multi-level CSpaces, and is not covered in this tutorial.
Node_offset
The node_offset is the CSlot to start creating new capabilities at, in the CNode selected by the previous parameters. In this case, the first empty CSlot in the initial CNode is selected.
Num_caps
The retype invocation can be used to create more than 1 capability and object at a time – the number of capabilities is specified using this argument. Note that there are two constraints on this value:
- The untyped must be big enough to fit all of the memory being retyped (
num_caps * (1u << size_bits)
). - The CNode must have enough consecutive free CSlots to fit all of the new capabilities.
Exercises
Create an untyped capability
When you first run this tutorial, you will see something like the following output, which lists all of the untyped capabilities provided to the root task on boot:
Booting all finished, dropped to user space
CSlot Paddr Size Type
0x12d 0x100000 2^20 device untyped
0x12e 0x200000 2^21 device untyped
0x12f 0x400000 2^22 device untyped
0x130 0xb2e000 2^13 device untyped
At the end of the output, there is an error:
<<seL4(CPU 0) [decodeUntypedInvocation/105 T0xffffff801ffb5400 "rootserver" @4012e1]: Untyped Retype: Requested UntypedItem size too small.>>
main@main.c:49 [Cond failed: error != seL4_NoError]
Failed to retype
This error happens because we are trying to create an untyped of size 0.
Exercise Calculate the size of the child untyped required, such that the child untyped can be used
to create all of the objects listed in the objects
array. Remember that object sizes are measured in
bits, that is, in powers of two.
// list of general seL4 objects
seL4_Word objects[] = {seL4_TCBObject, seL4_EndpointObject, seL4_NotificationObject};
// list of general seL4 object size_bits
seL4_Word sizes[] = {seL4_TCBBits, seL4_EndpointBits, seL4_NotificationBits};
// TODO work out what size object we need to create to be able to create all of the objects
// listed above. Remember that sizes are in bits, that is, the exponents of powers of two.
seL4_Word untyped_size_bits = 0;
seL4_CPtr parent_untyped = 0;
seL4_CPtr child_untyped = info->empty.start;
// First, find an untyped big enough to fit all of our objects
for (int i = 0; i < (info->untyped.end - info->untyped.start); i++) {
if (info->untypedList[i].sizeBits >= untyped_size_bits && !info->untypedList[i].isDevice) {
parent_untyped = info->untyped.start + i;
break;
}
}
On success, the tutorial will progress further, printing “Failed to set priority”
Create a TCB Object
The priority check is failing as child_tcb
is an empty CSlot.
Exercise fix this by creating a TCB object from child_untyped
and place its cap into the child_tcb
CSlot.
// use the slot after child_untyped for the new TCB cap:
seL4_CPtr child_tcb = child_untyped + 1;
/* TODO create a TCB in CSlot child_tcb */
// try to set the TCB priority
error = seL4_TCB_SetPriority(child_tcb, seL4_CapInitThreadTCB, 10);
ZF_LOGF_IF(error != seL4_NoError, "Failed to set priority");
On success, the tutorial will progress further, printing “Endpoint cap is null cap”.
Create an endpoint object
The error you see now is caused be an invalid endpoint capability.
Exercise Create an endpoint object from child_untyped
and place its cap into the child_ep
CSlot.
// use the slot after child_tcb for the new endpoint cap:
seL4_CPtr child_ep = child_tcb + 1;
/* TODO create an endpoint in CSlot child_ep */
// identify the type of child_ep
uint32_t cap_id = seL4_DebugCapIdentify(child_ep);
ZF_LOGF_IF(cap_id == 0, "Endpoint cap is null cap");
On success, ‘Failed to bind notification’ should be output.
Create a notification object
The next part of the tutorial attempts to use a notification object that does not yet exist.
Exercise create a notification object from child_untyped
and place its cap into the child_ntfn
CSlot.
// use the slot after child_ep for the new notification cap:
seL4_CPtr child_ntfn = child_ep + 1;
// TODO create a notification object in CSlot child_ntfn
// try to use child_ntfn
error = seL4_TCB_BindNotification(child_tcb, child_ntfn);
ZF_LOGF_IF(error != seL4_NoError, "Failed to bind notification.");
Delete the objects
The final part of the tutorial attempts to create enough endpoint objects from child_untyped
to consume the
entire untyped object. However, this fails, because the untyped is already completely consumed by the previous allocations.
Exercise revoke the child untyped, so we can create new objects from it that use up the whole thing.
// TODO revoke the child untyped
// allocate the whole child_untyped as endpoints
// Remember the sizes are exponents, so this computes 2^untyped_size_bits / 2^seL4_EndpointBits:
seL4_Word num_eps = BIT(untyped_size_bits - seL4_EndpointBits);
error = seL4_Untyped_Retype(child_untyped, seL4_EndpointObject, 0, seL4_CapInitThreadCNode, 0, 0, child_tcb, num_eps);
ZF_LOGF_IF(error != seL4_NoError, "Failed to create endpoints.");
printf("Success\n");
Once the tutorial is completed successfully, you should see the message “Success”.
Further exercises
That’s all for the detailed content of this tutorial. Below we list other ideas for exercises you can try to become more familiar with untyped objects and memory allocation in seL4.
- Allocate objects at specific physical addresses.
- Create a simple object allocator for allocating seL4 objects.
Getting help
Stuck? See the resources below.
Tutorial included from github repo edit