Brendan wrote:
rdos wrote:
It's not. It is just what it looks like. If the tick I would program into the PIT timer is less than the time calculated at boot-time for reloading the PIT, there is no sense in programming the PIT at all, rather it is more sensible to let the timer expire directly, and take the next timer instead.
Translation: If the tick you would program into the PIT timer is less than the minimum PIT delay, there is no sense in programming the PIT at all, rather it is more sensible to
[let the timer expire directly, and] take the next timer instead (which can't happen until after the minimum PIT delay).
The "minimum delay" only exists on older machines. On a modern CPU, it takes much lesser than one PIT tic to program the timer, meaning that the "minimum delay" is one tic.
Brendan wrote:
Translation: If the IRQs happen too late, and there are many timers with short intervalls, then each IRQ will screw up the precision of multiple timers in the same invokation.
If the IRQ happen to late, it is because of interrupt latencies, and you are out of luck with any implementation. I have no idea what is screwed up when timers that are expired are handled directly instead of waiting for an IRQ.
Brendan wrote:
There's only 3 ways to do that:
- screw up effective precision by combining "close" IRQs into one
- poll the PIT's current count and have a lot more overhead that's typically unnecessary
- lose track of the current time and screw everything up
I think you misunderstand the function of the IRQ. The IRQ is there to force an update of timers, not to trigger an event!
Brendan wrote:
For elapsed time (not real time), keep it in its native format. You don't convert something from "tens of a nanoseconds precision" format into a "several hundred nanoseconds precision" format unless your code is crap (but converting the other way is fine - e.g. "tens of nanoseconds precision" converted to "nanosecond precision" format).
For real time (not elapsed time), what matters most is long term accuracy (drift) and overhead (various pieces of software pound the daylights out of "real time", including file systems). Precision doesn't matter much as long as you can get within about 1 ms (and most code only really needs seconds).
I don't agree. There is no reason to separate elapsed time precision from real time precision. The most efficient way is to keep real time and elapsed time in the same format, and only add a constant to elapsed time in order to get real time.
Brendan wrote:
For real time, obviously you wouldn't do this at all if you can rely on HPET's main counter or the ACPI counter or TSC. If you have to use PIT or RTC it's perfectly fine for real time. Do the calculation backwards if you want. If that code costs 100 cycles per IRQ and is running on a slow 500 MHz CPU, and if you want it to consume 0.1% of one CPU's time; then "500000000 * (0.1/100) / 100 = 5000 Hz", which implies you could get 200 us precision out of it without making a significant dent in performance. Of course I'd do something like this calculation during boot, so that faster CPUs get more precision and slower CPUs get less precision but overhead stays about the same.
The only overhead that is needed to synchronize real time with elapsed time is two RTC ints per second. That would adjust for the drift, and doesn't cost any significant overhead even on a 386 processor.
Here is the IRQ handler for global timers:
Code:
irq:
push ds
push es
push fs
pushad
;
mov ax,apic_mem_sel
mov ds,ax
xor eax,eax
mov ds:APIC_EOI,eax ; EOI
;
mov ax,task_sel
mov ds,ax
mov ax,core_data_sel
mov fs,ax
;
GetSystemTime ; read elapsed time in EDX:EAX
call LockTimerGlobal ; take timer spinlock
add eax,cs:update_tics
adc edx,0
mov bx,ds:timer_head
sub eax,ds:[bx].timer_lsb ; check if head timer has expired
sbb edx,ds:[bx].timer_msb
jc timer_expired_reload
timer_expired_lock:
call TryLockCore ; it has expired, so lock scheduler so we cannot be swapped out
timer_expired_remove:
call LocalRemoveTimerGlobal ; call the expired callback (also releases spinlock)
GetSystemTime ; get elapsed time again so we have a fresh expire time
call LockTimerGlobal ; take the spinlock again
add eax,cs:update_tics
adc edx,0
mov bx,ds:timer_head
sub eax,ds:[bx].timer_lsb
sbb edx,ds:[bx].timer_msb
jnc timer_expired_remove ; check if next timer also expired
;
neg eax
ReloadSysTimer ; reload timer hardware with new expire count
jc timer_expired_remove ; if it already expired (HPET), recheck timers
;
call UnlockTimerGlobal ; unlock
call TryUnlockCore
jmp timer_expired_done
timer_expired_reload:
neg eax
ReloadSysTimer ; reload timer hardware with new expire count
jc timer_expired_lock
;
call UnlockTimerGlobal
timer_expired_done:
popad
pop fs
pop es
pop ds
iretd
; code to handle expired timer
LocalRemoveTimerGlobal Proc near
mov bx,ds:timer_head
mov ax,ds:[bx].timer_next
mov ds:timer_head,ax ; unlink
;
push ds
push es
push fs
;
xor eax,eax
mov ax,cs
push eax
mov ax,OFFSET timer_global_return
push eax
mov ax,ds:[bx].timer_sel
push eax
push ds:[bx].timer_offset
;
mov ax,ds:timer_free
mov ds:[bx].timer_next,ax
mov ds:timer_free,bx
;
mov ecx,ds:[bx].timer_id
mov eax,ds:[bx].timer_lsb
mov edx,ds:[bx].timer_msb
call UnlockTimerGlobal ; release the spinlock as we don't want the spinlock to be taken in the callback
;
xor bx,bx
mov ds,bx
mov es,bx
retf32 ; run the callback
timer_global_return:
pop fs
pop es
pop ds
ret
LocalRemoveTimerGlobal Endp
; Request the timer spinlock
LockTimerGlobal Proc near
push ax
ltigSpinLock:
mov ax,ds:timer_spinlock
or ax,ax
je ltigGet
;
sti
pause
jmp ltigSpinLock
ltigGet:
cli
inc ax
xchg ax,ds:timer_spinlock
or ax,ax
jne ltigSpinLock
;
pop ax
ret
LockTimerGlobal Endp
; Release the timer spinlock
UnlockTimerGlobal Proc near
mov ds:timer_spinlock,0
sti
ret
UnlockTimerGlobal Endp