System calls are grouped based on the object they operate on. For example, the system calls dealing with thread manipulation are called oz_sys_thread_.... They all return an uLong status value.
In general, the system call routines can be called from either user or kernel mode. However, if called from kernel mode, the highest smplevel is softint, ie, you cannot have any spinlocks held, because most system call routines call oz_hw_cpu_setsoftint to inhibit software interrupt delivery, which will crash if the smplevel is above softint level.
There are two sets of macros associated with the declaration and definition of the syscall routines. They are defined by the hardware layer header file (oz_hw_type.h).
The OZ_HW_SYSCALL_DCL_n macros are used to declare the system call prototypes in an appropriate header file. The n is used to indicate the number of parameters of the syscall routine. The macro will declare two prototypes, one name is prefixed by oz_sys_ and the other is prefixed by oz_syscall_. The oz_sys_ is the routine that normal user mode routines should call. They can also be called from kernel mode as noted above. The oz_syscall_ routine is the kernel mode equivalent 'backend' routine.
The OZ_HW_SYSCALL_DEF_n macros are used to define the system call routines in an appropriate source file. They define both the oz_sys_ and the oz_syscall_ routine. They also define any code used to transition from the oz_sys_ routine to the kernel mode oz_syscall_ routine.
For example, in the x86 implementation:
uLong oz_sys_name (type1 value1, type2 value2); uLong oz_syscall_name (OZ_Procmode cprocmode, void *__dummy_ebp__, void *__dummy_eip__, type1 value1, type2 value2);
The cprocmode is required by the syscall routines so they can determine the caller's processor mode (either OZ_PROCMODE_KNL or OZ_PROCMODE_USR.
The __dummy_ebp__ and __dummy_eip__ are dummy placeholder parameters used by the x86 implementation to make it easy for the underlying assembler routine.
uLong oz_sys_name (type1 value1, type2 value2) { asm volatile ("movw %%cs,%%ax\n" // see what mode the caller is in "testb $2,%%al\n" "jne oz_hw486_syscall\n" // if user, jump to kernel's user-to-kernel mode switching routine // %ecx = sizeof arglist, %edx = routine index "movl %%ebp,%%esp\n" // if kernel, wipe any junk put on stack by gcc "pushl $0\n" // pass cprocmode = OZ_PROCMODE_KNL "call oz_syscall_name\n" // call the oz_syscall_... routine directly "addl $4,%%esp\n" // wipe the cprocmode from stack "popl %%ebp\n" // restore ebp saved by gcc on entry "ret" // return to kernel mode caller of oz_sys_... routine : : "c" (OZ_HW486_SYSCALL_SIZEOF(type1) // put sizeof arglist in %ecx on entry +OZ_HW486_SYSCALL_SIZEOF(type2)), "d" (OZ_SYSCALL_name)); // put routine index in %edx return (0); // to keep gcc happy } uLong oz_syscall_name (OZ_Procmode cprocmode, void *__dummy_ebp__, void *__dummy_eip__, type1 value1, type2 value2)
The OZ_HW486_SYSCALL_SIZEOF macro computes the size the argument takes in the argument list, which is its normal size rounded up to a longword boundary.
For a user mode caller, %ecx gets loaded with the size of the arg list, %edx gets loaded with the oz_sys_syscall table index for the routine, then a jump is made to oz_hw486_syscall, which either does an int or a sysenter to get into kernel mode. Then the cpu calls the corresponding oz_syscall_name routine in kernel mode. Module oz_sys_syscall.c holds the table that translates the index in %edx to the corresponding oz_syscall_name entrypoint.
For a kernel mode caller, a simple call is made directly to the corresponding oz_syscall_name routine, using the __dummy_ebp__ and __dummy_eip__ placeholders for the redundant call frame.