Page 2 of 2

Re:System Call

Posted: Sat Mar 26, 2005 7:00 pm
by mystran
Here comes a personal opinion of mine: while it's good to have a fast system call mechanism, one should be able to get more benefits from reducing the number of system calls made.

The less you need to call kernel, the smaller the total time spent in system calls will be.

Re:System Call

Posted: Mon Mar 28, 2005 2:22 am
by gaiety
First, thank you for everyone who answering my question and second sorry as I have a lot of question, and I got one more :-[.

Brendan, now I understand how you code work. Thank you for that. But I am coming with a new question. ;D ;D

Code: Select all

    mov eax,1
    call 0x30:0x00000000 ;Free a page at EBX
    test eax,eax        ;Was there an error?
    je .error            ; yes
This will make a far call to the api call. So the code will be like these

Code: Select all

kernelAPIcallGate:
    cmp eax,MAXAPIfunction
    ja .l1
    call [kernelAPItable + eax * 4]
    retf
.l1:
    mov eax,errFunctionNotDefined
    retf
Let say I am calling the alocate memory with the marco

Code: Select all

APICALL 1
When I am calling the APICALL marco, it is now using user application stack before and when calling(I guess) , then it jump kernelAPIcallGate function. In the kernelAPIcallGate, it will using

Code: Select all

kernelAPItable dd allocpage

call [kernelAPItable + eax * 4]
to call the local API function allocpage, so in the allocpage function, do I need to switch back the stack to the kernel stack and ds or some other thing else that needed(ss:sp and ds).

I hope I don't make tribble mistake again :P :P. And

Thank you for answering my question.

Re:System Call

Posted: Mon Mar 28, 2005 6:37 am
by Brendan
Hi,
gaiety wrote:Let say I am calling the alocate memory with the marco

Code: Select all

APICALL 1
When I am calling the APICALL marco, it is now using user application stack before and when calling(I guess) , then it jump kernelAPIcallGate function. In the kernelAPIcallGate, it will using

Code: Select all

kernelAPItable dd allocpage

call [kernelAPItable + eax * 4]
to call the local API function allocpage, so in the allocpage function, do I need to switch back the stack to the kernel stack and ds or some other thing else that needed(ss:sp and ds).
My code uses the CPU registers, so for allocating a page it'd go something like:

Code: Select all

   mov ebx,linear_address  ;ebx = address for new page
   APICALL 1               ;Call kernel function 1
   test eax,eax            ;Was there an error?
   jne .error              ; yes
Because the CPU's registers are used I don't have to worry about copying values to/from the user level stack (or finding the address of the CPL=3 stack). The CPU automatically changes SS:ESP, and my kernel uses the same DS as user level code (so DS, ES, FS, etc doesn't need to be changed for my OS).

If you wanted to pass parameters on the stack, then you'll need to understand the difference between a "call gate" and a "far call" - they look the same, but they aren't. The main difference is that a call gate can be used to change CPL (a far call can't), and the offset used with a call gate is ignored. For the instruction "call 0x08:0x1234" the CPU will look at the GDT entry 0x08, and if it's a call gate it'll load the offset and CS from the GDT descriptor. If the GDT entry 0x08 is actually a code segment (not a call gate) the CPU will try to load CS with 0x08 and take the offset from the instruction (0x1234 in this case).

A call gate can also be used to automatically copy N values from the caller's stack to the CPL=0 stack - up to 15 values can be transfered as part of the call, where each value is a dword or word depending on the default stack size. Unfortunately this is a "one way" thing and can't be used to return any data. AFAIK this was intentionally designed to support the calling conventions of high level languages.

[continued in next message]

Re:System Call

Posted: Mon Mar 28, 2005 6:57 am
by Brendan
[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