system calls
-
- Member
- Posts: 97
- Joined: Thu Mar 15, 2007 2:27 pm
system calls
Is there any way other than interrupts to return control to the kernel for a system call?
Well Intel and AMD each have their processor specific "fast" system calls, which work if your idea of memory layout is effectively disabling segments (base 0, limit 4GB segments, one pair kernel, one pair user), so if you use those, you could implement support...
But then again, you probably want to have the interrupt-driven path anyway as a fall-back. Plus since you need interrupt support anyway for dealing with exceptions and hardware interrupts, adding one gate for a system call doesn't make much difference.
I'd say, implement interrupts first, worry about the other methods later..
But then again, you probably want to have the interrupt-driven path anyway as a fall-back. Plus since you need interrupt support anyway for dealing with exceptions and hardware interrupts, adding one gate for a system call doesn't make much difference.
I'd say, implement interrupts first, worry about the other methods later..
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
Re: system calls
On the x86 architecture you can use call-gates.anon19287473 wrote:Is there any way other than interrupts to return control to the kernel for a system call?
Software interrupts aren't masked by IF, so you can use interrupts for system calls whether or not you have hardware interrupts enabled.pcmattman wrote:What's wrong with interrupts? The only problem I can think of is needing interrupts enabled (which they won't be during an ISR).
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
Re: system calls
Hi,
Software interrupts - short (2 byte) instruction but not the fastest way. Works on all CPUs.
Call gates - longer instruction (7 bytes?) but slightly faster than software interrupts. Works on all CPUs.
Exceptions - instruction length depends on which exception and can be as small as one byte (for e.g. "INT3"). Speed also depends on which exception - slightly slower that software interrupts in general.
SYSCALL - the call itself requires some messing about (i.e. setting return address before use). Is faster, but also only works on some AMD 32-bit CPUs. Works all 64-bit code on any 64-bit CPU, but Intel doesn't support it for 32-bit code running under a 64-bit OS.
SYSENTER - the call itself requires some messing about (i.e. setting return address before use). Is faster, but also only works on some (Intel and AMD) 32-bit CPUs. For 64-bit CPUs Intel support it for 32-bit code and 64-bit code, while AMD support it for 32-bit code only.
Of these options I'd avoid using exceptions unless it's a special purpose API (I've been using INT3 for a "Boot API" in real mode, where no other exception handlers are setup).
For 64-bit code I'd use SYSCALL.
For 32-bit code (including 32-bit code running on a 64-bit OS) I'd support all of the methods above (except for exception handlers) so that code can use software interrupts (if optimised for size), or call gates (if optimized for speed), or SYSCALL or SYSENTER. For systems that don't support SYSCALL and SYSENTER I'd emulate them in the undefined opcode handler, so that they always work (but may be slow), and I'd provide a way for software to determine if they are supported by hardware or not.
Inside the kernel I'd have a call table that is used by everything. For (a rough and broken) example:
This means the kernel itself won't care which mechanism is used - it just has functions in the call table that use "RET" to return to whichever handler was used.
Cheers,
Brendan
There's 5 methods...anon19287473 wrote:Is there any way other than interrupts to return control to the kernel for a system call?
Software interrupts - short (2 byte) instruction but not the fastest way. Works on all CPUs.
Call gates - longer instruction (7 bytes?) but slightly faster than software interrupts. Works on all CPUs.
Exceptions - instruction length depends on which exception and can be as small as one byte (for e.g. "INT3"). Speed also depends on which exception - slightly slower that software interrupts in general.
SYSCALL - the call itself requires some messing about (i.e. setting return address before use). Is faster, but also only works on some AMD 32-bit CPUs. Works all 64-bit code on any 64-bit CPU, but Intel doesn't support it for 32-bit code running under a 64-bit OS.
SYSENTER - the call itself requires some messing about (i.e. setting return address before use). Is faster, but also only works on some (Intel and AMD) 32-bit CPUs. For 64-bit CPUs Intel support it for 32-bit code and 64-bit code, while AMD support it for 32-bit code only.
Of these options I'd avoid using exceptions unless it's a special purpose API (I've been using INT3 for a "Boot API" in real mode, where no other exception handlers are setup).
For 64-bit code I'd use SYSCALL.
For 32-bit code (including 32-bit code running on a 64-bit OS) I'd support all of the methods above (except for exception handlers) so that code can use software interrupts (if optimised for size), or call gates (if optimized for speed), or SYSCALL or SYSENTER. For systems that don't support SYSCALL and SYSENTER I'd emulate them in the undefined opcode handler, so that they always work (but may be slow), and I'd provide a way for software to determine if they are supported by hardware or not.
Inside the kernel I'd have a call table that is used by everything. For (a rough and broken) example:
Code: Select all
API_softwareInterruptHandler:
call [API_callTable + eax * 4]
iretd
API_callGateHandler:
call [API_callTable + eax * 4]
retf
API_sysCallHandler:
call [API_callTable + eax * 4]
sysret
API_sysEnterHandler:
call [API_callTable + eax * 4]
sysexit
undefined_opcode_exception_handler:
if( user code caused exception) {
switch( opcode ) {
case WAS_SYSCALL:
call [API_callTable + eax * 4]
iretd
case WAS_SYSENTER:
call [API_callTable + eax * 4]
iretd
default:
criticalError(UNDEFINED_OPCODE, USER_MODE):
} else {
criticalError(UNDEFINED_OPCODE, KERNEL_MODE):
}
Cheers,
Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Yes, really. Same as exceptions. If you think of it, it makes perfect sense that way. The purpose of the IF-flag is to prevent interrupts when the currently running code can't deal with them. You might not have a valid IDT, or you might not have a valid stack (say when you are switching threads in scheduler). Or you might want to prevent a new interrupt when you've just unmasked PIC but not yet returned, so that you don't end up growing stack by unnecessarily nesting interrupts. Life is also greatly simplified if you can avoid interrupts when you are doing processor synchronization (spinlocks) or some other such situation.pcmattman wrote:Really?
That's something I wish I had have known before...
So there's a flag in processor to prevent hardware from interrupting the CPU. There's ofcourse NMI which can't be masked with a simple flag, but you're supposed to be able to deal with NMI at almost any moment, since they are supposedly used to signal hardware failures. Safe to ignore for this discussion.
Now, software interrups and exceptions are never asynchronous: they only occur when the CPU itself does something that causes such a condition to be raised. You can always avoid exceptions (at least theoretically) by checking all the same conditions that processor would check, or simply by avoiding the conditions in the first place. Software interrupts only get raised when an explicit INTn, INTO, INT3 or BOUNDS is executed. As such both exceptions and software interrupts are perfectly predictable, so masking them would be pointless. You might ofcourse want to control whether ring3 code can raise a certain interrupt, but that's what the RPL in IDT is for, and such control needs to be separate from hardware interrupt control anyway, because you want hardware interrupts even in ring3 code which isn't allowed to trigger the same interrupts with INTn.
If you think about it this way, isn't it perfectly logical, after all.
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.