Autor Thema: Funktionsaufruf Kernel -> Modul  (Gelesen 24736 mal)

Programm Noob

  • Gast
Gespeichert
« am: 16. August 2010, 07:10 »
Moin

Wie kann ich vom Kernel auch eie Funktion in einem Modul/Programm ausführen. Beim Floppy Treiber müsste der Kernel nämlich auf read und write Funktionen für Sektoren zugreifen können. Meine Erste überlegung, die Funktionen beim laden der elf Datei aus der Symbolgabelle zu lesen und als handler regisgrieren recht unngünstig, da die adressen drch Paging nich passen.
Was ist für mein Problem die Praktikabelste Möglichkeit?
Wie macht Tyndur das?
Programm Noob


kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 16. August 2010, 13:36 »
Es gibt da unterschiedliche Möglichkeiten. Ich zähle mal ein paar auf, aber es gibt sicher noch endlos andere.

Zum einen könntest du das Programm direkt einen Handlercode ausführen lassen. Du müsstest eine Rücksprungadresse (das alte eip) auf den Userspace-Stack packen und eip auf den Anfang der Handlerfunktion setzen (die das Programm vorher per Syscall beim Kernel regsitriert hat). Unter Umständen musst du auch noch Parameter auf den Stack legen. Das ist, wie Unix-Signale funktionieren.

Du kannst stattdessen auch einen neuen Popup-Thread erzeugen, der die Handlerfunktion abarbeitet und sich danach gleich wieder beendet. Beide Ansätze haben das Problem, dass Code unter Umständen an ungünstigen Stellen unterbrochen wird und du irgendeine Art von Synchronisierung brauchst (z.B. RPCs blockieren in tyndur, oder Locking beim Modell mit Threads).

Dann gäbe es auch noch die Möglichkeit, einfach ein bisschen Shared Memory zwischen Kernel und Userspace herzunehmen und als einen Ringpuffer zu benutzen, in den Nachrichten geschrieben werden. Der Userspace würde den Befehl auslesen, die Funktion ausführen und dann auf die nächste Nachricht im Ring warten.

Letztendlich funktioniert also alles, was auch für IPC immer wieder in der Diskussion ist.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Programm Noob

  • Gast
Gespeichert
« Antwort #2 am: 16. August 2010, 19:37 »
Moin

Deine erste Methode ist doch das gleiche wie das was ich mir überlegt habe oder? Aber wie kann ich eip speichern? Das mit dem Unterbrechen von wichtigen operationen. Ich will erstmal nur Treiber ansprechen und da lockt der Managerschon. Da muss ich nichts weiter locken. Gibt es eine andere Möglichkeit

Programm Noob

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 17. August 2010, 17:04 »
Hallo,


Beide Ansätze haben das Problem, dass Code unter Umständen an ungünstigen Stellen unterbrochen wird
Da irrst Du aber etwas. Das ist ja gerade der Vorteil des PopUp-Thread-Konzepts das nichts unterbrochen wird, sondern es wird ein neuer Thread in den User-Mode-Prozess dazugepackt (injiziert) und alle anderen Threads laufen unbehindert weiter.

und du irgendeine Art von Synchronisierung brauchst (z.B. RPCs blockieren in tyndur, oder Locking beim Modell mit Threads).
Das ist allerdings richtig, wobei man aber auch dazuschreiben muss das bei dem Konzept in der Art von Unix-Signalen der Kernel in diese Synchronisierung zwangsläufig mit einbezogen werden muss, er also eventuell eine Message nicht sofort zustellen kann sondern den Sender blockieren muss (was ist wenn der Kernel selber der Sender ist?) bis der gewünschte Empfänger wieder bereit ist. Bei dem PopUp-Thread-Konzept ist das Problem zwar prinzipiell auch vorhanden aber weniger kritisch weil alle prozessinternen Dinge, wie z.B. die Heap-Verwaltungs-Strukturen, über normale Critical-Sections, ohne Kernel-Beteiligung, geschützt werden können (was bei einem Multithreading-Prozess ja eh vorhanden sein muss).

Dann gäbe es auch noch die Möglichkeit, einfach ein bisschen Shared Memory zwischen Kernel und Userspace herzunehmen und als einen Ringpuffer zu benutzen, in den Nachrichten geschrieben werden. Der Userspace würde den Befehl auslesen, die Funktion ausführen und dann auf die nächste Nachricht im Ring warten.
Du meinst Polling?


Hier im Forum wurde ja nun schon einige male gefragt ob man dazu nicht einen Wiki-Artikel machen könnte. Also ich bin dafür und würde auch dazu beitragen. Da könnten dann mal alle möglichen Varianten (ausführlich) erläutert werden. Dazu vielleicht noch eine Liste mit den Vor- und Nachteilen jeder Variante.


Grüße
Erik
« Letzte Änderung: 17. August 2010, 17:08 von erik.vikinger »
Reality is that which, when you stop believing in it, doesn't go away.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 17. August 2010, 18:16 »
Da irrst Du aber etwas. Das ist ja gerade der Vorteil des PopUp-Thread-Konzepts das nichts unterbrochen wird, sondern es wird ein neuer Thread in den User-Mode-Prozess dazugepackt (injiziert) und alle anderen Threads laufen unbehindert weiter.
Alter Erbsenzähler. ;)

Das Problem ist natürlich in beiden Fällen nicht die Unterbrechung an sich (die zumindest auf einem Einprozessorsystem selbstverständlich auch bei Threads vorhanden ist, wenn ich auch mal Erbsen zählen darf), sondern die (Pseudo-)Nebenläufigkeit, mit der man umgehen muss.

Zitat
Dann gäbe es auch noch die Möglichkeit, einfach ein bisschen Shared Memory zwischen Kernel und Userspace herzunehmen und als einen Ringpuffer zu benutzen, in den Nachrichten geschrieben werden. Der Userspace würde den Befehl auslesen, die Funktion ausführen und dann auf die nächste Nachricht im Ring warten.
Du meinst Polling?
Das kann man als Polling ausführen, muss man aber nicht. Sobald du beispielsweise eine Funktion hast, die den Task schlafen legt, bis was neues im Ring auftaucht, ist es kein Polling mehr.

Zitat
Hier im Forum wurde ja nun schon einige male gefragt ob man dazu nicht einen Wiki-Artikel machen könnte. Also ich bin dafür und würde auch dazu beitragen. Da könnten dann mal alle möglichen Varianten (ausführlich) erläutert werden. Dazu vielleicht noch eine Liste mit den Vor- und Nachteilen jeder Variante.
Gern, müsste nur mal jemand anfangen. Und ich glaube, erschöpfend kann man das ganze sowieso nicht behandeln, weil es so viele unterschiedliche denkbare Varianten gibt.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 17. August 2010, 22:18 »
Hallo,


die zumindest auf einem Einprozessorsystem selbstverständlich auch bei Threads vorhanden ist
Ich glaube Du hast mich missverstanden. Ich meinte nicht die Unterbrechung durch vorübergehendes wegnehmen der CPU (Scheduling) sondern das der Signal-Handler irgendetwas komplett unterbricht bis er komplett fertig ist und es eben gar keine Möglichkeit gibt den unterbrochenen Handlungsstrang parallel fortzusetzen. Wenn der Signal-Handler eine Ressource benötigt die der unterbrochene Code-Abschnitt bereits hatte dann hat man einen unüberwindbaren Dead-Lock den man nur dadurch vermeiden kann das man immer wenn man irgendeine Ressource benutzen will die auch ein Signal-Handler benötigen könnte dafür die Signal-Annahme komplett sperren muss. Wie doof das sein kann hast Du selber mir vorgejammert. Ein PopUp-Thread hat mit belegten Ressourcen keine Probleme, er blockiert einfach (wie jeder andere normale Thread auch) bis diese Ressource frei wird, was auch passieren kann weil ja eben nichts hart unterbrochen wurde. Die Möglichkeit von Dead-Locks wie bei den Signal-Handlern ist hier also erst gar nicht gegeben.

sondern die (Pseudo-)Nebenläufigkeit, mit der man umgehen muss
Das ist immer ein Problem aber dafür muss der Programmierer eben etwas Erfahrung mitbringen um potentielle Stolperfallen möglichst frühzeitig zu erkennen.

wenn ich auch mal Erbsen zählen darf
Aber klar doch, solange Du mir das Haarespalten gönnst werde ich Dich auch Erbsen zählen lassen. :-D

Das kann man als Polling ausführen, muss man aber nicht. Sobald du beispielsweise eine Funktion hast, die den Task schlafen legt, bis was neues im Ring auftaucht, ist es kein Polling mehr.
Okay, aber die performanteste Lösung ist das nicht. Außerdem benötigt man im Zweifelsfall eine Menge (schlafender) Threads die alle Speicher belegen. Und dazu einen kompletten Kontext besitzen wogegen die PopUp-Threads mit einen leerem Kontext vielleicht schneller zu starten sind (Okay bei den paar Registern auf x86 ist das Wurscht aber bei den vielen Registern auf meiner CPU ist das durchaus ein relevanter Punkt).


Gern, müsste nur mal jemand anfangen.
Genau das ist das Problem, solange sich keiner traut bleibt die Tanzfläche leer. Ich bin ja der Meinung das hier die Regulars gefragt sind den Anfang zu machen. ;)


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

DerHartmut

  • Beiträge: 236
    • Profil anzeigen
    • Mein Blog
Gespeichert
« Antwort #6 am: 18. August 2010, 09:03 »
Ich mache dann mal den Anfang ;-)
$_="krJhruaesrltre c a cnp,ohet";$_.=$1,print$2while s/(..)(.)//;
Nutze die Macht, nutze Perl ;-)

Programm Noob

  • Gast
Gespeichert
« Antwort #7 am: 18. August 2010, 10:06 »
Das is gut. Vielleicht verstehe ich das Was taljeth und erik.vikinger mir erklährt haben.

Programm Noob

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 18. August 2010, 11:22 »
Zitat
Das kann man als Polling ausführen, muss man aber nicht. Sobald du beispielsweise eine Funktion hast, die den Task schlafen legt, bis was neues im Ring auftaucht, ist es kein Polling mehr.
Okay, aber die performanteste Lösung ist das nicht.
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.

Zitat
Außerdem benötigt man im Zweifelsfall eine Menge (schlafender) Threads die alle Speicher belegen.
Eigentlich reicht einer. Wenn die Arbeit gethreaded sein soll, kann der dann je nach Bedarf weitere Threads erzeugen, die aber nicht schlafend warten. Oder man macht von Anfang an einen Threadpool (das ist wohl, was du dir vorgestellt hast?). Sind jetzt aber eigentlich alles keine sonderlich außergewöhnlichen Konzepte.

Zitat
Und dazu einen kompletten Kontext besitzen wogegen die PopUp-Threads mit einen leerem Kontext vielleicht schneller zu starten sind (Okay bei den paar Registern auf x86 ist das Wurscht aber bei den vielen Registern auf meiner CPU ist das durchaus ein relevanter Punkt).
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.

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. ;)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #9 am: 19. August 2010, 12:37 »
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.

Zitat
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 LDM gibt dann ist CLRM nur konsequent. ;)


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 19. August 2010, 22:08 »
Ich will mich auch mal einmischen ;)

Zitat von: erik
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.
Klingt irgendwie verdächtig nach einem SlabAllocator ;)

Wimre dann macht Windows etwas ähnliches, was du meinst, für die Prozesse. Ich meine irgendwo mal gelesen zu haben, das alle Prozessstrukturen (es werden glaube ich nur 2048 zugelassen), schon vorher alloziert werden und in eine Art "Ring" gepackt werden. Sprich der Speicher wird nie freigegeben, sondern es wird immer an das Ende herangehängt und am Anfang heraus genommen.

Programm Noob

  • Gast
Gespeichert
« Antwort #11 am: 19. August 2010, 22:45 »
Moin

@Flashburn Du mischt dich in letzter Zeit immer in Themen ein, wenn sie auf bestem Wege sind mehr als 20 Antworten zu bekommen. ;)

@all Ich glaube ihr habt meine Bemeekungen, das ich das nicht ganz veratehe und so überlesen. Ich fände es super, wenn einer von euch mal die Methode wo man einen Handler aufruft und eip speichern muss genauererklähren. Ich verstehe zum beispiel nicht warum ich eip speichern muss. Ich Danke demjenigen schonmal im vorraus.

Programm Noob


DerHartmut

  • Beiträge: 236
    • Profil anzeigen
    • Mein Blog
Gespeichert
« Antwort #12 am: 20. August 2010, 09:05 »
Ich glaube, du solltest mal deine Tastatur putzen oder einfach mal deinen geschriebenen Text noch ein- bis zweimal Korrektur lesen, bis du diesen abschickst, denn es ist echt nicht angenehm, so etwas zu lesen ;-)

Was du scheinbar willst ist, dass dir einer so eine Funktion vorschreibt, die du (mehr oder weniger) 1:1 übernehmen kannst.

Falls dem nicht so Fall ist will ich nichts gesagt haben ;-)
$_="krJhruaesrltre c a cnp,ohet";$_.=$1,print$2while s/(..)(.)//;
Nutze die Macht, nutze Perl ;-)

Programm Noob

  • Gast
Gespeichert
« Antwort #13 am: 20. August 2010, 09:58 »
Moim

@DerHartmut Ich möchte das nur genauer erklärt haben.

@SchlechtemText Ich schreibe am Handy und Entschuldige mich vielmals für meine Texte.

Programm Noob

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #14 am: 20. August 2010, 10:04 »
Hallo,


Klingt irgendwie verdächtig nach einem SlabAllocator
Ich habe keine Ahnung was das ist (aber ich vermute die Suchmaschine meines persönlichen Vertrauens wird mir eine Antwort liefern).

Wimre dann macht Windows etwas ähnliches, was du meinst, für die Prozesse. Ich meine irgendwo mal gelesen zu haben, das alle Prozessstrukturen (es werden glaube ich nur 2048 zugelassen), schon vorher alloziert werden und in eine Art "Ring" gepackt werden. Sprich der Speicher wird nie freigegeben, sondern es wird immer an das Ende herangehängt und am Anfang heraus genommen.
Sich vorher auf eine maximale Anzahl an Prozessen festzulegen fänd ich irgendwie doof, vor allem weil bei mir jeder Prozess eine LDT braucht die bis zu 512kByte groß sein kann. Außerdem sind IMHO die Threads das wichtigere, die müssen möglichst schnell angelegt und gelöscht werden können.


Ich glaube ihr habt meine Bemeekungen, das ich das nicht ganz veratehe und so überlesen.
Nein, überlesen hat das wohl keiner, nur mein optischer Decoder hatte gewisse Schwierigkeiten. ;)
Auch am Handy kann man sein Geschreibsel noch mal kurz durchlesen bevor man auf "Schreiben" klickt und den "Vorschau"-Button sollte man ebenfalls nicht ignorieren.

Ich fände es super, wenn einer von euch mal die Methode wo man einen Handler aufruft und eip speichern muss genauererklähren. Ich verstehe zum beispiel nicht warum ich eip speichern muss.
Dieser Methode habe ich keine weitere Aufmerksamkeit gewidmet weil sie meiner persönlichen Meinung nach die ungünstigste aller Varianten darstellt, da wäre aktives Abholen noch deutlich besser. Falls Du Code zur Inspiration suchst wirst Du bei tyndur fündig, dort wird diese Methode benutzt. Das mit dem EIP ist recht simpel: wenn der Signal-Handler aufgerufen werden soll simuliert der Kernel einen CALL an der aktuellen Stelle im Zielprogramm, das heißt es wird der EIP so auf dem Stack gelegt als würde das Programm einen CALL ausführen und in den EIP wird die Adresse des Signal-Handlers gespeichert. Wenn der Scheduler das Programm wieder an die CPU übergibt wird der Signal-Handler ausgeführt und wenn dieser ein RET macht geht es ganz normal an der unterbrochenen Stelle weiter (genau deswegen muss EIP auf den Stack) ohne das der Kernel bemüht werden müsste. In Wirklichkeit müssen aber noch alle anderen Register (+ FPU und alles was der Signal-Handler sonst noch benutzen will) gesichert werden (der unterbrochene Code enthält ja nicht wirklich ein CALL und darf demzufolge keine Registerinhalte verlieren) und die Parameter für den Signal-Handler müssen auch berücksichtigt werden aber das Grundprinzip ist recht simpel.

Ich Danke demjenigen schonmal im vorraus.
Gern geschehen.


Grüße
Erik
« Letzte Änderung: 20. August 2010, 10:09 von erik.vikinger »
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #15 am: 20. August 2010, 10:25 »
Zitat von: noob
@Flashburn Du mischt dich in letzter Zeit immer in Themen ein, wenn sie auf bestem Wege sind mehr als 20 Antworten zu bekommen. Wink
Könnte daran liegen das ich jetzt mehr Zeit habe ;) (und dies ein Interessantes Thema werden könnte/ist)

Zitat von: erik
Ich habe keine Ahnung was das ist (aber ich vermute die Suchmaschine meines persönlichen Vertrauens wird mir eine Antwort liefern).
Also den sollte man kennen und wenn nicht dann schnell darüber informieren ;)

Ich hatte mir letztens mal den Code von Haiku (teilweise) angesehen und wenn ich den richtig verstanden habe, dann werden "gelöschte" Threads in eine Liste der Prozessstruktur gepackt (nennen die "dead thread queue" oder so ähnlich), damit sie dann schneller wieder zur Verfügung stehen, wenn der Prozess das nächste Mal einen Thread erstellt.

Zitat von: erik
Außerdem sind IMHO die Threads das wichtigere, die müssen möglichst schnell angelegt und gelöscht werden können.
Ich kann mich nur wiederholen, SlabAllocator ;) Ist im Endeffekt (vereinfach ausgedrückt) nur ne allgemeinere Variante (also sprich für jede beliebige Art von Objekten) der Methode die du beschreibst um den Speicher der Threadstrukturen nicht immer gleich wieder freizugeben, sondern in einen "Pool" zu packen.

In der aktuellesten Fassung (damit meine ich das letzte Paper was dazu veröffentlich wurde) ist das ganze sogar soweit optimiert wurden, das es verdammt gut mit SMP skaliert (und auf 1-CPU-System auch ein wenig schneller geworden ist).

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #16 am: 22. August 2010, 10:59 »
Hallo,


und dies ein Interessantes Thema werden könnte/ist
Ob aus diesem Thread noch was interessantes wird bezweifle ich persönlich etwas, dafür hab ich den Fürsprechern der Signal-Handler-Variante mal wieder zu deutlich die Nachteile unter die Nase gerieben. :evil: Ich sollte endlich mal lernen nicht alles was ich denke auch so deutlich zu sprechen oder zu schreiben.


Also den sollte man kennen und wenn nicht dann schnell darüber informieren
Hab ich gemacht und muss sagen das ich für einen richtigen SlabAllocator wohl keinen Bedarf in meinem Kernel habe (sowas lohnt sich wohl eher in einem Monolithen), außerdem hab ich keinen BuddyAllocator im Hintergrund (ich will Segmente und nicht Pages als Basis für meine Speicherverwaltung). Mein kmalloc wird sich wohl mit maximal etwa 5 verschiedenen Größen konfrontiert sehen von daher versuche ich andere Wege zu gehen, vielleicht mach ich für jede Objekt-Größe gleich einen eigenen (passend optimierten) Heap und entsprechend viele kmalloc_??? Funktionen.

Ich kann mich nur wiederholen, SlabAllocator ;) Ist im Endeffekt (vereinfach ausgedrückt) nur ne allgemeinere Variante (also sprich für jede beliebige Art von Objekten) der Methode die du beschreibst um den Speicher der Threadstrukturen nicht immer gleich wieder freizugeben, sondern in einen "Pool" zu packen.
Also ich würde eher sagen das der SlabAllocator die Verwaltungsstrukturen des Speicher/Heap-Managements beschreibt (mehrere Objekte gleicher Größe zusammenzufassen so das sich eine Gesamtgröße ergibt die möglichst dich an einem Vielfachen der Page-Größe liegt), der Pool-Effekt ist da eher ein willkommener Zusatznutzen. Was mich etwas abschreckt sind die Fragmentierungsprobleme die sich möglicherweise ergeben können.

In der aktuellesten Fassung (damit meine ich das letzte Paper was dazu veröffentlich wurde) ist das ganze sogar soweit optimiert wurden, das es verdammt gut mit SMP skaliert (und auf 1-CPU-System auch ein wenig schneller geworden ist).
Der Vorteil für SMP ergibt sich vor allem dadurch das ein CPU-lokaler Pool (quasi mehrere kleine L1-Pools vor dem großen globalen L2-Pool) nicht erst gelockt werden muss (vorausgesetzt der Code ist nicht unterbrechbar, was auf meiner Plattform für Kernel-Code immer zutrifft). Das mit dem colorring ist zwar interessant aber ob der Beschleunigungseffekt wirklich so groß ist wage ich etwas zu bezweifeln, vor allem dürfte das nichts mit der TLB-Effizienz zu tun haben da der TLB ja mit Page-Granularität arbeitet.


Grüße
Erik
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #17 am: 22. August 2010, 12:11 »
Zitat von: erik
Hab ich gemacht und muss sagen das ich für einen richtigen SlabAllocator wohl keinen Bedarf in meinem Kernel habe (sowas lohnt sich wohl eher in einem Monolithen), außerdem hab ich keinen BuddyAllocator im Hintergrund (ich will Segmente und nicht Pages als Basis für meine Speicherverwaltung).
Deswegen (Segmente) wohl auch die LDT (das würde es zumindest erklären)?! Zu dem das es sich nur für einen Monolithen-Kernel lohnen würde, kommt ganz drauf an, wie mikro dein Mikrokernel werden soll.

Zitat von: erik
Mein kmalloc wird sich wohl mit maximal etwa 5 verschiedenen Größen konfrontiert sehen von daher versuche ich andere Wege zu gehen, vielleicht mach ich für jede Objekt-Größe gleich einen eigenen (passend optimierten) Heap und entsprechend viele kmalloc_??? Funktionen.
Naja, aber falls du dann in Zukunft doch mal noch was hinzufügen willst, würde sich ein allgemeiner Allocator schon anbieten.

Ich nutze bei 2 Sachen auch gleichartigen Code, der sich nur in ein paar Konstanten unterscheidet und jedes Mal wenn ich nen Fehler finde, darf ich in der anderen Funktion den Fehler auch korrigieren und das ist leider sehr fehleranfällig :(

Zitat von: erik
Der Vorteil für SMP ergibt sich vor allem dadurch das ein CPU-lokaler Pool (quasi mehrere kleine L1-Pools vor dem großen globalen L2-Pool) nicht erst gelockt werden muss (vorausgesetzt der Code ist nicht unterbrechbar, was auf meiner Plattform für Kernel-Code immer zutrifft).
Der Code muss gelockt werden, aber so dass du immer den Lock bekommst (es geht im Endeffekt nur darum, das die Ints ausgeschaltet werden, ob das nen Lock ist, darüber lässt sich jetzt streiten).

Zitat von: erik
Das mit dem colorring ist zwar interessant aber ob der Beschleunigungseffekt wirklich so groß ist wage ich etwas zu bezweifeln, vor allem dürfte das nichts mit der TLB-Effizienz zu tun haben da der TLB ja mit Page-Granularität arbeitet.
Beim Coloring ging es glaub ich eher um Cachelines und das hatte nichts mit dem TLB zu tun, aber ich nutze es auch nicht (war mir zu kompliziert es zu implementieren).

Zumal ich den SlabAllocator nicht 1:1 übernommen habe.

@back2topic

Ich muss zugeben, dass ich den "vermeintlichen" Vorteil eines Signal-Handlers (noch) nicht verstanden habe. (Um späteren Problemen aus dem Weg zu gehen, ich bin eh kein Fan vieler Unix-Vorgehensweisen ;) ) Für mich überwiegen eher die Probleme und Nachteile.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 22. August 2010, 12:17 »
Ich nutze bei 2 Sachen auch gleichartigen Code, der sich nur in ein paar Konstanten unterscheidet und jedes Mal wenn ich nen Fehler finde, darf ich in der anderen Funktion den Fehler auch korrigieren und das ist leider sehr fehleranfällig :(
Ich bin mal so frei, festzuhalten was du sowieso schon weißt: Das ist dann kaputter Code. Wenn du weißt, dass du irgendwo Codeduplikation hast, solltest du dich ranmachen, sie zu eliminieren. Wenn sich nur ein paar Konstanten unterscheiden, gib die Konstanten als Parameter rein und mach die zwei unterschiedlichen Varianten als Wrapper. Wenn du dich um die Performance sorgst, weis den Compiler an, dass er auf jeden Fall inlint.

Zitat von: erik
Ich muss zugeben, dass ich den "vermeintlichen" Vorteil eines Signal-Handlers (noch) nicht verstanden habe. (Um späteren Problemen aus dem Weg zu gehen, ich bin eh kein Fan vieler Unix-Vorgehensweisen ;) ) Für mich überwiegen eher die Probleme und Nachteile.
Keine Ahnung, hat irgendjemand behauptet, diese Art wäre toll?
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #19 am: 22. August 2010, 13:01 »
Hallo,


kommt ganz drauf an, wie mikro dein Mikrokernel werden soll.
Der soll schon richtig Micro werden.

Ich nutze bei 2 Sachen auch gleichartigen Code, der sich nur in ein paar Konstanten unterscheidet und jedes Mal wenn ich nen Fehler finde, darf ich in der anderen Funktion den Fehler auch korrigieren und das ist leider sehr fehleranfällig
Das ist wirklich sehr ungeschickt. Eine der wichtigsten Regeln bei der SW-Entwicklung lautet "Nichts, aber auch wirklich gar nichts, darf es doppelt geben!". Jag den einen Quell-Code 2 mal durch den Compiler, jeweils mit einem unterschiedlichen Define per -D oder nutze sed (das mach ich bei meinem Assembler, ich will ja nicht für ADD, SUB, SUBR, AND, NAND, OR, NOR und XOR alles mehrfach tippen).

Der Code muss gelockt werden, aber so dass du immer den Lock bekommst (es geht im Endeffekt nur darum, das die Ints ausgeschaltet werden, ob das nen Lock ist, darüber lässt sich jetzt streiten).
Auf meiner CPU ist Kernel-Code immer ununterbrechbar (meine CPU ist entweder im User-Mode und von dort kann per Syscall, Exception oder Interrupt in den System-Mode gewechselt werden oder sie ist bereits im System-Mode und dann geht nichts davon, nur per RFS (Return-From-Systemmode) zurück in den User-Mode, wenn im System-Mode doch mal ne Exception auftreten sollte dann geht die CPU in den Double-Error-Panic-Shutdown und braucht externe Hilfe von einer anderen CPU).

Beim Coloring ging es glaub ich eher um Cachelines und das hatte nichts mit dem TLB zu tun, aber ich nutze es auch nicht (war mir zu kompliziert es zu implementieren).
Das wirkt aber nur dann (richtig) wenn die Inhalte auch auf Cache-Line-Größe ausgerichtet sind und dieser Effekt sollte mit zunehmender Assoziativität (Anzahl der Cache-Wege) abnehmen.


@back2topic

Ich muss zugeben, dass ich den "vermeintlichen" Vorteil eines Signal-Handlers (noch) nicht verstanden habe. (Um späteren Problemen aus dem Weg zu gehen, ich bin eh kein Fan vieler Unix-Vorgehensweisen ;) ) Für mich überwiegen eher die Probleme und Nachteile.
Der Vorteil von Signal-Handler ist das sie relativ einfach zu implementieren sind und man keine Threads benötigt (Linux und viele andere Unixe auch können Threads noch gar nicht so lange). Die klassischen Unix-Signal-Handler sind nützlich wenn ein Programm z.B. noch was machen will wenn der User CTRL-C drückt aber als richtiges IPC sind sie denkbar ungeeignet.



Keine Ahnung, hat irgendjemand behauptet, diese Art wäre toll?
Aus irgendeinem Grund wurde dieser Mechanismus mal in tyndur eingebaut, also irgendetwas muss ja daran toll (gewesen) sein.  SCNR


Grüße
Erik
« Letzte Änderung: 22. August 2010, 13:06 von erik.vikinger »
Reality is that which, when you stop believing in it, doesn't go away.

 

Einloggen