[continued from last message]
SOME NOTES...
In general, (for high level languages) a kernel's API is hidden inside a library, so the details of how a system call is made effects library writers
not high level language programmers.
Assembly language programmers hate messing about with the stack. Using the registers is easier and results in faster code, and assembly programmers often write/use routines that return multiple values (the calling conventions used in high level languages are too limited for this). A good library written for a good compiler (e.g. the inline assembly used in GCC) can/will also take advantage of these benefits.
If you pass parameters on the stack with call gates you'll need multiple call gates because different kernel functions use a different number of parameters.
The fastest method that works on all 32 bit 80x86 CPU's is to use a seperate call gate for
each kernel API function. This limits you to less than 8190 functions and also means you can't easily add support for SYSENTER and/or SYSCALL later on.
Using a seperate software interrupt for each kernel function results in the least number of bytes of code (ie. it minimizes the size of executables). This limits you to 208 functions
or less (much less if you ever want to support computers with multiple IO APICs).
Using a call table (like mine) adds an extra indirect near call ("call [API_TABLE + eax *4]") to the overhead of a system call, but it can be extended to support SYSCALL, SYSENTER, etc easily.
If you do end up passing parameters in registers, try to make return values match input parameters. For example, if you've got a function that returns milli-seconds in EBX and you're writing a new function that accepts milli-seconds, use EBX for the input register so that software can call both functions one after the other without doing a "mov something, ebx" in between.
Regardless of what you do, be
explicit and
consistant. In your documentation write some generic system call details that applies to all of the kernel's functions, and make sure you always follow this. For example (from my own documentation):
There are minor differences between the different methods of calling the Kernel API that are imposed by hardware. Regardless of the way the Kernel API is called the same code in the kernel is used and parameters are passed in the same registers.
While the Kernel API functions are all different and the method of calling those functions can vary they all still share some of the same characteristics.
The input parameters are always:
EAX Function number
EBX Parameter 1
ECX Unused by the function
EDX Unused by the function
ESI Parameter 2
EDI Parameter 3
The output parameters are always:
EAX Status (0 for "OK")
EBX Parameter 1
ECX Unused by the function
EDX Unused by the function
ESI Parameter 2
EDI Parameter 3
If a register is not used for an output parameter it is always returned unchanged.
I'd also recommend writing the documentation for a kernel function
before writing the code for that function, and writing the documentation for a group of related functions before writing any of the code for any of the related functions.
Cheers,
Brendan