Also gut fangen wir mit dem Minimum an.
Der PC unterstützt kein ACPI und hat keinen APIC.
Für die periodische Zeitquelle nehme ich die RTC und für meinen Scheduler den PIT.
Der RTC wird auf ein Intervall von 62,5ms gestellt (sprich alle 62,5ms wird ein IRQ ausgelöst).
Was wichtig ist, ein Event was (zeitlich gesehen) >= 62,5ms in der Zukunft liegt kommt in die Event-Queue der RTC und ein Event < 62,5ms kommt in die Event-Queue des Schedulers.
(Ich beschreibe erstmal ein einmaliges Event (z.B. ein Sleep))
Innerhalb des IRQ-Codes wird ein Counter um 62500000ns erhöht. Desweiteren wird in der Event-Queue (dort wo alle Events drin sind; hier wird mit absoluten Zeiten gearbeitet) geguckt ob das 1. Event (sprich das was als nächstes kommt) innerhalb einer Zeit < 62,5ms ausgelöst werden muss. Ist das nicht der Fall braucht nichts gemacht zu werden und der IRQ ist fertig.
Ist da aber ein Event was innerhalb einer Zeit < 62,5ms ausgelöst werden muss, wird es aus dieser Queue entfernt und wird an den Scheduler "weitergereicht".
Der Scheduler packt das Event in seine Event-Queue (das ist ne andere als die von der RTC!). Als Zeit wird ab jetzt die relative Zeit genommen (sprich wie lange noch bis das Event auszulösen ist). Auf diese wird die Zeit drauf gerechnet wie lange seit dem letzten Scheduleraufruf vergangen ist (macht die Rechnung nachher einfacher).
Dann macht der Scheduler sein "scheduler-Ding" (es wird ein neuer Thread ausgewählt, der an der Reihe ist).
Da ich einen One-Shot-Scheduler (was so viel heißt das der Scheduler (und damit auch der IRQ) erst wieder aufgerufen wird, wenn der Thread seine Zeitscheibe verbraucht hat oder der Thread abgegeben möchte) implementiert habe, wird nun geguckt was von der Zeit her "kleiner" ist. Also ob das nächste Event zeitiger passiert oder das Ende des Threads.
Der PIT wird dann auf den jeweiligen Wert gesetzt und dieser Wert wird gespeichert (damit ich ihn beim nächsten Scheduler-Aufruf nutzen kann).
Jedes Mal wenn der IRQ des Schedulers aufgerufen wird (also der PIT einen IRQ auslöst oder der Thread den Scheduler aufruft) wird der Counter des PITs ausgelesen und es wird die Differenz zwischen dem ausgelesenen Wert und dem gespeicherten Wert ausgerechnet (um die Zeit zu berechnen die vergangen ist).
Diese Differenz wird dann vom 1. Eintrag in der Event-Queue abgezogen. Ist der Wert gleich "0" oder zu klein (da habe ich noch keinen Wert festgelegt, aber z.B. < 4µs) wird das Event ausgelöst (und aus der Liste gelöscht).
(Jetzt kommen wir zu den periodischen Events, die habe ich noch nicht implementiert, da ich noch nicht alles fertig gedacht habe)
Der Unterschied zu den einmaligen Events ist, das ich soviele Events in die Event-Queue (>= 0; die Event-Queue des Schedulers) packe das genau 1 Event >= 62,5ms in der Zukunft liegt.
Warum will ich das so machen?
Die Genauigkeit des Schedulers ist natürlich bescheiden und damit bekomme ich, gerade bei periodischen Events, keine vernünftige langzeit Genauigkeit hin.
Um diesen Umstand zu kaschieren will ich immer genau 1 Event in der Queue der RTC haben, denn diese hat eine sehr gute langzeit Genauigkeit.
Ist dann der Zeitpunkt erreicht, dass das Event (welches in der Event-Queue der RTC steht) an den Scheduler weitergegeben werden muss, dann packe ich wieder soviele Events in die Queue, das genau 1 in der Queue der RTC "landet".
Im Moment nutze ich für die "simulation" periodischer Events noch viele einmalige Events. Das wird noch "schöner" gemacht (irgendwie
).
Das hört sich jetzt alles erstmal komplizierter an als es eigentlich ist. Denn der Code ist eigentlich gar nicht so kompliziert (dank dessen das ich "nur" ein Interface implementiere).
Zum TSC bin ich ebenfalls etwas verwirrt. Meinst Du wirklich das Intel garantiert das der TSC in allen Kernen (auf dem selben Die oder im gesamten System?) synchron ist? Klar kann man alle Kerne auf einem Die auf den selben Zähler lesend zugreifen lassen aber wenn die Kerne mit unterschiedlichen Taktraten laufen und der TSC noch mal nen anderen Takt hat (welchen eigentlich?) dann kommt es doch zu Jitter und anderen Phänomenen (nebst den Latenzen beim Überführen der Zählerwerte in eine andere Takt-Domäne). Wenn der TSC nicht mehr mit der selben Frequenz wie der eigentliche CPU-Kern läuft dann ist er doch für kurze (taktgenaue) Performancemessungen kleiner Code-Abschnitte kaum noch zu gebrauchen, gerade die enge Kopplung dieses Zählers an die tatsächliche Arbeitsgeschwindigkeit der CPU macht ihn dafür so interessant. Im Prinzip hätte Intel den TSC mit diesem Schritt zu einem weiteren globalen Zähler degradiert (davon gibt es doch nun schon mehr als genug). Wenn man schon so einen globalen Zähler möchte dann hätte Intel dafür besser einen neuen erstellen sollen anstatt den TSC seines besonderen Alleinstellungsmerkmales zu berauben.
Also auf dem gleichen Sockel garantiert das Intel auf jeden Fall. Was sie jetzt geändert haben ist, dass sie nicht mehr sagen an welchen Taktgeber der TSC geknüpft ist. Aber meistens wohl die höchste Taktfrequenz mit der die CPU gestartet wird.
Ich bin mir nicht sicher, aber ich glaube das sie sagen, das die Frequenz mind. der höchsten CPU-Frequenz entspricht. Damit kann man doch sehr gut leben.
Dies wurde vorallem auf Grund von MS und Linux so gemacht (da es halt keinen verlässlichen hoch präzisen Counter gab).
Ich finde es so auch besser, macht dir (als OS Programmierer) das Leben leichter. Vorallem ist so ein Counter möglich der auch noch performant umgesetzt werden kann (komplett im Usermode).