Hi,
gaiety wrote:
I am ready a newbie, I don't know how to see and link the code of Brendan
The OS has "function numbers", not unlike DOS's "int 0x21" or Linux's "int 0x80". The function number is always passed in the EAX register. There's 4 different "system call mechanisms" supported by the kernel, each with different size/speed tradeoffs.
Each of the 4 different mechanisms use the same call table inside the kernel, called "APItable". This table is created like this:
Code: Select all
APItable:
dd function0x000
dd function0x001
dd function0x002
dd function0x003
..etc
A single bit of software could call the same kernel function 4 times using a different mechanism/macro each time. For example:
Code: Select all
APIINT 0x0123
APICALL 0x0123
APISYSENTER 0x0123
APISYSCALL 0x0123
For the software interrupt, a programmer would write "APIINT 0x0123" to use kernel function number 0x0123. This macro gets expanded into "mov eax,0x0123" followed by "int 0x20". The software interrupt is handled by code in the kernel that looks like:
Code: Select all
processAPIsoftInt:
cmp eax,0x200 ;Is function number out of range?
jae .badFunction ; yes, error
call [APItable+eax*4]
iretd
.badFunction:
mov eax,errUndefined
iretd
Basically, if the function number is too high for the table it returns an error. Otherwise it does the "call [APItable + eax*4]" to call the correct routine for the function number.
The "call gate mechanism" is almost identical, except it uses a call gate instead of a software interrupt. While they might look complicated, the "syscall" and "sysenter" mechanisms also work the same.
Unfortunately, the SYSCALL and SYSENTER instructions skip some important steps (like storing a return address), so they need extra code to make up for the stuff they skip. That's why they look complicated (and why I'm not convinced they are faster once you add the overhead of the extra code they need).
For example, if software used "APISYSENTER 0x0123" (instead of "APIINT 0x0123") then the macro would expand to:
Code: Select all
push edx
push ecx
mov eax,0x0123
mov edx,.here
mov ecx,esp
sysenter
.here:
pop ecx
pop edx
This is because the SYSENTER instruction doesn't store any return address or return ESP, and it would be impossible to return to the caller properly without them (the SYSEXIT instruction expects them to be in EDX and ECX, but the SYSENTER instruction doesn't put them in these registers).
The SYSCALL and SYSRET instructions are worse because it doesn't switch to the kernel's stack (which complicates memory management code), it disables interrupts (where my kernel is fully interruptable and re-entrant) and it also messes up CPL=3 segment limits (I haven't found a good work-around for this yet).
Anyway, the idea is that software can detect what is supported and use the fastest method where practical (and/or software can be compiled specifically for a specific CPU). For example, if you're optimizing you might find that the smaller software interrupt method actually works faster on a specific CPU because it's not thrashing the CPU's trace cache, or that the call gate method is faster than sysenter and/or syscall because of the extra instructions sysenter/syscall need, or that sysenter and/or syscall actually is faster. Of course for initialization code that is only ever run once, I'd recommend the software interrupt method anyway (smaller code where performance is irrelevant).
There was also a bug in the code I posted - I missed an instruction in the macro for SYSENTER. It should've been:
Code: Select all
%macro APISYSENTER 1
CPU 686
push edx
push ecx
mov eax,%1
mov edx,%%l1
mov ecx,esp
sysenter
%%l1: CPU 486
pop ecx
pop edx
%endmacro
Hope this makes it a little clearer...
This is only one possible way to do things though - you could design your OS to use newer AMD chips only (ie. SYSCALL/SYSRET only, which would make it much faster), or use software interrupts only (e.g. DOS), or do what Windows does (software does near call to a fixed address that contains the actual system call instructions followed by a near return).
The problem with Window's method is that the near call is going to cost more than you'd gain - 4 cycles for the near call plus 5 cycles for syscall (assuming you can avoid the extra baggage, which I doubt) plus 4 cycles for the near return adds up to about the same as a call gate anyway [note: I don't know exact cycle counts, this is just an estimate].
Cheers,
Brendan