Spawning a Task (Challenge)
This final chapter of Part I contains a more open-ended and challenging exercise. We start with an example that spawns an entire new process, which, in the context of low-level seL4 userspace, is often called a task:
cd workspaces/root-task/spawn-task
make simulate
Similarly to what we saw in Chapter 6 (Spawning a Thread), the code in this example is more low-level and complex compared to what you have seen in code that leverages <sel4utils/process.h>
.
Again, our code here is more like spawning a task using <sel4/sel4.h>
alone.
This example consists of two programs.
The spawn-task
crate is the root task, and the spawn-task-child
crate is the child task.
The child task does not spawn in any standard sort of environment, so is includes its own ad-hoc Rust language runtime in child/src/runtime.rs, complete with thread-local storage, a global heap allocator, and exception handling. This runtime is built using a few Rust langauge runtime building block crates:
This minimal, ad-hoc language runtime is a neat, instructive piece of code. If you are interested in learning more about building a new Rust language runtime out of the building blocks provided by the rust-sel4 project, let the instructor know.
Explore the root task and child task at will. Let the instructor know if you would like to discuss any particular aspect of it.
Right now, all the child task does is send a test message over an endpoint back to the root task. The challenge in this chapter, step 7.E, is to extend the root task so that it sets up the child task to be able to interact with the serial device, and to extend the child task to implement the same echo loop as in [./serial-device.html#step-5h]. Steps 7.A, 7.B, 7.C, and 7.D, which are not exercises, make some incremental extensions towards those goals to help you get started.
Step 7.A
This step extends the ObjectAllocator
type in workspaces/root-task/spawn-task/src/object_allocator.rs after 7.B with the recklessly_allocate_at()
method.
This method allocates an object according to the blueprint
parameter at the given physical address paddr
.
Instead of just allocating the object from the largest kernel untyped like the allocate()
method does, this method searches through the bootinfo to find the initial untyped capability whose corresponding untyped object contains paddr
, allocates dummy objects from this untyped object until its watermark reaches paddr
, and then allocates the desired object.
recklessly_allocate_at()
's procedure is similar to that which we followed in step 5.C.
This implementation is "reckless" because it modifies the state of the untyped capability it allocates from (allocating from it and changing its watermark) without keeping track of having done so.
So, subsequent calls for paddr
s contained in the same initial untyped would fail or, worse, misbehave.
However, we expect to only need to call it once, so we are okay with this caveat.
In step 7.E, you be able to use this method to allocate the serial device MMIO register frame.
Step 7.B
This step extends the create_child_vspace()
function in workspaces/root-task/spawn-task/src/child_vspace.rs after 7.A to take an extra_frames
parameter.
create_child_vspace()
now maps these extra frames into the child task's address space, after the end of the program image, and after the IPC buffer frame.
In step 7.E, you be able to use this parameter to pass in the serial device MMIO register frame to mapped into the child task's address space.
Step 7.C
This step simply copies the Device
type from chapter 5 into the child task.
In step 7.E, you be able to use this type to interact with the serial device's MMIO registers, just like we as part of step 5.E.
Step 7.D
This step just adds the SERIAL_DEVICE_MMIO_PADDR
and SERIAL_DEVICE_IRQ
constants from chapter 5 to the root task.
Step 7.E (challenge)
Exercise: Extend the root task so that it sets up the child task to be able to interact with the serial device, and extend the child task to implement the same echo loop as in [./serial-device.html#step-5h-exercise].
Hint for the root task (click to expand)
Try following this sequence of sub-steps:
-
Allocate
serial_device_frame_cap: sel4::cap::Granule
usingobject_allocator.recklessly_allocate_at()
. -
Map
serial_device_frame_cap
into the child task's address space usingcreate_child_vspace()
'sextra_frames
parameter. -
Similarly to how we did so in steps 5.F and 5.G, obtain
irq_handler_cap: sel4::cap::IrqHandler
forSERIAL_DEVICE_IRQ
(object_allocator.allocate_slot()
might come in handy), allocateirq_nfn_cap: sel4::cap::Notification
, and associateirq_nfn_cap
withSERIAL_DEVICE_IRQ
usingirq_handler_cap
. -
Copy
irq_handler_cap
andirq_nfn_cap
into the child task's CSpace in a similar way to howchild_tcb
andinter_task_ep
are copied.
Hint for the child task (click to expand)
Try following this sequence of sub-steps:
-
Declare constants
IRQ_HANDLER: sel4::cap::IrqHandler
andIRQ_NFN: sel4::cap::Notification
afterOWN_TCB
andINTRA_TASK_EP
. -
Obtain the virtual address of the serial device MMIO frame with
addr_of_page_beyond_image(1)
(recall howcreate_child_vspace()
'sextra_frames
parameter works). -
Initialize the serial device with
Device::new()
andDevice::init()
(as we did for part of step 5.E), and use the serial device just like we did in step 5.H.