Re: Another question about L4 system call

Raphael Neider <[email protected]> Sun, 07 Sep 2008 17:28:59 +0200
Newsgroups gmane.comp.micro-kernel.l4.l4ka.general
Message-ID <[email protected]>
Hi Bob,

 >  extern "C" void __L4_init_syscalls (void)
 > {
 >     L4_KernelInterfacePage_t * kip = (L4_KernelInterfacePage_t *)
 >  L4_KernelInterface ();
 >     // Make copy before starting to modify.
 >     __L4_copy_syscalls_out ();
 >     //L4_KDB_Enter ("Fixup syscalls");
 >
 >     FIXUP (Ipc);
 >     FIXUP (Lipc);
 >     FIXUP (ExchangeRegisters);
 >     FIXUP (ThreadControl);
 >     FIXUP (SystemClock);
 >     FIXUP (ThreadSwitch);
 >     FIXUP (Schedule);
 >     FIXUP (Unmap);
 >     FIXUP (SpaceControl);
 >     FIXUP (ProcessorControl);
 >     FIXUP (MemoryControl);
 > }
 >
 > Still have no idea about this question. especially the first. If don't
 > enter system level first, isn't a system call will cause a page fault?

 >> But the system call address getted from kip_area is in system space.
 >> If don't enter system level first, isn't there a page fault will
 >> occur?

The syscall address gotten from the KIP is an address inside the KIP
(which is mapped accessible to user space):

L4 syscalls are to be called via trampolines in the KIP. The trampolines
perform the "sysenter" or "int NUMBER" instructions to take the
processor into kernel mode. Now we have a problem: the syscalls are
wrapped in library routines (available in user mode, the functions
called __L4_<Ipc|Lipc|...>() as defined in
user/lib/l4/ia32-syscall-stubs.S via macros. These wrappers should just
jump into the KIP trampolines to enter the kernel in the way proposed by
the kernel at runtime (the KIP is mapped from the kernel!).
Now there is a problem: the library cannot know at which address the KIP
will be mapped into the application's address space; this is dictated by
the creator of the address space via its space_control() syscall. So,
the library routines cannot have the address of the KIP hardcoded in
them, but must aquire it at runtime.
You could use wrappers such as

RETURNTYPE __L4_Ipc (ARGUMENTS) {
   L4_KernelInterfacePage_t * kip = (L4_KernelInterfacePage_t *)
     L4_KernelInterface ();
   return (kip->Ipc(ARGUMENTS));
}
but that would require a slow syscall (L4_KernelInterface()) for every
syscall; a really bad idea.

Instead we could only do it once and store the reference to the KIP in a
library global variable:

static L4_KernelInterfacePage_t * kip = NULL;

RETURNTYPE __L4_Ipc (ARGUMENTS) {
   if (!kip) {
     kip = (L4_KernelInterfacePage_t *)L4_KernelInterface ();
   } // if
   return (kip->Ipc(ARGUMENTS));
}

This still adds the if (!kip) test to each syscall, which we avoid by
rewriting the wrappers at runtime.
Initially, each syscall wrapper is setup to call __L4_init_syscalls (in
user/lib/l4/ia32-syscall-stubs.S), and then restart itself.
__L4_init_syscalls() then goes and writes into the .text section of the
current address space (bad coding style, but fast), replacing the
call __L4_init_syscalls
in each of the library syscall wrappers with a jmp to the address of the
trampoline in the KIP: 0xe9 is the machine encoding for a jump
instruction, the following four bytes are relative address from the
instruction following the jump to the KIP trampoline (-5 accomodates for
the jump opcode plus 4 byte relative address, kip is the address of the
KIP, kip->syscall (e.g. kip->Ipc) adds the offset to the trampoline in
the KIP, __L4_##syscall (e.g., __L4_Ipc) subtracts the address of the
library wrapper to yield the relative address).

When __L4_init_syscalls() returns, the syscall is restarted and executes
the newly patched-in jmp instruction to reach the KIP trampoline.

Note that __L4_init_syscalls() is executed (at most) once: On its first
execution, it rewrites all wrappers to jump to their trampoline! All
subsequent syscalls via the library wrappers jump straight to their KIP
trampolines, how much better can you get?!?

This stuff *is* difficult, maybe it suffices to know *that* it works and
leave out the *why*...

Regards,
Raphael