Hi,
ScropTheOSAdventurer wrote:In terms of abstraction, in my HAL, I have "Drivers" and "Interactors." The "Drivers" do the dirty work of dealing with the hardware. The "Interactors" provide an abstraction interface between the kernel and the "Drivers." I created a PIT "Driver" and then added my timer "Interactor" for it, with the latter managing the actual timer system I spoke of. So, I could initialize a timer in my kernel like so:
Yes; but it can be tricky to design an abstraction that suits many different devices. For example; for recent 80x86 CPUs the local APIC timer has a "TSC deadline" mode (and the TSC runs at a fixed frequency that's typically 1 GHz or faster). This is the best possible timing hardware on 80x86 - e.g. it has low overhead, extremely good (1 ns or better) precision, and (for multi-CPU) it solves scalability problems because there's a local APIC timer in each CPU. However, it's very much like "one shot" mode - when the timer's IRQ occurs you set the next deadline.
Now, if you use timers in periodic ("fixed fequency") mode, then it ends up being a compromise between precision and overhead - to improve precision you have to make overhead worse (increase the number of IRQs per second) and to improve overhead you have to make precision worse (decrease the number of IRQs per second) - you can't have both. For example, you could setup the local APIC to generate 1 million IRQs per second (thousands of times more overhead than "TSC deadline mode") and you'd get 1 us precision (a thousand times less precision than "TSC deadline mode").
ScropTheOSAdventurer wrote:The idea you floated (especially with enabling "one shot" mode) sounds enticing to me. I'll take a look at it! Thanks!
There's one thing I probably should mention. For PIT, in "one shot" mode you have to set a new count every time the IRQ occurs, which means doing (relatively slow) IO port writes. If the PIT is in the "low byte then high byte" access mode you'd need to do 2 IO port writes to set the count every IRQ, and (for overhead) this isn't so nice. Basically, if you do 3 IO port writes (2 for PIT count and one for PIC chip EOI) and they cost a total of 3 us of overhead, then the minimum time between IRQs would be 3 us.
Instead, you can put the PIT into "low byte only" access mode or "high byte only" access mode so that you only need to do one IO port write to set the new count. In this case you end up with 2 IO port writes (one to set PIT count and one for PIC chip EOI) and end up with a minimum time between IRQs of 2 us.
For the "low byte only" access mode you get about 838 ns precision out of it, but the maximum count becomes 0x00FF so if no IRQ is needed you'd set the count to max. and end with a maximum time between IRQs of 213 us. This means you get a minimum of 4479 IRQs per second (whether you want the IRQs or not), even though you're using "one shot" mode. For 2 us of overhead every IRQ with a minimum of 4479 IRQs per second, you get a best case of 8958 us of overhead per second. Of course the worst case overhead is an IRQ (2 us of overhead) every 2 us, or 100%.
For the "high byte only" access mode you'd get about 213 us precision out of it, and the maximum count becomes 0xFF00, the maximum time between IRQs is about 54.71 ms. This means you get a minimum of 18.2 IRQs per second (whether you want the IRQs or not), even though you're using "one shot" mode. For 2 us of overhead every IRQ with a minimum of 18.2 IRQs per second, you get a best case of 36.4 us of overhead per second. Because the precision is so much less, the minimum time between IRQs becomes 213 us too; and in that case the worst case overhead is an IRQ (2 us of overhead) every 213 us, or 0.94%.
Basically, the choices are:
- low byte then high byte - 838 us precision, minimum of 18.2 IRQs per second, 3 us minimum time between IRQs, best case of 54.6 us of overhead per second (0.00546%), worst case overhead is 100% (minimum time between IRQs equal to overhead per IRQ)
low byte only - 838 ns precision, minimum of 4479 IRQs per second, 2 us minimum time between IRQs, best case of 8958 us of overhead per second (0.8958%), worst case overhead is 100%
high byte only - 213 us precision, minimum of 18.2 IRQs per second, 213 us minimum time between IRQs, best case of 36.4 us of overhead per second (0.00364%), worst case overhead is 0.94% (minimum time between IRQs much larger than overhead per IRQ)
For comparison, here's a few "fixed frequency" estimates:
- 100 Hz fixed frequency - 10 ms precision, 100 us of overhead per second (0.01%)
1000 Hz fixed frequency - 1 ms precision, 1000 us of overhead per second (0.1%)
4479 Hz fixed frequency - 213 us precision, 4479 us of overhead per second (0.448%)
596591 Hz fixed frequency - 1.676 us precision, 596591 us of overhead per second (59.6%)
Finally, I should mention that for cases where the precision from the timer's IRQ alone isn't adequate it's possible to have a loop that polls the timer's count after the "nearest" IRQ has occured. For example, if you used 1000 Hz fixed frequency and want a 1.9 ms delay, then you could use the timer's IRQ to get a 1 ms delay and then spend 900 us polling the timer's count until the 1.9 ms delay has expired. This approach can get you 838 us precision from the PIT in all cases; where the more precision you get from the timer's IRQ the less time you waste in the polling the timer's count afterwards.
Cheers,
Brendan