Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: Programm Noob 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
-
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.
-
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
-
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
-
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.
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.
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.
-
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
-
Ich mache dann mal den Anfang ;-)
-
Das is gut. Vielleicht verstehe ich das Was taljeth und erik.vikinger mir erklährt haben.
Programm Noob
-
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.
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.
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. ;)
-
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 LDM gibt dann ist CLRM nur konsequent. ;)
Grüße
Erik
-
Ich will mich auch mal einmischen ;)
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.
-
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
-
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 ;-)
-
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
-
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
-
@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)
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.
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).
-
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
-
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.
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 :(
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).
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.
-
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.
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?
-
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
-
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.
Das ist leider nicht so einfach :( (wenn ich dir jetzt sage warum, dann werde ich hier geköpft ;) )
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).
So einfach ist das wiederrum nicht. Es ist ungefähr so wie du das mit deinem kmalloc machen willst. Der Code ist in meinem VMM und ich nutze für den Kernel und den UserSpace unterschiedliche Datenstrukturen und die müssen (leider), zwecks Huhn-Ei-Problem, mit extra Code behandelt werden und dort sind halt die Funktionsalgorithmen (mehr oder weniger) gleich, aber es gibt halt Unterschiede die extra behandelt werden müssen.
Die einzige Möglichkeit wäre die For-Schleife in eine Funktion auszulagern, aber ganz ehrlich, ne For-Schleife in eine extra Funktion, das finde ich dann schon overkill.
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).
Das finde ich wiederum nicht so toll. Dann sollte dein Kernel wirklich sehr sehr mikro sein ;)
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.
Aber man muss auch aufpassen was man in so einem Handler alles macht, da es bestimmt nicht gut ist, wenn gerade malloc läuft und von einem Signal-Handler unterbrochen wird, der dann wiederum malloc aufruft.
Was mich noch interessieren würde, kann man die irgendwie durch Messages emulieren (zwecks POSIX-Kompatibilität)?
-
Das ist leider nicht so einfach :( (wenn ich dir jetzt sage warum, dann werde ich hier geköpft ;) )
Das müssen wir dann leider in Kauf nehmen. ;)
Was mich noch interessieren würde, kann man die [Signale] irgendwie durch Messages emulieren (zwecks POSIX-Kompatibilität)?
Nicht wirklich, Signale müssen eigentlich sofort ankommen (am offensichtlichsten dürfte es bei SIGSEGV sein).
-
Hallo,
Signale müssen eigentlich sofort ankommen (am offensichtlichsten dürfte es bei SIGSEGV sein).
Warum sofort? Wenn der Thread welcher SIGSEGV ausgelöst hat erstmal geblockt ist (so das der Scheduler ihn nicht aktivieren möchte) dann besteht doch keine so große Eile. Selbst andere Threads in diesem Prozess sollten durchaus normal weiterarbeiten können solange sie nicht den selben Fehltritt ausführen.
So einfach ist das wiederrum nicht. Es ist ungefähr so wie du das mit deinem kmalloc machen willst. Der Code ist in meinem VMM und ich nutze für den Kernel und den UserSpace unterschiedliche Datenstrukturen und die müssen (leider), zwecks Huhn-Ei-Problem, mit extra Code behandelt werden und dort sind halt die Funktionsalgorithmen (mehr oder weniger) gleich, aber es gibt halt Unterschiede die extra behandelt werden müssen.
Scheint ein SW-Design-Problem zu sein. Klingt vielleicht ziemlich arrogant aber wenn Du ein OS programmieren möchtest solltest Du fähig sein soetwas sauber zu lösen. Du muss auch über den Fall nachdenken wenn der User-Code absichtlich etwas falsch macht. Bringt das Deinen Kernel in Gefahr?
Die einzige Möglichkeit wäre die For-Schleife in eine Funktion auszulagern, aber ganz ehrlich, ne For-Schleife in eine extra Funktion, das finde ich dann schon overkill.
Pack diese Funktion, mit der For-Schleife, in 2 verschiedene H-Dateien und includiere jeweils die richtige, wenn die Funktion static ist und nur ein einziges mal benutzt wird wird der Compiler wohl eh inlinen. Wenn Du lieber 2 fertige leicht unterschiedliche C-Dateien haben möchtest dann generiere sie Dir doch per sed, das kann ich wirklich nur wärmstens empfehlen. Mit ein wenig Magie im Make-File geht das sogar automagisch wenn sich die eine Vorlage oder die sed-Steuerungsdateien geändert haben. Ich habe sed schon oft für derartige Probleme benutzt und war damit immer sehr zufrieden.
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).
Das finde ich wiederum nicht so toll. Dann sollte dein Kernel wirklich sehr sehr mikro sein ;)
Warum? Was genau ist daran nicht so toll wenn ein Micro-Kernel nicht unterbrechbar ist? Ich empfinde das als Vorteil, das schließt viele Fehlerquellen von vornherein zuverlässig aus. Ein reinrassiger Micro-Kernel hat mit den eigentlichen User-Space-Daten nichts zu tun er muss sie nur verwalten. Und wenn er doch irgendwo rein sehen will muss er vorher prüfen ob das gefahrlos möglich ist, auf x86 gibt es doch auch VERR und VERW um zu prüfen wo man hingreifen darf und wo nicht, das wird es bei mir in ähnlicher Form auch geben.
Aber man muss auch aufpassen was man in so einem Handler alles macht, da es bestimmt nicht gut ist, wenn gerade malloc läuft und von einem Signal-Handler unterbrochen wird, der dann wiederum malloc aufruft.
Oh ja! Das ist es ja was taljeth mir hier im Forum (http://forum.lowlevel.eu/index.php?topic=2321.msg26297#msg26297 (http://forum.lowlevel.eu/index.php?topic=2321.msg26297#msg26297), ließ Dir am besten den ganzen Thread durch) vor ner Weile "vorgejammert" hat. Für Signale wie SIGSEGV ist das Konzept ganz brauchbar aber für richtiges IPC oder gar synchrones RPC (was wohl die überwiegende Mehrheit aller IPC-Nutzungen darstellt) ist es extrem ungeeignet (hier http://forum.lowlevel.eu/index.php?topic=2466 (http://forum.lowlevel.eu/index.php?topic=2466) wurden mal die Nachteile verbildlicht). In tyndur kann sogar während eines laufenden Signal-Handlers ein neuer aufgerufen werden, das ist dann ein LIFO-Prinzip das geradezu danach schreit für DoS-Attaken benutzt zu werden.
Was mich noch interessieren würde, kann man die irgendwie durch Messages emulieren (zwecks POSIX-Kompatibilität)?
Ich habe vor das in meiner libc zu emulieren. Es soll bei mir einen allgemeinen prozess-globalen asynchronen Signal-Handler geben, der mit der Prozess-ID als Target angesprochen werden kann und der nur dazu dient allgemeine Informationen vom Kernel und ähnliches entgegen zu nehmen, welcher dann in der libc den registrierten Handler aufruft. Falls der betreffende Thread nicht eh schon deaktiviert ist, wie es bei SIGSEGV sein sollte, dann wird eben einer pausiert und nachdem die eigentliche User-Signal-handler-Funktion zurückkommt wieder reaktiviert. Wenn es keinen "schuldigen" Thread gibt, wie z.B. wenn der User CTRL-C drückt, dann wird eben der Haupt-Thread (mit welchen main aufgerufen wurde) vorübergehend abgeklemmt (falls das überhaupt nötig ist). Ob das so auch wirklich immer funktioniert weiß ich nicht genau, ich hab da erst gerade eben kurz drüber nachgedacht, aber für die meisten Anwendungsfälle klassischer Unix-Signale (so häufig werden die wohl nicht benutzt) sollte es schon reichen.
Grüße
Erik
-
Signale müssen eigentlich sofort ankommen (am offensichtlichsten dürfte es bei SIGSEGV sein).
Warum sofort? Wenn der Thread welcher SIGSEGV ausgelöst hat erstmal geblockt ist (so das der Scheduler ihn nicht aktivieren möchte) dann besteht doch keine so große Eile. Selbst andere Threads in diesem Prozess sollten durchaus normal weiterarbeiten können solange sie nicht den selben Fehltritt ausführen.
Ich weiß, dass du ganz gern den kompliziertest möglichen Fall betrachtet, aber nehmen wir doch zur Abwechslung einfach mal einen ganz einfachen Prozess, der keine weiteren Threads hat. Und "sofort" ist natürlich aus Prozesssicht gemeint, nicht zeitbezogen. Was du in der Tat machen kannst, ist den Thread anzuhalten, einen neuen Thread für den Signalhandler anzulegen und dort weiterzumachen. Aber damit hast du keinen Vorteil gewonnen und machst nur eventuelles Exceptionhandling (z.B. per longjmp) kaputt.
Aber das war eigentlich auch nicht der Hintergrund meiner Antwort, sondern die Frage, ob man das mit Message Passing emulieren kann. Und Message Passing erfordert es, dass irgendjemand die Nachricht abholt. Dazu ist es unter Umständden vorteilhaft, wenn derjenige noch nicht abgestürzt ist. ;)
-
Scheint ein SW-Design-Problem zu sein. Klingt vielleicht ziemlich arrogant aber wenn Du ein OS programmieren möchtest solltest Du fähig sein soetwas sauber zu lösen. Du muss auch über den Fall nachdenken wenn der User-Code absichtlich etwas falsch macht. Bringt das Deinen Kernel in Gefahr?
Pack diese Funktion, mit der For-Schleife, in 2 verschiedene H-Dateien und includiere jeweils die richtige, wenn die Funktion static ist und nur ein einziges mal benutzt wird wird der Compiler wohl eh inlinen. Wenn Du lieber 2 fertige leicht unterschiedliche C-Dateien haben möchtest dann generiere sie Dir doch per sed, das kann ich wirklich nur wärmstens empfehlen. Mit ein wenig Magie im Make-File geht das sogar automagisch wenn sich die eine Vorlage oder die sed-Steuerungsdateien geändert haben. Ich habe sed schon oft für derartige Probleme benutzt und war damit immer sehr zufrieden.
Du hast nicht richtig verstanden wie ich das meinte.
Also stell dir ne Funktion vor die in einer Bitmap ne "freie" Position sucht und dann etwas in einem Array macht.
Das ist soweit der gemeinsame Nenner der Funktionen. Das Problem ist nun das die Funktionen auf verschiedene Strukturen (wo die Bitmaps/Arrays unterschiedlich groß sind) zugreifen. Du kannst den Code 1:1 kopieren, aber es muss der Pointer-Type, sowie die Adresse worauf der Pointer zeigt, geändert werden und die Länge der For-Schleife.
Wie ich die Länge der For-Schleife per Parameter übergebe ist mir noch klar, aber wie willst du das mit den unterschiedlichen Strukturen lösen?
Der Vorteil, wenn ich für jede Struktur ne eigene Funktion nutze, ist halt das er anstatt ein Register für die Überprüfung, ob das Schleifenende erreicht ist, eine Konstante nutzt. Dass Selbe gilt für die Adresse der Struktur.
Bsp:
int getMeSomething() {
struct kernelFoo_t *ptr= KERNEL_FOO_ADDR;
for(int x=0; x < KERNEL_FOO_SLOTS; x++) {
//finde ne freie Position in der Bitmap ptr->bitmap
}
int y= _bsr(ptr->bitmap[x]);
y+= x*32; //weil er bei jedem Durchlauf der Schleife immer gleich 32bit der Bitmap überprüft
return ptr->array[y];
}
Das wäre jetzt ne ziemlich vereinfachte Variante, aber ich hoffe es kommt rüber was ich meine. Das Problem ist nun, das man zwar "x" als Parameter übergeben kann, nicht aber "struct kernelFoo_t *ptr".
Was das Köpfen betrifft, ich springe, unter einer bestimmten Bedingung, aus der For-Schleife mit "goto" an eine Stelle ziemlich am Ende der Funktion (liest sich zwar scheiße, aber fand ich besser als noch ne Variable zu nutzen, nur um zu gucken ob er nen If-Zweig abarbeiten soll).
Warum? Was genau ist daran nicht so toll wenn ein Micro-Kernel nicht unterbrechbar ist? Ich empfinde das als Vorteil, das schließt viele Fehlerquellen von vornherein zuverlässig aus. Ein reinrassiger Micro-Kernel hat mit den eigentlichen User-Space-Daten nichts zu tun er muss sie nur verwalten. Und wenn er doch irgendwo rein sehen will muss er vorher prüfen ob das gefahrlos möglich ist, auf x86 gibt es doch auch VERR und VERW um zu prüfen wo man hingreifen darf und wo nicht, das wird es bei mir in ähnlicher Form auch geben.
Naja, für mich heißt nicht unterbrechbar, das die Interupts aus sind und damit sollte alles was im Kernel gemacht wird, verdammt schnell ablaufen! D.h. auch das ein busy-waiting eigentlich unmöglich wird. Denn wenn die Ints aus sind und du busy-waiting machst, kann das schonmal nach hinten losgehen (natürlich nur auf einem SMP System).
-
Hallo,
Ich weiß, dass du ganz gern den kompliziertest möglichen Fall betrachtet
So bin ich eben. Wenn ich nur einfache Probleme lösen wollte wäre die geistige Befriedigung, wenn man es geschafft hat, auch nur von einfacher Natur. ;)
Außerdem bin ich persönlich der Meinung das Threading heutzutage der Normalfall ist. Alle ordentlichen OSe können das, tyndur bemüht sich doch auch in diese Richtung.
aber nehmen wir doch zur Abwechslung einfach mal einen ganz einfachen Prozess, der keine weiteren Threads hat. Und "sofort" ist natürlich aus Prozesssicht gemeint, nicht zeitbezogen. Was du in der Tat machen kannst, ist den Thread anzuhalten, einen neuen Thread für den Signalhandler anzulegen und dort weiterzumachen. Aber damit hast du keinen Vorteil gewonnen und machst nur eventuelles Exceptionhandling (z.B. per longjmp) kaputt.
Falls der Thread oder Task (als simples Äquivalent zum Prozess) irgendeinen Fehler, wie z.B. einen Segfault, verursacht hat dann muss er sowieso angehalten werden. Falls es etwas externes ist, wie eben ein CTRL-C, dann sehe ich keine akute Notwendigkeit irgendetwas anzuhalten. Das mit dem longjmp ist natürlich ein Problem wenn man das Signal mit einem PopUp-Thread (oder einem anderen zusätzlichen Thread der aktiv/wartend Signale abholt) verarbeitet. Wird das mit dem longjmp tatsächlich irgendwo benutzt?
Aber das war eigentlich auch nicht der Hintergrund meiner Antwort, sondern die Frage, ob man das mit Message Passing emulieren kann. Und Message Passing erfordert es, dass irgendjemand die Nachricht abholt. Dazu ist es unter Umständden vorteilhaft, wenn derjenige noch nicht abgestürzt ist.
Da hatte ich wohl die Frage von FlashBurn nicht ganz verstanden, sorry.
Klar mit dem aktivem Abholen gibt es da ein Problemchen. Das wäre ein spezifischer Nachteil dieser Methode es sei denn man lässt für diese System-Signale immer einen extra Thread warten (falls man über komplexe Prozesse verfügt) was aber wieder unnützen Speicherverbrauch darstellt.
Du hast nicht richtig verstanden wie ich das meinte.
Möglich. Deshalb hab ich Dir ja verschiedene Lösungsvorschläge unterbreitet.
Das wäre jetzt ne ziemlich vereinfachte Variante, aber ich hoffe es kommt rüber was ich meine. Das Problem ist nun, das man zwar "x" als Parameter übergeben kann, nicht aber "struct kernelFoo_t *ptr".
Genau für solche Probleme ist sed Dein bester Freund. Du hast eine Vorlage in welcher der Pointer-Typ ein spezielles Wort ist und das wird von sed einfach durch etwas spezifisches ersetzt wenn aus der einen Vorlage zwei (oder mehr) C-Dateien generiert werden. Das funktioniert auch wenn Du eine Zahl einfügen möchtest.
In C++ könnte man dieses Problem vielleicht mit einem Template lösen.
Was das Köpfen betrifft, ich springe, unter einer bestimmten Bedingung, aus der For-Schleife mit "goto" an eine Stelle ziemlich am Ende der Funktion (liest sich zwar scheiße, aber fand ich besser als noch ne Variable zu nutzen, nur um zu gucken ob er nen If-Zweig abarbeiten soll).
Ich hab auch schon mal goto benutzt. Es ist ein mächtiges Werkzeug mit dem man sich aber auch extrem leicht ins Knie schießen kann. Wenn man dieses Werkzeug wirklich nur dann benutzt wenn es keinen anderen vernünftigen Weg gibt und größte Vorsicht walten lässt ist das IMHO Okay.
Naja, für mich heißt nicht unterbrechbar, das die Interupts aus sind und damit sollte alles was im Kernel gemacht wird, verdammt schnell ablaufen!
Richtig, genau so hab ich mir das vorgestellt. Alles wo eventuell für unbestimmte Zeit gewartet werden müsste passiert im User-Space. Ich möchte das alle (wichtigen) Syscalls in vorhersehbarer Zeit abgeschlossen werden also wieder in den User-Mode zurückkehren (was bei RPC aber nicht der selbe Thread ist).
Denn wenn die Ints aus sind und du busy-waiting machst, kann das schonmal nach hinten losgehen (natürlich nur auf einem SMP System).
Hm, gerade auf einem SMP-System sollte es schon mal gehen wenn eine CPU etwas länger im System-Mode bleibt, es sind ja noch andere CPUs da die den Interrupt bearbeiten können. Die Critical-Sections in meinem Kernel will ich jedenfalls mit simplen busy-waiting implementieren, ich sehe da auch kein Problem solange es gewährleistet ist das keine Critical-Section länger als unbedingt erforderlich in Benutzung bleibt. Der Code zwischen Critical_Section_Enter() und Critical_Section_Leave() darf eben unter keinsten Umständen blockieren (Endlosschleife o.ä.).
Grüße
Erik
-
Falls der Thread oder Task (als simples Äquivalent zum Prozess) irgendeinen Fehler, wie z.B. einen Segfault, verursacht hat dann muss er sowieso angehalten werden. Falls es etwas externes ist, wie eben ein CTRL-C, dann sehe ich keine akute Notwendigkeit irgendetwas anzuhalten.
Was sollte ein Prozess machen wollen, wenn er von "außen" beendet wird? Sprich wozu ist der Signal Handler in dem Fall gut?
Klar mit dem aktivem Abholen gibt es da ein Problemchen. Das wäre ein spezifischer Nachteil dieser Methode es sei denn man lässt für diese System-Signale immer einen extra Thread warten (falls man über komplexe Prozesse verfügt) was aber wieder unnützen Speicherverbrauch darstellt.
Es gibt ja cygwin und die müssen ja auch irgendwie die Signal Handler emuliert haben. Wird das dann nicht auch über Messages gelöst?
Zumal man das ja nur bräuchte wenn man Programme ohne großen Aufwand portieren möchte, sprich die Lösung mit dem zusätzlichem Thread wäre durchaus eine mögliche Lösung.
Genau für solche Probleme ist sed Dein bester Freund. Du hast eine Vorlage in welcher der Pointer-Typ ein spezielles Wort ist und das wird von sed einfach durch etwas spezifisches ersetzt wenn aus der einen Vorlage zwei (oder mehr) C-Dateien generiert werden. Das funktioniert auch wenn Du eine Zahl einfügen möchtest.
In C++ könnte man dieses Problem vielleicht mit einem Template lösen.
An C++ und Template dachte ich auch schon, aber da ich mich damit gar nicht auskenne (noch nicht) und auch kein C++ in meinem Kernel haben will, fällt das leider aus ;)
Was sed betrifft, nutze ich für eine ähnliche Sache schon in einem Makefile. In dieser Situation wäre es aber für mich umständlicher alles in mehrere Dateien zu packen. Ich sags mal so, inzwischen ist (bzw. scheint) der Code fehlerfrei und von daher ist es im Moment eher ein "kosmetisches" Problem.
Ich hab auch schon mal goto benutzt. Es ist ein mächtiges Werkzeug mit dem man sich aber auch extrem leicht ins Knie schießen kann. Wenn man dieses Werkzeug wirklich nur dann benutzt wenn es keinen anderen vernünftigen Weg gibt und größte Vorsicht walten lässt ist das IMHO Okay.
Nicht jeder ist so liberal wie du was das goto betrifft ;)
Ich nutze es vorallem um bei Fehlern "aufzuräumen" und um den Code nicht unnötig zu vergrößern. Außerdem habe ich jahrelang in Assembler programmiert und die Denkweise wird man nicht so einfach wieder los und vieles was in Assembler kein Problem ist, sieht in C einfach nur schrecklich aus.
Hm, gerade auf einem SMP-System sollte es schon mal gehen wenn eine CPU etwas länger im System-Mode bleibt, es sind ja noch andere CPUs da die den Interrupt bearbeiten können. Die Critical-Sections im Kernel will ich jedenfalls mit simplen busy-waiting implementieren, ich sehe da auch kein Problem solange es gewährleistet ist das keine Critical-Section länger als unbedingt erforderlich in Benutzung bleibt. Der Code zwischen Critical_Section_Enter() und Critical_Section_Leave() darf eben unter keinsten Umständen blockieren (Endlosschleife o.ä.).
Ich weiß jetzt nicht mit vielen CPUs/Kernen du deine Platform planst, aber wir hatten das Thema ja schonmal ;)
Wenn du 16 CPUs hast die alle im Kernel sind und alle auch noch dummerweise den selben Lock wollen, dann kann das Warten schonmal länger ausfallen und keiner nimmt in dem Moment (bzw. kann) Ints entgegen! (unwahrscheinlich aber möglich, das wäre dann ein Problem was nur selten auftritt und sich wahrscheinlich auch nicht reproduzieren lässt, also der größte Albtraum den man haben kann)
-
Hallo,
Was sollte ein Prozess machen wollen, wenn er von "außen" beendet wird? Sprich wozu ist der Signal Handler in dem Fall gut?
Dieser Handler könnte eine Ressource freigeben die das OS nicht automatisch freigeben kann, genau dafür hab ich zumindest mal einen solchen Handler implementieren müssen.
Es gibt ja cygwin und die müssen ja auch irgendwie die Signal Handler emuliert haben. Wird das dann nicht auch über Messages gelöst?
Da wirst Du einfach mal in den Quell-Code schauen müssen. Ich denke cygwin kann in vielen Situationen, wo man POSIX emulieren muss, eine interessante Inspirationsquelle sein.
Was sed betrifft, nutze ich für eine ähnliche Sache schon in einem Makefile. In dieser Situation wäre es aber für mich umständlicher alles in mehrere Dateien zu packen. Ich sags mal so, inzwischen ist (bzw. scheint) der Code fehlerfrei und von daher ist es im Moment eher ein "kosmetisches" Problem.
Ich kann es ja nicht wirklich beurteilen aber so wie Du Dein Problem beschreibst erscheint mir sed ziemlich geeignet.
Nicht jeder ist so liberal wie du was das goto betrifft
Ich bin wirklich kein Freund von goto, die paar mal wo ich das in den letzten fast 20 Jahren benutzt habe kann ich an einer Hand abzählen, aber man darf sich nicht immer nur von ideologischen Scheuklappen leiten lassen.
Ich nutze es vorallem um bei Fehlern "aufzuräumen" und um den Code nicht unnötig zu vergrößern.
Ich bin mir sicher das moderne Compiler da auch redundanten Quell-Code gut klein optimieren aber unlesbarer oder unwartbarer Quell-Code weil man x mal das selbe macht ist auch nicht toll. Trotzdem erscheint mir goto nur fürs Aufräumen etwas übertrieben. In C++ gibt es dafür Exceptions und catch.
Außerdem habe ich jahrelang in Assembler programmiert und die Denkweise wird man nicht so einfach wieder los und vieles was in Assembler kein Problem ist, sieht in C einfach nur schrecklich aus.
Da kann ich nur zustimmen. Auch ich ärgere mich oft das man einfache Kleinigkeiten in C manchmal extrem umständlich machen muss, manchmal sieht sogar der vom Compiler erzeugte Assembler-Code einfach nur zum schreien aus weil der Compiler eben nicht immer versteht was man will. Aber wenn man eine Hochsprache benutzen möchte dann muss man eben auch für eine Hochsprache programmieren.
Ich weiß jetzt nicht mit vielen CPUs/Kernen du deine Platform planst
Mindestens 2, weniger geht eigentlich nicht.
Wenn du 16 CPUs hast die alle im Kernel sind und alle auch noch dummerweise den selben Lock wollen, dann kann das Warten schonmal länger ausfallen und keiner nimmt in dem Moment (bzw. kann) Ints entgegen! (unwahrscheinlich aber möglich, das wäre dann ein Problem was nur selten auftritt und sich wahrscheinlich auch nicht reproduzieren lässt, also der größte Albtraum den man haben kann)
In diesem Szenario (das wirklich sehr extrem ist, es soll ja schließlich kein großes Big-Kernel-Lock geben) würde irgendeine der CPUs als erste fertig werden und die nimmt dann eben den Interrupt entgegen. Ich habe nicht geschrieben das ich ein Echt-Zeit-OS mit deterministischem Reaktionsverhalten entwickeln will, das IRQs manchmal eine gewisse Latenz haben ist eben so (typische HW kann damit gut umgehen) aber ich denke das mein Konzept im durchschnittlichen Normal-Fall durchaus eine gute Reaktionszeit vorlegen wird, immerhin wird es bei mir keine starre IRQ-Zuordnung geben sondern es nimmt immer die CPU an die gerade den Code mit der niedrigsten Priorität ausführt.
Grüße
Erik
-
Dieser Handler könnte eine Ressource freigeben die das OS nicht automatisch freigeben kann, genau dafür hab ich zumindest mal einen solchen Handler implementieren müssen.
Ich arbeite gerade daran, das sowas (zumindest für den Kernel) nicht nötig sein wird. Sprich das alle Ressourcen "entzogen" werden können ohne dass das andere Prozesse auch "abstürzen" lässt. Ist zwar bei bestimmten Sachen ganz schön arbeit fehlertoleranten Code zu schreiben, aber ich denke das lohnt sich.
Da wirst Du einfach mal in den Quell-Code schauen müssen. Ich denke cygwin kann in vielen Situationen, wo man POSIX emulieren muss, eine interessante Inspirationsquelle sein.
Ich denke genau das werde ich dann irgendwann mal tun wenn ich an einer "libposix" arbeite. Besser ist aber wenn man dann auch versteht wie etwas funktioniert und nicht nur irgendeine Idee übernimmt.
Ich bin mir sicher das moderne Compiler da auch redundanten Quell-Code gut klein optimieren aber unlesbarer oder unwartbarer Quell-Code weil man x mal das selbe macht ist auch nicht toll. Trotzdem erscheint mir goto nur fürs Aufräumen etwas übertrieben. In C++ gibt es dafür Exceptions und catch.
Also ich kann nur sagen, wenn man "O2" beim GCC einsetzt, dann optimiert er solchen Code nicht weg sondern dazu :( Problem ist halt das er es so schnell wie möglich machen will und Speed über Size stellt, aber wenn ein Fehler aufgetreten ist, dann ist mir die Performance eher egal.
Das nächste ist, das Freigeben von Ressourcen und das Freigeben von Locks, in der richtigen Reihenfolge und das macht sich am einfachsten wenn man dazu "goto" verwendet. Ich hatte das alles schonmal mit "if" gemacht, aber da wären wir wieder bei redundanten Code und Fehler. Denn bei irgendeinem der vielen "if"´s vergisst du dann den neuen Lock wieder freizugeben und schon hast du den Salat. Mit nem "goto" passiert mir das nicht und mein Code ist auch noch ein ganzes Stückchen kleiner.
In diesem Szenario (das wirklich sehr extrem ist, es soll ja schließlich kein großes Big-Kernel-Lock geben) würde irgendeine der CPUs als erste fertig werden und die nimmt dann eben den Interrupt entgegen. Ich habe nicht geschrieben das ich ein Echt-Zeit-OS mit deterministischem Reaktionsverhalten entwickeln will, das IRQs manchmal eine gewisse Latenz haben ist eben so (typische HW kann damit gut umgehen) aber ich denke das mein Konzept im durchschnittlichen Normal-Fall durchaus eine gute Reaktionszeit vorlegen wird, immerhin wird es bei mir keine starre IRQ-Zuordnung geben sondern es nimmt immer die CPU an die gerade den Code mit der niedrigsten Priorität ausführt.
Ich dachte in dem Moment an den pyhsischen Speichermanager. Da finde ich es am wenigsten unwahrscheinlich, das mal alle CPUs gleichzeitig neuen Speicher brauchen.
Aber wenn du deinen Kernel wirklich mikro machen willst, dürfte der gar nicht im Kernel sein (es gibt jedenfalls Mikrokernel die sogar das auslagern) und du hast das Problem nicht.
@back2topic
Ich habe mir den Thread nochmal durchgelesen und wollte mal was zum Thema beitragen ;)
Also ansich reden wir hier von einem Treiber der im UserSpace läuft und es wurde das Beispiel Diskettenlaufwerktreiber gebracht.
Ich stelle mir das so vor, das (wer auch immer) wenn ein Sektor gelesen/geschrieben werden soll, eine Nachricht an den Treiber geschickt wird. Dieser holt immer die Nachrichten ab und blockiert wenn keine vorhanden sind.
Hat er eine Nachricht abgeholt, arbeitet er sie ab, sprich er lädt/schreibt einen oder mehrere Sektoren. Wie die Daten in den Treiber oder aus den Treiber kommen ist erstmal egal (ich würde/mache es über SharedMemory). Ist er fertig versucht er die nächste Nachricht abzuholen.
Da das Diskettenlaufwerk sowieso nur eine Sache gleichzeitig machen kann, hat man so auch schon schön alles serialisiert.
Hat man nun 2 Diskettenlaufwerke, erstellt man einfach 2 Threads und stellt 2 Services mit 2 unterschiedlichen MessageQueues zur Verfügung.
Das OS sollte dann natürlich auch in der Lage sein, auf die Antwort eines bestimmen Threads/Ports warten zu können, sprich alle anderen Messages die vorher in die Queue kommen wecken den Thread nicht auf, sondern erst die Message die von dem bestimmten Thread/Port kommt!
Damit hat man keine Probleme mit irgendeinem Handler der was unterbricht und man braucht sich nicht um irgendwelche Serialisierung kümmern.
Für ein Bsp für parallele Bearbeitung, nehme ich mir mal nen Storage-Server (der kümmert sich um alles was mit Dateien lesen/schreiben zu tun hat).
Der hat einen globalen (bekannten) Port/MessageQueue und bei dieser "meldest" du dich sozusagen an und machst dir mit dem Server einen neuen Port/MessageQueue aus wo du deine Anfragen hinstellst. Für diesen neuen Port/MessageQueue wird ein neuer Thread erstellt. (Die Sache wo ich noch nicht so sicher bin, ist ob ein Port/MessageQueue pro Prozess oder Thread besser ist.)
So geht man sicher das Aufgaben parallel abgearbeitet werden können.
Ich weiß das meine Variante ziemlich viele Threads verursacht, aber das ist gewollt!
-
Wie ich die Länge der For-Schleife per Parameter übergebe ist mir noch klar, aber wie willst du das mit den unterschiedlichen Strukturen lösen?
Könnte man sicher auch lösen, indem man die Größe der Struktur und die Offsets der interessanten Felder als Parameter mit reingibt. Aber an dieser Stelle würde ich wahrscheinlich eher einfach ein Makro draus machen, dem kann man ja auch Typ- und Feldnamen reingeben. Evtl. Teile in eine static-inline-Funktion auslagern, damit das Makro nicht ganz so groß wird.
Der Vorteil, wenn ich für jede Struktur ne eigene Funktion nutze, ist halt das er anstatt ein Register für die Überprüfung, ob das Schleifenende erreicht ist, eine Konstante nutzt. Dass Selbe gilt für die Adresse der Struktur.
Das sollte der Compiler auch merken, wenn du ihn die Funktion inlinen lässt. Am Ende sollte dafür keine Variable mehr benutzt sein.
Was das Köpfen betrifft, ich springe, unter einer bestimmten Bedingung, aus der For-Schleife mit "goto" an eine Stelle ziemlich am Ende der Funktion (liest sich zwar scheiße, aber fand ich besser als noch ne Variable zu nutzen, nur um zu gucken ob er nen If-Zweig abarbeiten soll).
Und? goto hat seine berechtigten Einsatzzwecke. Zum Beispiel Ferhlerbehandlung bedeutet fast immer ein goto ans Ende der Funktion, wenn mehr als ein return -EDUDOOF zu tun ist. Exceptions für Arme, sozusagen. ;)
Außerdem bin ich persönlich der Meinung das Threading heutzutage der Normalfall ist. Alle ordentlichen OSe können das, tyndur bemüht sich doch auch in diese Richtung.
Das bedeutet nicht automatisch, dass es die Anwendungen auch alle benutzen.
Das mit dem longjmp ist natürlich ein Problem wenn man das Signal mit einem PopUp-Thread (oder einem anderen zusätzlichen Thread der aktiv/wartend Signale abholt) verarbeitet. Wird das mit dem longjmp tatsächlich irgendwo benutzt?
Ich habe das beispielsweise schonmal in einem kleinen Testframework benutzt (für unsere libext2). Bei einem Segfault per longjmp rausretten, den Testcase als fehlgeschlagen markieren und weiter mit dem nächsten.
Ich bin mir sicher, dass es auch an wesentlich kritischeren Stellen eingesetzt wird. Wenn ein Feature da ist, wird es auch benutzt.
-
Könnte man sicher auch lösen, indem man die Größe der Struktur und die Offsets der interessanten Felder als Parameter mit reingibt. Aber an dieser Stelle würde ich wahrscheinlich eher einfach ein Makro draus machen, dem kann man ja auch Typ- und Feldnamen reingeben. Evtl. Teile in eine static-inline-Funktion auslagern, damit das Makro nicht ganz so groß wird.
Genau solche Lösungen machen mir momentan das Leben schwer. Denn die Offsets müssten dann auch mit Hilfe eines anderen Makros übergeben werden (falls die sich mal ändern sollte, so dass man nicht alle Stellen suchen muss wo man die benutzt hat).
Ich bin nicht wirklich ein Freund vieler Makros (das liegt aber nur daran, das ich die meisten nicht verstehe, bzw. es verdammt aufwendig ist sich alles Makros zusammen zu suchen).
Und? goto hat seine berechtigten Einsatzzwecke. Zum Beispiel Ferhlerbehandlung bedeutet fast immer ein goto ans Ende der Funktion, wenn mehr als ein return -EDUDOOF zu tun ist. Exceptions für Arme, sozusagen. Wink
Genau dafür nutze ich sie vorrangig! Wieder etwas Offtopic, aber dein letzter Satz impleziert, dass man das ganze mit Exceptions lösen kann, aber eigentlich ersetzen doch Exceptions nur das Vergleichen des Rückgabewerts einer Funktion mit Fehlercodes, das Aufräumen musst du trotzdem machen, oder habe ich das falsch verstanden?
-
Genau solche Lösungen machen mir momentan das Leben schwer. Denn die Offsets müssten dann auch mit Hilfe eines anderen Makros übergeben werden (falls die sich mal ändern sollte, so dass man nicht alle Stellen suchen muss wo man die benutzt hat).
offsetof aus stddef.h?
Genau dafür nutze ich sie vorrangig! Wieder etwas Offtopic, aber dein letzter Satz impleziert, dass man das ganze mit Exceptions lösen kann, aber eigentlich ersetzen doch Exceptions nur das Vergleichen des Rückgabewerts einer Funktion mit Fehlercodes, das Aufräumen musst du trotzdem machen, oder habe ich das falsch verstanden?
Naja, das Vergleichen mit dem Fehlercode ist der uninteressantere Teil. Was Exceptions vor allem machen, ist irgendwo mitten aus dem try-Block heraus in einen catch-Block zu springen, und das ist nicht ganz unähnlich zu einem goto in einer Sprache, die keine Exceptions kennt. Aufräumen musst du natürlich trotzdem selber.
-
Hallo,
Ich arbeite gerade daran, das sowas (zumindest für den Kernel) nicht nötig sein wird. Sprich das alle Ressourcen "entzogen" werden können ohne dass das andere Prozesse auch "abstürzen" lässt. Ist zwar bei bestimmten Sachen ganz schön arbeit fehlertoleranten Code zu schreiben, aber ich denke das lohnt sich.
Das halte ich für ein gänzlich unmögliches Unterfangen, nicht jede Art von Ressource ist dem OS überhaupt bekannt/bewusst. Dieses Forum hier ist ein gutes Beispiel dafür, wenn Du angemeldet bist kannst Du zwar den Browser schließen aber abgemeldet wirst Du dadurch nicht (was auch daran liegt das sich noch nicht mal Dein Browser dessen bewusst ist das Du in diesem Forum angemeldet bist). Wenn der Session-Cookie noch existiert kannst Du diesen sogar einem anderen Browser auf einem anderen Computer injizieren und bist dann plötzlich mit diesem Browser hier im Forum angemeldet. Übertrage dieses Problem mal auf Datenbanken oder den Zustand bestimmter HW-Komponenten, auch davon hat das OS keine Ahnung, den meisten Effekt erreichst Du dann wenn Du den Programmen auch im Fehlerfall die Möglichkeit gibst zumindest zu versuchen selber Aufzuräumen.
Besser ist aber wenn man dann auch versteht wie etwas funktioniert und nicht nur irgendeine Idee übernimmt.
Ideen muss man doch verstehen wenn man sie ordentlich übernehmen möchte, dachte ich zumindest immer. ;)
... Mit nem "goto" passiert mir das nicht und mein Code ist auch noch ein ganzes Stückchen kleiner.
Lerne C++ (oder eine andere moderne OO-Sprache) und Du wirst sehen wie einfach und vor allem elegant sich solche Dinge lösen lasen. Das ist einer der Gründe warum ich seit über 5 Jahren kaum noch reines C programmiere.
Ich dachte in dem Moment an den pyhsischen Speichermanager. Da finde ich es am wenigsten unwahrscheinlich, das mal alle CPUs gleichzeitig neuen Speicher brauchen.
Klar kann es passieren das mal gleich einige CPUs (oder gar alle) gleichzeitig Speicher benötigen aber dann dauert es eben etwas länger bis die erste CPU fertig ist (wegen dem Cache-Kohärenz-Protokoll usw.). Der entscheidende Punkt ist das sie in überschaubarer Zeit fertig wird und nicht für unbestimmte Zeit blockiert, solange das gewährleistet ist ist ununterbrechbarer Kernel-Code kein Negativ-Kriterium.
Aber wenn du deinen Kernel wirklich mikro machen willst, dürfte der gar nicht im Kernel sein (es gibt jedenfalls Mikrokernel die sogar das auslagern) und du hast das Problem nicht.
So extrem mikro möchte ich nun auch wieder nicht. Man soll die Dinge ja immer so einfach wie möglich machen aber auf gar keinen Fall noch einfacher.
Da das Diskettenlaufwerk sowieso nur eine Sache gleichzeitig machen kann, hat man so auch schon schön alles serialisiert.
Das ist zwar richtig aber was ist mit Festplatten? Für die hat man sich extra NCQ ausgedacht.
Hat man nun 2 Diskettenlaufwerke, erstellt man einfach 2 Threads und stellt 2 Services mit 2 unterschiedlichen MessageQueues zur Verfügung.
Ja, ich denke das wäre eine sinnvolle Vorgehensweise. Aber bedenke wie viele (möglicherweise nutzlos) wartende Threads dabei in einem üppig ausgestattetem Computer entstehen.
Das OS sollte dann natürlich auch in der Lage sein, auf die Antwort eines bestimmen Threads/Ports warten zu können, sprich alle anderen Messages die vorher in die Queue kommen wecken den Thread nicht auf, sondern erst die Message die von dem bestimmten Thread/Port kommt!
Das verstehe ich nicht. Du meinst die Antwort soll wieder als aktives IPC an den Client geschickt werden? Also sowas wie das http://forum.lowlevel.eu/index.php?topic=2466 (http://forum.lowlevel.eu/index.php?topic=2466)? Da wird mit Hilfe zweier asynchroner IPC-Vorgänge ein synchroner IPC-Vorgang emuliert. Klar das funktioniert, tyndur ist der Beweis dafür, aber als besonders toll kann man das nun wirklich nicht bezeichnen.
Damit hat man keine Probleme mit irgendeinem Handler der was unterbricht und man braucht sich nicht um irgendwelche Serialisierung kümmern.
Hm, bei nicht ganz so offensichtlichen Ressourcen wirst Du eventuell trotzdem in irgendeiner Form serialisieren müssen.
....
Ich weiß das meine Variante ziemlich viele Threads verursacht, aber das ist gewollt!
Echt, ich hoffe Dein OS läuft nur auf Computern die mit sehr viel RAM ausgestattet sind. Mit diesem Konzept könnten es extrem viele Threads werden und es ist recht wahrscheinlich das für jeden beliebigen Zeitpunkt die meisten davon gerade schlafen(warten).
Außerdem bin ich persönlich der Meinung das Threading heutzutage der Normalfall ist. Alle ordentlichen OSe können das, tyndur bemüht sich doch auch in diese Richtung.
Das bedeutet nicht automatisch, dass es die Anwendungen auch alle benutzen.
Gewiss, aber es bedeutet das eine OS-spezifische LIB das problemlos nutzen kann.
Bei einem Segfault per longjmp rausretten
Hm, wenn Du ungefähr weist bei welchen Speicherzugriff der Segfault auftritt wäre es dann nicht besser vor dem Speicherzugriff zu prüfen ob dieser überhaupt geht? Außerdem ist ein Segfault IMHO ein Fehler den Du als Programmierer beheben musst und nicht das OS zur Laufzeit.
Ich bin mir sicher, dass es auch an wesentlich kritischeren Stellen eingesetzt wird. Wenn ein Feature da ist, wird es auch benutzt.
Ich hab mich gestern da etwas eingelesen und bin mir nicht ganz sicher ob ich das überhaupt auf meiner Plattform ordentlich unterstützen kann, aber da ich der Meinung bin das man die Szenarien mit setjmp/longjmp auch immer anders lösen kann sehe ich das im Moment nicht als dringendes Problem.
Grüße
Erik
-
indest immer. ;)
... Mit nem "goto" passiert mir das nicht und mein Code ist auch noch ein ganzes Stückchen kleiner.
Lerne C++ (oder eine andere moderne OO-Sprache) und Du wirst sehen wie einfach und vor allem elegant sich solche Dinge lösen lasen. Das ist einer der Gründe warum ich seit über 5 Jahren kaum noch reines C programmiere.
Dann erklär mal, wie OO an sich in dieser Situation hilft. ;)
Ich bin mir relativ sicher, dass wenn sich überhaupt was verändert, es nicht an OO, sondern an anderen Sprachfeatures liegt. (Nichts gegen OO, aber es ist kein Allheilmittel)
Hm, wenn Du ungefähr weist bei welchen Speicherzugriff der Segfault auftritt wäre es dann nicht besser vor dem Speicherzugriff zu prüfen ob dieser überhaupt geht? Außerdem ist ein Segfault IMHO ein Fehler den Du als Programmierer beheben musst und nicht das OS zur Laufzeit.
Wie gesagt, bei mir war es ein Testframework. Ich muss davon ausgehen, dass der zu testende Code kaputt ist (sonst müsste ich ihn ja nicht testen), aber ich weiß natürlich nicht, an welcher Stelle.
Ich hab mich gestern da etwas eingelesen und bin mir nicht ganz sicher ob ich das überhaupt auf meiner Plattform ordentlich unterstützen kann, aber da ich der Meinung bin das man die Szenarien mit setjmp/longjmp auch immer anders lösen kann sehe ich das im Moment nicht als dringendes Problem.
Vermutlich geht es schon anders. Ist nur eine Frage der Portabilität. Wenn du auf POSIX-Programme verzichten kannst, brauchst du es nicht zu implementieren. Wenn du es teilweise implementierst, bekommst du eben auch nur einen Teil der Programme.
-
Dann erklär mal, wie OO an sich in dieser Situation hilft. Wink
Das hätte ich auch gerne gewusst ;) Denn 1. (sollte) verwendet man keine Exceptions im Kernel und 2. ändert das am Aufräumen nichts.
Das halte ich für ein gänzlich unmögliches Unterfangen, nicht jede Art von Ressource ist dem OS überhaupt bekannt/bewusst. Dieses Forum hier ist ein gutes Beispiel dafür, wenn Du angemeldet bist kannst Du zwar den Browser schließen aber abgemeldet wirst Du dadurch nicht (was auch daran liegt das sich noch nicht mal Dein Browser dessen bewusst ist das Du in diesem Forum angemeldet bist). Wenn der Session-Cookie noch existiert kannst Du diesen sogar einem anderen Browser auf einem anderen Computer injizieren und bist dann plötzlich mit diesem Browser hier im Forum angemeldet. Übertrage dieses Problem mal auf Datenbanken oder den Zustand bestimmter HW-Komponenten, auch davon hat das OS keine Ahnung, den meisten Effekt erreichst Du dann wenn Du den Programmen auch im Fehlerfall die Möglichkeit gibst zumindest zu versuchen selber Aufzuräumen.
Deswegen habe ich ja dazu geschrieben, das ich das erstmal nur für den Kernel machen will. Im Endeffekt geht es mir nur darum, das ein abgestürzter Prozess nicht beendet wird, wenn er mitten in einer Semaphore oder sowas steckt (diese also gerade hat) und genauso meine ich damit die Threads aus Queues herauszuholen, in denen sie warten (das geht wieder nur für Queues die dem Kernel bekannt sind).
Wo ich gleich mal bei einem Problem bin (mal wieder Offtopic). Im POSIX Standard steht (glaub ich) das die Prozess/Thread IDs nur 1x verwendet werden dürfen. Das hieße ja aber das wenn du 2^32 Prozesse/Threads gestartet hattest, das du dann keinen mehr starten kannst oder habe ich da was falsch verstanden?
Ja, ich denke das wäre eine sinnvolle Vorgehensweise. Aber bedenke wie viele (möglicherweise nutzlos) wartende Threads dabei in einem üppig ausgestattetem Computer entstehen.
Unter meinem Windows laufen gerade 733 Threads und was macht der Computer die meiste Zeit? Idlen! Also kannst du sagen das mindestens 700 Threads gerade auf Arbeit warten.
Gerade bei der Entwicklung hin zu noch mehr Kernen finde ich es besser für jede Ressource (die man auch parallel zu anderen nutzen kann, ansonsten macht es keinen Sinn) einen Thread zu haben.
Der Punkt ist, es macht mir das Leben einfacher, was das serialisieren von solchen Ressourcenzugriffen betrifft und kann das System wesentlich leistungsfähiger machen.
Das ist zwar richtig aber was ist mit Festplatten? Für die hat man sich extra NCQ ausgedacht.
Schonmal Tests dazu gelesen? Ist zwar ein schönes Feature, aber bringt nicht wirklich was! Zumal ich gerade auch nicht ganz verstehe wo das Problem sein soll.
Das verstehe ich nicht. Du meinst die Antwort soll wieder als aktives IPC an den Client geschickt werden? Also sowas wie das http://forum.lowlevel.eu/index.php?topic=2466? Da wird mit Hilfe zweier asynchroner IPC-Vorgänge ein synchroner IPC-Vorgang emuliert. Klar das funktioniert, tyndur ist der Beweis dafür, aber als besonders toll kann man das nun wirklich nicht bezeichnen.
Ich sag jetzt mal nein ;) Ich habe in dem Sinne keine RPC-Handler, sondern einfach nur Msg-Queues (gebunden an einen Port). Der Syscall send+recv ist gerade für solche RPC-Sachen sehr gut geeignet, genauso wie für Server. Denn was machen denn Server, die holen eine neue Nachricht ab, bearbeiten sie, senden die Antwort und dann holen sie die nächste Nachricht ab. Mit diesem Syscall wird 1 Ringwechsel gespart. Das ganze funktioniert natürlich da ich fixed-size Msgs habe, bei dynamic-size Msgs wäre das schwieriger umzusetzen.
Also ich habe 3 Syscalls fürs IPC, send, recv und send+recv. Jedesmal wenn du ein recv machst, kannst du angeben das du auf eine Antwort von dem Port (an den du gerade sendest) warten möchtest. Jede Msg die an dich gesendet wird, wird in die Queue gepackt. Wird aber eine Msg von genau diesem Port empfangen, dann wird diese genommen, der Thread wird wieder aufgeweckt und du bekommst deine Antwort. So habe ich ansich asynchrones IPC, aber die Möglichkeit das ganze auch synchron zu machen (das erleichtert viele Aufgaben)!
So um jetzt nochmal auf das Bsp mit dem Diskettenlaufwerk zurückzukommen. Ich würde, wie gesagt, die Daten die gelesen werden in einen SharedMem-Bereich schreiben und wenn ich (als Treiber) fertig bin, sende ich eine Msg an den Port, der die Anfrage gestellt hatte und dieser wird dann aufgeweckt, bekommt seine Antwort (was in dem Sinne dann der Rückgabewert der Funktion "fread" sein könnte) und kann weiter machen. Die Daten hat dann "fread" in den übergebenden Buffer, aus dem SharedMem, geschrieben. Das sollte sogar sehr performant sein.
Ich hab mich gestern da etwas eingelesen und bin mir nicht ganz sicher ob ich das überhaupt auf meiner Plattform ordentlich unterstützen kann, aber da ich der Meinung bin das man die Szenarien mit setjmp/longjmp auch immer anders lösen kann sehe ich das im Moment nicht als dringendes Problem.
Ich weiß jetzt nicht, aber unter Windows gibt es sowas doch auch nicht und man kommt scheinbar auch ohne aus (nicht das Windows jetzt das OS aller OS ist, aber es macht auch nicht alles falsch).
Edit::
Ich habe gerade mal nachgesehen, also im Moment belegt eine Thread-Struktur bei mir ganze 232byte, dann würde nur noch der Stack dazukommen, den man auch auslagern kann! Das macht bei 1000 Threads gerade mal 226kb Speicher, das finde ich nicht wirklich viel!
-
Hallo,
Dann erklär mal, wie OO an sich in dieser Situation hilft. ;)
Sorry, da hab ich mich ungeschickt ausgedrückt. Ich persönlich kenne keine OO-Sprache die nicht auch gutes Exception-Handling bietet (was nicht bedeutet das es sowas nicht doch gibt). Da hätte ich besser "oder eine andere Sprache mit ordentlichem Exception-Handling" schreiben sollen. Aber ich denke das OO eine Voraussetzung (von mehreren) für gutes Exception-Handling ist von daher glaube ich nicht das es rein funktionale Sprachen gibt die das können (ich persönlich kenne zumindest keine).
Wie gesagt, bei mir war es ein Testframework. Ich muss davon ausgehen, dass der zu testende Code kaputt ist (sonst müsste ich ihn ja nicht testen), aber ich weiß natürlich nicht, an welcher Stelle.
Dann würde ich das Testframework aus einer Schleife in einem Shell-Script aufrufen, mit der Test-Nummer als Parameter, und immer den Rückgabe-Wert prüfen und die Ausgaben von OUT und ERR loggen. Bei einem Segfault sollte das OS auf den Assemblerbefehl genau melden was schief gegangen ist, das ist IMHO sogar sinnvoller als ein einfaches OK/NAK.
Wenn du auf POSIX-Programme verzichten kannst, brauchst du es nicht zu implementieren. Wenn du es teilweise implementierst, bekommst du eben auch nur einen Teil der Programme.
Also ich hab das setjmp/longjmp-Pärchen noch nie in irgendeinem Programm gesehen (von C-Tutorials mal abgesehen), ich glaube nicht das mir so viele Programme entgehen nur weil ich das nicht unterstütze. Bei den paar Programmen die das doch benutzen muss ich eben den Source so modifizieren das es auch ohne geht.
Im Endeffekt geht es mir nur darum, das ein abgestürzter Prozess nicht beendet wird, wenn er mitten in einer Semaphore oder sowas steckt (diese also gerade hat) und genauso meine ich damit die Threads aus Queues herauszuholen, in denen sie warten (das geht wieder nur für Queues die dem Kernel bekannt sind).
Das mit den Semaphoren stelle ich mir sehr aufwendig vor, das bedeutet das Du für jedes Enter und Leave je einen Syscall brauchst um den Kernel zu informieren. Grundsätzlich kann der Kernel nur die Ressourcen berücksichtigen die er auch kennt und das sind bei einem Mikro-Kernel nur sehr wenige. Zu diesem Thema hab ich schon mal diskutiert http://forum.lowlevel.eu/index.php?topic=2400 (http://forum.lowlevel.eu/index.php?topic=2400).
Wo ich gleich mal bei einem Problem bin (mal wieder Offtopic). Im POSIX Standard steht (glaub ich) das die Prozess/Thread IDs nur 1x verwendet werden dürfen. Das hieße ja aber das wenn du 2^32 Prozesse/Threads gestartet hattest, das du dann keinen mehr starten kannst oder habe ich da was falsch verstanden?
Das hast Du wohl richtig verstanden. Brauchst Du denn mehr als 2^32 Prozesse/Threads?
Unter meinem Windows laufen gerade 733 Threads und was macht der Computer die meiste Zeit? Idlen! Also kannst du sagen das mindestens 700 Threads gerade auf Arbeit warten.
Ich vermute das etwa 90% der Threads in irgendeinem blockierendem Syscall hängen und der Rest einfach so schlafen gelegt wurde (um bei anfallender Arbeit geweckt zu werden). Zu der Anzahl als solches: Du darfst nicht vergessen wie viele Programme und Dienste auf einem typischen Windows laufen, in Relation dazu sind die 700 Threads noch recht human, bis eines unserer OSe dieses Komplexitätslevel erreicht vergeht sicher noch einiges an Zeit.
Gerade bei der Entwicklung hin zu noch mehr Kernen finde ich es besser für jede Ressource (die man auch parallel zu anderen nutzen kann, ansonsten macht es keinen Sinn) einen Thread zu haben.
Der Punkt ist, es macht mir das Leben einfacher, was das serialisieren von solchen Ressourcenzugriffen betrifft und kann das System wesentlich leistungsfähiger machen.
Das sehe ich ganz genau so wie Du und deshalb möchte ich dem OS die Kontrolle über die Anzahl der existierenden Threads geben, damit werden nur so viele Threads erzeugt wie auch wirklich benötigt werden.
Ist zwar ein schönes Feature, aber bringt nicht wirklich was! Zumal ich gerade auch nicht ganz verstehe wo das Problem sein soll.
Ich weiß das NCQ nicht so wirklich der Bringer ist aber wenn es bei einem 8-Kern-PC mit 8 parallelen Compiler-Instanzen einige Prozent weniger CPU-IDLE-Zeit bewirken kann dann sollte man das ruhig unterstützen. Aber dafür muss der Festplatten-Treiber in der Lage sein mehrere Anfragen gleichzeitig anzunehmen, in Deinem Szenario würde das bedeuten das mehrere Threads auf diese eine Queue (über die der Treiber seine Dienste anbietet) warten müssen.
Ich habe in dem Sinne keine RPC-Handler, sondern einfach nur Msg-Queues (gebunden an einen Port). Der Syscall send+recv ist gerade für solche RPC-Sachen sehr gut geeignet, genauso wie für Server. Denn was machen denn Server, die holen eine neue Nachricht ab, bearbeiten sie, senden die Antwort und dann holen sie die nächste Nachricht ab. Mit diesem Syscall wird 1 Ringwechsel gespart. Das ganze funktioniert natürlich da ich fixed-size Msgs habe, bei dynamic-size Msgs wäre das schwieriger umzusetzen.
Also ich habe 3 Syscalls fürs IPC, send, recv und send+recv. Jedesmal wenn du ein recv machst, kannst du angeben das du auf eine Antwort von dem Port (an den du gerade sendest) warten möchtest. Jede Msg die an dich gesendet wird, wird in die Queue gepackt. Wird aber eine Msg von genau diesem Port empfangen, dann wird diese genommen, der Thread wird wieder aufgeweckt und du bekommst deine Antwort. So habe ich ansich asynchrones IPC, aber die Möglichkeit das ganze auch synchron zu machen (das erleichtert viele Aufgaben)!
Das Konzept sieht doch recht gut aus, nur für den send+recv würde ich 2 Varianten machen: eine für Clients die eine Anfrage schicken und dann auf ihre zugehörige Antwort warten (irgendetwas anderes will der Client dann eh nicht) und eine für Server die eine Antwort schicken und dann auf eine beliebige neue Anfrage warten.
Das mit den fixed-size Messages empfinde ich irgendwie als ungeschickt, bedeutet dass das der Client nur Anfragen mit einer bestimmten Größe benutzen darf? Das stelle ich mir bei fopen, wo ein unbekannt langer Dateiname mit zusätzlichem unbekannt langem Path (unbekannt aus Sicht des Services) übergeben werden muss, als ungeschickt vor. Ich möchte in mein Konzept auch Obergrenzen für die Größe der übergebenen Speicherbereiche beim erstellen eines Message-Targets angeben können aber das bedeutet nicht das die Anfragen immer exakt genau so groß sein müssen.
So um jetzt nochmal auf das Bsp mit dem Diskettenlaufwerk zurückzukommen. Ich würde, wie gesagt, die Daten die gelesen werden in einen SharedMem-Bereich schreiben und wenn ich (als Treiber) fertig bin, sende ich eine Msg an den Port, der die Anfrage gestellt hatte und dieser wird dann aufgeweckt, bekommt seine Antwort (was in dem Sinne dann der Rückgabewert der Funktion "fread" sein könnte) und kann weiter machen. Die Daten hat dann "fread" in den übergebenden Buffer, aus dem SharedMem, geschrieben. Das sollte sogar sehr performant sein.
Muss der Client auch einen Port öffnen? Wenn ja, warum?
Verstehe ich das richtig das Du den Shared-Memory zusätzlich (mit weiteren Syscalls) einrichten möchtest? Dann muss der Client das doch auch dem Service vorher mitteilen wie die Nutzdaten übermittelt werden sollen. Ich möchte das Memory-Sharing mit in die IPC-Syscalls integrieren, bei einer synchronen RPC-Anfrage werden bis zu vier Speicherbereiche (mit beliebiger Größe) mitgegeben in denen der Service dann die eigentliche Anfrage + zugehörige Nutzdaten (bei Schreibzugriffen) findet und die Antwort (also den Rückgabewert/Status) + zugehörige Nutzdaten (bei Lesezugriffen) ablegen kann. Diese Speicherbereiche soll ein Service auch seinerseits wieder (teilweise) weiterreichen können, z.B. von einem Dateisystem-Treiber an einen Block-Device-Treiber, so das die Nutzdaten möglichst nie kopiert werden müssen.
Ich weiß jetzt nicht, aber unter Windows gibt es sowas doch auch nicht und man kommt scheinbar auch ohne aus (nicht das Windows jetzt das OS aller OS ist, aber es macht auch nicht alles falsch).
Wieso sollte es unter Windows kein setjmp/longjmp geben? Das ist Sache des C-Compilers und nicht Sache des OS, der C-Compiler muss das nur so umsetzen das es der C-Spec genügt und unter dem OS funktioniert. Das setjmp/longjmp nicht richtig funktionieren wenn Multi-Threading ins Spiel kommt ist klar aber in einem Single-Thread-Programm sollte das erstmal tun. Mein Problem ist eher das ich aus fast jedem Programm quasi zwangsläufig ein Multi-Threading-Prozess mache.
Ich habe gerade mal nachgesehen, also im Moment belegt eine Thread-Struktur bei mir ganze 232byte, dann würde nur noch der Stack dazukommen, den man auch auslagern kann! Das macht bei 1000 Threads gerade mal 226kb Speicher, das finde ich nicht wirklich viel!
Klar kann man die vielen Stacks auslagern aber sie belegen im virtuellen Speicher Deiner Prozesse einiges an wertvollem Adressraum (zumindest in einem Flat-Memory-System, mit meinen Segmenten hab ich es da sogar deutlich einfacher).
Grüße
Erik
-
Da hätte ich besser "oder eine andere Sprache mit ordentlichem Exception-Handling" schreiben sollen. Aber ich denke das OO eine Voraussetzung (von mehreren) für gutes Exception-Handling ist von daher glaube ich nicht das es rein funktionale Sprachen gibt die das können (ich persönlich kenne zumindest keine).
Funktional ist sowieso ein ganz andere Baustelle... Ich vermute, du meinst prozedural.
Was macht "gutes" Exceptionhandling deiner Meinung nach denn aus, dass es unbedingt OO braucht? In Ada habe ich das jedenfalls schon benutzt ohne irgendwas zu machen, das verdächtig nach OO ausgesehen hätte.
Also ich hab das setjmp/longjmp-Pärchen noch nie in irgendeinem Programm gesehen (von C-Tutorials mal abgesehen)
Ich vermeide es ja bei den meisten Programmen, in den Code zu schauen, wenn es nicht sein muss. ;) Aber das eine Programm, das ich wirklich näher kenne, benutzt es zufällig und insofern hab ich dir ein Beispiel: qemu wäre das.
-
Das mit den Semaphoren stelle ich mir sehr aufwendig vor, das bedeutet das Du für jedes Enter und Leave je einen Syscall brauchst um den Kernel zu informieren. Grundsätzlich kann der Kernel nur die Ressourcen berücksichtigen die er auch kennt und das sind bei einem Mikro-Kernel nur sehr wenige.
Du musst doch bei einer Semaphore so oder so in den Kernel, warum dann nicht auch gleich alles im Kernel machen (so wird das möchte ich meinen auch unter Linux/Windows gemacht)?
Das hast Du wohl richtig verstanden. Brauchst Du denn mehr als 2^32 Prozesse/Threads?
Wenn ich das richtig verstanden habe, kann man aber ein POSIX System sehr einfach unbenutzbar machen! Ich denke mal das ich da irgendeinen Denkfehler drin habe, denn ansonsten wären Server die ewig laufen gar nicht möglich. Denn es wäre nur ein Script nötig was einen Prozess startet der nichts macht, sich gleich wieder beendet und das ganze halt so lange bis keine IDs mehr verfügbar sind. Wenn das wirklich so ist, sehe ich das schon als ne ganz schöne Schwachstelle!
Ich habe das so verstanden das du eine neue ID für einen Prozess im Endeffekt über "prozess->id= procID++" holst, sprich wenn ein Prozess die ID 4 hatte, selbst wenn es ihn nicht mehr gibt er also beendet ist, das kein anderer Prozess wieder die ID 4 bekommt.
Ich weiß das NCQ nicht so wirklich der Bringer ist aber wenn es bei einem 8-Kern-PC mit 8 parallelen Compiler-Instanzen einige Prozent weniger CPU-IDLE-Zeit bewirken kann dann sollte man das ruhig unterstützen. Aber dafür muss der Festplatten-Treiber in der Lage sein mehrere Anfragen gleichzeitig anzunehmen, in Deinem Szenario würde das bedeuten das mehrere Threads auf diese eine Queue (über die der Treiber seine Dienste anbietet) warten müssen.
Du konntest mir immernoch nicht sagen, wo das Problem ist, bzw. was du anders machen würdest so das dieses Problem nicht auftritt!
Was ich meine ist, das du dem Festplattentreiber doch "nur" sagst das er mal eben Daten laden soll, z.B. von LBA 123-130 (4kb). Wenn er das gemacht hat, dann holt er die nächste Nachricht ab und lädt wieder ein paar Sektoren. Der Punkt ist aber, das egal wie du es machst, die Festplatte kann immer nur eine Anfrage gleichzeitig entgegen nehmen.
Das einzige was du jetzt meinen könntest, ist das du nicht wartest bis die Festplatte fertig ist mit laden und dann schon die nächste Anfrage sendest.
Das Konzept sieht doch recht gut aus, nur für den send+recv würde ich 2 Varianten machen: eine für Clients die eine Anfrage schicken und dann auf ihre zugehörige Antwort warten (irgendetwas anderes will der Client dann eh nicht) und eine für Server die eine Antwort schicken und dann auf eine beliebige neue Anfrage warten.
Brauch ich nicht. Ob und wie gewartet wird, wird über Flags die man mit übergibt festgelegt!
Das mit den fixed-size Messages empfinde ich irgendwie als ungeschickt, bedeutet dass das der Client nur Anfragen mit einer bestimmten Größe benutzen darf? Das stelle ich mir bei fopen, wo ein unbekannt langer Dateiname mit zusätzlichem unbekannt langem Path (unbekannt aus Sicht des Services) übergeben werden muss, als ungeschickt vor. Ich möchte in mein Konzept auch Obergrenzen für die Größe der übergebenen Speicherbereiche beim erstellen eines Message-Targets angeben können aber das bedeutet nicht das die Anfragen immer exakt genau so groß sein müssen.
Problem ist einfach, ich will in meinem Kernel kein malloc, sondern nutze nur den SlabAllocator (ja man kann malloc auch mit Hilfe des SlabAllocators implementieren). Außerdem soll das ganz so schnell wie möglich sein.
Gelöst habe ich das so, das die Msgs sowieso nur als Synchronisierung oder kurze Statusmeldung genutzt werden soll (oder für Initialisierung ->).
Das IPC an sich soll über meine UserSpace Pipes laufen! Darüber werden dann auch Pfad und sowas übergeben, man könnte es sogar machen, das man die Anfragen auch darüber sendet (mit dem Gedanken spiele ich auch schon, könnte wesentlich schneller sein).
Muss der Client auch einen Port öffnen? Wenn ja, warum?
Ja! Ich habe auch erst überlegt ob das wirklich sein muss, aber wenn man eine Nachricht verschickt, möchte man eigentlich auch mal eine bekommen. Gerade was den Clienten betrifft, der sendet eine Nachricht und wartet auf die Antwort, sprich er will auch eine Nachricht bekommen.
Verstehe ich das richtig das Du den Shared-Memory zusätzlich (mit weiteren Syscalls) einrichten möchtest? Dann muss der Client das doch auch dem Service vorher mitteilen wie die Nutzdaten übermittelt werden sollen. Ich möchte das Memory-Sharing mit in die IPC-Syscalls integrieren, bei einer synchronen RPC-Anfrage werden bis zu vier Speicherbereiche (mit beliebiger Größe) mitgegeben in denen der Service dann die eigentliche Anfrage + zugehörige Nutzdaten (bei Schreibzugriffen) findet und die Antwort (also den Rückgabewert/Status) + zugehörige Nutzdaten (bei Lesezugriffen) ablegen kann. Diese Speicherbereiche soll ein Service auch seinerseits wieder (teilweise) weiterreichen können, z.B. von einem Dateisystem-Treiber an einen Block-Device-Treiber, so das die Nutzdaten möglichst nie kopiert werden müssen.
Wie gesagt, habe ich meine UserSpace Pipes und im Endeffekt sollen die Nachrichten nur für die Initialisierung genutzt werden.
Sprich das ganze würde dann so ablaufen, das sich ein Client bei einem Server registriert/anmeldet und die beiden machen sich dann aus über welche Pipe gesendet und über welche Pipe empfangen wird.
Meine Pipe ist nichts anderes als SharedMem, der als Ringbuffer genutzt wird (es findet aber fast alles im UserSpace statt).
Was du schon ansprichst, das Weiterreichen von Daten, würde ich auch über SharedMem machen, ist einfach wesentlich schneller.
Wieso sollte es unter Windows kein setjmp/longjmp geben? Das ist Sache des C-Compilers und nicht Sache des OS, der C-Compiler muss das nur so umsetzen das es der C-Spec genügt und unter dem OS funktioniert. Das setjmp/longjmp nicht richtig funktionieren wenn Multi-Threading ins Spiel kommt ist klar aber in einem Single-Thread-Programm sollte das erstmal tun. Mein Problem ist eher das ich aus fast jedem Programm quasi zwangsläufig ein Multi-Threading-Prozess mache.
Ich muss dir ehrlich sagen, das ich nicht genau weiß was setjmp/longjmp ist! Aber ich meinte eigentlich auch mehr die Signalhandler die das dann in irgendeiner Weise nutzen.
Sprich Windows kommt auch ohne Signalhandler aus und trotzdem scheinen alle Programm auch zu laufen (mit den selben Features).
@Exception Handling
Mir ist immernoch nicht klar was mit dem Exception-Handling so viel besser wäre.
Denn wenn ich da an einige meiner Funktionen denke, da hätte man dann viele try/catch Blöcke, aber ich wüsste jetzt auch nicht wie ich dann das goto vermeiden sollte (es gäbe einen Weg, aber der wäre sehr sehr unschön, da wären dann die goto´s wieder besser)?
Edit::
Was die Ressourcen betrifft (Dateien/Sockets).
Ich wollte das so lösen, das sich jeder Server, der Ressourcen eines Prozessen hat, sich im Kernel registriert, das er informiert werden möchte, wenn dieser Prozess beendet wird/ist.
Das kommt dann dem Node-Monitor (man will informiert werden, wenn sich eine Node, bzw. deren Kinder, irgendwie geändert haben, nützlich für einen Dateiexplorer, um informiert zu werden, wenn eine Datei gelöscht (oder ähnliches) wurde) ziemlich nahe.
So wäre das Problem dann auch gelöst ;)
-
Hallo,
Funktional ist sowieso ein ganz andere Baustelle... Ich vermute, du meinst prozedural.
Äh, ja, sorry.
Was macht "gutes" Exceptionhandling deiner Meinung nach denn aus, dass es unbedingt OO braucht? In Ada habe ich das jedenfalls schon benutzt ohne irgendwas zu machen, das verdächtig nach OO ausgesehen hätte.
Bei "gutem" Exceptionhandling kann der ich Exception etwas mitgeben (z.B. einen Fehlertext) eben weil sie ein Objekt (oder wenigstens eine Struktur) ist und ich kann den Typ der Exception feststellen als grobe Gruppe oder ganz exakt (das geht IMHO nur mit Vererbung).
Aber das eine Programm, das ich wirklich näher kenne, benutzt es zufällig und insofern hab ich dir ein Beispiel: qemu wäre das.
Also qemu brauche ich auf meinem Nicht-x86-System vielleicht wirklich mal (um Closed-Source-SW übernehmen zu können), von daher muss ich doch mal schauen was geht. :D Ne, also im Ernst, wenn Dir sonst keine "wichtigen" Programme einfallen dann mach ich mir darüber erstmal keine weiteren Gedanken.
Du musst doch bei einer Semaphore so oder so in den Kernel, warum dann nicht auch gleich alles im Kernel machen (so wird das möchte ich meinen auch unter Linux/Windows gemacht)?
Wozu? Wenn ich keine OS-verwaltete Semaphore brauche, das lohnt sich IMHO nur wenn man da länger drin bleiben will so das die wartenden Threads wirklich geblockt(pausiert) werden sollten, dann möchte ich keine verwenden. Normale Semaphoren kann man auch allein im User-Space implementieren, dazu reicht es das die CPU zuverlässig passende atomare Operationen bereitstellt. Darüber hatten wir doch schon mal vor einem halben Jahr diskutiert, da http://forum.lowlevel.eu/index.php?topic=2468.msg27861#msg27861 (http://forum.lowlevel.eu/index.php?topic=2468.msg27861#msg27861) hatte ich doch extra mal 4 Varianten aufgelistet.
Wenn ich das richtig verstanden habe, kann man aber ein POSIX System sehr einfach unbenutzbar machen! Ich denke mal das ich da irgendeinen Denkfehler drin habe, denn ansonsten wären Server die ewig laufen gar nicht möglich.
Ich vermute mal die freigebenen IDs werden wieder recycled, ansonsten wären echte Langläufer wirklich kaum möglich.
Der Punkt ist aber, das egal wie du es machst, die Festplatte kann immer nur eine Anfrage gleichzeitig entgegen nehmen.
Genau da irrst Du, das ist eben der Punkt der mit NCQ geändert wurde. Siehe dazu http://de.wikipedia.org/wiki/NCQ (http://de.wikipedia.org/wiki/NCQ). Damit kann man einer aktuellen SATA-HDD bis zu 32 Jobs geben die dann von der HDD in beliebiger Reihenfolge abgearbeitet werden dürfen und wenn ein paar der Jobs fertig sind darf man bereits neue Jobs nach schieben. Es dürften schon ein paar Szenarien existieren wo das tatsächlich etwas zusätzliche Performance bringt. Mit meinem PopUp-Thread-Konzept dürfte sich NCQ fast ohne Mehraufwand (im STAT-Treiber) nutzen lassen, ich muss nur darauf achten nicht mehr als 32 Jobs gleichzeitig an die HDD zu geben, ansonsten sehe ich keine konzeptionellen Beschränkungen.
Brauch ich nicht. Ob und wie gewartet wird, wird über Flags die man mit übergibt festgelegt!
Dann ist gut, das war aus Deiner ersten Erläuterung nur nicht so deutlich erkennbar.
Gelöst habe ich das so, das die Msgs sowieso nur als Synchronisierung oder kurze Statusmeldung genutzt werden soll (oder für Initialisierung ->).
Das IPC an sich soll über meine UserSpace Pipes laufen! Darüber werden dann auch Pfad und sowas übergeben, man könnte es sogar machen, das man die Anfragen auch darüber sendet (mit dem Gedanken spiele ich auch schon, könnte wesentlich schneller sein).
Das klingt nach nem dicken Haufen Arbeit wenn für jeden Kram erst ein Haufen verschiedener Kommunikationskanäle benutzt werden müssen, das müsstest Du mal etwas konkreter an einem Beispiel erklären. Wenn Du dann für das Leseende einer Pipe auch wieder einen Thread brauchst, der auf ankommende Daten wartet, dann werden es immer mehr Threads ohne das tatsächlich mehr gearbeitet wird.
Ja! Ich habe auch erst überlegt ob das wirklich sein muss, aber wenn man eine Nachricht verschickt, möchte man eigentlich auch mal eine bekommen. Gerade was den Clienten betrifft, der sendet eine Nachricht und wartet auf die Antwort, sprich er will auch eine Nachricht bekommen.
Also für mich ist eine Antwort auf eine Anfrage keine eigenständige Nachricht sondern nur eine weitere Phase eines synchronen IPC-Vorgangs. In meinem Kernel will ich auch konkret zwischen synchroner und asynchroner IPC unterscheiden und beides anbieten (wobei die asynchrone Variante wohl nur eine vereinfachte/abgekürzte Form der synchronen Variante ist, also nur wenig zusätzlicher Code erforderlich ist um beides zu unterstützen). Das meiste was in einem Mikro-Kernel-OS gemacht wird ist wohl synchrone IPC, eben RPC, nur wenige Dinge, wie z.B. Signale oder IRQs, müssen als asynchrones IPC behandelt werden.
Wie gesagt, habe ich meine UserSpace Pipes und im Endeffekt sollen die Nachrichten nur für die Initialisierung genutzt werden.
Sprich das ganze würde dann so ablaufen, das sich ein Client bei einem Server registriert/anmeldet und die beiden machen sich dann aus über welche Pipe gesendet und über welche Pipe empfangen wird.
Wie gesagt, das klingt sehr kompliziert.
Meine Pipe ist nichts anderes als SharedMem, der als Ringbuffer genutzt wird (es findet aber fast alles im UserSpace statt).
Was du schon ansprichst, das Weiterreichen von Daten, würde ich auch über SharedMem machen, ist einfach wesentlich schneller.
Also gerade das weiterleiten der Nutzdaten stell ich mir damit nicht so einfach vor, ich schätze da wirst Du in jeder Ebene kopieren müssen. Oder willst du es ermöglichen das 2 Pipes miteinander verbunden werden?
Ich muss dir ehrlich sagen, das ich nicht genau weiß was setjmp/longjmp ist!
Ich hab das auch noch nie benutzt oder in irgendeinem Programm-Source gesehen. Dieses Konstrukt würde ich persönlich als noch sehr viel verachtenswürdiger als goto einsortieren.
Anständige C-Coder benutzen soetwas nicht! ;)
Mir ist immernoch nicht klar was mit dem Exception-Handling so viel besser wäre.
Denn wenn ich da an einige meiner Funktionen denke, da hätte man dann viele try/catch Blöcke
Du sollst ja auch nur einen try/catch Block benutzen. In den catch-Abschnit kommt das Aufräumen und in den try-Abschnitt kannst Du den Code packen der möglicherweise Fehler generiert, wenn dort ein Fehler auftritt (also eine Exception geworfen wird) wird sofort in den nächsten catch-Abschnitt gesprungen der sich für diese Exception zuständig fühlt (das throw kannst Du wie ein goto betrachten und das sogar über Funktionsebenen hinweg).
Ich wollte das so lösen, das sich jeder Server, der Ressourcen eines Prozessen hat, sich im Kernel registriert, das er informiert werden möchte, wenn dieser Prozess beendet wird/ist.
Also so wie taljeth das in der genannten Diskussion erklärt hat?
Das kommt dann dem Node-Monitor (man will informiert werden, wenn sich eine Node, bzw. deren Kinder, irgendwie geändert haben, nützlich für einen Dateiexplorer, um informiert zu werden, wenn eine Datei gelöscht (oder ähnliches) wurde) ziemlich nahe.
Also zwischen einer Prozess-Sterbe-Nachricht und einer Veränderungsmitteilung ist IMHO schon ein gewisser Unterschied. Für die Veränderungsmitteilungen bei den Dateien wirst Du wohl einen zusätzlichen (aber ähnlichen) Mechanismus implementieren müssen.
Grüße
Erik
-
Ich vermute mal die freigebenen IDs werden wieder recycled, ansonsten wären echte Langläufer wirklich kaum möglich.
Das ist halt nicht möglich, so wie ich den Standard verstanden habe. Das eine ID nicht gleichzeitig für 2 Prozesse/Threads verwendet wird, ist ja wohl logisch!
Genau da irrst Du, das ist eben der Punkt der mit NCQ geändert wurde. Siehe dazu http://de.wikipedia.org/wiki/NCQ. Damit kann man einer aktuellen SATA-HDD bis zu 32 Jobs geben die dann von der HDD in beliebiger Reihenfolge abgearbeitet werden dürfen und wenn ein paar der Jobs fertig sind darf man bereits neue Jobs nach schieben. Es dürften schon ein paar Szenarien existieren wo das tatsächlich etwas zusätzliche Performance bringt. Mit meinem PopUp-Thread-Konzept dürfte sich NCQ fast ohne Mehraufwand (im STAT-Treiber) nutzen lassen, ich muss nur darauf achten nicht mehr als 32 Jobs gleichzeitig an die HDD zu geben, ansonsten sehe ich keine konzeptionellen Beschränkungen.
Meine Aussage bleibt trotzdem richtig ;) Denn die Festplatte kann nur eine Anfrage zur gleichen Zeit entgegen nehmen, denn du hast ja nicht 32x Ports um die Festplatte anzusprechen!
Bei dem Fall würde es sich aber wirklich lohnen, wenn man 32 Threads hätte. Dann könnte jeder Thread warten bis neue Arbeit da ist und er könnte auch warten bis die Festplatte fertig ist (ich hoffe du verstehst wie ich das meine).
Das klingt nach nem dicken Haufen Arbeit wenn für jeden Kram erst ein Haufen verschiedener Kommunikationskanäle benutzt werden müssen, das müsstest Du mal etwas konkreter an einem Beispiel erklären. Wenn Du dann für das Leseende einer Pipe auch wieder einen Thread brauchst, der auf ankommende Daten wartet, dann werden es immer mehr Threads ohne das tatsächlich mehr gearbeitet wird.
Ich sehe es erstmal so, das die Initialisierung ruhig ein wenig komplexer/langsamer sein darf, wenn der Vorgang an sich dann schneller geht.
Ist doch ganz einfach ;) Gehen wir mal davon aus, das du weißt in welche Pipe du schreiben sollst und aus welcher du lesen sollst.
Dann holst du dir die Anfrage mit "pipeRead(&msg,sizeof(struct msg_t))" (lohnt sich vorallem bei größeren Msgs, wie z.B. wenn man eine Datei laden soll), arbeitest die Msg ab und schreibst deine Daten dann per pipeWrite(&data,dateLen)". Abschließend musst du dann noch ein "pipeFlush()" aufrufen, damit der Thread der auf die Daten wartet sie lesen kann.
Der Vorteil ist, das diese Pipe´s fast komplett im UserSpace laufen, du also nicht erst die Daten in den Kernel kopierst (Server) um sie dann wieder in den UserSpace zu kopieren (Client).
Mal vom "pipeFlush()" abgesehen, ist das doch nicht viel anders als ein "msgRecv(&msg)" und ein "msgSend(&data,dataLen)".
Also für mich ist eine Antwort auf eine Anfrage keine eigenständige Nachricht sondern nur eine weitere Phase eines synchronen IPC-Vorgangs. In meinem Kernel will ich auch konkret zwischen synchroner und asynchroner IPC unterscheiden und beides anbieten (wobei die asynchrone Variante wohl nur eine vereinfachte/abgekürzte Form der synchronen Variante ist, also nur wenig zusätzlicher Code erforderlich ist um beides zu unterstützen). Das meiste was in einem Mikro-Kernel-OS gemacht wird ist wohl synchrone IPC, eben RPC, nur wenige Dinge, wie z.B. Signale oder IRQs, müssen als asynchrones IPC behandelt werden.
Genau deswegen überlege ich auch ob nicht rein synchrones IPC doch besser ist (siehe L4), aber meine Momentane Lösung ist auch nicht so schlecht.
Obwohl wenn ich gerade mal genauer drüber nachdenke, dann muss ich sagen, dass ich es mir so wie ich es habe einfacher vorstelle.
Folgendes Szenario, ein Client sendet eine Nachricht und der Server kann sie gleich entgegen nehmen. Also kehrt der Client gleich wieder zurück ohne zu blockiert, dann wartet er auf eine Antwort, blockiert also. Aber in der Zwischenzeit sendet, aus welchem Grund auch immer, ein anderer Thread eine Nachricht an den Clienten und dieser wird aufgeweckt und bekommt nun eine Nachricht die er gar nicht haben will (im Moment) bzw. nicht erwartet.
Also müsstest du dort genauso etwas implementieren, das ein Thread auf eine Nachricht von einem bestimmten anderen Thread warten kann und du musst Queues implementieren wo du die Threads reinpacken kannst, falls sie nicht der gewünschte Thread sind.
Da wären wir ja aber praktisch schon wieder beim asynchronen IPC, nur das du halt anstatt Msgs die Threads in die Queue packst.
Wenn ich das von der Seite betrachte, ist asynchrones IPC mit aufgesetztem synchronen (das Warten auf eine Nachricht eines bestimmten Threads) doch besser. Da du dir die Möglichkeit der Asynchronität offen hälst.
Also gerade das weiterleiten der Nutzdaten stell ich mir damit nicht so einfach vor, ich schätze da wirst Du in jeder Ebene kopieren müssen. Oder willst du es ermöglichen das 2 Pipes miteinander verbunden werden?
Ich habe das alles noch nicht 100%ig zu Ende gedacht und wenn der Code dann endlich mal geschrieben wird, wird es auch bestimmt noch verbessert. Aber ansich ist das erstmal nur für den Weg Server->Client gedacht, wo die Daten eine dynamische Länge haben.
Wenn der Festplattentreiber Daten liest und diese an den Storage-Server weitergibt, dann würde ich das natürlich über SharedMem machen, viel einfacher und schneller. Denn da bekommst du ja (meistens) eh Blöcke die sich auf 4kb Pages aufteilen lassen und es muss nicht mehr kopiert werden.
Der Client will dann ja vielleicht nur ein paar Bytes haben und die kopierst du dann per Pipe. Das Einblenden (MemoryMapping) von Dateien wird dann ja eh wieder über SharedMem gemacht.
Du sollst ja auch nur einen try/catch Block benutzen. In den catch-Abschnit kommt das Aufräumen und in den try-Abschnitt kannst Du den Code packen der möglicherweise Fehler generiert, wenn dort ein Fehler auftritt (also eine Exception geworfen wird) wird sofort in den nächsten catch-Abschnitt gesprungen der sich für diese Exception zuständig fühlt (das throw kannst Du wie ein goto betrachten und das sogar über Funktionsebenen hinweg).
Genau dort liegt jetzt wieder das Problem (und der redundante Code). Stell dir vor du musst mehrmals Speicher anfordern, dann wird jedes Mal eine "NotEnoughMemoryException" geschmissen, aber da das Anfordern an verschieden Stellen stattfindet, musst du unterschiedlich Aufräumen, aber desto weiter du in einer Funktion bist, desto mehr musst du "gleich" machen!
Bsp:
try {
mem= memAlloc4kb();
} catch (NotEnoughMemoryException e) {
return 0;
}
try {
id= allocNewID();
} catch (NoFreeIDException e) {
memFree4kb(mem);
return 0;
}
try {
stack= memAlloc4kb();
} catch (NotEnoughMemoryException e) {
memFree4kb(mem);
freeID(id);
return 0;
}
... //das Spiel kannst du jetzt weiter treiben, indem noch mehr Ressourcen benötigt werden
Ich weiß das man den 1. und 2. try-Block zusammenfassen könnte, aber mir ist gerade die Syntax nicht eingefallen. Aber den 3. try-Block musst du doch vom 1. trennen, da du ja ansonsten nicht weißt welche Anforderung nicht geklappt hat und welchen Speicher du wieder freigeben musst.
Ich hoffe du siehst was ich meine.
Also zwischen einer Prozess-Sterbe-Nachricht und einer Veränderungsmitteilung ist IMHO schon ein gewisser Unterschied. Für die Veränderungsmitteilungen bei den Dateien wirst Du wohl einen zusätzlichen (aber ähnlichen) Mechanismus implementieren müssen.
Da ich einen Storage-Server habe, würdest du dich als Client bei dem registrieren, das du eine bestimmte Node beobachten möchtest und jedes Mal wenn sich was ändert, dann bekommst du eine Nachricht. Was in der Nachricht steht, darüber kann man jetzt diskutieren. Sprich ob da schon drin steht was sich genau geändert hat oder ob du das jetzt erst nachfragen musst.
Hier ist jetzt auch asynchrones IPC besser. Denn du willst einfach nur diese Nachricht loswerden und es interessiert dich nicht, ob der Client diese im Moment auch empfangen kann. Auch wäre hier noch ein Syscall nicht schlecht, wo du mehrere Nachrichten mit einmal versenden könntest!
Was den Unterschied betrifft, wenn du dem Unix-Prinzip treu bleiben willst "alles ist eine Datei". Dann bräuchtest du nur diesen Node-Monitor implementieren und alles andere läuft dann über das VFS.
-
Moin
Da es zum Topic passt schreibe ich es mal hier rein.
Wie Komuniziert CDI mit den Treibern? Also über welche Methode.
Programm Noob
-
CDI ist eine Bibliothek, insofern über ganz normale Funktionsaufrufe.
-
Hallo,
CDI ist eine Bibliothek, insofern über ganz normale Funktionsaufrufe.
Ich denke mal das ist allen klar, ich vermute die Frage zielt eher darauf ab was dahinter steckt also wie diese Funktionsaufrufe bei einem Mikro-Kernel-OS von einem User-Mode-Prozess zum nächsten User-Mode-Prozess kommen.
Ich persönlich vermute mal das da überall ein paar Stubs drin stecken die das dann auf das OS-spezifische IPC umsetzen und wieder entgegennehmen.
@taljeth: Falls diese Vermutung richtig ist und sie auch auf tyndur zutrifft wäre es schön wenn Du uns dazu mal etwas detailliertere Infos geben könntest.
Ich vermute mal die freigebenen IDs werden wieder recycled, ansonsten wären echte Langläufer wirklich kaum möglich.
Das ist halt nicht möglich, so wie ich den Standard verstanden habe.
Also wenn Du den Standard richtig verstanden hast dann ist das schon ziemlich mysteriös. Im Prinzip verbraucht ja jedes fork eine ID und wenn die endlich sein sollten dürften so manche Web-Server (solche die für jede ankommende TCP-Verbindung einen fork machen) nicht besonders lange laufen. Ich vermute mal die OSe halten sich in diesem Punkt nicht an den Standard, anders kann ich mir das nicht erklären.
Meine Aussage bleibt trotzdem richtig ;) Denn die Festplatte kann nur eine Anfrage zur gleichen Zeit entgegen nehmen, denn du hast ja nicht 32x Ports um die Festplatte anzusprechen!
Nein! Man kann einer modernen SATA-HDD, wenn sie an einem Host-Controller hängt der im AHCI-Modus arbeitet (und nicht mehr im klassischen IDE-Modus), wirklich mehrere Jobs geben ohne warten zu müssen bis die vorangegangenen Jobs fertig sind. SATA arbeitet paket-orientiert und da ist es wirklich möglich mehrere Aufträge hintereinander an die Platte zu schicken, ließ Dir am besten mal die SATA-Spezifikation und die AHCI-Spezifikation durch.
Bei dem Fall würde es sich aber wirklich lohnen, wenn man 32 Threads hätte. Dann könnte jeder Thread warten bis neue Arbeit da ist und er könnte auch warten bis die Festplatte fertig ist (ich hoffe du verstehst wie ich das meine).
Eben genau das meinte ich, wenn Du nur dann Anfragen entgegennehmen kannst wenn ein Thread darauf wartet dann muss es möglich sein das mehrere Threads gleichzeitig an einem Deiner Message-Ports/Pipes/was-auch-immer warten.
Ich sehe es erstmal so, das die Initialisierung ruhig ein wenig komplexer/langsamer sein darf, wenn der Vorgang an sich dann schneller geht.
Was ist mit einfachem Code in der Art:int randValue;
{
FILE* randFile = fopen("/dev/random",ro);
fread(randFile,&randValue,sizeof(int));
fclose(randFile);
}
oder etwas anderem wo nur von einer (oder vielen) Datei(en) die Größe ermittelt werden soll. Da würdest Du einen Haufen Arbeit machen nur um wenige Infos zu bekommen, das erscheint mir unnötig kompliziert (und auch nicht schneller). Nebst dessen das Du in Deinem Kernel eine Menge Zeugs implementieren musst und ja theoretisch jeder Teil davon Fehler enthalten kann, zumindest generierst Du so eine Menge Code für den Kernel und steigerst damit die Wahrscheinlichkeit für Fehler (ohne nennenswert mehr Funktionalität zu bekommen).
Dann holst du dir die Anfrage mit "pipeRead(&msg,sizeof(struct msg_t))" (lohnt sich vorallem bei größeren Msgs, wie z.B. wenn man eine Datei laden soll)
Woher weiß Dein Dateisystemtreiber wie groß dieser Auftrag ist wenn er die Länge des Dateinamens noch nicht kennt? Byte-orientierte Pipes sind für solche Dinge extrem ungeeignet weil der Empfänger niemals die Synchronisation auf die Message-Grenzen verlieren darf.
arbeitest die Msg ab und schreibst deine Daten dann per pipeWrite(&data,dateLen)". Abschließend musst du dann noch ein "pipeFlush()" aufrufen, damit der Thread der auf die Daten wartet sie lesen kann.
Das sieht nach ner Menge Syscalls aus. 1. der Client für das Abschicken der Anfrage. 2. der Service für das Abholen der Anfrage (hier eventuell noch ein weiterer weil er erst mal nur die Länge der eigentlichen Anfrage erfahren wollte). 3. der Service für das Schreiben der Antwort (das Flush zähle ich mal nicht extra, das ist ein Flag beim Schreiben). 4. der Client zum Abholen der Antwort. Macht 4 Syscalls mit insgesamt 8 CPU-Mode-Wechseln (+ zusätzliches zur Vorbereitung/Aufräumen). Bei mir sind es nur 2 Syscalls (zum einen macht der Client seine Anfrage und zum anderen meldet der PopUp-Thread das er fertig ist und gekillt werden kann) mit insgesamt 4 CPU-Mode-Wechseln (ohne das irgendwelche Shared-Memorys o.ä. extra vorbereitet/aufgeräumt werden müssen).
Der Vorteil ist, das diese Pipe´s fast komplett im UserSpace laufen, du also nicht erst die Daten in den Kernel kopierst (Server) um sie dann wieder in den UserSpace zu kopieren (Client).
In meinem Konzept wird auch nichts kopiert sondern der Client stellt alle Speicherbereiche zur Verfügung und diese werden in den Service eingeblendet. Falls der Service seinerseits auch wieder die Dienste eines weiteren Service benötigt werden die geerbten Speicherbereiche einfach weitergereicht so das auch der letzte Service direkt mit dem Speicher des ursprünglichen Clients arbeitet. Ein HDD-Treiber würde also direkt in den Speicher der Applikation schreiben (bzw. den HDD-Host-Controller schreiben lassen) obwohl da noch das VFS und ein Datei-System-Treiber dazwischen hängen, das heißt das bevor die Applikation die Daten verarbeitet (nach dem fread) keines der Nutzdatenbytes überhaupt von einer CPU angefasst wurde. Der HDD-Controller schreibt die Daten (als Bus-Master) in den Haupt-Speicher und erst die Applikation (nach dem ganzen IPC-Vorgang) greift tatsächlich auf diese Daten zu.
Das fread in meiner libc würde etwa so aussehen (ich beziehe mich auf die IPC-Syscalls von http://forum.lowlevel.eu/index.php?topic=2433 (http://forum.lowlevel.eu/index.php?topic=2433)):int fread(FILE* const handle,void* const data,const ulong length)
{
ulong fread_length; //hier kommt die Anzahl der tatsächlich gelesenen Bytes rein
{
File_Read_Request_t request;
//'request' passend befüllen mit Infos von 'handle'
File_Read_Responce_t responce;
ulong responce_length = ~0;
//ein blockierender Syscall (synchrones IPC) :
const long retval = syscall_msg_sync_send(vfs_msg_id,&request,sizeof(request),NULL,0,&responce,&responce_length,sizeof(responce),data,&fread_length,length);
//den Erfolg des Syscalls ansich auswerten :
if (retval != KERNEL_SYSCALL_OK_Code)
{ /* IPC ansich ist fehlgeschlagen */ return ???; }
//die Antwort vom aufgerufenen Service (RPC-Handler) auswerten :
if (responce_length != sizeof(responce))
{ /* die Antwort vom RPC-Handler hat nicht die passende Größe */ return ???; }
if (responce != ????)
{ /* Fehler beim eigentlichen fread im VFS oder tiefer */ return ???; }
assert(fread_length <= length); //nur zur Sicherheit
}
return fread_length; //Anzahl der tatsächlich gelesenen Bytes zurückmelden
}
Ich finde das recht einfach und übersichtlich und es ist völlig egal ob 5 Bytes oder 500Mbytes gelesen werden sollen. Als einzigste Vorbereitung muss 'vfs_msg_id' von der libc einmal beim Prozessstart ermittelt werden, damit man weiß wohin mit den IPC-Anfragen.
Folgendes Szenario, ein Client sendet eine Nachricht und der Server kann sie gleich entgegen nehmen. Also kehrt der Client gleich wieder zurück ohne zu blockiert, dann wartet er auf eine Antwort, blockiert also.
Das sind schon 2 Syscalls für den Client obwohl IMHO einer reicht.
Aber in der Zwischenzeit sendet, aus welchem Grund auch immer, ein anderer Thread eine Nachricht an den Clienten und dieser wird aufgeweckt und bekommt nun eine Nachricht die er gar nicht haben will (im Moment) bzw. nicht erwartet.
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Das simple Programm ist doch darauf gar nicht ausgelegt. Ich persönlich sehe es als enormes Risiko an wenn man Prozessen etwas geben kann womit sie nichts anfangen können bzw. was sie gar nicht explizit erlaubt haben. Einem PC im Internet kann man doch auch nur auf die TCP/UDP-Ports was schicken die dieser PCs explizit geöffnet hat (um eben was von außen empfangen zu können).
Was ist eigentlich wenn ein böser Prozess in genau diesem Moment dem Client eine fingierte Antwort schickt? Ich persönlich bin der Meinung das der OS-Kernel es garantieren muss das der Client genau seine Antwort erhält und nichts anderes.
Wenn ich das von der Seite betrachte, ist asynchrones IPC mit aufgesetztem synchronen (das Warten auf eine Nachricht eines bestimmten Threads) doch besser. Da du dir die Möglichkeit der Asynchronität offen hälst.
Ich bleibe lieber dabei das ich beides anbiete, der zusätzliche Code im Kernel dürfte sehr überschaubar bleiben und den Performance-Verlust um aus etwas Asynchronem etwas Synchrones zu machen will ich nicht (eben weil das Synchrone das signifikant Häufigere und deutlich Wichtigere ist).
Wenn der Festplattentreiber Daten liest und diese an den Storage-Server weitergibt, dann würde ich das natürlich über SharedMem machen, viel einfacher und schneller.
Wenn Du erst für jeden kurzen Datei-Lesevorgang dann ein paar Ebenen tiefer Shared-Memory vorbereiten musst stelle ich mir das nicht besonders performant vor.
Genau dort liegt jetzt wieder das Problem (und der redundante Code). Stell dir vor du musst mehrmals Speicher anfordern, dann wird jedes Mal eine "NotEnoughMemoryException" geschmissen, aber da das Anfordern an verschieden Stellen stattfindet, musst du unterschiedlich Aufräumen, aber desto weiter du in einer Funktion bist, desto mehr musst du "gleich" machen! ...
Das soll man ja auch anders machen:try
{ //Ebene 1
mem = memAlloc4kb();
try
{ //Ebene 2
id = allocNewID();
try
{ //Ebene 3
stack = memAlloc4kb();
... //das Spiel kannst du jetzt weiter treiben, indem noch mehr Ressourcen benötigt werden
} //Ebene 3
catch (NotEnoughMemoryException e)
{
freeID(id);
throw(NoFreeIDException); //noch den Aufräum-Code von Ebene 2 ansteuern
}
} //Ebene 2
catch (NoFreeIDException e)
{
memFree4kb(mem);
throw(NotEnoughMemoryException); //noch den Aufräum-Code von Ebene 1 ansteuern
}
} //Ebene 1
catch (NotEnoughMemoryException e)
{
return 0;
}
Man räumt quasi von innen nach außen auf. Jeden Aufräum-Code hast Du nur ein einziges mal (keine Redundanzen), Du musst immer nur darauf achten das beim von innen nach außen gehen keine Ebene ausgelassen wird.
In diesem konkreten Fall (wo es nur um malloc u.ä. geht) würde ich das aber schon etwas anders (einfacher) machen:try
{
mem = memAlloc4kb();
id = allocNewID();
stack = memAlloc4kb();
//arbeiten ....
memFree4kb(stack);
freeID(id);
memFree4kb(mem);
return OK_Code;
}
catch (KernelException e) //KernelExpection ist Parent von NotEnoughMemoryException und NoFreeIDException
{
if (stack != NULL) { memFree4kb(stack); }
if (id != NULL) { freeID(id); }
if (mem != NULL) { memFree4kb(mem); }
return Fehler_Code;
}
Grüße
Erik
-
Also wenn Du den Standard richtig verstanden hast dann ist das schon ziemlich mysteriös. Im Prinzip verbraucht ja jedes fork eine ID und wenn die endlich sein sollten dürften so manche Web-Server (solche die für jede ankommende TCP-Verbindung einen fork machen) nicht besonders lange laufen. Ich vermute mal die OSe halten sich in diesem Punkt nicht an den Standard, anders kann ich mir das nicht erklären.
Es gibt Situationen wo das Sinn macht, aber im Großen und Ganzen glaube ich eher das ich den Standard da nicht richtig verstanden habe.
Nein! Man kann einer modernen SATA-HDD, wenn sie an einem Host-Controller hängt der im AHCI-Modus arbeitet (und nicht mehr im klassischen IDE-Modus), wirklich mehrere Jobs geben ohne warten zu müssen bis die vorangegangenen Jobs fertig sind. SATA arbeitet paket-orientiert und da ist es wirklich möglich mehrere Aufträge hintereinander an die Platte zu schicken, ließ Dir am besten mal die SATA-Spezifikation und die AHCI-Spezifikation durch.
Was ich meinte war, das du immer nur einen Job zur gleichen Zeit an die Festplatte geben kannst und dass das nicht parallel, auf mehreren CPUs, gleichzeitig läuft.
oder etwas anderem wo nur von einer (oder vielen) Datei(en) die Größe ermittelt werden soll. Da würdest Du einen Haufen Arbeit machen nur um wenige Infos zu bekommen, das erscheint mir unnötig kompliziert (und auch nicht schneller). Nebst dessen das Du in Deinem Kernel eine Menge Zeugs implementieren musst und ja theoretisch jeder Teil davon Fehler enthalten kann, zumindest generierst Du so eine Menge Code für den Kernel und steigerst damit die Wahrscheinlichkeit für Fehler (ohne nennenswert mehr Funktionalität zu bekommen).
Also die Initialisierung, würde ja nur einmal ablaufen und das würde dann auch von meiner Libc gemacht werden.
Was meinst du genau, was ich da alles im Kernel implementieren müsste?
Das sieht nach ner Menge Syscalls aus. 1. der Client für das Abschicken der Anfrage. 2. der Service für das Abholen der Anfrage (hier eventuell noch ein weiterer weil er erst mal nur die Länge der eigentlichen Anfrage erfahren wollte). 3. der Service für das Schreiben der Antwort (das Flush zähle ich mal nicht extra, das ist ein Flag beim Schreiben). 4. der Client zum Abholen der Antwort. Macht 4 Syscalls mit insgesamt 8 CPU-Mode-Wechseln (+ zusätzliches zur Vorbereitung/Aufräumen). Bei mir sind es nur 2 Syscalls (zum einen macht der Client seine Anfrage und zum anderen meldet der PopUp-Thread das er fertig ist und gekillt werden kann) mit insgesamt 4 CPU-Mode-Wechseln (ohne das irgendwelche Shared-Memorys o.ä. extra vorbereitet/aufgeräumt werden müssen).
Als erstes, wie willst du es denn bei Msgs dynamischer Länge machen? Ich meine an sich müsstes du erstmal fragen wie groß die Msg ist, dann nen Buffer allokieren und dann die Msg abholen, oder?
Dann noch was zu meinen Pipe´s, die sind fast ausschließlich im UserSpace, um daraus was zu lesen musst du halt nicht in den Kernel, das ist ja der ganze Sinn und Zweck der Sache. Du musst nur in den Kernel, wenn du lesen willst und keine Daten vorhanden sind oder du schreiben willst und kein "freier" Speicher mehr da ist.
Woher weiß Dein Dateisystemtreiber wie groß dieser Auftrag ist wenn er die Länge des Dateinamens noch nicht kennt? Byte-orientierte Pipes sind für solche Dinge extrem ungeeignet weil der Empfänger niemals die Synchronisation auf die Message-Grenzen verlieren darf.
Deswegen war es nur ein einfaches Bsp.!
Es gibt die Möglichkeit das ich eine Msg mit nem festgelegten Header habe und der String danach kommt (wobei die Länge gegeben werden kann oder einfach gelesen wird bis "\0" kommt, ich weiß fehleranfällig).
Was du mit der Synchronisation meinst ist mir gerade auch nicht klar.
Ich finde das recht einfach und übersichtlich und es ist völlig egal ob 5 Bytes oder 500Mbytes gelesen werden sollen. Als einzigste Vorbereitung muss 'vfs_msg_id' von der libc einmal beim Prozessstart ermittelt werden, damit man weiß wohin mit den IPC-Anfragen.
D.h. ja das du dich um alles im Clienten kümmern willst, sprich das wenn du 5bytes an der Position 100 lesen willst, dann musst du "wissen" wieviel du von der Datei laden musst und dann musst du die richtigen Daten noch rumkopieren (das Rumkopieren muss ich auch machen).
Ich weiß nicht wie du das mit dem SharedMem machst, aber ich stelle es mir schwierig vor, da dann nen vernünftiges VFS mit Caching aufzusetzen.
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Das simple Programm ist doch darauf gar nicht ausgelegt. Ich persönlich sehe es als enormes Risiko an wenn man Prozessen etwas geben kann womit sie nichts anfangen können bzw. was sie gar nicht explizit erlaubt haben. Einem PC im Internet kann man doch auch nur auf die TCP/UDP-Ports was schicken die dieser PCs explizit geöffnet hat (um eben was von außen empfangen zu können).
Was ist eigentlich wenn ein böser Prozess in genau diesem Moment dem Client eine fingierte Antwort schickt? Ich persönlich bin der Meinung das der OS-Kernel es garantieren muss das der Client genau seine Antwort erhält und nichts anderes.
Also bei mir kannst du beim Erstellen des Ports festlegen, ob ein bestimmter Prozess oder alle Prozesse an diesen Port Nachrichten schicken dürfen. Willst du aber als Clienten nur einen Port (was vollkommen ausreichend sein sollte) öffnen, dann musst du alle Prozesse senden lassen.
Ich will ja nicht noch eine Liste an den Port hängen welche Prozesse etwas senden dürfen. (das selbe gilt für das Abholen der Msgs)
Ich bleibe lieber dabei das ich beides anbiete, der zusätzliche Code im Kernel dürfte sehr überschaubar bleiben und den Performance-Verlust um aus etwas Asynchronem etwas Synchrones zu machen will ich nicht (eben weil das Synchrone das signifikant Häufigere und deutlich Wichtigere ist).
Welcher Performanceverlust?
Wenn Du erst für jeden kurzen Datei-Lesevorgang dann ein paar Ebenen tiefer Shared-Memory vorbereiten musst stelle ich mir das nicht besonders performant vor.
Also ich habe schon vor irgendwelches Caching zu nutzen (so dass z.B. der Storage-Server die Daten schon hat und den Festplattentreiber gar nicht erst danach fragen muss, da wird dann auch nichts mit SharedMem initialisiert) und das was du beschrieben hast, ist doch auch nur eine Form des SharedMem!?
Was deine Lösung (die 1.) für die Exceptions betrifft. Das findest du lesbarer und schöner (mal abgesehen von dem ganzen Overhead und das du es im Kernel nicht nutzen kannst)?
Also da bleibe ich lieber bei meinem goto ;)
Wie machst du es eigentlich, wenn eine Funktion zwar einen Fehler zurückgibt, du aber aus dieser Ebene nichts mehr freigeben musst, nur halt aus den äußeren, einen leeren catch-Block?
-
Ich denke mal das ist allen klar, ich vermute die Frage zielt eher darauf ab was dahinter steckt also wie diese Funktionsaufrufe bei einem Mikro-Kernel-OS von einem User-Mode-Prozess zum nächsten User-Mode-Prozess kommen.
Ich persönlich vermute mal das da überall ein paar Stubs drin stecken die das dann auf das OS-spezifische IPC umsetzen und wieder entgegennehmen.
@taljeth: Falls diese Vermutung richtig ist und sie auch auf tyndur zutrifft wäre es schön wenn Du uns dazu mal etwas detailliertere Infos geben könntest.
Richtig, der standardisierte Teil von CDI ist das Interface. Damit haben die Treiber etwas einheitliches, gegen das sie programmieren können. Die Implementierung, also die eigentliche Lib, ist völlig OS-spezifisch. Manche Teile kann man von anderen Systemen übernehmen, aber die meisten sind einfach genau die Abbildung auf die nativen Schnittstellen des Systems. Wenn man CDI implementiert, geht man also beispielsweise her und implementiert Funktionen wie cdi_net_receive_packet. Der Netzwerktreiber ruft diese Funktion auf, damit das OS was damit machen kann, aber wohin man das Paket am Ende hinliefert, ist CDI völlig egal.
Insofern ist auch die Implementierung unter tyndur nicht außergewöhnlich spannend. Meistens wird einfach eine Dateisystem-Schnittstelle als Wrapper um die CDI-Funktionen gebastelt, unter Umständen aber auch direkt in RPC umgesetzt.
-
Hallo,
Wenn man CDI implementiert, geht man also beispielsweise her und implementiert Funktionen wie cdi_net_receive_packet. Der Netzwerktreiber ruft diese Funktion auf, damit das OS was damit machen kann, aber wohin man das Paket am Ende hinliefert, ist CDI völlig egal.
Ich hätte jetzt vermutet dass das Netzwerkpaket zum IP-Stack (auch wieder ein User-Mode-Prozess) muss (nicht zum OS selber) welcher das Paket entpackt und dann an TCP (oder UDP oder was anderes) weiterleitet. Ich bin davon ausgegangen das Netzwerk-Treiber und IP-Stack beides Clients von CDI sind und sich CDI darum kümmert (mit einem OS-spezifischen Mechanismus) das die zueinander finden.
Insofern ist auch die Implementierung unter tyndur nicht außergewöhnlich spannend. Meistens wird einfach eine Dateisystem-Schnittstelle als Wrapper um die CDI-Funktionen gebastelt, unter Umständen aber auch direkt in RPC umgesetzt.
Ich weiß zwar theoretisch wie das mit den Stubs ungefähr funktioniert aber es wäre trotzdem sehr nett da mal ein praktisches Beispiel sehen zu können, eventuell garniert mit ein paar erklärenden Sätzen.
Was ich meinte war, das du immer nur einen Job zur gleichen Zeit an die Festplatte geben kannst und dass das nicht parallel, auf mehreren CPUs, gleichzeitig läuft.
Das übergeben eines neuen Jobs an den AHCI-Host-Controller läuft auf das einhängen einer Struktur in eine Art verkettete Liste hinaus. Der kritische Code-Abschnitt (welcher serialisiert werden muss) dürfte nur sehr wenige Assemblerbefehle lang sein, in so fern sollte sich das extrem gut parallelisieren lassen. So wie ich die AHCI-Spezifikation verstanden habe ist AHCI ein gutes Beispiel dafür wie man eine HW-Komponente entwerfen muss damit sie möglichst vielen parallelen Jobs gleichzeitig nachgehen kann. Ein AHCI-Controller darf sogar richtig viele verschiedene Interrupts (dann per MSI/MSI-X) haben und man kann jedem Job sagen welcher davon nach Erledigung ausgelöst werden soll, wenn man jeden Interrupt einer anderen CPU zuweist und für jeden Job den richtigen Interrupt wählt kann man damit erreichen das alle Arbeiten an einem Job immer von der selben CPU erledigt werden (wegen Cache-Effizienz usw.).
Also die Initialisierung, würde ja nur einmal ablaufen
Einmal pro was? In meinem Konzept brauch ich nur die Message-Target-ID des VFS und schon kann ich ohne weitere Vorbereitungen ein (oder beliebig viele) fopen machen. Diese ID soll dem Prozess bei seiner Erstellung gleich mitgegeben werden so das in der Init-Funktion der libc diese ID nur noch in die richtige Variable kommt (noch bevor main aufgerufen wird).
Was meinst du genau, was ich da alles im Kernel implementieren müsste?
Kann ich nicht genau sagen, nur Du kennst Dein Konzept gut genug um das sagen zu können. Bis jetzt hast Du von Messages und Ports, von Pipes und von Shared-Memory geschrieben. Für all das musst Du zumindest Verwaltungsfunktionen in Deinen Kernel implementieren.
Als erstes, wie willst du es denn bei Msgs dynamischer Länge machen? Ich meine an sich müsstes du erstmal fragen wie groß die Msg ist, dann nen Buffer allokieren und dann die Msg abholen, oder?
In meinem Code-Beispiel für fread gab es doch File_Read_Request_t request;
//'request' passend befüllen mit Infos von 'handle'
und später beim IPC-Syscall gibt es das als Parameter,&request,sizeof(request),
Der Request liegt also beim Client im Speicher (hier im Stack-Frame der fread-Funktion) und der Client kennt auch seine Größe. Der Kernel blendet diesen Speicher einfach in den Kontext des aufgerufenen Service ein (und da ich Segmente benutze ist es egal ob dieser Speicherbereich nur 3 Bytes oder gleich 3 GBytes groß ist, das kostet nicht einen Assemblerbefehl mehr), der Service muss den Request also nicht in einen eigenen Buffer holen sondern bekommt direkt den Original-Buffer seines Clients übergeben (zusammen mit der Info wie Groß dieser ist). Des selbe trifft auch für die Antwort zu, der Client hat einen Speicherbereich wo die Antwort rein soll und der Service bekommt quasi einen Pointer in diesen Speicher und die Info wie lang seine Antwort maximal sein darf (dank der Segmente kann die Antwort auch nicht länger werden, sonst löst der Service eine Segmentation-Fault-Exception aus).
Diese Structur (für fread) könnte diesen Aufbau haben:typedef File_Read_Request_t{
ulong job_code; //muss immer an dieser Stelle liegen damit der IPC-Handler im VFS weiß was zu tun ist (wird mit einer Funktionsnummer belegt)
ulong file_handle; //dürfte klar sein (fread holt das aus 'FILE* handle')
ulong file_offset; //das ist der aktuelle File-Pointer (welcher auch aus 'FILE* handle' kommt)
ulong read_length; //dürfte auch klar sein
}File_Read_Request_t;
Der Handler sieht dann etwa so aus:void vfs_file_handler(const void* const param,
const long sender_id,
const void* const question1,
const ulong question1_length,
const void* const question2,
const ulong question2_length,
void* const answer1,
const ulong answer1_max_length,
void* const answer2,
const ulong answer2_max_length)
{
switch(*((const ulong* const)question1)) //Job-Code auswerten (deswegen muss der immer an Offset 0 im Request liegen)
{
case VFS_IPC_FUNCTION_FREAD:
{
const File_Read_Request_t* const request = (const File_Read_Request_t* const)question1;
//den Rest vom request auswerten und Job erledigen und gelesene Daten nach '*answer2' schreiben ...
ulong real_read_length = ???; //mit der Anzahl der tatsächlich gelesenen Bytes befüllen
File_Read_Response_t* const response = (File_Read_Responce_t* const)answer1;
//response mit dem Job-Ergebnis befüllen (also Okay oder passender Fehler-Code)
//IPC-Handler ist fertig, die IPC-Ende-Funktion kommt nicht zurück, dieser PopUp-Thread wird damit vom Kernel gekillt
syscall_msg_sync_handler_end(sizeof(response),real_read_length); //die beiden Parameter melden die tatsächliche Größe der beiden Antworten zurück
//(müssen natürlich kleiner oder gleich sein als die Max-Werte)
}
}
}
Dann noch was zu meinen Pipe´s, die sind fast ausschließlich im UserSpace, um daraus was zu lesen musst du halt nicht in den Kernel, das ist ja der ganze Sinn und Zweck der Sache. Du musst nur in den Kernel, wenn du lesen willst und keine Daten vorhanden sind oder du schreiben willst und kein "freier" Speicher mehr da ist.
Ich verstehe zwar nicht was Du damit meinst das die Pipes "fast ausschließlich im User-Space" sind aber zumindest muss der Sender den Empfänger irgendwie (wohl per Syscall) darüber benachrichtigen wenn er neue Daten reingelegt hat, dauerndes Pollen des Empfängers willst du bestimmt nicht. Das selbe noch in die andere Richtung wenn der Buffer voll ist und der Empfänger dem (wartenden weil sendewilligen) Sender mitteilen muss das wieder Platz geschaffen wurde.
Es gibt die Möglichkeit das ich eine Msg mit nem festgelegten Header habe und der String danach kommt (wobei die Länge gegeben werden kann oder einfach gelesen wird bis "\0" kommt, ich weiß fehleranfällig).
Eben genau diese Fehleranfälligkeit meinte ich damit das der Empfänger niemals eine Nachricht fehlerhaft decodieren darf, dann erkennt er nämlich nicht mehr die nachfolgenden Nachrichten (er weiß nicht mehr wo diese Anfangen) und die Pipe ist erledigt.
Ich weiß nicht wie du das mit dem SharedMem machst, aber ich stelle es mir schwierig vor, da dann nen vernünftiges VFS mit Caching aufzusetzen.
Das mit dem Caching ist allerdings ein kleines Problem, im Endeffekt bedeutet dass das die von der HDD gelesenen Daten in zwei verschiedene Speicherbereiche rein sollen, einmal in den Speicherbereich der fread übergeben wurde (in der eigentlichen Applikation) und zusätzlich in den Cache (im VFS). Da muss ich auf jeden Fall noch mal gründlich drüber nachdenken aber ich schätze einmal muss ich dafür die Daten dann doch kopieren (das ist aber auch in allen anderen Konzepten so). Ein Lesezugriff auf den Cache läuft ja auch auf einen Kopiervorgang hinaus.
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Das simple Programm ist doch darauf gar nicht ausgelegt. Ich persönlich sehe es als enormes Risiko an wenn man Prozessen etwas geben kann womit sie nichts anfangen können bzw. was sie gar nicht explizit erlaubt haben. Einem PC im Internet kann man doch auch nur auf die TCP/UDP-Ports was schicken die dieser PCs explizit geöffnet hat (um eben was von außen empfangen zu können).
Was ist eigentlich wenn ein böser Prozess in genau diesem Moment dem Client eine fingierte Antwort schickt? Ich persönlich bin der Meinung das der OS-Kernel es garantieren muss das der Client genau seine Antwort erhält und nichts anderes.
Also bei mir kannst du beim Erstellen des Ports festlegen, ob ein bestimmter Prozess oder alle Prozesse an diesen Port Nachrichten schicken dürfen. Willst du aber als Clienten nur einen Port (was vollkommen ausreichend sein sollte) öffnen, dann musst du alle Prozesse senden lassen.
Du hast meine Frage(n) nicht beantwortet! Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Diese potentielle Gefahr empfinde ich als groben Design-Fehler.
Dieses Warten auf bestimmte Antworten macht IMHO nur den Kernel-Code noch komplizierter.
und den Performance-Verlust um aus etwas Asynchronem etwas Synchrones zu machen will ich nicht (eben weil das Synchrone das signifikant Häufigere und deutlich Wichtigere ist).
Welcher Performanceverlust?
Beim asynchronen IPC muss der Client 2 Syscalls machen um das Verhalten von synchronem IPC zu erzielen (einmal Anfrage schicken und einmal auf Antwort warten/abholen).
Wann läuft der Service-Thread (der den Job bearbeitet) eigentlich los? Wird der Client gleich bei der Anfrage geschedult und die CPU dem Service-Thread übergeben so das der Job möglichst schnell anfangen kann (was bedeutet das der Client erst später dazu kommt seine Antwort abzuholen)? Oder wird der Service-Thread nur auf "runnable" gesetzt und läuft erst dann los wenn der Scheduler mal wieder vorbei schaut.
Was deine Lösung (die 1.) für die Exceptions betrifft. Das findest du lesbarer und schöner (mal abgesehen von dem ganzen Overhead und das du es im Kernel nicht nutzen kannst)?
Natürlich ist Beispiel 1 kein Vorbild im Punkt Lesbarkeit, ich wollte nur mal demonstrieren wie man mit Exceptions mehrfachen Aufräum-Code vermeiden kann, es gibt aber noch viele andere Wege die zum Teil auch schöner zu lesen sind.
Wie machst du es eigentlich, wenn eine Funktion zwar einen Fehler zurückgibt, du aber aus dieser Ebene nichts mehr freigeben musst, nur halt aus den äußeren, einen leeren catch-Block?
Dann kann man das try/catch wohl auch ganz weglassen, Du musst nur sicherstellen dass das nächst äußere catch auch diese Art von Exception fängt (siehst Du in meinem 2. Beispiel).
Grüße
Erik
-
Einmal pro was? In meinem Konzept brauch ich nur die Message-Target-ID des VFS und schon kann ich ohne weitere Vorbereitungen ein (oder beliebig viele) fopen machen. Diese ID soll dem Prozess bei seiner Erstellung gleich mitgegeben werden so das in der Init-Funktion der libc diese ID nur noch in die richtige Variable kommt (noch bevor main aufgerufen wird).
Das ist der Punkt der noch ein wenig schwammig ist ;) Ich weiß nicht ob die Initialisierung einmal pro Prozess (was das arbeiten von mehreren Threads aus wieder unschön macht) oder pro Thread (mehr "Aufwand" bzw. MemoryOverhead).
Ich wäre ja dafür einen neuen Thread aufzumachen, wo man sich mal über verschiedene IPC Methoden und eine effiziente Implementierung unterhalten kann!
Kann ich nicht genau sagen, nur Du kennst Dein Konzept gut genug um das sagen zu können. Bis jetzt hast Du von Messages und Ports, von Pipes und von Shared-Memory geschrieben. Für all das musst Du zumindest Verwaltungsfunktionen in Deinen Kernel implementieren.
Hab ich schon, aber (mal die Pipes ausgenommen) müsstest du doch auch haben, oder?
Was die dynamischen (die Größe) Msgs betrifft. Dann gehst du aber immer davon aus das an einem "Port" (oder was du als Abstraktion nimmst) immer nur eine Art von Msg ankommen kann. Sprich die müssen immer die selbe Länge haben.
Ich verstehe zwar nicht was Du damit meinst das die Pipes "fast ausschließlich im User-Space" sind aber zumindest muss der Sender den Empfänger irgendwie (wohl per Syscall) darüber benachrichtigen wenn er neue Daten reingelegt hat, dauerndes Pollen des Empfängers willst du bestimmt nicht. Das selbe noch in die andere Richtung wenn der Buffer voll ist und der Empfänger dem (wartenden weil sendewilligen) Sender mitteilen muss das wieder Platz geschaffen wurde.
Im Moment (da ich gerade nen schönen Artikel über IPC gefunden habe und überlege diese Konzept zu nutzen) kannst du dir meine Pipes wie SharedMem vorstellen. Du schreibst/liest in diesem SharedMem. Wie gesagt, du musst dann halt in den Kernel um dem eventuell wartenden Reader/Writer "bescheid" zu geben.
Eben genau diese Fehleranfälligkeit meinte ich damit das der Empfänger niemals eine Nachricht fehlerhaft decodieren darf, dann erkennt er nämlich nicht mehr die nachfolgenden Nachrichten (er weiß nicht mehr wo diese Anfangen) und die Pipe ist erledigt.
Da wollte ich dann "Dummheit wird sofort bestraft" ;) anwenden. Sprich macht der Client etwas was er nicht durfte/sollte, dann wird die Pipe halt dicht gemacht und die Kommunikation wird abgebrochen.
Du hast meine Frage(n) nicht beantwortet! Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Diese potentielle Gefahr empfinde ich als groben Design-Fehler.
Dieses Warten auf bestimmte Antworten macht IMHO nur den Kernel-Code noch komplizierter.
Eine Tastatureingabe, wäre z.B. auch ne Msg oder dass das Fenster verkleinert wurde und und und ... Also es gibt genug Gründe wieso man einer Anwendung Msgs schicken kann.
Dass das alles bei einer Konsolenanwendung keinen Sinn macht ist klar. Aber als Gegenfrage würde ich stellen, wieso sollte dir (als PC) jemand ein schädliches Netzwerkpaket schicken?
Mein Code ist deswegen nicht wirklich komplizierter geworden, ich meine es wird einfach jedes Mal wenn eine neue Msg in die Queue kommt, überprüft ob der Sender gleich der ID ist auf die man wartet und wenn dem so ist, wird der wartende Thread aufgeweckt.
Beim asynchronen IPC muss der Client 2 Syscalls machen um das Verhalten von synchronem IPC zu erzielen (einmal Anfrage schicken und einmal auf Antwort warten/abholen).
Also ich würde sagen, das hat nichts mit synchron oder asynchron zu tun! Synchron heißt doch ansich nur, das solange wie der Empfänger die Nachricht nicht entgegen nimmt/kann, blockiert der Sender.
Außerdem habe ich genau für die Situation doch meinen "send+recv"-Syscall.
Wann läuft der Service-Thread (der den Job bearbeitet) eigentlich los? Wird der Client gleich bei der Anfrage geschedult und die CPU dem Service-Thread übergeben so das der Job möglichst schnell anfangen kann (was bedeutet das der Client erst später dazu kommt seine Antwort abzuholen)? Oder wird der Service-Thread nur auf "runnable" gesetzt und läuft erst dann los wenn der Scheduler mal wieder vorbei schaut.
Also wenn ein Thread auf eine Antwort wartet und diese bekommt wird er wieder in die Ready-Queue des Schedulers gepackt (wann der Scheduler den Thread laufen lässt, kommt auf die Strategie und auf die anderen laufenden Threads an).
Was die Exceptions betrifft, ich bleibe bei meinem goto ;)
Aber es gibt durchaus Sachen wo die sehr hilfreich sein können, in der Libc wird sowas ja mit "errno" gelöst, sprich alle Werte die die Funktion zurückgibt sind "gültig", du musst halt nur in "errno" nachgucken, ob ein Fehler und was für ein Fehler aufgetreten ist.
-
Ich hätte jetzt vermutet dass das Netzwerkpaket zum IP-Stack (auch wieder ein User-Mode-Prozess) muss (nicht zum OS selber) welcher das Paket entpackt und dann an TCP (oder UDP oder was anderes) weiterleitet. Ich bin davon ausgegangen das Netzwerk-Treiber und IP-Stack beides Clients von CDI sind und sich CDI darum kümmert (mit einem OS-spezifischen Mechanismus) das die zueinander finden.
Ich denke, das ist vor allem ein Problem der Terminologie. Unter OS verstehe ich das große Ganze, selbstverständlich einschließlich IP-Stack. Ich meine nicht den Kernel damit.
Ich weiß zwar theoretisch wie das mit den Stubs ungefähr funktioniert aber es wäre trotzdem sehr nett da mal ein praktisches Beispiel sehen zu können, eventuell garniert mit ein paar erklärenden Sätzen.
Okay, nehmen wir als einfachstes Beispiel Netzwerk: tyndur-Code (http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/modules/cdi/lib/net/net.c;h=2b4d1c9602a2159658aba07f1b8dcc38a28f8dc7;hb=HEAD#l72)
Oben sind ein paar Initialisierungsfunktionen, die einen RPC-Handler für das Senden von Paketen definieren und solche Dinge. Das eigentliche Interface fängt bei cdi_net_receive() an. Das ist eine Funktion, die vom Treiber aufgerufen wird, wenn ein Paket angekommen ist. Was sie macht ist das Paket einfach an alle registrierten Empfänger per send_message() weiterzuleiten.
rpc_send_packet() ist das Gegenteil, nämlich ein RPC-Handler, der aufgerufen wird, wenn der TCP/IP-Stack ein Paket senden möchte. Der Wrapper holt sich die passende Struktur, die das Gerät beschreibt und ruft den Funktionspointer dev->send_packet() auf, der in den Treiber zeigt.
Ist das ungefähr, was du sehen wolltest?
-
Hallo,
Ist das ungefähr, was du sehen wolltest?
Ja, recht herzlichen Dank dafür!
Ich wäre ja dafür einen neuen Thread aufzumachen, wo man sich mal über verschiedene IPC Methoden und eine effiziente Implementierung unterhalten kann!
Ja gerne.
Hab ich schon, aber (mal die Pipes ausgenommen) müsstest du doch auch haben, oder?
Noch hab ich gar nichts programmiert, sind bis jetzt alles nur Design-Überlegungen die ich zum großen Teil in Textform habe.
Was die dynamischen (die Größe) Msgs betrifft. Dann gehst du aber immer davon aus das an einem "Port" (oder was du als Abstraktion nimmst) immer nur eine Art von Msg ankommen kann. Sprich die müssen immer die selbe Länge haben.
Wie kommst Du denn da drauf, ich hab doch eindeutig geschrieben das dem Handler die Größe der Speicherbereiche (die er vom Client geerbt bekommt) erfährt. Schau Dir Bitte noch mal die letzten 8 Parameter im Signal-Handler an, mein letztes Code-Beispiel von heute Mittag, da sind 4 Pointer mit je einer Längenangabe. Der Pointer zeigt direkt in den Speicher des Clients (per Shared-Memory) und die Längenangabe kommt ebenfalls vom Client. Auf diese Art kann der Handler mit jeder beliebigen Größe umgehen, er muss ja nichts dafür vorbereiten, das macht alles der Kernel.
(da ich gerade nen schönen Artikel über IPC gefunden habe und überlege diese Konzept zu nutzen)
Wenn der Artikel wirklich schön ist wäre es toll wenn Du da mal einen Link geben könntest.
Eben genau diese Fehleranfälligkeit meinte ich damit das der Empfänger niemals eine Nachricht fehlerhaft decodieren darf, dann erkennt er nämlich nicht mehr die nachfolgenden Nachrichten (er weiß nicht mehr wo diese Anfangen) und die Pipe ist erledigt.
Da wollte ich dann "Dummheit wird sofort bestraft" ;) anwenden. Sprich macht der Client etwas was er nicht durfte/sollte, dann wird die Pipe halt dicht gemacht und die Kommunikation wird abgebrochen.
Ich meinte eher dass das Risiko besteht das der Service durcheinander gerät (trotz dessen das der Client alles richtig macht).
Eine Tastatureingabe, wäre z.B. auch ne Msg
Die hat das Programm aber auch angefordert, indem aus cin gelesen wird. Wenn das Programm aber nicht mal cin öffnet dann darf es IMHO auch nicht mit Tastatureingaben belästigt werden.
oder dass das Fenster verkleinert wurde und und und ... Also es gibt genug Gründe wieso man einer Anwendung Msgs schicken kann.
Das sind alles Dinge wo das Programm vorher explizit sagen muss das es soetwas überhaupt verarbeiten kann/möchte.
Aber als Gegenfrage würde ich stellen, wieso sollte dir (als PC) jemand ein schädliches Netzwerkpaket schicken?
Meinst Du diese Frage ernst? Hast Du etwa zu lange für unsere letzte Familienministerin gearbeitet?
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken?
Hierauf hab ich immer noch keine plausible Antwort.
Beim asynchronen IPC muss der Client 2 Syscalls machen um das Verhalten von synchronem IPC zu erzielen (einmal Anfrage schicken und einmal auf Antwort warten/abholen).
Also ich würde sagen, das hat nichts mit synchron oder asynchron zu tun! Synchron heißt doch ansich nur, das solange wie der Empfänger die Nachricht nicht entgegen nimmt/kann, blockiert der Sender.
Beim synchronen Vorgang blockiert der Client so lange bis er eine Antwort erhält, was natürlich das zustellen der Anfrage beim Service mit einschließt. Beim asynchronen Vorgang kann man es zwar so implementieren das der Sender immer sofort weiter machen kann aber das bedeutet das die gesamte Message irgendwo (im Kernel) gequeuet werden muss (falls der Empfänger gerade nicht bereit ist) und das kostet Speicher. Ich werde es in meinem OS wahrscheinlich so einrichten dass das queuen nur für kleine Nachrichten möglich ist die der Kernel selber senden will, denn kann man ja eben nicht blockieren. Wenn ein User-Mode-Prozess eine große asynchrone Nachricht verschicken will und der Empfänger gerade nicht bereit ist, weil z.B. kein zusätzlicher PopUp-Thread mehr erstellt werden darf, dann wird er eben blockiert, ich sehe da keine negativen Folgen.
Also wenn ein Thread auf eine Antwort wartet und diese bekommt wird er wieder in die Ready-Queue des Schedulers gepackt (wann der Scheduler den Thread laufen lässt, kommt auf die Strategie und auf die anderen laufenden Threads an).
Das klingt irgendwie ungünstig langsam. Für eine simple RPC-Anfrage, z.B. die Größe einer Datei ermitteln, wird 2 mal auf den Scheduler gewartet. Ein ls in einem Verzeichnis mit 10000 Dateien dürfte da eine echt harte Geduldsprobe werden.
Was die Exceptions betrifft, ich bleibe bei meinem goto
Nur zu. Ich empfehle Dir trotzdem irgendwann einmal eine mächtige Programmiersprache, wie z.B. C++, zu erlernen und damit auch größere Programm zu entwickeln. Das eröffnet einem auch neue Sichtweisen für viele Dinge.
Grüße
Erik
-
Wie kommst Du denn da drauf, ich hab doch eindeutig geschrieben das dem Handler die Größe der Speicherbereiche (die er vom Client geerbt bekommt) erfährt. Schau Dir Bitte noch mal die letzten 8 Parameter im Signal-Handler an, mein letztes Code-Beispiel von heute Mittag, da sind 4 Pointer mit je einer Längenangabe. Der Pointer zeigt direkt in den Speicher des Clients (per Shared-Memory) und die Längenangabe kommt ebenfalls vom Client. Auf diese Art kann der Handler mit jeder beliebigen Größe umgehen, er muss ja nichts dafür vorbereiten, das macht alles der Kernel.
Mir sind deine Parameter schon klar, aber d.h. auch das du davon ausgehst, dass du immer weißt wann was für eine Nachricht kommt.
Die hat das Programm aber auch angefordert, indem aus cin gelesen wird. Wenn das Programm aber nicht mal cin öffnet dann darf es IMHO auch nicht mit Tastatureingaben belästigt werden.
Ich muss ehrlich sagen, das ich nicht genau weiß wie das z.B. unter Windows läuft, aber ich dachte du bekommst halt ständig Nachrichten und die die du verarbeiten willst machst du halt und alle anderen werden gleich wieder verworfen.
Bzw. ich stelle es mir so auch einfacher vor, du sendest dem Programm halt das die Mouse gerade da und da geklickt hat und wenn das Programm daraus was machen will dann macht es was drauß und wenn nicht, dann wird die Nachricht halt gelöscht.
Meinst Du diese Frage ernst? Hast Du etwa zu lange für unsere letzte Familienministerin gearbeitet?
Es ging mir darum, das du immer mit allem rechnen musst. Ansonsten fällt das dann unter die Kategorie Sicherheitslücke/Bug was irgendwie ausgenutzt werden könnte.
Beim synchronen Vorgang blockiert der Client so lange bis er eine Antwort erhält, was natürlich das zustellen der Anfrage beim Service mit einschließt. Beim asynchronen Vorgang kann man es zwar so implementieren das der Sender immer sofort weiter machen kann aber das bedeutet das die gesamte Message irgendwo (im Kernel) gequeuet werden muss (falls der Empfänger gerade nicht bereit ist) und das kostet Speicher. Ich werde es in meinem OS wahrscheinlich so einrichten dass das queuen nur für kleine Nachrichten möglich ist die der Kernel selber senden will, denn kann man ja eben nicht blockieren. Wenn ein User-Mode-Prozess eine große asynchrone Nachricht verschicken will und der Empfänger gerade nicht bereit ist, weil z.B. kein zusätzlicher PopUp-Thread mehr erstellt werden darf, dann wird er eben blockiert, ich sehe da keine negativen Folgen.
Ich sehe da auch erstmal keine negativen Folgen. Ich wollte halt nur darauf hinaus das du synchron damit verbindest das du automatisch auch gleich eine Antwort bekommst und das hat damit nichts zu tun. Denn du musst ja auch die Möglichkeit haben einfach eine Msg zu senden ohne eine Antwort zu wollen (Bsp. Server) und da macht sich dann asynchron einfach besser. Ansonsten ist natürlich aus Programmierersicht synchron viel sympatischer.
Das klingt irgendwie ungünstig langsam. Für eine simple RPC-Anfrage, z.B. die Größe einer Datei ermitteln, wird 2 mal auf den Scheduler gewartet. Ein ls in einem Verzeichnis mit 10000 Dateien dürfte da eine echt harte Geduldsprobe werden.
Ich weiß jetzt nicht wie du das machen willst, aber wenn ein anderer Thread aufgerufen wird (was ja praktisch beim RPC passiert), dann kommt es auf die Scheduling-Strategie an, wann er läuft. Sprich wenn ein anderer Thread eine höhere Priorität hat als der Service-Thread dann darf natürlich erstmal der andere Thread laufen.
Ich weiß nicht was du sonst mit warten auf den Scheduler meinen könntest, kannst das ja mal genauer an einem Bsp erklären.
Nur zu. Ich empfehle Dir trotzdem irgendwann einmal eine mächtige Programmiersprache, wie z.B. C++, zu erlernen und damit auch größere Programm zu entwickeln. Das eröffnet einem auch neue Sichtweisen für viele Dinge.
Ich denke durchaus daran C++ für meine Server zu nutzen, nur im Kernel fällt es halt aus. Im Moment scheitert es noch am mangelnden Wissen und das ich weder eine Libc noch eine Libc++ habe.
edit::
Wenn der Artikel wirklich schön ist wäre es toll wenn Du da mal einen Link geben könntest.
Link kommt sofort ;) http://www.ddj.com/showArticle.jhtml?articleID=189401457 (http://www.ddj.com/showArticle.jhtml?articleID=189401457)
-
Hallo,
Mir sind deine Parameter schon klar, aber d.h. auch das du davon ausgehst, dass du immer weißt wann was für eine Nachricht kommt.
Nein, den Typ der Nachricht erkennt der Handler am Inhalt der Nachricht. Das hatte ich wohl nicht deutlich genug veranschaulicht, sorry.
In der Header-Datei vom VFS ist etwas das:#define VFS_IPC_FUNCTION_OPEN 1
#define VFS_IPC_FUNCTION_READ 2
#define VFS_IPC_FUNCTION_SEEK 3
....
typedef VFS_File_Open_Request_t{
ulong job_code; //hier kommt die richtige Nummer (VFS_IPC_FUNCTION_OPEN) rein
ulong attributes; //dürfte wohl klar sein
ulong file_name_length; //zur Sicherheit noch mal die Länge das Dateinamens welcher als Request 2 (Nutzdaten) übergeben wird
}File_Read_Request_t;
typedef VFS_File_Read_Request_t{
ulong job_code; //hier kommt die richtige Nummer (VFS_IPC_FUNCTION_READ) rein
ulong file_handle; //dürfte klar sein (fread holt das aus 'FILE* handle')
ulong file_offset; //das ist der aktuelle File-Pointer (welcher auch aus 'FILE* handle' kommt)
ulong read_length; //dürfte auch klar sein
}File_Read_Request_t;
typedef VFS_File_Seek_Request_t{
ulong job_code; //hier kommt die richtige Nummer (VFS_IPC_FUNCTION_SEEK) rein
ulong file_handle; //dürfte klar sein (fseek holt das aus 'FILE* handle')
ulong file_offset; //das ist der neue File-Pointer
}VFS_File_Seek_Request_t;
Im Handler sieht das switch dann etwa so aus:void vfs_file_handler(const void* const param,
const long sender_id,
const void* const question1,
const ulong question1_length,
const void* const question2,
const ulong question2_length,
void* const answer1,
const ulong answer1_max_length,
void* const answer2,
const ulong answer2_max_length)
{
switch(*((const ulong* const)question1)) //Job-Code auswerten (deswegen muss der immer an Offset 0 im Request liegen)
{
case VFS_IPC_FUNCTION_OPEN:
{
//Open-Job erledigen
}
case VFS_IPC_FUNCTION_READ:
{
//Lese-Job erledigen
}
case VFS_IPC_FUNCTION_SEEK:
{
//Seek-Job erledigen
}
case ....:
}
}
Der Handler wertet immer das erste Element vom Request aus das immer eine passende Nummer enthalten muss.
Ich hab auch schon mal darüber nachgedacht beim Sende-Syscall für synchrones IPC zusätzlich einen Wert direkt zu übergeben und auch einen Wert direkt zurückgeben zu lassen. Das macht aber noch 2 Parameter (also eigentlich 1 Parameter und 1 Rückgabewert) mehr und der Sende-Syscall für synchrones IPC hat jetzt schon 11 Parameter und 3 Rückgabewerte (so das es dann 12 Parameter und 4 Rückgabewerte wären). Register hab ich genug dafür also eigentlich ist das ne gute Idee. Bei den Syscalls gibt man ja auch immer ne Nummer mit (auf x86 meist in EAX) damit der Kernel weiß was er zu tun hat, das wäre bei den IPC-Handlern auch keine schlechte Idee, damit erspare ich mir die Anforderung das man dem Request selber entnehmen können muss welcher Request genau vorliegt (also jeweils der erste Member in den Strukturen oben kann damit entfallen).
Ich muss ehrlich sagen, das ich nicht genau weiß wie das z.B. unter Windows läuft, ....
So weit ich das weiß bindet eine WIN32-Konsolen-Applikation eine passende DLL ein welche dann das Fenster generiert, dafür einen Message-Handler registriert (ich glaube unter Windows kann man Messages nur an Fenster schicken, würde ja auch Sinn ergeben) und der libc dann cin, cout und cerr zur Verfügung stellt.
Es ging mir darum, das du immer mit allem rechnen musst. Ansonsten fällt das dann unter die Kategorie Sicherheitslücke/Bug was irgendwie ausgenutzt werden könnte.
Eben deshalb halte ich es für Sinnvoll einer Applikation keine Messages zu schicken die diese gar nicht explizit angefordert hat, man kann doch nicht von einem Hello-World-Programm erwarten mit allem fertig zu werden. Daher ist es mir wichtig das ein Client eben nicht auch zwangsläufig Service sein muss nur um seine Antwort erhalten zu können.
Um noch mal auf das Beispiel mit den TCP-Ports zurück zu kommen, wenn Dein Browser eine HTTP-Verbindung (per TCP) zu irgendeinem Server aufbaut dann sagt er dem TCP-Stack "gib mir eine Verbindung zu IP ?.?.?.? Port 80" dazu nimmt der TCP-Stack einen beliebigen unbenutzten lokalen TCP-Port (er braucht ja eine vollständige Absenderadresse) und eröffnet damit eine Verbindung zum Server. Wenn jetzt ein anderer Computer im iNetz ein TCP-Paket an Deine IP-Adresse mit dem Port jener Verbindung schickt dann geht das ins Leere weil der Port ja eben nicht als Server-Socket sondern als Client-Socket (zu genau der einen angeforderten Gegenstelle) benutzt wird. Stell Dir mal vor was der Browser denken würde wenn er jetzt wegen diesem Port (er weiß üblicherweise noch nicht mal welche Portnummer der TCP-Stack für diese Verbindung ausgewürfelt hat) mit ankommenden Verbindungswünschen konfrontiert wird. Wenn die TCP-Spezifikation so ein unsinniges Verhalten vorschreiben würde wäre das ein massives Sicherheitsproblem. In einem OS ist das ein lustiges Einfallstor für DoS-Angriffe von einem Programm auf ein anderes Programm.
Ich wollte halt nur darauf hinaus das du synchron damit verbindest das du automatisch auch gleich eine Antwort bekommst und das hat damit nichts zu tun.
Ja, da hast Du recht. Sicher kann man das etwas unterschiedlich definieren aber da kommen wir dann bald zum Haarespalten. Ich weiß jetzt was du mit "synchron" meinst und ich gehe davon aus das Du weißt was ich mit "synchron" meine.
Denn du musst ja auch die Möglichkeit haben einfach eine Msg zu senden ohne eine Antwort zu wollen (Bsp. Server) und da macht sich dann asynchron einfach besser.
Deswegen will ich ja beides anbieten, ganz ohne asynchronen Messages geht ein Micro-Kernel-OS eben nicht.
Ansonsten ist natürlich aus Programmierersicht synchron viel sympatischer.
Schon weil das einfach der häufigere Anwendungsfall ist, das meiste läuft eben auf RPC hinaus.
Ich weiß jetzt nicht wie du das machen willst, aber wenn ein anderer Thread aufgerufen wird (was ja praktisch beim RPC passiert), dann kommt es auf die Scheduling-Strategie an, wann er läuft.
Ich möchte für synchrones IPC den Sheduler gar nicht bemühen. Der Client macht seinen Syscall für das IPC (damit landet die CPU im System-Mode), der passende Syscall-Handler markiert den aktuellen Thread als "blockiert wegen sync. IPC" und sichert seine wichtigsten Register in dessen Thread-Strukt, dann wird ein neuer Thread im Prozess des gewünschten Service erstellt, der mitgegebene Speicher wird in den Service-Prozess eingeblendet, der Stack und die Register für den neuen Thread vorbereitet und als letztes wird dieser PopUp-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Die Zeitscheibe des Callers läuft auch noch weiter (im System-Mode läuft die aber nicht weiter). Der Service-Thread erledigt seine Aufgabe und macht am Ende ein 'syscall_msg_sync_handler_end()', wenn das noch vor Ablauf der geerbten Zeitscheibe passiert wurde eben gar nichts geschedult. Im Syscall für 'syscall_msg_sync_handler_end()' (die CPU ist wieder im System-Mode) wird der geerbte Speicher wieder aus dem Service-Prozess entfernt, der PopUp-Thread gekillt, die gesicherten Register vom Client-Thread wieder hergestellt, die syscall_msg_sync_handler_end()-Parameter in die richtigen syscall_msg_sync_send()-Rückgabewert-Register geladen und als letztes der Client-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Es gab also genau 4 CPU-Mode-Wechsel und der Scheduler wurde nicht bemüht falls die Zeitscheibe des Clients noch gereicht hat. Ich denke das ist der schnellst mögliche Weg für synchrones IPC. Falls der Job beim Service doch länger dauert und dieser unterbrochen wird kommt es natürlich auch auf den Scheduller an wann der Job weiter bearbeitet wird.
Grüße
Erik
-
So weit ich das weiß bindet eine WIN32-Konsolen-Applikation eine passende DLL ein welche dann das Fenster generiert, dafür einen Message-Handler registriert (ich glaube unter Windows kann man Messages nur an Fenster schicken, würde ja auch Sinn ergeben) und der libc dann cin, cout und cerr zur Verfügung stellt.
So ungefähr habe ich mir das auch vorgestellt. Der Punkt ist nun aber, das dieses Fenster jede Art von Msg bekommen kann (das Fenster hat sich geändert, eine Taste wurde gedrückt, mit der Maus wurde geklickt, ...) und halt auch damit umgehen können muss.
Sicherlich werde ich bei mir davon gebrauch machen, das ich sagen kann auf diesen Port darf nur ein bestimmter Prozess senden, aber das ändert halt an unterschiedlich langen Msgs nichts.
Pass auf, du hast einen allgemeinen Port bei deinem VFS Service und der muss halt verschiedene unterschiedlich lange Nachrichten entgegen nehmen. Du weißt nicht wann welche Nachricht ankommt.
Du holst also eine Nachricht ab mit der Meinung das die angegebene Länge des Buffers für die Nachricht reicht, was ist aber wenn das nicht der Fall ist?
Zu deinem Bsp mit dem TCP Paket an einen Port. Um das jetzt mal auf das umzumünzen was ich meine, stell dir einfach vor das du ja unterschiedlich lange Pakete bekommen kannst, dies aber vor dem Empfang nicht wissen kannst. Einfachste Möglichkeit ist natürlich einfach einen Buffer zu nehmen, der das größte mögliche Paket repräsentiert, dann bist du immer auf der sicheren Seite.
Ich möchte für synchrones IPC den Sheduler gar nicht bemühen. Der Client macht seinen Syscall für das IPC (damit landet die CPU im System-Mode), der passende Syscall-Handler markiert den aktuellen Thread als "blockiert wegen sync. IPC" und sichert seine wichtigsten Register in dessen Thread-Strukt, dann wird ein neuer Thread im Prozess des gewünschten Service erstellt, der mitgegebene Speicher wird in den Service-Prozess eingeblendet, der Stack und die Register für den neuen Thread vorbereitet und als letztes wird dieser PopUp-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Die Zeitscheibe des Callers läuft auch noch weiter (im System-Mode läuft die aber nicht weiter). Der Service-Thread erledigt seine Aufgabe und macht am Ende ein 'syscall_msg_sync_handler_end()', wenn das noch vor Ablauf der geerbten Zeitscheibe passiert wurde eben gar nichts geschedult. Im Syscall für 'syscall_msg_sync_handler_end()' (die CPU ist wieder im System-Mode) wird der geerbte Speicher wieder aus dem Service-Prozess entfernt, der PopUp-Thread gekillt, die gesicherten Register vom Client-Thread wieder hergestellt, die syscall_msg_sync_handler_end()-Parameter in die richtigen syscall_msg_sync_send()-Rückgabewert-Register geladen und als letztes der Client-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Es gab also genau 4 CPU-Mode-Wechsel und der Scheduler wurde nicht bemüht falls die Zeitscheibe des Clients noch gereicht hat. Ich denke das ist der schnellst mögliche Weg für synchrones IPC. Falls der Job beim Service doch länger dauert und dieser unterbrochen wird kommt es natürlich auch auf den Scheduller an wann der Job weiter bearbeitet wird.
Wow ... du willst aber ganz schön in die Prozesse eingreifen. Das klingt ja schon fast nach nem Signal Handler, der halt als Thread implementiert ist.
Das der Service-Thread die Zeit und Priorität vom Client-Thread bekommt gibt es glaub ich auch schon woanders. In irgendeinem Hobby-OS hatte das jemand schonmal probiert und wirklich was gebracht hatte es da glaub ich nicht.
Ich stelle mir das zwar kompliziert vor sowas zu implementieren, aber damit hättest du dann sowas wie eine Prioriäten Warteschlange für Msgs.
Was ich auch noch wollte. Ansich lassen sich unsere verschiedenen Ansichten eh nicht so einfach miteinander vergleichen, da du keinen "flat address space" hast, was ansich heutezutage die Normalität darstellt.
Ich würde auch sagen, das du dir da ziemliche Probleme mit aufhalst! Ich denke da an Fragmentierung und das es das Auslagern von Speicher auf einen Datenträger eher schwieriger macht bzw. solche Segmente dann gar nicht ausgelagert werden können (genaus wie ich nie SharedMem auslagern würde).
-
Hallo,
So weit ich das weiß bindet eine WIN32-Konsolen-Applikation eine passende DLL ein welche dann das Fenster generiert, dafür einen Message-Handler registriert (ich glaube unter Windows kann man Messages nur an Fenster schicken, würde ja auch Sinn ergeben) und der libc dann cin, cout und cerr zur Verfügung stellt.
So ungefähr habe ich mir das auch vorgestellt. Der Punkt ist nun aber, das dieses Fenster jede Art von Msg bekommen kann (das Fenster hat sich geändert, eine Taste wurde gedrückt, mit der Maus wurde geklickt, ...) und halt auch damit umgehen können muss.
Ich denke das ist das Problem dieser DLL, die muss eben einen vollständigen Fenster-Signal-Handler implementieren.
Pass auf, du hast einen allgemeinen Port bei deinem VFS Service und der muss halt verschiedene unterschiedlich lange Nachrichten entgegen nehmen. Du weißt nicht wann welche Nachricht ankommt.
Also in meinem Konzept gibt es dieses Problem nicht und auch das Konzept von tyndur hat dieses Problem nicht (denke ich zumindest da die Daten ja auf den Stack gepackt werden und außer das der Stack knapp werden könnte sind da ja erst mal keine bestimmten Größenbeschränkungen). Das dieses Problem durch das aktive Abholen der Messages entsteht ist Dir ja bewusst. Da musst Du jetzt die richtigen Design-Entscheidungen treffen um damit umgehen zu können.
Wow ... du willst aber ganz schön in die Prozesse eingreifen. Das klingt ja schon fast nach nem Signal Handler, der halt als Thread implementiert ist.
Ja, mein IPC-System greift ziemlich tief in die Strukturen rein die der Kernel verwaltet. Mein Konzept hat in der Tat mehr Ähnlichkeiten mit dem von tyndur (der wesentliche unterschied ist der das bei tyndur ein vorhandener Thread benutzt wird und ich dagegen einen neuen injizieren will) als mit Deinem Konzept. Aber Vielfalt ist ja schließlich was tolles und daher hoffe ich das Du Dein Konzept erfolgreich umsetzt.
Das der Service-Thread die Zeit und Priorität vom Client-Thread bekommt gibt es glaub ich auch schon woanders. In irgendeinem Hobby-OS hatte das jemand schonmal probiert und wirklich was gebracht hatte es da glaub ich nicht.
Wirklich viel bringen dürfte das auch nur auf gut ausgelasteten System und auch nur in der Hinsicht das die Applikationen mit höherer Priorität eben nicht so sehr von der Gesamt-Last beeinträchtigt werden wie die Applikationen mit niedrigerer Priorität. Man könnte damit z.B. einem Audio/Video-Player ermöglichen trotz hoher Systemlast flüssig zu arbeiten.
Ich stelle mir das zwar kompliziert vor sowas zu implementieren, aber damit hättest du dann sowas wie eine Prioriäten Warteschlange für Msgs.
Warum sollten auch die Wünsche einer Applikation mit niedriger Priorität jene Wünsche von Applikationen mit hoher Priorität behindern? Interessant wird es z.B. beim AHCI-Treiber, der könnte z.B. dafür sorgen das Jobs von Applikationen mit niedrigerer Priorität erst nach den Jobs von Applikationen mit höherer Priorität von der HDD abgearbeitet werden, das SATA-Protokoll bietet dafür einige Tricks.
Ansich lassen sich unsere verschiedenen Ansichten eh nicht so einfach miteinander vergleichen, da du keinen "flat address space" hast
Ich bin mir ziemlich sicher das sich mein IPC-Konzept auch auf Flat-Memory-Systemen implementieren lässt, ich sehe da jedenfalls keine Probleme. Wenn ich mit taljeth einig geworden wäre hätte ich das möglicherweise für tyndur umgesetzt.
Ich würde auch sagen, das du dir da ziemliche Probleme mit aufhalst! Ich denke da an Fragmentierung und das es das Auslagern von Speicher auf einen Datenträger eher schwieriger macht bzw. solche Segmente dann gar nicht ausgelagert werden können (genaus wie ich nie SharedMem auslagern würde).
Das ich mir da keine leichte Aufgabe gesucht hab ist mir bewusst, es soll ja auch ruhig eine richtige Herausforderung sein, um so größer ist der Genus wenn es läuft. In dem Thread http://forum.lowlevel.eu/index.php?topic=2470 (http://forum.lowlevel.eu/index.php?topic=2470) (ab der 2. Seite) haben Svenska und ich recht intensiv über die Verwaltung von segmentiertem Speicher diskutiert.
Grüße
Erik
-
Also in meinem Konzept gibt es dieses Problem nicht und auch das Konzept von tyndur hat dieses Problem nicht (denke ich zumindest da die Daten ja auf den Stack gepackt werden und außer das der Stack knapp werden könnte sind da ja erst mal keine bestimmten Größenbeschränkungen). Das dieses Problem durch das aktive Abholen der Messages entsteht ist Dir ja bewusst. Da musst Du jetzt die richtigen Design-Entscheidungen treffen um damit umgehen zu können.
Wieso gibt es das Problem bei deinem Konzept nicht? Ich meine du sagst ja bei dem Abholen der Nachricht wie groß diese sein darf, meine Frage ist nun was passiert wenn die Nachricht zu groß ist (aus welchem Grund auch immer)?
Was verstehst du unter aktivem und was unter passivem Warten?
Warum sollten auch die Wünsche einer Applikation mit niedriger Priorität jene Wünsche von Applikationen mit hoher Priorität behindern? Interessant wird es z.B. beim AHCI-Treiber, der könnte z.B. dafür sorgen das Jobs von Applikationen mit niedrigerer Priorität erst nach den Jobs von Applikationen mit höherer Priorität von der HDD abgearbeitet werden, das SATA-Protokoll bietet dafür einige Tricks.
KISS? Ich meine wenn du nur "einfach" sagst, dass du die Nachrichten (in deinem Fall die sendenden Threads) nach der Priorität sortierst, dann finde ich das auch ungünstig. Denn dann kann es passieren das eine Nachricht mit niedriger Priorität verhungert, weil ständig Nachrichten mit höherer Priorität ankommen. Wenn du sowas machen willst, dann sollte die Zeit bei der Prioritätenberechnung auch eine Rolle spielen und dann finde ich wird das ganze wieder so komplex das es sich gar nicht mehr lohnt und du mit der einfachen Variante wieder besser dran bist ;)
Ich bin mir ziemlich sicher das sich mein IPC-Konzept auch auf Flat-Memory-Systemen implementieren lässt, ich sehe da jedenfalls keine Probleme. Wenn ich mit taljeth einig geworden wäre hätte ich das möglicherweise für tyndur umgesetzt.
Ich habe mir mal die Beschreibung deines Hardwarekonzepts angeguckt. Daher kann ich sagen, das du bei deinem Konzept entweder ziemlich viel Speicher verschwendest (da deine Segmente ja an 4kb aligned und mindestens 256byte groß sein sollen (habe ich zumindest so gelesen/verstanden) oder du dir ne ziemliche Fragmentierung einhandelst (was übrigens auch mein größter Kritikpunkt an Nachrichten mit dynamischer Länge ist, da müsste dann schon ein verdammt guter MemoryManager dahinter stecken und der dürfte dann wieder nicht der schnellste sein).
Was das designen einer eigenen CPU angeht, das hätte ich wohl besser nicht lesen sollen. Jetzt habe ich richtig lust bekommen mich eventuell auch mal daran zu probieren ;) ("nur" mit Hilfe von Logisim)
-
Hallo,
Wieso gibt es das Problem bei deinem Konzept nicht?
Ich dachte das hätte ich bereits deutlich genug erklärt. Hast Du meine letzten Beiträge wirklich gelesen? (Bitte nimm mir diese Frage nicht übel aber irgendwie bin ich verwundert)
Ich meine du sagst ja bei dem Abholen der Nachricht wie groß diese sein darf
Ich hole keine Nachrichten ab, ich bekomme diese Zugestellt! Der Service-Handler wird quasi vom Kernel aufgerufen, wie eine Art Call-Back, und bekommt die Message als Parameter mitgegeben. Die Funktion printf lässt sich doch auch mit beliebig langen Strings als Parameter aufrufen, es gibt keine bestimmten Grenzen, auf einer 64Bit-Plattform sollte ein sauber programmiertes printf sogar mit Strings in der Größe sehr vieler TBytes umgehen können.
Was verstehst du unter aktivem und was unter passivem Warten?
Ich schrieb von "aktivem Abholen" und darunter verstehe ich das der Service irgendeine Aktion aktiv ausführt um eine empfangene Nachricht zu bekommen (Du gehst quasi zur Post und musst das hoffentlich passende Transportbehältnis mitbringen, während ich möchte das ein Postbote zu mir kommt und dieser garantiert ein passendes Transportbehältnis von der Paket-Zentrale bekommen hat), das diese Aktion eventuell blockiert (Du in der Post warten/schlafen musst) falls noch keine Nachricht da ist spielt in dieser Betrachtung keine Rolle.
KISS? Ich meine wenn du nur "einfach" sagst, dass du die Nachrichten (in deinem Fall die sendenden Threads) nach der Priorität sortierst, dann finde ich das auch ungünstig. Denn dann kann es passieren das eine Nachricht mit niedriger Priorität verhungert, weil ständig Nachrichten mit höherer Priorität ankommen. Wenn du sowas machen willst, dann sollte die Zeit bei der Prioritätenberechnung auch eine Rolle spielen und dann finde ich wird das ganze wieder so komplex das es sich gar nicht mehr lohnt und du mit der einfachen Variante wieder besser dran bist
Wenn die Applikationen mit hoher Priorität den kompletten Computer beanspruchen um ihre Arbeiten zu erledigen dann verhungern natürlich die Applikationen mit niederer Priorität aber das ist ja gewollt, genau deswegen gibt es ja extra die Prioritäten damit das OS weiß welche Dinge dem User wichtig sind (also mit Vorrang bearbeitet werden müssen) und welche nicht (also warten dürfen). Bei dem Scheduler könnte man natürlich eine Art Fairness-Regel einbauen damit auch Applikationen mit niedrigerer Priorität nicht komplett verhungern, Windows kann das angeblich, aber davon ob das wirklich sinnvoll ist bin ich nicht überzeugt. Prioritäten sind ein Werkzeug um dem Computer zu sagen welche Wichtigkeit eine Aufgabe hat, es liegt am User dieses Werkzeug richtig zu nutzen. Wenn Du möchtest das ein Audio-Player im Hintergrund Musik spielt und diese nicht ins stocken geraten darf dann gibst Du dem Audio-Player eine hohe Priorität, wenn dadurch die Applikation im Vordergrund spürbar beeinträchtigt wird brauchst Du entweder einen schnelleren Computer oder Du musst Deine Ansprüche ändern so das die Musik auch mal stocken darf (der Audio-Player also keine hohe Priorität bekommt). Prioritäten machen Deinen Computer nicht schneller sie sorgen nur dafür das die vorhandene Performance gemäß Deiner Präferenzen genutzt wird.
Grüße
Erik
-
Ich dachte das hätte ich bereits deutlich genug erklärt. Hast Du meine letzten Beiträge wirklich gelesen? (Bitte nimm mir diese Frage nicht übel aber irgendwie bin ich verwundert)
Nein, die Frage war berechtigt ;) Ich vergesse immer das du Segmente nutzt! Bzw. dein Bsp.code vom fread irritiert mich da ein wenig. Wenn du Segmente benutzt dann macht es natürlich Sinn, das du ein Segment zurück bekommst und die Nachricht nicht erst in einen Buffer kopieren musst, aber bei deinem Bsp. übergibst du einen Pointer auf einen Buffer und die Länge des Buffers, das hat mich total durcheinander gebracht.
Was passiert eigentlich mit dem Segment auf der Senderseite? Kann der noch schreibend auf das Segment zugreifen? Und wie bekommt/erstellt er das Segment? Weil du ja auf 4kb aligned Segmente haben willst, muss sich der Sender darum kümmern dass der Speicher das ist oder fragt er nach einem passendem Segment?
Ich sag mal Segmente sind für sowas natürlich ganz praktische, es geht schneller (da kein kopieren) und du hast wesentlich besseren Schutz (weil man nicht außerhalb des Segments lesen/schreiben kann), nur die Verwaltung dürfte ziemlich anstrengend werden.
Wenn die Applikationen mit hoher Priorität den kompletten Computer beanspruchen um ihre Arbeiten zu erledigen dann verhungern natürlich die Applikationen mit niederer Priorität aber das ist ja gewollt, genau deswegen gibt es ja extra die Prioritäten damit das OS weiß welche Dinge dem User wichtig sind (also mit Vorrang bearbeitet werden müssen) und welche nicht (also warten dürfen). Bei dem Scheduler könnte man natürlich eine Art Fairness-Regel einbauen damit auch Applikationen mit niedrigerer Priorität nicht komplett verhungern, Windows kann das angeblich, aber davon ob das wirklich sinnvoll ist bin ich nicht überzeugt. Prioritäten sind ein Werkzeug um dem Computer zu sagen welche Wichtigkeit eine Aufgabe hat, es liegt am User dieses Werkzeug richtig zu nutzen. Wenn Du möchtest das ein Audio-Player im Hintergrund Musik spielt und diese nicht ins stocken geraten darf dann gibst Du dem Audio-Player eine hohe Priorität, wenn dadurch die Applikation im Vordergrund spürbar beeinträchtigt wird brauchst Du entweder einen schnelleren Computer oder Du musst Deine Ansprüche ändern so das die Musik auch mal stocken darf (der Audio-Player also keine hohe Priorität bekommt). Prioritäten machen Deinen Computer nicht schneller sie sorgen nur dafür das die vorhandene Performance gemäß Deiner Präferenzen genutzt wird.
Also bei deinem Bsp gibt es aber einen Fehler (wie ich finde, du kannst da natürlich anderer Meinung sein), das Programm im Vordergrund sollte immer bevorzeugt werden und nicht irgendein Programm im Hintergrund.
Ansich sieht man eigentlich schon zu das es sowas wie verhungern nicht gibt bzw. das es nicht passiert. Also bei meinem momentanen Scheduler passiert das auch nicht (aber was noch nicht ist kann noch werden ;) ).
-
Hallo,
Ich vergesse immer das du Segmente nutzt! Bzw. dein Bsp.code vom fread irritiert mich da ein wenig.
Der würde in einem Flat-Memory-System ganz genau so aussehen. Ich bin der Meinung das jeder Beispiel-Code, den ich in diesem Thread gepostet habe, auch auf einem Flat-Memory-System ganz exakt genau so aussehen würde. Auch die Funktionalität wäre die selbe, der einzigste Unterschied ist das man bei segmentiertem Speicher eben Segmente fürs Memory-Sharing benutzt und in einem MMU-basiertem Flat-Memory-System eben Pages. Auch die detaillierte Beschreibung meines IPC-Vorgangs, am Ende meines Beitrags vom 27.08. um 12:49, lässt sich 1 zu 1 auf Flat-Memory übertragen (deswegen hatte ich ja angeboten das für tyndur umzusetzen). Wenn Du das anders siehst dann erkläre mir das Bitte mal genau.
das du ein Segment zurück bekommst und die Nachricht nicht erst in einen Buffer kopieren musst
Wo bitte bekomme ich etwas "zurück"? Der Client gibt seinen Speicher für den Service "frei" und der Service ließt daraus was er zu tun hat und legt auch die Daten dort hinein. Das ist so als würdest Du am Buffet der Bedienung Deinen Teller hin halten mit einem Zettel drauf auf dem steht was Du drauf getan haben möchtest, die Bedienung tut das und signalisiert Dir wenn Du Deinen Arm wieder einholen darfst. Es war die ganze Zeit über Dein Teller und die Bedienung hat direkt damit gearbeitet anstatt Deine Wünsche erstmal lokal auf einem eigenen (internen) Teller zusammen zu stellen und am Ende dann umzuladen.
Was passiert eigentlich mit dem Segment auf der Senderseite? Kann der noch schreibend auf das Segment zugreifen?
Ja, aber der Client sollte so lange diesen Speicher nicht anfassen. Um bei dem Beispiel mit dem Buffet zu bleiben, da Du Deinen Teller immer noch selber in der Hand hällst kannst Du natürlich auch was drauf tun aber weil Du dabei wohl mit der Bedienung kollidieren würdest solltest Du es besser lassen. Bei meinem IPC-Konzept ist es so das der Cleint-Thread für diese Zeit blockiert (also schläft) und dadurch eigentlich gar nichts tun kann, natürlich könnte noch ein anderer Thread, aus dem Client-Prozess, auf diesem Speicher rumarbeiten aber das müsste man extra Programmieren und wer das tut ist eben selber schuld. Am Buffet würde das bedeuten das Du für die Dauer der Bearbeitung durch die Bedienung in Stase versetzt wirst, Du könntest zwar vorher dafür sorgen das z.B. ein anderes Mitglied Deiner Familie auch Zugriff auf Deinen Teller hat, während dieser von der Bedienung bearbeitet wird, aber das ist eben extra Aufwand.
Und wie bekommt/erstellt er das Segment?
Der Client erstellt gar kein Segment für IPC, er nimmt den Speicher den er schon hat und interessiert sich nicht dafür in welchem seiner Segmente dieser Speicher liegt.
Weil du ja auf 4kb aligned Segmente haben willst, muss sich der Sender darum kümmern dass der Speicher das ist oder fragt er nach einem passendem Segment?
Es ist Aufgabe des Kernels sich um diese Details zu kümmern und Aufgabe des CPU-Entwicklers dem Kernel das passende Werkzeug zu geben.
Ich sag mal Segmente sind für sowas natürlich ganz praktische, es geht schneller (da kein kopieren) und du hast wesentlich besseren Schutz (weil man nicht außerhalb des Segments lesen/schreiben kann), nur die Verwaltung dürfte ziemlich anstrengend werden.
Ja, das stimmt und war auch einer der Gründe warum ich diese Idee tatsächlich umsetzen will. Ich möchte beweisen das man mit Segmentierung ein sehr sicheres und gleichzeitig schnelles System erstellen kann, Segmente also kein überholtes Relikt vergangener Zeiten sind.
Also bei deinem Bsp gibt es aber einen Fehler (wie ich finde, du kannst da natürlich anderer Meinung sein), das Programm im Vordergrund sollte immer bevorzeugt werden und nicht irgendein Programm im Hintergrund.
Also wenn im Hintergrund der VLC läuft dann möchte ich schon das er bevorzugt wird auch wenn ich im Vordergrund einen längeren Compiler-Job starte, schließlich soll die Musik ja flüssig weiter laufen. Mir ist bewusst das der Compiler-Job dadurch (minimal) Verlangsamt wird aber das gehe ich ein wenn ich dafür beim warten wenigstens Musik hören kann (wenn ich will das der Compiler so schnell wie möglich fertig wird dann darf ich eben keine Musik hören).
Grüße
Erik
-
Also gut folgender Code irritiert mich ein wenig:
File_Read_Responce_t responce;
ulong responce_length = ~0;
//ein blockierender Syscall (synchrones IPC) :
const long retval = syscall_msg_sync_send(vfs_msg_id,&request,sizeof(request),NULL,0,&responce,&responce_length,sizeof(responce),data,&fread_length,length);
Die erstellst auf deinem Stack den Speicher für "response", der hat ne bestimmte Größe (entspricht der Struktur die hinter "File_Read_Response_t" steckt) die du ja auch dem Syscall mitgibts.
Ich habe das jetzt so verstanden das er also die Nachricht dort in den Speicher von "responce" und nur der Länge "sizeof(responce)" reinkopiert.
Der Client erstellt gar kein Segment für IPC, er nimmt den Speicher den er schon hat und interessiert sich nicht dafür in welchem seiner Segmente dieser Speicher liegt.
Das Problem was ich hier nicht ganz verstehe ist, er muss also sehr wohl irgendwann mal ein Segment mit der Größe der Nachricht erstellt haben oder er nimmt einfach einen Buffer der irgendwo in irgendeinem Segment liegt.
Ist letzteres der Fall hast du doch aber ein Problem, da du entweder das ganze Segment mit dem Buffer einblenden musst (was dann wieder nicht sicher ist, da da ja auch andere Daten drin stehen können) oder du musst aus diesem Buffer ein neues Segment machen, was halt nicht immer geht, da du ja deine Segmente an 4kb ausrichtest/ausrichten musst.
Also entweder fordert der Sender vorher ein Segment entsprechend seines benötigten Buffers vom Kernel an oder er muss sich darum kümmern das der Buffer auch an 4kb ausgerichtet wird.
Das selbe Problem hast du wenn du es auf nem Flat-Memory-System umsetzen willst. Der Server muss dann dafür sorgen das der Buffer für die Nachricht an 4kb (Pagegröße) ausgerichtet ist und das nichts anderes in dieser Page steht, dann kannst du das mit dem einblenden machen, aber ansonsten nicht.
Wenn ich mir jetzt mal vorstelle, das der Sender die Nachricht selbst noch für was benutzen will und der Empfänger auch ganz gerne mit der Nachricht arbeiten will, dann muss einer von beiden die Nachricht doch in einen Buffer kopieren, dieser Schritt entfällt bei mir natürlich.
Es ist Aufgabe des Kernels sich um diese Details zu kümmern und Aufgabe des CPU-Entwicklers dem Kernel das passende Werkzeug zu geben.
Wie gesagt ist meine Frage dann, was macht der Kernel wenn die Nachricht nicht an 4kb ausgerichtet ist?
Ja, das stimmt und war auch einer der Gründe warum ich diese Idee tatsächlich umsetzen will. Ich möchte beweisen das man mit Segmentierung ein sehr sicheres und gleichzeitig schnelles System erstellen kann, Segmente also kein überholtes Relikt vergangener Zeiten sind.
Ich weiß jetzt nicht was dafür alles in Hardware getan werden muss, könnte mir aber vorstellen, dass wenn du komfortabel mit Segmenten arbeiten willst, der Hardwareoverhead in keinem Verhältnis dazu steht. Bzw. müssten dann auch noch alle Compiler neu geschrieben werden und auch diese Arbeit ist nicht zu unterschätzen. Denn ansich sind alle Compiler auf Flat-Memory ausgelegt (inzwischen, denn früher mussten sie ja auch mit Segmenten klarkommen).
-
Hallo,
Also gut folgender Code irritiert mich ein wenig: File_Read_Responce_t responce;
ulong responce_length = ~0;
//ein blockierender Syscall (synchrones IPC) :
const long retval = syscall_msg_sync_send(vfs_msg_id,&request,sizeof(request),NULL,0,&responce,&responce_length,sizeof(responce),data,&fread_length,length);
Die erstellst auf deinem Stack den Speicher für "response", der hat ne bestimmte Größe (entspricht der Struktur die hinter "File_Read_Response_t" steckt) die du ja auch dem Syscall mitgibts.
Ich habe das jetzt so verstanden das er also die Nachricht dort in den Speicher von "responce" und nur der Länge "sizeof(responce)" reinkopiert.
In Response kommt nur die Antwort für fread also der Error-Code und vielleicht noch was anderes kleines. Der Buffer für die eigentlichen Nutzdaten wird mit den letzten 3 Parametern (beim Syscall) übergeben. Deshalb möchte ich immer 2 Nachrichten pro Richtung benutzen, einmal Request/Response und einmal Nutzdaten schreiben/lesen. Dabei ist es durchaus zulässig wenn nicht benutzte Buffer mit NULL + Länge 0 übergeben werden (siehe Parameter 4 und 5 vom Syscall, da in dem Beispiel ja keine Nutzdaten zu schreiben sind).
Das Problem was ich hier nicht ganz verstehe ist, er muss also sehr wohl irgendwann mal ein Segment mit der Größe der Nachricht erstellt haben oder er nimmt einfach einen Buffer der irgendwo in irgendeinem Segment liegt.
Letzteres.
Also entweder fordert der Sender vorher ein Segment entsprechend seines benötigten Buffers vom Kernel an oder er muss sich darum kümmern das der Buffer auch an 4kb ausgerichtet wird.
Weder noch. Beim Service ist einfach ein bischen mehr Speicher vom Client zu sehen als tatsächlich benötigt wird. Ich muss zwar meine Segmente auf 4kBytes (oder auf der 64Bit-Plattform auf 64kBytes), also kleinste Page-Größe, ausrichten (weil in einer Page niemals mehrere Segmente drin sein dürfen, wegen Defragmentieren und Swappen) aber wo die zugreifbaren Daten Anfangen und Enden kann ich jeweils auf 256 Bytes genau festlegen (theoretisch sogar noch genauer). Das bedeutet das im Worst-Case der Service vom Client pro Buffer bis zu 510 Bytes (2 * 255) Speicher sieht in dem er eigentlich nichts zu suchen hat, das ist zwar ein gewisses Risiko aber ich denke das ist verschmerzbar. Bei einem Monolithischen Kernel sieht der Kernel alles von der Applikation da ist mein Konzept schon mal ein echter Fortschritt. Wenn die Applikation das nicht will kann sie immer noch einen ausgerichteten Buffer benutzen, Vorne und Hinten 255 Bytes Platz lassen oder explizit das Kopieren beim Kernel anfordern. Sehr kleine Datenmengen (< ca. 4 kBytes) werden wohl immer kopiert, davon wären im obigen Beispiel Request und Responce betroffen so das der Service zumindest nicht im Stack-Frame der fread-Funktion rumschnüffeln kann.
Das selbe Problem hast du wenn du es auf nem Flat-Memory-System umsetzen willst. Der Server muss dann dafür sorgen das der Buffer für die Nachricht an 4kb (Pagegröße) ausgerichtet ist und das nichts anderes in dieser Page steht, dann kannst du das mit dem einblenden machen, aber ansonsten nicht.
Beim Paging existiert natürlich das selbe Problem nur etwas größer, hier sind es im Worst-Case 8190 Bytes, oder man kopiert zumindest die erste und letzte Page (was ich bei meinen Segmenten nicht so einfach machen kann).
Wenn ich mir jetzt mal vorstelle, das der Sender die Nachricht selbst noch für was benutzen will und der Empfänger auch ganz gerne mit der Nachricht arbeiten will, dann muss einer von beiden die Nachricht doch in einen Buffer kopieren, dieser Schritt entfällt bei mir natürlich.
Das wäre dann aber asynchrones I/O, also z.B. ein Schreibzugriff wo Du die Daten nur ans VFS übergen willst um so schnell wie möglich weitermachen zu können, hierfür müsste man dann eben wieder einen neuen VFS-Job definieren der die Nutzdaten nur in einen Cache kopiert und dann möglichst schnell zum Client zurückkehrt. Zumindest für das Kopieren wird der Client aber blockieren müssen (egal ob das Kopieren vom Kernel, im Syscall, oder vom Service, im IPC-Handler, gemacht werden muss, der zeitliche Unterschied beider Varianten dürfte sehr minimal sein).
Ich weiß jetzt nicht was dafür alles in Hardware getan werden muss, könnte mir aber vorstellen, dass wenn du komfortabel mit Segmenten arbeiten willst, der Hardwareoverhead in keinem Verhältnis dazu steht. Bzw. müssten dann auch noch alle Compiler neu geschrieben werden und auch diese Arbeit ist nicht zu unterschätzen. Denn ansich sind alle Compiler auf Flat-Memory ausgelegt (inzwischen, denn früher mussten sie ja auch mit Segmenten klarkommen).
Der Hardware-Aufwand sind Segmentregister, so wie bei x86 auch nur das ich davon 16 haben will und nicht blos 6. Segmentregister sind kein Hexenwerk, es läuft im wesentlichen auf jeweils ein Satz Schattenregister für den dekodiertem Segment-Descriptor hinaus. Beim eigentlichen Speicherzugriff kommt eine Addition (für die Segment-Basis) dazu und noch 2 Vergleiche ob das Offset zwischen Minimum und Maximum liegt, alle 3 Operationen können parallel ablaufen und sind auch recht schnell.
Das mit den Compilern ist allerdings ein Problem. Ich habe mich deswegen mal genauer mit dem LLVM beschäftigt und denke das dieser der richtige Kandidat ist weil er Pointer nicht auf Integer abbildet sondern als eigenständigen Basis-Daten-Typ betrachtet (neben Integer und Float). Beim Register-Allocator (wenn dann aus der internen Datenflussdarstellung Assemblercode generiert wird) muss ich dann eben berücksichtigen das ein Pointer immer 2 Register belegt, ein Segment-Register (für den Selector) und ein normales Register (für das Offset). Ein neues Back-End für den LLVM zu schreiben soll angeblich gar nicht mal schwer sein, für einfache RISC-CPUs (was auf meine CPU ja zutrifft) wurde da mal von unter 6 Monaten geschrieben. Für viele übliche Problem sind im LLVM-Framework bereits recht brauchbare generische Lösungen enthalten, die dürften für meine CPU zwar nicht immer gleich das optimale Ergebnis bringen aber wenigstens ein funktionierendes Ergebnis.
Grüße
Erik
-
Bei einem Monolithischen Kernel sieht der Kernel alles von der Applikation da ist mein Konzept schon mal ein echter Fortschritt.
Ähm, der Kernel sieht immer alles von der Applikation. Was genau meinst du in dem Fall?
Beim Service ist einfach ein bischen mehr Speicher vom Client zu sehen als tatsächlich benötigt wird. Ich muss zwar meine Segmente auf 4kBytes (oder auf der 64Bit-Plattform auf 64kBytes), also kleinste Page-Größe, ausrichten (weil in einer Page niemals mehrere Segmente drin sein dürfen, wegen Defragmentieren und Swappen) aber wo die zugreifbaren Daten Anfangen und Enden kann ich jeweils auf 256 Bytes genau festlegen (theoretisch sogar noch genauer). Das bedeutet das im Worst-Case der Service vom Client pro Buffer bis zu 510 Bytes (2 * 255) Speicher sieht in dem er eigentlich nichts zu suchen hat, das ist zwar ein gewisses Risiko aber ich denke das ist verschmerzbar.
Was ist wenn der Empfänger jetzt in den Buffer schreiben will? Muss er dann kopieren oder darf er in das Segment schreiben?
Damit (wenn er in das Segment schreiben darf) gibt es dann 2 Probleme, 1. kann er dann im ungünstigsten Fall Daten beim Empfänger ändern und der Sender kann mit den Daten in dem Segment praktisch nichts mehr anfangen, da sie ja nicht mehr dem entsprechen was er mal gesendet hat.
Das letzte Problem ist besonders fürs VFS entscheidend. Denn du willst ja das Kopieren vermeiden, wenn der Empfänger aber die Daten ändern darf, dann kannst du sie nicht mehr einfach an jeden Clienten senden (der die Datei lesen möchte), da sie nicht unbedingt dem entsprechen muss was eigentlich in der Datei steht.
Darf der Empfänger nicht in das Segment schreiben, musst du ja doch wieder kopieren, damit er das machen kann.
Also sparst du im Endeffekt 1 Kopiervorgang (aber auch das wären im "schlimmsten" Fall 50% Ersparnis).
-
Hallo,
Bei einem Monolithischen Kernel sieht der Kernel alles von der Applikation da ist mein Konzept schon mal ein echter Fortschritt.
Ähm, der Kernel sieht immer alles von der Applikation. Was genau meinst du in dem Fall?
Ich meine, das wenn wir einen OS-Kernel als potentiell fehlerhaft betrachten, so wie absolut jede Software, dann hat ein Micro-Kernel den Vorteil das er deutlich kleiner ist und damit (hoffentlich) weniger Fehler enthält. Ein gut desingter Micro-Kernel hat nicht mal ein tausendstel der Größe eine dicken Monolithen (z.B. Linux) und da viele Fehler erst durch Wechselwirkungen entstehen sollte er deutlich weniger als ein tausendstel der Fehler enthalten. Fehler in einem OS-Kernel sind besonders schlimm weil sie potentiell das gesamt System beeinflussen können, genau deshalb hat man sich ja das Konzept der Micro-Kernel erdacht. Viele der OS-Funktionalitäten liegen bei einem Micro-Kernel-OS in normalen User-Mode-Prozessen (der Personality) und enthaltene Fehler können sich damit nicht so auswirken wie bei einem monolithischen Kernel. Der VFS-Code in einem Monolithen kann prinzipiell eine komplette Applikation beeinflussen, bei einem Micro-Kernel mit meinem IPC-Konzept nur die Speicherbereiche die die Applikation explizit dem VFS zur Verfügung stellt. Bei meinem Konzept besteht noch das Risiko das in den Rand-Bereichen der Speicher-Bereiche Schaden angerichtet werden könnte, das ist eben der Preis wenn man auf das Kopieren komplett verzichten möchte. Ich habe mir daher noch mal meine Segment-Descriptoren angesehen und denke das ich das Alignment problemlos auf 16 Byte reduzieren kann. Ich denke in den übrigen maximal 15 Bytes dürfte das Schadenspotential extrem gering sein und auf einer richtigen 64Bit-RISC-CPU sind alle Datenstrukturen passend ausgerichtet so das diese Rand-Bytes mit hoher Wahrscheinlichkeit eh unbenutzt sind. Auch die Pointer die malloc liefert sind wohl immer mindestens auf 16 Bytes ausgerichtet. Wie seht ihr das Risiko mit diesen kleinen Rand-Bereichen?
Zusätzlich bieten meine Segmente den Vorteil das ich das Lesen und Schreiben unabhängig von einander freischalten kann, so das ein Service auf die Request-Speicher-Bereiche nur Lesend und auf die Response-Speicher-Bereiche nur Schreibend zugreifen kann.
Was ist wenn der Empfänger jetzt in den Buffer schreiben will? Muss er dann kopieren oder darf er in das Segment schreiben?
Dort wo der Service seine Antwort oder eingelesene Nutzdaten ablegen soll darf er natürlich Schreiben (aber nicht Lesen). In den Segmenten wo der Request oder die zu schreibenden Nutzdaten liegen darf der Service natürlich nur Lesen (aber nicht Schreiben). Kopiert werden muss erst mal gar nichts.
Damit (wenn er in das Segment schreiben darf) gibt es dann 2 Probleme, 1. kann er dann im ungünstigsten Fall Daten beim Empfänger ändern und der Sender kann mit den Daten in dem Segment praktisch nichts mehr anfangen, da sie ja nicht mehr dem entsprechen was er mal gesendet hat.
Ich verstehe nicht was Du meinst! Du scheinst das Wort "er" in verschiedenen Bedeutungen zu nutzen.
Der Client kann beim Service weder etwas verändern noch etwas lesen, er gibt ja seine Speicher-Bereiche für den Service frei und nicht umgekehrt. (Ich hoffe das war es was Du wissen wolltest, wenn nicht dann formuliere Deine Frage Bitte noch mal neu)
Das letzte Problem ist besonders fürs VFS entscheidend. Denn du willst ja das Kopieren vermeiden, wenn der Empfänger aber die Daten ändern darf, dann kannst du sie nicht mehr einfach an jeden Clienten senden (der die Datei lesen möchte), da sie nicht unbedingt dem entsprechen muss was eigentlich in der Datei steht.
Darf der Empfänger nicht in das Segment schreiben, musst du ja doch wieder kopieren, damit er das machen kann.
Also sparst du im Endeffekt 1 Kopiervorgang (aber auch das wären im "schlimmsten" Fall 50% Ersparnis).
Also hier verstehe ich gar nichts mehr. Sorry, aber Bitte formuliere Deine Frage noch mal neu.
Ein Lesezugriff auf den Cache im VFS läuft immer auf einen Kopiervorgang hinaus (vom Cache in den Buffer der Applikation), weil der Service (in diesem Fall das VFS) ja keine Speicherbereiche an den Client geben kann (wäre auch irgendwie doof).
Grüße
Erik
-
Wie seht ihr das Risiko mit diesen kleinen Rand-Bereichen?
Naja, ist halt so ne Sache ;) Ich sag mal wenn du sowas zulässt, dann kannst du auch sagen, das du die Segmente gar nicht brauchst und kannst alles über Paging machen (weil da hast du ja auch nur "kleine" Randbereiche).
Ich würde halt sagen, wenn dann richtig. Obwohl das mit den Segmenten natürlich auch nur Sinn macht, wenn kein Paging dahinter steckt, weil ansonsten könntest du ja auch fast alles übers Paging machen, bzw. könntest du auf x86 theoretisch auch solche Einblendungen per Paging machen und dann ein Segment drüber legen, damit nicht auf die Randbereiche zugegriffen werden kann. Dies heißt dann aber auch, das der ganze restliche Speicher auch über Segmente gemacht werden muss und genau da sehe ich halt auch das Problem.
Der Aufwand dafür wäre (mir) zu groß. Weil du speicherst dann neben dem Pointer auch noch das Segment wo der Poiter drin liegt.
Ich verstehe nicht was Du meinst! Du scheinst das Wort "er" in verschiedenen Bedeutungen zu nutzen.
Der Client kann beim Service weder etwas verändern noch etwas lesen, er gibt ja seine Speicher-Bereiche für den Service frei und nicht umgekehrt. (Ich hoffe das war es was Du wissen wolltest, wenn nicht dann formuliere Deine Frage Bitte noch mal neu)
Ich seh gerade, dass ich mich da wirklich sehr dumm ausgedrückt habe.
Also was ich meine ist, das man ja im Normalfall eine Nachricht abholt und dieser Speicher wo die Nachricht drin ist, ist dann meiner und ich kann machen was ich will. Mir fällt gerade kein Bsp dafür ein, aber was ist wenn du die Nachricht nicht nur liest, sondern vielleicht auch noch ein wenig veränderst um dann damit zu arbeiten?
Das würde bei dir dann heißen, dass du die Daten doch wieder kopieren musst und um mal bei dem Bsp mit der 300MB Datei zu bleiben, könnte das im Extrem Fall heißen, dass du die 300MB zum Lesen und nochmal 300MB zum Schreiben brauchst.
Wo sich mir dann auch die Frage stellt, wie lange ist das Segment gültig oder musst du es als Empfänger dann wieder selbst freigeben? Oder was passiert wenn der Sender das Segment freigibt, aber der Empfänger noch damit arbeiten will?
Also hier verstehe ich gar nichts mehr. Sorry, aber Bitte formuliere Deine Frage noch mal neu.
Ein Lesezugriff auf den Cache im VFS läuft immer auf einen Kopiervorgang hinaus (vom Cache in den Buffer der Applikation), weil der Service (in diesem Fall das VFS) ja keine Speicherbereiche an den Client geben kann (wäre auch irgendwie doof).
Du hast mir die Frage schon beantwortet (man darf in die erhaltenen Daten nicht schreiben).
-
Hallo,
Wie seht ihr das Risiko mit diesen kleinen Rand-Bereichen?
Naja, ist halt so ne Sache ;) Ich sag mal wenn du sowas zulässt, dann kannst du auch sagen, das du die Segmente gar nicht brauchst und kannst alles über Paging machen (weil da hast du ja auch nur "kleine" Randbereiche).
Beim Paging sind diese Randbereiche aber deutlich größer und damit wirklich ein ernsthaftes Risiko.
Obwohl das mit den Segmenten natürlich auch nur Sinn macht, wenn kein Paging dahinter steckt
Genau das ist das Ziel das ich mit den Segmenten verfolge, ich möchte das langsame und speicherkostende Paging los werden.
Der Aufwand dafür wäre (mir) zu groß. Weil du speicherst dann neben dem Pointer auch noch das Segment wo der Poiter drin liegt.
Ein Pointer besteht bei mir aus 2 Teilen, dem Selector und dem Offset, und ist damit schon etwas größer als ein Pointer in einem Flat-Memory-System, dafür kann ich an etlichen anderen Stellen einsparen. Ich denke die Segmentierung wird sich auch für die Performance lohnen.
Also was ich meine ist, das man ja im Normalfall eine Nachricht abholt und dieser Speicher wo die Nachricht drin ist, ist dann meiner und ich kann machen was ich will.
Auf welches IPC-Konzept bezieht sich diese Frage? Ich gehe nicht davon aus das Du mein IPC-Konzept meinst denn bei mir wird nichts "abgeholt"!
aber was ist wenn du die Nachricht nicht nur liest, sondern vielleicht auch noch ein wenig veränderst um dann damit zu arbeiten?
Wenn der Service den Request, den er vom Client bekommen hat, erst modifiziert und dann abarbeitet ist das sein Problem. Das muss er beim meinem IPC-Konzept auf jeden Fall in eigenem Speicher machen (also vorher kopieren) den Requests werden dem Service nur ReadOnly übergeben.
Das würde bei dir dann heißen, dass du die Daten doch wieder kopieren musst und um mal bei dem Bsp mit der 300MB Datei zu bleiben, könnte das im Extrem Fall heißen, dass du die 300MB zum Lesen und nochmal 300MB zum Schreiben brauchst.
Wovon schreibst Du da? Lesen und Schreiben (also fread und fwrite) sind bei mir 2 verschiedene/unabhängige IPC-Vorgänge.
Wo sich mir dann auch die Frage stellt, wie lange ist das Segment gültig oder musst du es als Empfänger dann wieder selbst freigeben? Oder was passiert wenn der Sender das Segment freigibt, aber der Empfänger noch damit arbeiten will?
Ich denke mein Kernel wird das freigeben von Segmenten aus denen Teile vererbt wurden verhindern müssen und einen passenden Error-Code zurück geben oder das Segment wird als gelöscht markiert und wenn es keine Vererbungen (quasi Links) mehr darauf gibt kann es endgültig gelöscht werden.
Grüße
Erik
-
Auf welches IPC-Konzept bezieht sich diese Frage? Ich gehe nicht davon aus das Du mein IPC-Konzept meinst denn bei mir wird nichts "abgeholt"!
Gut dann bekommst du halt eine Nachricht, aber das was ich meine bleibt gleich.
Wovon schreibst Du da? Lesen und Schreiben (also fread und fwrite) sind bei mir 2 verschiedene/unabhängige IPC-Vorgänge.
Mir geht es darum, dass du die Daten ja anforderst, weil du damit arbeiten willst und wenn wir bei unserem Bsp bleiben, dann hast du die ganze 300MB Datei geladen (bzw. als Segment in deinem Prozess) und willst jetzt die Datei anbestimmten Stellen bearbeiten (das können viele, aber auch nur ein paar sein) und dann stehst du vor der Wahl, machst du für jede kleine Änderung einen neuen IPC Aufruf oder änderst du alles mit einmal und sagst dann das du die Datei speichern willst.
In dem Fall müsstest du die ganzen 300MB in deinen eigenen (ich meine mit deinen, den vom Clienten) Speicher kopieren und dann ändern.
Den Speicher (mit den geänderten Daten) willst du (als Client) jetzt aber auch behalten, weil du damit noch weiter arbeiten willst.
Beim Paging sind diese Randbereiche aber deutlich größer und damit wirklich ein ernsthaftes Risiko.
Der Punkt ist aber, dass das nur "halbherzig" ist. Wirklich sicher ist es nicht.
Wer sagt, dass nicht genau diese 16bytes den Server (Service) abstürzen lassen bzw. das dieser dann mit falschen Daten arbeitet?! Ist dann ähnlich einem Bufferoverflow.
-
Beim Paging sind diese Randbereiche aber deutlich größer und damit wirklich ein ernsthaftes Risiko.
Das ist Blödsinn, erik, und das weißt du auch. Entweder es ist dir hochoffiziell egal, dass Prozesse auf Daten zugreifen können, die nicht für sie bestimmt sind (ob lesend oder schreibend macht dabei wieder keinen Unterschied, Lesen ist bei sensiblen Daten schlimm genug), oder es ist dir nicht egal. Ob das Fenster für Leaks kleiner oder größer ist, spielt keine Rolle. Kaputt ist kaputt.
Bevor ich sowas mache, würde ich einfach sagen, wer fwrite() benutzt, ist selber schuld. Wenn er Performance will, soll er ein natives erikos_write() benutzen, das ein Alignment erfordert.
-
Hallo,
Gut dann bekommst du halt eine Nachricht
Richtig, in meiner Welt kommt der Postbote zu mir und bringt das Paket.
aber das was ich meine bleibt gleich.
Das kann ich wirklich nicht beurteilen weil ich echte keine Ahnung hab was Du eigentlich meinst.
Mir geht es darum, dass du die Daten ja anforderst, weil du damit arbeiten willst und wenn wir bei unserem Bsp bleiben, dann hast du die ganze 300MB Datei geladen (bzw. als Segment in deinem Prozess) und willst jetzt die Datei anbestimmten Stellen bearbeiten (das können viele, aber auch nur ein paar sein) und dann stehst du vor der Wahl, machst du für jede kleine Änderung einen neuen IPC Aufruf oder änderst du alles mit einmal und sagst dann das du die Datei speichern willst.
In dem Fall müsstest du die ganzen 300MB in deinen eigenen (ich meine mit deinen, den vom Clienten) Speicher kopieren und dann ändern.
Den Speicher (mit den geänderten Daten) willst du (als Client) jetzt aber auch behalten, weil du damit noch weiter arbeiten willst.
Ich glaube Du hast noch nicht verstanden wie mein Konzept arbeitet.
Bei mir muss der Client den Speicher schon haben, ob das ein statisches Array ist oder per malloc kreiert wurde ist egal, um dann (per IPC) einen Service aufzufordern dort etwas hinein zu legen. Am Buffet musst Du Deinen Teller schon mitbringen damit Dir die Bedienung was drauf tut. Ein Client kann also per malloc ein 300MByte-Array erstellen, danach dem VFS-Service sagen er soll dort den Inhalt einer Datei hinein packen (ob der VFS-Service das aus seinem privaten File-Cache kopiert oder einem File-System-Treiber sagt das dieser einem Block-Device-Treiber sagt er soll die richtigen Sektoren dort ablegen lassen ist für den Client egal und auch nicht sichtbar), dann kann der Client in seinem Speicher rummachen wie er will (in dieser Zeit gehört sein Speicher nur ihm) und als letztes kann er dem VFS-Service wieder sagen das dieser die Daten aus dem Array abspeichern soll (auch hier ist es wieder egal ob der VFS die Daten nur in seinen Cache kopiert oder an ein Datei-System zum abspeichern auf HDD weitergibt). Für die 2 IPC-Aktionen (fread und fwrite) wird dem VFS-Service temporär Zugriff auf diesen Speicher-Bereich des Clients gestattet (und der VFS kann das auch seinerseits weiterreichen, also einem untergeordnetem Service wiederum Zugriff gewähren), sonst nicht.
Wer sagt, dass nicht genau diese 16bytes den Server (Service) abstürzen lassen bzw. das dieser dann mit falschen Daten arbeitet?! Ist dann ähnlich einem Bufferoverflow.
Mal davon abgesehen das der Service den Client abstürzen lassen könnte (weil ja der Service auf den Speicher des Clients zugreifen darf) hast Du natürlich recht.
Das ist Blödsinn, erik, und das weißt du auch. [.....] Kaputt ist kaputt.
Danke für Deine ehrlichen Worte. Mein Problem ist das die Bits in den Descriptoren begrenzt sind. Wenn ich mehr Bits am unteren Ende vom Limit brauche gehen die oben verloren also die maximal mögliche Segmentgröße wird weniger (das gleiche Problem ist auch beim Minimum).
In der 64 Bit-Variante meiner CPU habe ich fürs Limit einen 28 Bit-Wert und einen Shift-Faktor mit 4 Bit (alle geraden Werte von 0 bis 30). Damit kann ich z.B. eine Segmentgröße von 2^58 Bytes darstellen aber eben nur mit einer Genauigkeit von 2^30 Bytes (wer so riesige Segmente braucht bekommt die nur noch in Schritten zu 1 GByte), ich kann damit aber auch ein Segment bis zu 256 MBytes auf das Byte genau darstellen. Bei der Variante mit den 16 Bytes Genauigkeit wären immerhin Segmente bis 4 GBytes möglich. Hier wäre es vermutlich am geschicktesten wenn man diese Regeln ganz klar darlegt und der Client kann dann entscheiden ob ihm 256 MBytes pro IPC-Vorgang genug sind oder er für etwas mehr Performance ein ganz klein wenig die Hosen runter lässt.
Bei der 32 Bit-Variante meiner CPU sind die Descriptoren etwas anders aufgebaut, da kann ich immer die nötigen Bits haben um das Limit und das Minimum aufs Byte genau fest zulegen.
Bevor ich sowas mache, würde ich einfach sagen, wer fwrite() benutzt, ist selber schuld. Wenn er Performance will, soll er ein natives erikos_write() benutzen, das ein Alignment erfordert.
Man könnte auch einfach sagen das sich die Performance von allein einstellt wenn die Daten ein entsprechendes Alignment vorweisen, das wäre mir zumindest lieber.
Was hältst Du von der Variante:
Wenn man dem fread/fwrite (per define o.ä.) sagt wie viel Risiko es eingehen darf (also wie groß die Lücke maximal sein darf, was auch 0 sein kann) dann kann es klar entscheiden (anhand der Größe und des Alignments des übergebenen Speicherbereichs) ob es mehrere IPC-Vorgänge benötigt. Wenn z.B. ein Speicherbereich mit 4 GBytes (also ein vielfaches von 16) mit einem Alignment von 16 Bytes gegeben ist dann reicht ein IPC-Vorgang, wenn die Daten aber nur auf 4 Bytes genau ausgerichtet sind dann müssen auch 4 IPC-Vorgänge benutzt werden wenn keine Lücke entstehen darf und wenn 4 Byte Lücke erlaubt sind dann reichen doch wieder 2 IPC-Vorgänge (mit 8 Byte Alignment). Ich denke das lässt sich in sehr wenige Zeilen C-Code gießen und sollte immer das Optimum an Performance, bei Einhaltung des gegeben Sicherheitsbedürfnis, erreichen.
Ist das Okay?
Grüße
Erik
-
Danke für Deine ehrlichen Worte. Mein Problem ist das die Bits in den Descriptoren begrenzt sind. Wenn ich mehr Bits am unteren Ende vom Limit brauche gehen die oben verloren also die maximal mögliche Segmentgröße wird weniger (das gleiche Problem ist auch beim Minimum).
Das kann ich schon nachvollziehen. Aber wenn man zwei Probleme hat, ist es keine gute Idee, das erste so zu lösen, dass das zweite zwangsläufig ungelöst bleibt.
Ich glaube, du könntest sogar ein paar Gründe finden, warum es nicht schlimm ist, wenn der andere Prozess Zugriff hat. Oder zumindest unter bestimmten Bedingungen, z.B. wenn der Zielprozess höher privilegiert ist. Aber "es sind doch nur 12 Bytes" ist sicher keine solche Begründung, sondern maximal eine faule Ausrede. Und das ist es, was ich meinte, dass dir sicher auch selbst bewusst ist.
Bevor ich sowas mache, würde ich einfach sagen, wer fwrite() benutzt, ist selber schuld. Wenn er Performance will, soll er ein natives erikos_write() benutzen, das ein Alignment erfordert.
Man könnte auch einfach sagen das sich die Performance von allein einstellt wenn die Daten ein entsprechendes Alignment vorweisen, das wäre mir zumindest lieber.
Ich hab es lieber, wenn mir das OS auf die Finger klopft als dass es heimlich, still und leise meine Performance kaputtmacht, ohne mir was davon zu sagen. Aber das ist Geschmackssache.
Wenn man dem fread/fwrite (per define o.ä.) sagt wie viel Risiko es eingehen darf (also wie groß die Lücke maximal sein darf, was auch 0 sein kann)
Nein. Das ist eine boolesche Entscheidung. Entweder sicher oder unsicher. Zwischen 16 und 1024 ist kein wesentlicher Unterschied mehr, nur 0 oder nicht 0 sind entscheidend.
Dass du je nach Situation das eine oder das andere wählst, wäre aus meiner Sicht vertretbar.
-
Das kann ich wirklich nicht beurteilen weil ich echte keine Ahnung hab was Du eigentlich meinst.
Das könnte daran liegen das ich noch immer nicht dein Konzept mit den Segmenten vollständig verstanden habe, bzw. mir nicht klar ist wie du bestimmte Probleme lösen willst. Aber so langsam (hoffe ich) verstehe ich es.
Was machst du eigentlich wenn dir die Segmente ausgehen und du aber noch freien virtuellen Speicher hast? Was ich mir auch nicht so richtig vorstellen kann, wie löst du das mit dem Auslagern der Daten?
Ich sage mal (vielleicht auch aus Gewohnheit) das wenn du die Segmente auch auf 4kb Einheiten festlegen würdest, dass dann viele Sachen einfacher werden, aber dann wären sie natürlich auch wieder sinnlos ;)
Im Endeffekt, habe ich bei mir schon Caching drin (da die Daten immer vom VFS Service in den Clienten kopiert werden) und du müsstest da halt genauso kopieren. Du würdest dir also einmal kopieren (bei mir aus der Pipe in den Buffer des Clienten) ersparen.
Aber wenn ich dein Konzept so übernehmen würde, dann bräuchte ich einfach zu sagen, dass ich in dem Fall was ähnliches mache und einfach den Bereich der zu lesen ist (bzw. halt die 4kb Pages wo der Bereich drin liegt) einblendet (natürlich read-only) und dann die Daten von da aus in den Buffer des Clienten kopiere, so würde ich auch auf einmal kopieren kommen.
Die Idee ansich ist zwar gut, hat aber das Problem (was eher selten bis gar nicht auftauchen sollte) wenn mir der virtuelle Speicher ausgeht, funktioniert das ganze nicht mehr, da sind dann meine 4kb Pipe´s wieder von Vorteil.
Edit::
Für das Problem das mir der virtuelle Speicher ausgehen sollte, werden halt immer nur 4kb eingeblendet und ich kopiere die Daten dann von da aus in den Buffer.
Ich denke ich werde das Konzept so nehmen, es sein denn ihr findet Probleme damit.
-
Hallo,
Aber wenn man zwei Probleme hat, ist es keine gute Idee, das erste so zu lösen, dass das zweite zwangsläufig ungelöst bleibt.
Auch wieder ein gutes Argument. Gut das ich gefragt hab.
Ich hab es lieber, wenn mir das OS auf die Finger klopft als dass es heimlich, still und leise meine Performance kaputtmacht, ohne mir was davon zu sagen. Aber das ist Geschmackssache.
Wieso, es gibt Dir heimlich, still und leise mehr Performance wenn Du Dich zufällig (oder auch absichtlich) an die Alignment-Vorgaben hältst. Aber da sind wir wohl mal wieder beim Haare-Spalten.
Nein. Das ist eine boolesche Entscheidung. ...
Okay, also entweder fread/fwrite dürfen mit voller Performance laufen und scheren sich nicht um die Sicherheit oder sie müssen genau aufpassen wie viel Performance sie benutzen können ohne die Sicherheit zu gefährden. Wie schon gesagt, ich denke das lässt sich sehr simpel implementieren.
Dass du je nach Situation das eine oder das andere wählst, wäre aus meiner Sicht vertretbar.
Dann sind wir uns ja einig. :)
Das könnte daran liegen das ich noch immer nicht dein Konzept mit den Segmenten vollständig verstanden habe, bzw. mir nicht klar ist wie du bestimmte Probleme lösen willst. Aber so langsam (hoffe ich) verstehe ich es.
Achte nicht zu sehr auf meine Segmente, die machen zwar ein paar Dinge ganz einfach aber sind keine zwingende Voraussetzung für mein IPC-Konzept.
Was machst du eigentlich wenn dir die Segmente ausgehen und du aber noch freien virtuellen Speicher hast?
Pro LDT sind 32768 Segmente möglich (jeder Prozess hat seine eigene LDT), das sollte für ne Weile reichen und wenn die doch mal knapp werden dann geht eben der IPC-Vorgang gerade nicht und wird gequeued (also der Client blockiert).
Was ich mir auch nicht so richtig vorstellen kann, wie löst du das mit dem Auslagern der Daten?
Per Paging, siehe dazu in den Thread mit der eigenen CPU, da diskutieren Svenska und ich recht ausführlich darüber.
Ich sage mal (vielleicht auch aus Gewohnheit) das wenn du die Segmente auch auf 4kb Einheiten festlegen würdest, dass dann viele Sachen einfacher werden, aber dann wären sie natürlich auch wieder sinnlos ;)
Die Segmente sind auch auf 4 kBytes festgelegt, es gibt nur bei jedem Segment ein Minimum und ein Limit. Das bedeutet die Offsets fangen nicht immer bei 0 an. Für den Speicher-Manager vom Kernel belegen die Segmente immer ganze Pages, sonst wäre das auch zu kompliziert. Den dadurch entstehenden Verschnitt hast Du ja bei Flat-Memory-System ganz genau so.
Im Endeffekt, habe ich bei mir schon Caching drin (da die Daten immer vom VFS Service in den Clienten kopiert werden) und du müsstest da halt genauso kopieren. Du würdest dir also einmal kopieren (bei mir aus der Pipe in den Buffer des Clienten) ersparen.
Ja, wenn im VFS ein Cache vorhanden ist dann bedeutet das immer kopieren, aber der HDD-Treiber könnte direkt in den Cache vom VFS reinschreiben (lassen) obwohl noch der Datei-System-Treiber dazwischen hängt (also Zero-Copy von der HDD bis in den VFS-Cache).
Aber wenn ich dein Konzept so übernehmen würde, dann bräuchte ich einfach zu sagen, dass ich in dem Fall was ähnliches mache und einfach den Bereich der zu lesen ist (bzw. halt die 4kb Pages wo der Bereich drin liegt) einblendet (natürlich read-only) und dann die Daten von da aus in den Buffer des Clienten kopiere, so würde ich auch auf einmal kopieren kommen.
Hä, wer gibt hier an wen Speicher weiter?
Die Idee ansich ist zwar gut
Das möchte ich aber auch meinen, bin ich schließlich fast von alleine drauf gekommen (mal davon abgesehen das taljeth mich in diese Richtung gestoßen hat und die Schriften des Tanenbaum halfen dann das etwas zu verfeinern). :-D
hat aber das Problem (was eher selten bis gar nicht auftauchen sollte) wenn mir der virtuelle Speicher ausgeht, funktioniert das ganze nicht mehr
Spätestens auf einer 64 Bit-Plattform ist dieses Problem erst mal für absehbare Zeit in weiter Ferne.
Grüße
Erik
-
Ich denke ich werde das Konzept so nehmen, es sein denn ihr findet Probleme damit.
Wenn wir da Probleme finden sollen dann musst Du Dein Konzept schon mal konkret vorstellen. ;)
-
Achte nicht zu sehr auf meine Segmente, die machen zwar ein paar Dinge ganz einfach aber sind keine zwingende Voraussetzung für mein IPC-Konzept.
Und genau da muss ich dir wiedersprechen (bzw. habe bestimmt noch nicht alles verstanden) ;)
Wie funktioniert denn dein malloc? Das Problem was ich habe, wenn wir die 300MB Datei nehmen, dann wäre es ja möglich das dir malloc nen Speicherbereich zurückgibt der nicht an 4kb ausgerichtet ist (sehr unwahrscheinlich, aber möglich). Wie würdest du das dann mit deinen Segmenten lösen? Und mit reinem Paging kannst du dann nicht einfach sagen, ich blende dann mal in den 300MB Buffer meinen (den vom Service) Speicher ein!
Pro LDT sind 32768 Segmente möglich (jeder Prozess hat seine eigene LDT), das sollte für ne Weile reichen und wenn die doch mal knapp werden dann geht eben der IPC-Vorgang gerade nicht und wird gequeued (also der Client blockiert).
Dazu wäre es auch gut, wenn du mal erklärst wie dein malloc funktionieren soll oder besser wann ein neues Segment erstellt wird und wann nicht.
Hä, wer gibt hier an wen Speicher weiter?
Also fread würde einfach zum Service sagen, gib mir mal den Speicher von da bis da (den Bereich wo die zu lesenden Daten drin sind) und der Service gibt dann als Antwort ne ID für SharedMem zurück die in den Clienten eingeblendet wird und fread kann aus diesem "Cache" dann in den Buffer des Clienten kopieren.
Spätestens auf einer 64 Bit-Plattform ist dieses Problem erst mal für absehbare Zeit in weiter Ferne.
Ich habe mir schon überlegt immer nur 4kb einzublenden, zu kopieren und die nächsten 4kn einblenden zu lassen.
Einziges Problem mit dem Caching ist, was passiert wenn dem Storage-Server der virtuelle Speicher ausgeht und noch eine Datei geöffnet werden soll?
Leider habe ich nicht vor so schnell mein OS auf eine 64bit Architektur zu portieren bzw. neuzuschreiben.
Wenn wir da Probleme finden sollen dann musst Du Dein Konzept schon mal konkret vorstellen. Wink
Ich hoffe die obige Beschreibung reicht?!
-
Hallo,
Wie funktioniert denn dein malloc?
Ich wollte den Heap über mehrere Segmente verteilen, eventuell sogar für jede angefragte Objekt-Größe ein eigenes Segment oder zumindest etwas in dieser Richtung. Genau weiß ich das noch nicht. Es wird auf jeden Fall nicht für jeden Aufruf von malloc ein neues Segment erstellt, da wären die Segmente ziemlich schnell alle und der Verschnitt wäre riesig (weil ein Segment immer ganze Pages belegt). Vielmehr möchte ich das gleichgroße Objekte alle im selben Segment drin sind und wie ein Array verwaltet werden. Der Vorteil meiner Segmente ist das ich die einzelnen Segmente unabhängig voneinander Vergrößern und auch wieder Verkleinern kann, ich hab nicht nur einen virtuellen Adressraum, wo man schon mal mit Konflikten rechnen muss, sondern ziemlich viele die eben völlig unabhängig voneinander sind.
Das Problem was ich habe, wenn wir die 300MB Datei nehmen, dann wäre es ja möglich das dir malloc nen Speicherbereich zurückgibt der nicht an 4kb ausgerichtet ist (sehr unwahrscheinlich, aber möglich). Wie würdest du das dann mit deinen Segmenten lösen?
Mit meinen Segmenten kann ich recht flexibel auch unausgerichteten Speicher beschreiben, so ein Segment hat nicht nur eine Basisadresse (die immer auf die kleinste Page-Größe ausgerichtet sein muss) sondern auch ein Minimum (gültige Offsets müssen größer oder gleich sein) und ein Limit (gültige Offsets müssen kleiner oder gleich sein). Wenn die 300 MBytes mitten in einem Segment drin liegen (z.B. an 0x123456) dann wird das Offset auf die nächste Page-Grenze abgerundet (also auf 0x123000) und dort die Basisadresse der erbenden Segments hin gelegt (die Basisadresse muss sich natürlich auf den echten linearen Speicher beziehen und ist daher nicht 0x123000 sondern 0x123000 + Basis vom beerbten Segments), die restliche Distanz (0x000456) ist dann das Minimum im neuen Segment und das Limit ist dann das Ende der Daten (also 0x000456 + Datengröße). Ich hoffe das war einigermaßen verständlich. Es ist jedenfalls nur ein kurze Prozedur ein neues Segment für das Memory-Sharing für IPC zu erstellen, deswegen hoffe ich das mein IPC kaum teurer ist als auf anderen Plattformen ein Syscall.
Und mit reinem Paging kannst du dann nicht einfach sagen, ich blende dann mal in den 300MB Buffer meinen (den vom Service) Speicher ein!
Wenn man beim Paging mit unausgerichteten Daten konfrontiert wird dann muss eben die erste und letzte Page kopiert werden ansonsten sehe ich da keine Probleme (außer natürlich das mit größeren Daten auch die Mapping-Kosten steigen, wogegen es meinen Segmenten ziemlich egal ist ob diese 3 Bytes oder 3 GBytes repräsentieren).
Also fread würde einfach zum Service sagen, gib mir mal den Speicher von da bis da (den Bereich wo die zu lesenden Daten drin sind) und der Service gibt dann als Antwort ne ID für SharedMem zurück die in den Clienten eingeblendet wird und fread kann aus diesem "Cache" dann in den Buffer des Clienten kopieren.
Wenn der Service immer seinen Speicher an den Client weitergibt damit sich dieser daran bedienen kann dann gibt es bei Dir prinzipiell kein Zero-Copy. Überlege Dir mal wie damit ein HDD-Treiber arbeiten müsste, der müsste die gewünschten Sektoren erst mal in eigenen Speicher holen um dann diesen Speicher seinem Client zu zeigen damit dieser sich die Daten dort raus kopiert. Daran gefallen mir persönlich 2 Dinge nicht: zum einen der Zwang zum teuren und unsinnigen Kopieren und zum anderen das für das Memory-Sharing usw. ne ganze Menge Syscalls erforderlich sein dürften. Ein ls über ein Verzeichnis mit 10000 Dateien möchte ich damit nicht machen müssen.
Einziges Problem mit dem Caching ist, was passiert wenn dem Storage-Server der virtuelle Speicher ausgeht und noch eine Datei geöffnet werden soll?
Wenn der Cache eng wird müssen eben die am längsten nicht mehr benutzen Daten raus fliegen. Das ist die Basis-Philosophie eines jeden Caches.
Leider habe ich nicht vor so schnell mein OS auf eine 64bit Architektur zu portieren bzw. neuzuschreiben.
Das ist ungeschickt, der kommende Mainstream ist 64 Bit.
Eben weil es mit Segmentierung keine Plattform gibt die 64Bit-tauglich ist (böses AMD!!) habe ich mich entschlossen was eigenes zu bauen.
Grüße
Erik
-
Ich wollte den Heap über mehrere Segmente verteilen, eventuell sogar für jede angefragte Objekt-Größe ein eigenes Segment oder zumindest etwas in dieser Richtung. Genau weiß ich das noch nicht. Es wird auf jeden Fall nicht für jeden Aufruf von malloc ein neues Segment erstellt, da wären die Segmente ziemlich schnell alle und der Verschnitt wäre riesig (weil ein Segment immer ganze Pages belegt). Vielmehr möchte ich das gleichgroße Objekte alle im selben Segment drin sind und wie ein Array verwaltet werden. Der Vorteil meiner Segmente ist das ich die einzelnen Segmente unabhängig voneinander Vergrößern und auch wieder Verkleinern kann, ich hab nicht nur einen virtuellen Adressraum, wo man schon mal mit Konflikten rechnen muss, sondern ziemlich viele die eben völlig unabhängig voneinander sind.
Klingt wieder verdächtig nach dem SlabAllocator ;) So schlecht scheint das Konzept also nicht zu sein.
Wenn der Service immer seinen Speicher an den Client weitergibt damit sich dieser daran bedienen kann dann gibt es bei Dir prinzipiell kein Zero-Copy. Überlege Dir mal wie damit ein HDD-Treiber arbeiten müsste, der müsste die gewünschten Sektoren erst mal in eigenen Speicher holen um dann diesen Speicher seinem Client zu zeigen damit dieser sich die Daten dort raus kopiert. Daran gefallen mir persönlich 2 Dinge nicht: zum einen der Zwang zum teuren und unsinnigen Kopieren und zum anderen das für das Memory-Sharing usw. ne ganze Menge Syscalls erforderlich sein dürften. Ein ls über ein Verzeichnis mit 10000 Dateien möchte ich damit nicht machen müssen.
Also den HDD-Treiber interessiert es prinzipiell schon mal gar nicht was mit den Daten passiert, der läd sie nur und gibt sie einfach an den VFS-Service weiter (da findet kein kopieren statt) und damit ist der Speicher für ihn auch weg.
Ich weiß nun nicht wie "ls" genau funktioniert, aber ich stelle mir das schon so vor, das er für jede einzelne Datei in dem Verzeichnis mind. 1 Syscall machen muss (müsste die Datei nicht sogar vorher geöffnet werden nur um an die Infos zu kommen, dann wären da ja sogar noch mehr Syscalls nötig).
Die Daten über die Dateien liegen im VFS-Service und dieser macht in dem Fall kein Memory-Sharing mit dem Clienten!
Der Client würde seine Anfrage per Pipe stellen und wenn er das geschickt macht, kann er sogar mehrere Anfragen mit einmal in die Pipe schreiben (so könnte ich sogar einige Syscalls vermeiden, wo auch mal ein Vorteil meiner UserSpace Pipe´s zum Tragen kommt).
Das ist ungeschickt, der kommende Mainstream ist 64 Bit.
Ja, aber nicht überall ist die Situation mit den PCs so wie hier bei uns in Deutschland, ich möchte sogar behaupten die ist nirgends so wie hier. Damit meine ich das die Mehrheit der Leute relativ neue PCs haben.
Meistens ist es eher so das noch viele auf dem Stand von z.B. Athlon XP und sowas sind.
Was du eher meinst sind Spiele-PCs, aber das sollte weltweit gesehen, wohl eher die Minderheit sein.
Dann kommt noch hinzu das ich gerne auf realer Hardware teste und das auf so viel wie möglich und da sind viele 64bit Systeme nicht gerade günstig. Wenn ich mir da überlege wie viele 32bit Systeme ich mehr als günsitg erstanden habe.
Auch ist z.B. ARM (meines Wissens nach) noch nicht bei 64bit angekommen (um mal ne andere Platform zu nennen).
Also ich will erstmal ein halbwegs brauchbares System in 32bit haben. Dann habe ich vor ein neues für 64bit zu schreiben.
-
Hallo,
Klingt wieder verdächtig nach dem SlabAllocator ;) So schlecht scheint das Konzept also nicht zu sein.
Naja, ich bin mir noch nicht sicher ob man damit wirklich einen generischen Heap bauen kann. Für viele (kleine) gleichartige Objekte ist diese Variante schon recht toll aber was ist wenn das Programm viele unterschiedlich große Objekte erzeugen will? Mehr als 1000 Heap-Segmente will ich definitiv auch nicht haben weil ja immer mit etwas Verschnitt gerechnet werden muss und auch mit jedem Segment der Verwaltungsaufwand steigt. Ich hab die letzten Monate eine Reihe interessanter Heap-Management-Artikel gelesen die teilweise recht unterschiedliche Ansätze favorisieren, da muss ich auf jeden Fall noch mal gründlich drüber nachdenken welche dieser Lösungen überhaupt zu meinen Segmenten passt. Eventuell programmiere ich auch verschiedene Lösungen (obwohl ich so viel Aufwand gerne vermeiden möchte) und schau mir dann an welche Lösung sich in der Praxis am besten bewährt. Bevor ich einen C-Compiler hab muss ich aber erst mal eine Primitiv-Version in Assembler bauen und die wird wohl einfach in einem einzelnen Segment eine verkettete Liste (ähnlich dem was DOS früher gemacht hat) nehmen.
Also den HDD-Treiber interessiert es prinzipiell schon mal gar nicht was mit den Daten passiert, der läd sie nur und gibt sie einfach an den VFS-Service weiter (da findet kein kopieren statt) und damit ist der Speicher für ihn auch weg.
Du meinst der HDD-Treiber alloziert Speicher (um dort Daten rein zu legen), gibt diesen Speicher weiter (an seinen Client, einen Dateisystem-Treiber) und damit ist der Speicher vom HDD-Treiber wieder entzogen (so als hätte er free benutzt)? Das ist ein sehr merkwürdiges Konzept, das mag zwischen VFS, Dateisystem-Treibern und Block-Device-Treiber noch funktionieren aber wie ist das z.B. mit USB oder Sound oder seriellen Schnittstellen? Außerdem wie geht das beim Schreiben auf eine HDD? Muss da der HDD-Treiber erst Speicher allozieren damit das VFS da was rein legen (kopieren) kann und der HDD-Treiber (nachdem die Daten auf die HDD geschrieben wurden) diesen Speicher wieder freigeben muss? Speicher von unten nach oben zu reichen halte ich für extrem ungeschickt! Das macht IMHO nur in ganz speziellen Situationen sind und wiederspricht auch allen vorhandenen Programmier-Paradigmen (einem printf wird ja auch der Speicher mit dem String übergeben). Anders zu sein ist IMHO an sich schon mal was gutes aber das lässt sich nicht auf alles anwenden. Ich weiß ich reite ziemlich auf dem Zero-Copy-Prinzip rum aber das verspricht eben die maximale Performance (unnützes kopieren kostet nur Zeit und Energie) und sollte von daher angestrebt werden. Wenn man im VFS einen Cache haben will, der ja immer ein Kopieren erfordert, dann macht man das ja weil man sich insgesamt davon einen Performance-Vorteil erhofft (ein paar mal Kopieren ist dann doch schneller als ein paar mal auf die HDD zu warten).
Der Client würde seine Anfrage per Pipe stellen und wenn er das geschickt macht, kann er sogar mehrere Anfragen mit einmal in die Pipe schreiben (so könnte ich sogar einige Syscalls vermeiden, wo auch mal ein Vorteil meiner UserSpace Pipe´s zum Tragen kommt).
An dieser Stelle könnte ich auch mehrere Anfragen (weil das ja alles die gleichen sind) in einen RPC-Request packen und sagen dass das ein Vorteil meiner flexiblen Message-Größe ist. ;) Klar kann man für bestimmte Situationen eine extra Optimierung vornehmen aber wie viele Alltagsaufgaben profitieren den wirklich davon?
Auch ist z.B. ARM (meines Wissens nach) noch nicht bei 64bit angekommen (um mal ne andere Platform zu nennen).
Ja, bei ARM hat man den Anbruch der 64 Bit-Epoche dem Anschein nach noch völlig verschlafen und das wo doch auch bereits Handys mal 1 GByte RAM haben können.
Also ich will erstmal ein halbwegs brauchbares System in 32bit haben. Dann habe ich vor ein neues für 64bit zu schreiben.
Versuch doch Dein OS so zu programmieren das es sich für beide Welten eignet. Ich werde meinen OS-Kernel wohl zu großen Teilen in Assembler coden müssen und trotzdem wird er sich für beide Welten assemblieren lassen, ich denke es wird nur wenige Stellen geben wo wirklich unterschiedlicher Programm-Code erforderlich ist (z.B. bei den Segment-Descriptoren).
Grüße
Erik
-
Ich denke mal beim Heap sprichst du vom User-Heap? Da ist natürlich ein anderer Ansatz wesentlich besser geeignet. Obwohl die (z.B. Linux, Solaris) auch den SlabAllocator dafür nutzen und es scheint zu funktionieren.
Was den Heap allgemein betrifft, ist das so eine der Sachen die ich nie richtig verstanden habe und ich habe auch das Gefühl das viele sich da an Linux/Unix orientieren, aber ich bin der Meinung das deren Heap-Prinzip nicht so toll für Multithreading geeignet ist.
Du meinst der HDD-Treiber alloziert Speicher (um dort Daten rein zu legen), gibt diesen Speicher weiter (an seinen Client, einen Dateisystem-Treiber) und damit ist der Speicher vom HDD-Treiber wieder entzogen (so als hätte er free benutzt)? Das ist ein sehr merkwürdiges Konzept, das mag zwischen VFS, Dateisystem-Treibern und Block-Device-Treiber noch funktionieren
Das ist doch erstmal das einzige was mich interessiert ;)
Außerdem wie geht das beim Schreiben auf eine HDD? Muss da der HDD-Treiber erst Speicher allozieren damit das VFS da was rein legen (kopieren) kann und der HDD-Treiber (nachdem die Daten auf die HDD geschrieben wurden) diesen Speicher wieder freigeben muss?
Nein, der User hat ja seinen Buffer aus dem die Daten kommen, die (Daten) werden dann in neuen Speicher kopiert welcher an den VFS-Service weitergegeben wird (bzw. wenn nicht die ganze Datei geändert wird, werden die neuen Daten einfach über die alten Daten geschrieben) und dieser gibt den Speicher dann wiederrum an den HDD-Treiber weiter.
Der Treiber schreibt das ganze dann auf die HDD und gibt den Speicher wieder frei (das muss er auch beim Lesen machen, das habe ich glaub ich nicht so deutlich gesagt).
Das größte Problem was ich bisher nicht gelöst habe (aber ich habe schon eine Idee dafür) ist, dass ich nicht weiß wie ich es einem Treiber ermögliche das er rausbekommt, welche physikalische Adresse hinter einer virtuellen Steckt. Ich dachte mir das ich dafür nen Syscall mache und bei jedem Aufruf wird überprüft ob es ein Treiber ist (das ist bei mir immer dann der Fall wenn der Prozess auf Ports zugreifen darf).
Genauso müsste ein Prozess dann auch Speicher als nicht auslagerbar markieren dürfen (was dann halt schon bei der Allokierung passieren muss).
Obwohl ich mit dem Auslagern wohl die größten Probleme bekommen werde, da ich dafür noch gar keine Idee habe, wie das zu lösen ist, deswegen ignorier ich das im Moment auch fleissig ;)
Ja, bei ARM hat man den Anbruch der 64 Bit-Epoche dem Anschein nach noch völlig verschlafen und das wo doch auch bereits Handys mal 1 GByte RAM haben können.
Dem würde ich jetzt entgegenhalten naund ;) Ich meine nur weil die schon bei 1GB RAM angekommen sind, heißt das ja noch lange nicht das denen der virtuelle Speicher ausgeht.
Ich denke mal Handys brauchen nicht unbedingt ne 64bit Architektur, aber schaden kann es natürlich nicht (mal von den Transistoren abgesehen, die du dann mehr bräuchtest).
Versuch doch Dein OS so zu programmieren das es sich für beide Welten eignet.
Gerade das werde ich nicht tun! Ich bin der Meinung wer einen Mikrokernel (und nicht einen Mix aus Mikro und Monolith) schreibt, der sollte für jede Architektur einen extra Mikrokernel schreiben. Ist der Kernel wirklich klein, würde ich sogar sagen, das es sich lohnen würde diesen in Assembler zu schreiben.
Der Punkt ist, ich optimiere viele Sachen für die x86 Architektur und möchte nicht noch mehr Abstraktionsebenen einbauen, nur damit der Kernel leichter auf eine neue Architektur portiert werden kann. Gerade bei nem Mikrokernel ist doch der Aufwand den Kernel für ne andere Architektur (wo dann auch deren Besonderheiten beachtet werden, bzw. ausgenutzt werden) neu zu schreiben nicht sonderlich groß!
Wenn du die Interfaces gleicht behälst solltest du die meisten Sachen einfach nur neu kompilieren brauchen. Ich plane im Moment sogar wie ich meinen Device-Server portabel gestalte, da ja auf der ARM Architektur sowas wie ein BIOS nicht existiert. Da würde ich dem Device-Server wahrscheinlich als Startmodul auf jeder Architektur nen angepasstest Skript mitgeben, welches dann bestimme Module in einer bestimmten Reihenfolge lädt.
Damit wäre es dann einfach zu sagen, das dieses und jenes Gerät diesen und jenen Speicher/IO-Ports benutzt ohne das ich dafür Code ändern müsste (ziel wäre es nur die Adresse und IO-Ports in einem Skript zu ändern und schon hat man die Unterstützung für ein anderes Board ohne das man irgendwelchen Code neukompiliert hat).
-
Hallo,
Ich denke mal beim Heap sprichst du vom User-Heap?
Ja, ich habe die ganze Zeit vom Heap der User-Mode-Applikationen gesprochen. Den Speicher den der Kernel verwaltet ist bei mir der lineare Speicher.
Was den Heap allgemein betrifft, ist das so eine der Sachen die ich nie richtig verstanden habe und ich habe auch das Gefühl das viele sich da an Linux/Unix orientieren, aber ich bin der Meinung das deren Heap-Prinzip nicht so toll für Multithreading geeignet ist.
Ich weiß nicht ob die Default-Mechanismen von Linux usw. so schlecht sind aber aus irgend einem Grund muss es ja die teuren kommerziellen Heap-Librarys geben.
Das ist doch erstmal das einzige was mich interessiert
Das ist dann aber ziemlich kurzsichtig. Soll Dein OS nicht mal per LAN kommunizieren oder eine simple serielle Schnittstelle nutzen können?
Außerdem wie geht das beim Schreiben auf eine HDD? Muss da der HDD-Treiber erst Speicher allozieren damit das VFS da was rein legen (kopieren) kann und der HDD-Treiber (nachdem die Daten auf die HDD geschrieben wurden) diesen Speicher wieder freigeben muss?
Nein, der User hat ja seinen Buffer aus dem die Daten kommen, die (Daten) werden dann in neuen Speicher kopiert welcher an den VFS-Service weitergegeben wird
Hier wird dann doch der Speicher vom Client an den Service weitergegeben? Hm, das wird immer komplexer. Also den Mechanismus für beide Richtungen anzubieten scheint zwar verlockend aber ist bestimmt auch komplexer als sich auf eine Richtung zu beschränken. Oder haben Deine Memory-Sharing-Mechanismen nichts direkt mit dem IPC-Mechanismen zu tun? Wenn nicht, dann kosten sie natürlich wieder extra Syscalls.
Der Treiber schreibt das ganze dann auf die HDD und gibt den Speicher wieder frei (das muss er auch beim Lesen machen, das habe ich glaub ich nicht so deutlich gesagt).
Verstehe ich das richtig das der HDD-Treiber den Speicher der Applikation oder des VFS frei gibt? Oh man, das klingt ganz schön schräg.
dass ich nicht weiß wie ich es einem Treiber ermögliche das er rausbekommt, welche physikalische Adresse hinter einer virtuellen Steckt.
Das ist einfach, der Kernel-Code schaut halt einfach selber ins Paging-Directory und schon hat er die physische Adresse. Das ist ein recht kurzer Syscall.
(das ist bei mir immer dann der Fall wenn der Prozess auf Ports zugreifen darf).
Falls Du dieses mal mit "Ports" die I/O-Ports meinst dann muss ich Dich enttäuschen es gibt auch HW die nur mit Memory-Mapped-I/O arbeitet und keine Ports hat.
Ja, bei ARM hat man den Anbruch der 64 Bit-Epoche dem Anschein nach noch völlig verschlafen und das wo doch auch bereits Handys mal 1 GByte RAM haben können.
Dem würde ich jetzt entgegenhalten naund ;) Ich meine nur weil die schon bei 1GB RAM angekommen sind, heißt das ja noch lange nicht das denen der virtuelle Speicher ausgeht.
Aber der physische Adressraum könnte bald zu knapp werden. Sowas wie einem iPad würden sicherlich 2 GBytes RAM gut tun aber dann sind die 32Bit auch (fast) am Ende, schließlich muss noch allerlei Memory-Mapped-Hardware mit in den physischen Adressraum mit rein (ARM kennt keine I/O-Ports, das ist eine x86-Besonderheit).
Ich denke mal Handys brauchen nicht unbedingt ne 64bit Architektur, aber schaden kann es natürlich nicht (mal von den Transistoren abgesehen, die du dann mehr bräuchtest).
Das mit den Transistoren dürfte sich auf sehr wenige Prozent beschränken, das meiste Silizium wird in heutigen Handys für die ganze Peripherie (Bluetooth, USB, Display-Controller .....) benötigt.
Versuch doch Dein OS so zu programmieren das es sich für beide Welten eignet.
Gerade das werde ich nicht tun! Ich bin der Meinung wer einen Mikrokernel (und nicht einen Mix aus Mikro und Monolith) schreibt, der sollte für jede Architektur einen extra Mikrokernel schreiben. Ist der Kernel wirklich klein, würde ich sogar sagen, das es sich lohnen würde diesen in Assembler zu schreiben.
Okay, dafür sind sich die 32 Bit-Version und die 64 Bit-Version meiner CPU zu extrem ähnlich als das es sich da lohnen würde unterschiedlichen Code zu verfassen, bei x86 könnte es sich vielleicht schon an einigen Stellen lohnen. Obwohl es ne Menge OSe gibt die auf den unterschiedlichsten CPU-Architekturen laufen und nur sehr kleine Teile davon spezifisch sind, die überlassen dem Compiler die Anpassungsarbeit.
Grüße
Erik
-
Ich weiß nicht ob die Default-Mechanismen von Linux usw. so schlecht sind aber aus irgend einem Grund muss es ja die teuren kommerziellen Heap-Librarys geben.
Mir gefällt nicht das der Heap, mehr oder weniger zusammenhängend ist und von oben (wie ein Stack) herunter wächst.
Das ist dann aber ziemlich kurzsichtig. Soll Dein OS nicht mal per LAN kommunizieren oder eine simple serielle Schnittstelle nutzen können?
Da hast du mich in dem Moment falsch verstanden. Ich will nicht die eine Methode für alles finden (sowas wie ne Weltformel), sondern nutze das was ich (also mein Kernel) zur Verfügung habe aus, sprich für verschiedene Probleme, verschiedene Lösungen.
Hier wird dann doch der Speicher vom Client an den Service weitergegeben? Hm, das wird immer komplexer. Also den Mechanismus für beide Richtungen anzubieten scheint zwar verlockend aber ist bestimmt auch komplexer als sich auf eine Richtung zu beschränken. Oder haben Deine Memory-Sharing-Mechanismen nichts direkt mit dem IPC-Mechanismen zu tun? Wenn nicht, dann kosten sie natürlich wieder extra Syscalls.
Also erstmal was hast du immer mit deinen Syscalls ;) Ich weiß nicht wie du dir das vorstellst, aber wenn ich von SharedMem rede, dann läuft das bei mir folgender Maßen ab.
Du machst nen Syscall wo du ne BaseAddr und eine Länge (in Pages) angibst (sowie nen Flag ob der Speicher dann read-only ist oder nicht), der Kernel speichert dann die physischen Adressen der einzelnen Pages in einer Liste und markiert diese als SharedMem (damit sie nicht ausgelagert werden können) und du (der Syscall-Aufrufer) bekommst ne ID zurück. Als nächstes musst du noch einen Syscall machen (ich weiß nicht toll, aber mir ist nichts besseres eingefallen) in dem du sagst das du nem anderen Prozess Zugriff geben willst. Dann kannst du die ID an nen anderen Prozess weitergeben (den Prozess den du gerade Zugriff gegeben hast) und dieser macht dann mit der ID nen Syscall das er den Speicher mappen will, ist genug virtueller Speicher vorhanden wird das gemacht. Bist du (der Client, also der der den Speicher gemappt hat) fertig mit deiner Arbeit, rufst du nen Syscall auf, dass du den Speicher wieder unmappen willst und das wird dann auch gemacht, der Speicher wird aber in dem Sinne nicht freigegeben, sondern ist ja noch beim eigentlichen "Owner" vorhanden.
Das sollte auch ungefähr die Prozedur sein, wie sie auf allen (fast??) OS zur Verfügung gestellt wird.
Verstehe ich das richtig das der HDD-Treiber den Speicher der Applikation oder des VFS frei gibt? Oh man, das klingt ganz schön schräg.
Siehe oben, wird nicht freigegeben, sondern ge-unmappet (scheiß denglisch ;) ).
Falls Du dieses mal mit "Ports" die I/O-Ports meinst dann muss ich Dich enttäuschen es gibt auch HW die nur mit Memory-Mapped-I/O arbeitet und keine Ports hat.
Schitt erwischt :( Da muss ich mir auch nochmal Gedanken machen, wie ich einem Prozess (Treiber) erlauben darf nen bestimmten physikalischen Speicherbereich in seinen Adressraum zu mappen.
Das ist einfach, der Kernel-Code schaut halt einfach selber ins Paging-Directory und schon hat er die physische Adresse. Das ist ein recht kurzer Syscall.
Das ist klar, aber das will ich nicht jedem X-beliebigen Prozess erlauben, sondern nur Treibern!
Obwohl es ne Menge OSe gibt die auf den unterschiedlichsten CPU-Architekturen laufen und nur sehr kleine Teile davon spezifisch sind, die überlassen dem Compiler die Anpassungsarbeit.
Ja, aber zu welchem Preis? Ich meine das Linux auf allen Architekturen die selben Page-Tables nutzt, aber je nach dem wie das Paging dann auf der Architektur Hardware mäßig implementiert ist, sehen die dann in der Hardware ganz anders aus.
Außerdem gefällt mir diese ganze bedingte Kompilieren nicht, der Source sieht nicht mehr wirklich "schön" aus und ich habe auch oft Probleme den richtigen Code zu finden und zu lesen.
Außerdem wäre mir das viel zu viel Arbeit, denn du musst ja höllisch aufpassen, was du dann alles auf ner neuen Architektur implementieren musst, welche Funktionen eigentlich gar nichts machen usw.