Hallo,
Ich mache dann mal den Anfang
Ja Bitte.
Hm, was ist dabei das Performance-Problem? Viel direkter geht es doch nicht mehr als die Parameter gleich in den Ring zu schreiben und einen schon wartenden Task loslaufen zu lassen.
Wenn dieser Thread wirklich bereits wartet ist das natürlich sehr direkt aber was ist wenn er momentan noch was anderes macht? Aktives Abholen hat meiner persönlichen Meinung nach zu viele Nachteile.
Außerdem benötigt man im Zweifelsfall eine Menge (schlafender) Threads die alle Speicher belegen.
Eigentlich reicht einer.
Für einen einzelnen Service der nur wenig frequentiert wird ja, aber denke mal an ein komplettes Micro-Kernel-System wo jedes gemountete Dateisystem und jedes verfügbare Block-Device einen Service anbieten, TCP/UDP/IP-Services auf Wünsche warten und noch ne Menge mehr läuft. Was ist mit asynchronem I/O, z.B. ein Web-Server der viele Anfragen unterschiedlich schneller Clients gleichzeitig bedienen können soll? Ich finde es da deutlich besser wenn der OS-Kernel genau so viele Threads erzeugt wie auch tatsächlich benötigt werden. Außerdem kann ein Thread, der Anfragen aktiv holen muss, auch immer nur eine Anfrage gleichzeitig bearbeiten. Bei meinem Konzept ist es vorgesehen das pro angebotenen Service auch mehrere Threads (bis zu einem vorgegebenen Limit) gestartet werden dürfen. Wenn der PopUp-Thread dann noch die Priorität des zugehörigen anfragenden Clients erbt können die parallel Anfragen sogar entsprechend ihrer Priorität bearbeitet werden (eine spätere Anfrage mit hoher Priorität könnte die bereits laufende Bearbeitung einer früheren Anfrage mit niedriger Priorität verdrängen), vorausgesetzt man hat einen Scheduler der Prioritäten kennt.
Wenn die Arbeit gethreaded sein soll, kann der dann je nach Bedarf weitere Threads erzeugen, die aber nicht schlafend warten.
Das CreateThread() ist aber wieder ein zusätzlicher Syscall (ja ich weiß das ich auch gerne Erbsen zähle) und bis der neue Thread dann mit seiner eigentlichen Arbeit beginnt vergeht auch noch etwas Zeit, da empfinde ich persönlich das PopUp-Thread-Konzept doch etwas direkter.
Oder man macht von Anfang an einen Threadpool (das ist wohl, was du dir vorgestellt hast?).
Ich wollte keinen prozesslokalen Thread-Pool, wie das manche (User-Space-)Librarys machen, sondern die eigentliche CreateThread-Funktion im Kernel, die auch vom IPC-Subsystem benutzt wird, holt sich Stacks und Thread-Descriptor-Structs aus zwei vorallozierten Speicher-Pools so das die 2 malloc entfallen können und nur der Stack in die LDT des gewünschten Prozesses und das Thread-Descriptor-Struct in dessen Prozess-Verwaltung integriert werden müssen. Ich hoffe damit das CreateThread sehr billig machen zu können, nur wenn der Pool leer ist wird tatsächlich Speicher alloziert aber dann gleich für X Threads auf ein mal. Jedes mal wenn ein KillThread() aufgerufen wird wird der Stack und das Thread-Descriptor-Struct nicht freigegeben sondern erstmal in den Pool zurückgelegt (wirklich freigegeben wird der Speicher erst wenn der Pool sehr voll ist), auf einem gleichmäßig belastetem System dürfte damit für IPC kaum neuer Speicher für Threads alloziert werden müssen. Damit wäre es auf einem SMP-System sogar möglich das auf zwei CPUs (von Threads aus unterschiedlichen Prozessen) gleichzeitig zwei RPC-Anfragen an den
selben Service gestellt werden, im Kernel gleichzeitig zwei neue Threads erstellt und in den Service-Prozess eingebunden werden und dann auch gleichzeitig loslaufen. Nur das Entnehmen vom Speicher-Pool (jeweils ein Stack-Segment und ein Thread-Descriptor-Struct) und das Einhängen in den Service-Prozess müssen kurz serialisiert werden, in der kernelinternen CreateThread-Funktion.
Sind jetzt aber eigentlich alles keine sonderlich außergewöhnlichen Konzepte.
Nunja, das Konzept mit den PopUp-Threads wurde meines Wissens nach noch nie umgesetzt (ich hab jedenfalls nichts gefunden) sondern nur theoretisch beschrieben. Aber in die Kategorie "Rocket-Science" fällt wirklich nichts davon.
Was genau verstehst du in diesem Zusammenhang unter Kontext? Wenn du meinst, nur den halben Registerzustand zu initialisieren, das lohnt sich doch nur bei wirklich sehr kurzlebigen Threads.
Da bei einem echten Micro-Kernel ja auch Anfragen der Art "wie viele Bytes kann ich noch von der Pipe lesen ohne zu blockieren?" per IPC gemacht werden müssen ist es schon wichtig das IPC möglichst schnell geht. IPC ist in einem Micro-Kernel die
zentrale Kernfunktionalität, da darf man sich doch wohl noch bemühen auch einzelne Takte zu sparen. Bei einem frisch loslaufenden PopUp-Thread muss gar kein Kontext aus dem Speicher geladen werden da die benötigten Registerwerte (Funktions-Parameter für die Handler-Funktion) ja schon in Registern vorliegen (als Parameter dem Syscall mitgegeben).
Und außerdem, um zum Haarespalten zurückzukommen und es mit ein bisschen Paranoia aufzuwerten, was ist mit der Sicherheit? In Registern könnten gerade bei einem sehr großen Registersatz Werte rumliegen, die das aufgerufene Programm nichts angehen.
Das ist natürlich ein sehr berechtigter Einwand, aber Register auf 0 zu setzen dürfte immer noch schneller gehen als Register aus dem Speicher zu laden.
Vielleicht mache ich noch einen extra CPU-Befehl um mehrere Register auf einmal zu löschen, wenn es ein LD
M gibt dann ist CLR
M nur konsequent.
Grüße
Erik