mirror of
https://github.com/genodelabs/genode.git
synced 2025-05-04 17:53:07 +00:00
sel4: IPC between two threads
This commit is contained in:
parent
45b90aa898
commit
1fdf36efed
145
repos/base-sel4/doc/ipc_and_virt_mem.txt
Normal file
145
repos/base-sel4/doc/ipc_and_virt_mem.txt
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
|
||||||
|
|
||||||
|
=======================================
|
||||||
|
Genode on seL4 - IPC and virtual memory
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
|
||||||
|
Norman Feske
|
||||||
|
|
||||||
|
|
||||||
|
This is the second part of a series of hands-on articles about bringing Genode
|
||||||
|
to the seL4 kernel.
|
||||||
|
[http://genode.org/documentation/articles/sel4_part_1 - Read the previous part here...]
|
||||||
|
|
||||||
|
After having created a minimalistic root task consisting of two threads, we
|
||||||
|
can move forward with exercising the functionality provided by the kernel,
|
||||||
|
namely inter-process communication and the handling of virtual memory.
|
||||||
|
Once we have tested those functionalities in our minimalistic root task
|
||||||
|
environment, we will be able to apply the gained knowledge to the actual
|
||||||
|
porting effort of Genode's core process.
|
||||||
|
|
||||||
|
|
||||||
|
Inter-process communication
|
||||||
|
###########################
|
||||||
|
|
||||||
|
In the L4 universe, the term IPC (inter-process communication) usually stands
|
||||||
|
for synchronous communication between two threads. In seL4, IPC has two uses.
|
||||||
|
First, it enables threads of different protection domains (or the same
|
||||||
|
protection domain) to exchange messages. So information can be transferred
|
||||||
|
across protection-domain boundaries. Second, IPC is the mechanism used to
|
||||||
|
delegate access rights throughout the system. This is accomplished by sending
|
||||||
|
capabilities as message payload. When a capability is part of a message, the
|
||||||
|
kernel translates the local name of the capability in the sender's protection
|
||||||
|
domain to a local name in the receiver's protection domain.
|
||||||
|
|
||||||
|
In Genode, IPC is realized as a two thin abstractions that build upon each
|
||||||
|
other:
|
||||||
|
|
||||||
|
# At the low level, the IPC library _src/base/ipc/ipc.cc_ is responsible
|
||||||
|
for sending and receiving messages using the kernel mechanism. It has a
|
||||||
|
generic interface _base/include/base/ipc.h_, which supports the marshalling
|
||||||
|
and un-marshalling of message arguments and capabilities using C++ streaming
|
||||||
|
operators. Genode users never directly interact with the IPC library.
|
||||||
|
|
||||||
|
# Built on top the IPC library, the so-called RPC framework adds the notion
|
||||||
|
of RPC functions and RPC objects. RPC interfaces are declared using
|
||||||
|
abstract C++ base classes with a few annotations. Under the hood, the
|
||||||
|
RPC framework uses C++ meta-programming techniques to turn RPC definitions
|
||||||
|
into code that transfers messages via the IPC library. In contrast to
|
||||||
|
the IPC library, the RPC library is platform-agnostic.
|
||||||
|
|
||||||
|
To enable Genode's RPC mechanism on seL4, we merely have to provide a
|
||||||
|
seL4-specific IPC library implementation. To warm up with seL4's IPC
|
||||||
|
mechanism, however, we first modify our test program to let the main thread
|
||||||
|
perform an IPC call to the second thread.
|
||||||
|
|
||||||
|
To let the second thread receive IPC messages, we first need to create a
|
||||||
|
synchronous IPC endpoint using the 'seL4_Untyped_RetypeAtOffset' function
|
||||||
|
with 'seL4_EndpointObject' as type, an offset that skips the already allocated
|
||||||
|
TCB (the TCB object has a known size of 1024 bytes) and the designated
|
||||||
|
capability number, let's call it EP_CAP. Of course, we have to create the
|
||||||
|
entrypoint before starting the second thread.
|
||||||
|
|
||||||
|
As a first test, we want the second thread to receive an incoming message.
|
||||||
|
So we change the entry function as follows:
|
||||||
|
|
||||||
|
! PDBG("call seL4_Wait");
|
||||||
|
! seL4_MessageInfo_t msg_info = seL4_Wait(EP_CAP, nullptr);
|
||||||
|
! PDBG("returned from seL4_Wait, call seL4_Reply");
|
||||||
|
! seL4_Reply(msg_info);
|
||||||
|
! PDBG("returned from seL4_Reply");
|
||||||
|
|
||||||
|
At the end of the main function, we try call the second thread via 'seL4_Call':
|
||||||
|
|
||||||
|
! PDBG("call seL4_Call");
|
||||||
|
! seL4_MessageInfo_t msg_info = seL4_MessageInfo_new(0, 0, 0, 0);
|
||||||
|
! seL4_Call(EP_CAP, msg_info);
|
||||||
|
! PDBG("returned from seL4_Call");
|
||||||
|
|
||||||
|
When executing the code, we get an error as follows:
|
||||||
|
|
||||||
|
! int main(): call seL4_Call
|
||||||
|
! void second_thread_entry(): call seL4_Wait
|
||||||
|
! Caught cap fault in send phase at address 0x0
|
||||||
|
! while trying to handle:
|
||||||
|
! vm fault on data at address 0x4 with status 0x6
|
||||||
|
! in thread 0xe0100080 at address 0x10002e1
|
||||||
|
|
||||||
|
By looking at the output of 'objdump -lSd', we see that fault happens at the
|
||||||
|
instruction
|
||||||
|
! mov %edi,%gs:0x4(,%ebx,4)
|
||||||
|
The issue is the same as the one we experienced for the main thread - we
|
||||||
|
haven't initialized the GS register with a proper segment, yet. This can be
|
||||||
|
easily fixed by adding a call to our 'init_ipc_buffer' function right at the
|
||||||
|
start of the second thread's entry function. Still, the program does not work
|
||||||
|
yet:
|
||||||
|
|
||||||
|
! vm fault on data at address 0x4 with status 0x6
|
||||||
|
! in thread 0xe0100080 at address 0x10002e8
|
||||||
|
|
||||||
|
Looking at the objdump output, we see that the fault still happens at the same
|
||||||
|
instruction. So what is missing? The answer is that we haven't equipped the
|
||||||
|
second thread with a proper IPC buffer. The attempt to call 'seL4_Wait',
|
||||||
|
however, tries to access the IPC buffer of the calling thread. The IPC buffer
|
||||||
|
can be configured for a thread using the 'seL4_TCB_SetIPCBuffer' function. But
|
||||||
|
wait - what arguments do we need to pass? In addition to the TCB capability,
|
||||||
|
there are two arguments a pointer to the IPC buffer and a page capability,
|
||||||
|
which contains the IPC buffer. Well, I had hoped to get away without dealing
|
||||||
|
with the memory management at this point. I figure that setting up the IPC
|
||||||
|
buffer for the second thread would require me to create a seL4_IA32_4K page
|
||||||
|
object via 'seL4_Untyped_RetypeAtOffset' and insert a mapping of the page
|
||||||
|
within the roottask's address space, and possibly also create and install a
|
||||||
|
page table object.
|
||||||
|
|
||||||
|
To avoid becoming side-tracked by those memory-management issues, I decide
|
||||||
|
to assign the IPC buffer of the second thread right at the same page as
|
||||||
|
the one for the initial thread. Both the local address and the page
|
||||||
|
capability for the initial thread's IPC buffer are conveniently provided by
|
||||||
|
seL4's boot info structure. So let's give this a try:
|
||||||
|
|
||||||
|
! /* assign IPC buffer to second thread */
|
||||||
|
! {
|
||||||
|
! static_assert(sizeof(seL4_IPCBuffer) % 512 == 0,
|
||||||
|
! "unexpected seL4_IPCBuffer size");
|
||||||
|
!
|
||||||
|
! int const ret = seL4_TCB_SetIPCBuffer(SECOND_THREAD_CAP,
|
||||||
|
! (seL4_Word)(bi->ipcBuffer + 1),
|
||||||
|
! seL4_CapInitThreadIPCBuffer);
|
||||||
|
!
|
||||||
|
! PDBG("seL4_TCB_SetIPCBuffer returned %d", ret);
|
||||||
|
! }
|
||||||
|
|
||||||
|
With the initialization of the IPC buffer in place, we finally get our
|
||||||
|
desired output:
|
||||||
|
|
||||||
|
! int main(): call seL4_Call
|
||||||
|
! void second_thread_entry(): call seL4_Wait
|
||||||
|
! void second_thread_entry(): returned from seL4_Wait, call seL4_Reply
|
||||||
|
! int main(): returned from seL4_Call
|
||||||
|
! void second_thread_entry(): returned from seL4_Reply
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -68,13 +68,32 @@ static inline void init_ipc_buffer()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int volatile cnt = 0;
|
enum { SEL4_TCB_SIZE = 0x1000,
|
||||||
|
SEL4_EP_SIZE = 16 };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Capability for the second thread's TCB
|
||||||
|
*/
|
||||||
|
enum { SECOND_THREAD_CAP = 0x100 };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Capability for IPC entrypoint, set up by the main thread, used by the second
|
||||||
|
* thread.
|
||||||
|
*/
|
||||||
|
enum { EP_CAP = 0x101 };
|
||||||
|
|
||||||
|
|
||||||
void second_thread_entry()
|
void second_thread_entry()
|
||||||
{
|
{
|
||||||
for (;;)
|
init_ipc_buffer();
|
||||||
cnt++;
|
|
||||||
|
PDBG("call seL4_Wait");
|
||||||
|
seL4_MessageInfo_t msg_info = seL4_Wait(EP_CAP, nullptr);
|
||||||
|
PDBG("returned from seL4_Wait, call seL4_Reply");
|
||||||
|
seL4_Reply(msg_info);
|
||||||
|
PDBG("returned from seL4_Reply");
|
||||||
|
|
||||||
|
*(int *)0x2244 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,12 +112,17 @@ int main()
|
|||||||
PDBG("seL4_SetUserData");
|
PDBG("seL4_SetUserData");
|
||||||
seL4_SetUserData((seL4_Word)bi->ipcBuffer);
|
seL4_SetUserData((seL4_Word)bi->ipcBuffer);
|
||||||
|
|
||||||
enum { SECOND_THREAD_CAP = 0x100 };
|
/* yse first untyped memory region for allocating kernel objects */
|
||||||
|
seL4_Untyped const untyped = bi->untyped.start;
|
||||||
|
|
||||||
|
/* offset to next free position within the untyped memory range */
|
||||||
|
unsigned long untyped_offset = 0;
|
||||||
|
|
||||||
|
/* create second thread */
|
||||||
{
|
{
|
||||||
seL4_Untyped const service = 0x38; /* untyped */
|
seL4_Untyped const service = untyped;
|
||||||
int const type = seL4_TCBObject;
|
int const type = seL4_TCBObject;
|
||||||
int const offset = 0;
|
int const offset = untyped_offset;
|
||||||
int const size_bits = 0;
|
int const size_bits = 0;
|
||||||
seL4_CNode const root = seL4_CapInitThreadCNode;
|
seL4_CNode const root = seL4_CapInitThreadCNode;
|
||||||
int const node_index = 0;
|
int const node_index = 0;
|
||||||
@ -106,6 +130,8 @@ int main()
|
|||||||
int const node_offset = SECOND_THREAD_CAP;
|
int const node_offset = SECOND_THREAD_CAP;
|
||||||
int const num_objects = 1;
|
int const num_objects = 1;
|
||||||
|
|
||||||
|
untyped_offset += SEL4_TCB_SIZE;
|
||||||
|
|
||||||
int const ret = seL4_Untyped_RetypeAtOffset(service,
|
int const ret = seL4_Untyped_RetypeAtOffset(service,
|
||||||
type,
|
type,
|
||||||
offset,
|
offset,
|
||||||
@ -116,9 +142,49 @@ int main()
|
|||||||
node_offset,
|
node_offset,
|
||||||
num_objects);
|
num_objects);
|
||||||
|
|
||||||
PDBG("seL4_Untyped_RetypeAtOffset returned %d", ret);
|
PDBG("seL4_Untyped_RetypeAtOffset (TCB) returned %d", ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* create synchronous IPC entrypoint */
|
||||||
|
{
|
||||||
|
seL4_Untyped const service = untyped;
|
||||||
|
int const type = seL4_EndpointObject;
|
||||||
|
int const offset = untyped_offset;
|
||||||
|
int const size_bits = 0;
|
||||||
|
seL4_CNode const root = seL4_CapInitThreadCNode;
|
||||||
|
int const node_index = 0;
|
||||||
|
int const node_depth = 0;
|
||||||
|
int const node_offset = EP_CAP;
|
||||||
|
int const num_objects = 1;
|
||||||
|
|
||||||
|
untyped_offset += SEL4_EP_SIZE;
|
||||||
|
|
||||||
|
int const ret = seL4_Untyped_RetypeAtOffset(service,
|
||||||
|
type,
|
||||||
|
offset,
|
||||||
|
size_bits,
|
||||||
|
root,
|
||||||
|
node_index,
|
||||||
|
node_depth,
|
||||||
|
node_offset,
|
||||||
|
num_objects);
|
||||||
|
|
||||||
|
PDBG("seL4_Untyped_RetypeAtOffset (EP) returned %d", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* assign IPC buffer to second thread */
|
||||||
|
{
|
||||||
|
static_assert(sizeof(seL4_IPCBuffer) % 512 == 0,
|
||||||
|
"unexpected seL4_IPCBuffer size");
|
||||||
|
|
||||||
|
int const ret = seL4_TCB_SetIPCBuffer(SECOND_THREAD_CAP,
|
||||||
|
(seL4_Word)(bi->ipcBuffer + 1),
|
||||||
|
seL4_CapInitThreadIPCBuffer);
|
||||||
|
|
||||||
|
PDBG("seL4_TCB_SetIPCBuffer returned %d", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* start second thread */
|
||||||
long stack[0x1000];
|
long stack[0x1000];
|
||||||
{
|
{
|
||||||
seL4_UserContext regs;
|
seL4_UserContext regs;
|
||||||
@ -143,8 +209,12 @@ int main()
|
|||||||
|
|
||||||
seL4_TCB_SetPriority(SECOND_THREAD_CAP, 0xff);
|
seL4_TCB_SetPriority(SECOND_THREAD_CAP, 0xff);
|
||||||
|
|
||||||
for (;;)
|
PDBG("call seL4_Call");
|
||||||
PDBG("cnt = %d", cnt);
|
|
||||||
|
seL4_MessageInfo_t msg_info = seL4_MessageInfo_new(0, 0, 0, 0);
|
||||||
|
seL4_Call(EP_CAP, msg_info);
|
||||||
|
|
||||||
|
PDBG("returned from seL4_Call");
|
||||||
|
|
||||||
*(int *)0x1122 = 0;
|
*(int *)0x1122 = 0;
|
||||||
return 0;
|
return 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user