Autor Thema: Shared Memory  (Gelesen 36183 mal)

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #40 am: 24. September 2010, 23:53 »
Zitat von: taljeth
Du könntest theoretisch auch nur einen Kernelstack pro CPU benutzen und den Zustand woanders hin wegkopieren.
Dazu sag ich erstmal zero-copy ;) und was unterscheidet den Speicher (den du so oder so benötigst und nur der Kernel draufzugreifen darf) wo du die Daten hinkopierst von einem Stack?

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #41 am: 24. September 2010, 23:59 »
Du könntest ihn knapper bemessen, weil es nicht gleichzeitig noch der Stack ist, auf dem der Kernel arbeiten und z.B. seine Syscalls ausführen muss.

Zero-copy ist ein Taskwechsel eh nicht, ob du die Register jetzt auf den Stack oder woanders hin sicherst. ;)
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #42 am: 25. September 2010, 02:50 »
Hallo,

Zitat von: erik
Das würde bedeuten wenn ein Web-Server mit vielen Threads arbeitet um möglichst viele Verbindungen parallel zu betreiben würden in Deinem TCP-Service auch ne Menge an Ports und Threads existieren, selbst wenn die Daten nur so dahin tröpfeln, verstehe ich das richtig?
Ich weiß gar nicht wie sowas unter anderen OSs gemacht wird, aber der Web-Server ansich macht ja für jeden Clienten einen neuen Thread auf (wo sich mir gerade die Frage stellt wie das überhaupt funktioniert, ich erinnere mich da an einen Port (TCP-Port) pro Client, aber das kann nicht stimmen) und je nachdem über wieviele TCP-Ports das dann läuft, so viele Threads wären dann auch im Service.
Da fallen mir vier Möglichkeiten ein:
(a) Bei einkommender Verbindung wird ein fork() mit dieser Verbindung gemacht, der neue Prozess kümmert sich. Einfach implementierbar, je nach OS recht gut skalierbar. Setzt schnelles fork() und guten Scheduler voraus.
(b) Bei einkommender Verbindung wird ein Thread erzeugt, der sich darum kümmert. Setzt eine vernünftige Threadunterstützung (Erzeugung, Scheduling) voraus. Hab ich keine Erfahrung.
(c) Es gibt eine Menge an rumliegenden Threads, bei einkommender Verbindung wird ein unbenutzter ausgesucht, der sich kümmert. Kommen mehr Verbindungen rein als Threads da sind, muss gewartet werden. Spart die Erzeugungszeit der Threads, wird oft unter Windows gemacht.
(d) AIO. In einer blockierenden Endlosschleife/Signalwarteschleife wird geschaut, auf welcher Verbindung irgendetwas geschehen ist und diese wird verarbeitet: poll(), epoll(), kqueue oder so. Skaliert je nach OS optimal und kann sehr schnell sein. Setzt aber eine High-Performance-AIO-API voraus. Kann man mit (c) mischen (z.B. ein Worker-Thread je CPU).

Wenn du einen Webserver auf Port 80 hast und der kriegt ne Anfrage, dann wird ein lokaler Port zugeteilt (z.B. 38855) und über den läuft die Kommunikation. Dein Webserver kann also keine 64k TCP-Verbindungen gleichzeitig halten, weil es soviele Ports dann nicht gibt. Korrigiert mich, wenn ich hier falsch liege.

Gruß

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #43 am: 25. September 2010, 07:45 »
Zitat von: svenska
Wenn du einen Webserver auf Port 80 hast und der kriegt ne Anfrage, dann wird ein lokaler Port zugeteilt (z.B. 38855) und über den läuft die Kommunikation. Dein Webserver kann also keine 64k TCP-Verbindungen gleichzeitig halten, weil es soviele Ports dann nicht gibt. Korrigiert mich, wenn ich hier falsch liege.
Genauso habe ich das auch in Erinnerung, aber wie macht das dann ein Web-Server? Denn seien wir mal ehrlich, 64k gleichzeitige Zugriffe sind nicht wirklich viel für bestimmte Web-Seiten.

Zitat von: taljeth
Zero-copy ist ein Taskwechsel eh nicht, ob du die Register jetzt auf den Stack oder woanders hin sicherst.
Also ich weis ja nicht wie bei euch ein IRQ abläuft, aber sobald ein IRQ aufgerufen wurde (und das gilt auch für den Scheduler) sind alle Register schon auf dem Stack, also würde es durchaus etwas kosten diese nochmal, vom Stack, irgendwo hinzusichern! Ein Thread-Wechsel sollte nicht mehr als ein Austausch von "ESP" sein und bei einem Prozess-Wechsel wird dann nur noch zusätzlich "CR3" verändert.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #44 am: 25. September 2010, 10:02 »
Hallo,


weil solange wie ich kein A-I/O habe kann ein Thread sowieso nur eine Datei gleichzeitig lesen/schreiben.
Das bedeutet das wenn Du asynchrones I/O implementieren möchtest soll das über noch mehr Ports (und damit Threads) im Service gehen? Ich gebe zu das ich über asynchrones I/O auch noch nicht so viel nachgedacht habe aber diese Idee erscheint mir nicht sonderlich geschickt.

Der Port wird nur erstellt wenn er auch wirklich benötigt wird, sprich wenn eine Anwendung keine Dateien öffnet dann wird auch kein Port erstellt
Das bedeutet aber das wenn Du 8 parallele Compiler-Instanzen hast dann gibt es auch 8 Ports + Threads beim VFS-Service obwohl die Compiler die meiste Zeit damit verbringen die CPU zu beschäftigen (also kompilieren), File-I/O findet dort nur am Anfang (Quell-Code einlesen) und am Ende (Assembler-Code schreiben) statt.

Ich weiß gar nicht wie sowas unter anderen OSs gemacht wird, aber der Web-Server ansich macht ja für jeden Clienten einen neuen Thread auf (....) und je nachdem über wieviele TCP-Ports das dann läuft, so viele Threads wären dann auch im Service.
Ich denke bei einem Micro-Kernel-OS, wo der TCP-IP-Stack ein ganz normaler User-Mode-Service ist, ist das eine brauchbare Vorgehensweise.

Mit der x86-Architektur hast du dich noch nicht so viel beschäftigt oder?
Doch, sehr lange und intensiv sogar, aber ich hatte kurz vergessen das die x86-CPU die Register ja zwangsweise in den Speicher schreibt aber auch so bin ich davon ausgegangen das man das gesicherte Register-Set vom Stack in die Thread-Descriptor-Struktur kopiert und wenn man einen anderen Thread weiterlaufen lassen will dann wird sein Register-Set eben in den CPU-Lokalen-Kernel-Stack kopiert und ein IRET gemacht. Selbst bei x86-64 ist ein Register-Set nur 128 Bytes groß und dafür einen kompletten Stack zu reservieren finde ich eher ungeschickt, bei den paar Bytes fällt das kopieren nun wahrlich nicht ins Gewicht (das passt ja bequem in den L1-Cache so das der von Dir erhoffte Performance-Vorteil vermutlich weniger als 10 Takte pro Context-Switch beträgt). Im Tutorial wird doch wimre auch nur ein TSS pro CPU empfohlen, wo mir dann auffällt das Du dann dort immer einen anderen Kernel-Stack eintragen musst (also Speicherzugriffe) oder Du hast so viele TSS wie es Threads gibt und da dürften Dir dann doch ziemlich schnell die GDT-Einträge ausgehen (vor allem weil Du ja mit Threads seeeehr großzügig bist).

Denn was bringen dir viele Threads die von ein und der selben HDD lesen wollen (und wir lassen SATA job-queueing mal außen vor)?
Also gerade das SATA-Job-Queueing will ich ganz sicher nicht außen vor lassen. Aber etwas Recht hast Du schon, 2 Dateien von der selben HDD einzulesen geht eben nicht ganz gleichzeitig, aber es könnten ja auch 2 HDDs im Spiel sein oder ne Flash-Disk oder der HDD-Cache könnte vielleicht noch was haben oder oder ..... Ich denke es schadet nichts für 2 getrennte Vorgänge auch 2 Threads zu benutzen, im Worst-Case bin ich nicht schneller als Du und habe auch blockierte Threads rumgammeln aber im Best-Case kann ich eben sehr viel mehr rausholen.

Wo sich mir dann auch die Frage stellt, willst du eigentlich einem Programm auch die Möglichkeit geben, das keine Popup-Threads erstellt werden, sondern das alles nur über einen Thread läuft (der nicht erstellt werden muss)?
Ich möchte das ein Service bei der Erstellung seines Message-Targets bestimmen kann wie viele Threads maximal für dieses Message-Target erstellt werden dürfen, das bedeutet dann das wenn mehr Anfragen gleichzeitig kommen das dann gequeued werden muss. Dieses Maximum darf auch 1 betragen. Aber das ein bereits vorhandener Thread in dem Prozess benutzt werden soll ist definitiv nicht vorgesehen. Es wird weder aktives abholen von Messages geben (eben wegen den Problemen mit der unbekannten Message-Größe) und auch keine Unterbrechung eines Threads (in der Art der Unix-Signale, das hat mir zu viele Nachteile für die Signal-Handler bei der Benutzung von Ressourcen wie malloc usw.).

Ok, aber nach welchen Kriterium entscheidest du wieviele Threads in diesen Pool kommen?
Das hab ich noch nicht entschieden aber worum es mir geht ist das dieser Pool ja für alle Services zur Verfügung steht er also kleiner sein kann als die Menge Threads die Du benötigst (weil ja wohl kaum alle Services gleichzeitig stark belastet werden). Die Threads in einem Web-Server machen entweder TCP-Verkehr oder sie lesen Daten von der HDD oder machen eine Datenbankabfrage, aber nicht alles gleichzeitig (es könnte also passieren das für File-I/O, Datenbank und TCP immer wieder der selbe PopUp-Thread zum Einsatz kommt wogegen bei Dir dafür 3 Service-Threads benötigt werden obwohl maximal einer davon auch was zu tun bekommt).

Bei dir sehe ich dann, wie gesagt, das Problem das du entweder viel Zeit mit der Thread Erstellung verbringst
Deswegen den Pool, ich hoffe das ich die Thread-Erstellung so schnell machen kann das es kaum ins Gewicht fällt und außerdem hat ein neuer Thread ja keinen Kontext (also Register-Set) der aus dem Speicher gelesen werden muss (und auch die Register die vom Client-Thread gesichert werden müssen sind weniger weil der Syscall die selbe Aufrufkonvention benutzt wie ein normaler CALL und dort nur etwa ein Drittel der Register als Callee-Saved definiert sind, bei meinen 64 Registern wird das sicher spürbar sein).

(was passiert eigentlich wenn die Nachricht abgearbeitet ist bzw. muss der User den Thread selbst beenden und wo kommt er dann hin?)
Der PopUp-Thread muss bei synchronem IPC je eh das Ergebnis zurückgeben (mit einem speziellen Syscall) und wird dabei auch gleich wieder in den Pool zurückgelegt (also gekillt, wobei auch hier wieder der Kontext-Switch sehr klein ausfällt weil von dem PopUp-Thread nichts gesichert werden muss und vom Client-Thread nur ein Teil der Register geladen werden müssen, die Rückgabewerte usw. sind ja schon in Registern).

oder du halt viel zu viele Threads hast, die nichts machen und sinnlos im Pool rumliegen ;) Damit hättest du dann das selbe Problem wie ich.
Klar kann es sein das der Pool mal ziemlich voll wird aber dann muss der eben etwas geleert werden. Es kommt letztendlich auf eine gute Pool-Verwaltungsstrategie an aber wenn ich die finden sollte werde ich wohl weniger Speicher verbrauchen als Dein Konzept.


Da fallen mir vier Möglichkeiten ein ....
Variente a und b setzen beide einen guten Scheduler voraus und sind aus meiner Sicht daher ziemlich gleichwertig. Trotzdem denke ich dürfte Variante b schneller gehen weil es leichter ist einen neuen Thread zu erstellen als einen Prozess zu forken, mal abgesehen davon das bei einem fork das Paging-Directory geklont werden muss (zumindest teilweise was aber trotzdem Speicher kostet) und dann zur Laufzeit einige Paging-Exceptions anfallen in denen dann modifizierte Pages kopiert werden müssen. Für einen neuen Thread muss man einmalig einen Stack anlegen (neue Verwaltungsstrukturen usw. im Kernel fallen bei fork auch an) und das war es dann auch. Also kostet fork definitiv mehr Speicher und mehr CPU-Zeit.
Ein Gemisch aus Variante c und d klingt auch sehr interessant wobei dann wieder die Frage aufkommt: "Was kostet weniger ein Kontext-Switch oder ein State-Wechsel in der State-Maschine?". Nebst dessen das ein gutes Asynchrones I/O-Interface sicher keine leichte Übung ist und in Summe womöglich mehr Syscalls kostet.

Wenn du einen Webserver auf Port 80 hast und der kriegt ne Anfrage, dann wird ein lokaler Port zugeteilt (z.B. 38855) und über den läuft die Kommunikation. Dein Webserver kann also keine 64k TCP-Verbindungen gleichzeitig halten, weil es soviele Ports dann nicht gibt. Korrigiert mich, wenn ich hier falsch liege.
Bitte nicht übel nehmen aber das ist völliger Quatsch! Eine TCP-Verbindung wird über die 2 IP-Addressen und die 2 Port-Nummern (jeweils vom Client und vom Server) unterschieden und selbst wenn davon immer eine IP-Adresse und ein Port gleich sind (also die vom Server) so sind doch trotzdem mit IPv4 theoretisch bis zu 2^48 Verbindungen zu einem einzelnen Web-Server möglich (mit IPv6 entsprechend mehr).


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

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #45 am: 25. September 2010, 11:04 »
Also ich weis ja nicht wie bei euch ein IRQ abläuft, aber sobald ein IRQ aufgerufen wurde (und das gilt auch für den Scheduler) sind alle Register schon auf dem Stack, also würde es durchaus etwas kosten diese nochmal, vom Stack, irgendwo hinzusichern! Ein Thread-Wechsel sollte nicht mehr als ein Austausch von "ESP" sein und bei einem Prozess-Wechsel wird dann nur noch zusätzlich "CR3" verändert.
Ich weiß ja nicht, wo du deine x86-Fälschung erworben hast, die das so macht, aber mein x86 sichert von sich aus bei einem Interrupt oder Trap Gate nur ss, esp, eflags, cs und eip auf den Stack. Der Rest landet nur dann auf dem Stack, wenn du das so programmierst. Bei einem Task Gate geht sogar alles direkt in eine andere Struktur (nämlich das TSS).

Dass du es gewohnt bist, alles auf den Stack zu sichern, heißt ja nicht, dass es nicht anders ginge.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #46 am: 25. September 2010, 12:32 »
Zitat von: taljeth
Ich weiß ja nicht, wo du deine x86-Fälschung erworben hast, die das so macht, aber mein x86 sichert von sich aus bei einem Interrupt oder Trap Gate nur ss, esp, eflags, cs und eip auf den Stack. Der Rest landet nur dann auf dem Stack, wenn du das so programmierst. Bei einem Task Gate geht sogar alles direkt in eine andere Struktur (nämlich das TSS).
Naja, ich bin halt einfach davon ausgegangen das man alles auf dem Stack speichert, weil es das schnelleste und einfachste ist. Es dann nochmal umzukopieren wäre ja quatsch.

Zitat von: erik
Das bedeutet das wenn Du asynchrones I/O implementieren möchtest soll das über noch mehr Ports (und damit Threads) im Service gehen? Ich gebe zu das ich über asynchrones I/O auch noch nicht so viel nachgedacht habe aber diese Idee erscheint mir nicht sonderlich geschickt.
Ich wollte damit nur sagen, dass mein Konzept bei A-I/O nicht mehr funktioniert, aber diese Baustelle werde ich frühestens bearbeiten wenn alles andere läuft und bis dahin vergeht noch ne ganze Weile.

Zitat von: erik
Doch, sehr lange und intensiv sogar, aber ich hatte kurz vergessen das die x86-CPU die Register ja zwangsweise in den Speicher schreibt aber auch so bin ich davon ausgegangen das man das gesicherte Register-Set vom Stack in die Thread-Descriptor-Struktur kopiert und wenn man einen anderen Thread weiterlaufen lassen will dann wird sein Register-Set eben in den CPU-Lokalen-Kernel-Stack kopiert und ein IRET gemacht.
Naja, du vergisst, dass auf dem Stack aber nicht nur die Register liegen, da liegen auch noch Funktionsargumente, lokale Variablen usw., das müsstest du dann alles mit in den Thread-Descriptor speichern und das artet dann mal schnell darin aus, das du den gesamten Stack woanders hinkopierst, nur um zu erreichen das du nur einen Stack im Kernel verwendest. Das einzigste was du dadurch erreichen würdest wäre, dass du pro CPU einen Stack mehr hast als normalerweise nötig.

Ich bin jetzt mal ganz einfach davon ausgegangen, dass jeder Thread auch mal den Syscall macht und das der Kernel unterbrechbar ist.

Übrigens wiedersprichst du da deiner eigenen Philosophie des zero-copy (ich betrachte das Speichern auf dem Stack nicht als kopieren in dem Sinne, weil mind. 1 Register sowieso erstmal irgendwo zwischen gespeichert werden muss um den Rest dann irgendwo zu speichern).

Zitat von: erik
Selbst bei x86-64 ist ein Register-Set nur 128 Bytes groß und dafür einen kompletten Stack zu reservieren finde ich eher ungeschickt, bei den paar Bytes fällt das kopieren nun wahrlich nicht ins Gewicht (das passt ja bequem in den L1-Cache so das der von Dir erhoffte Performance-Vorteil vermutlich weniger als 10 Takte pro Context-Switch beträgt). Im Tutorial wird doch wimre auch nur ein TSS pro CPU empfohlen, wo mir dann auffällt das Du dann dort immer einen anderen Kernel-Stack eintragen musst (also Speicherzugriffe) oder Du hast so viele TSS wie es Threads gibt und da dürften Dir dann doch ziemlich schnell die GDT-Einträge ausgehen (vor allem weil Du ja mit Threads seeeehr großzügig bist).
Du hast, wie gesagt, das Szenario Syscall vergessen und das die Kernel-Funktionen ja auch den Stack verwenden. Wie kommst du auf 10 Takte bei 128byte? Zumal es mehr als nur die 16 Register sind, da wäre noch das RIP und das RFlags Register.
Und ja ich verwende nur ein TSS pro CPU, weil es halt nötig ist. Denn keiner von uns wird wohl Hardware-Taskswitching auf der x86-Architektur nutzen (wenn doch komm ich dem gleich mit der Portabilität ;) )!?

Zitat von: erik
Ich möchte das ein Service bei der Erstellung seines Message-Targets bestimmen kann wie viele Threads maximal für dieses Message-Target erstellt werden dürfen, das bedeutet dann das wenn mehr Anfragen gleichzeitig kommen das dann gequeued werden muss. Dieses Maximum darf auch 1 betragen. Aber das ein bereits vorhandener Thread in dem Prozess benutzt werden soll ist definitiv nicht vorgesehen. Es wird weder aktives abholen von Messages geben (eben wegen den Problemen mit der unbekannten Message-Größe) und auch keine Unterbrechung eines Threads (in der Art der Unix-Signale, das hat mir zu viele Nachteile für die Signal-Handler bei der Benutzung von Ressourcen wie malloc usw.).
Ok, wenn du das so machen willst, darfst du mir nie mehr damit kommen, das man nicht alzu sehr von gewohnten Weg abweichen sollte ;)

Ich sag mal ne normal GUI-App zu schreiben/portieren dürfte noch gehen, aber wenn es dann zu sachen kommen die normaler Weise nicht blockieren wenn keine Nachricht da ist, sondern weiter arbeiten, da stelle ich es mir dann schon schwierig vor (Bsp. Spiele, die warten ja auch nicht bis die Taste gedrückt wurde, sondern es wird geguckt, wurde eine gedrückt und dann wird der nächste Frame berechnet).

Aber ansich ist die Idee von daher gut, weil man damit zum Multithreading gezwungen wird, macht die Sache nicht einfacher, aber dann gibt es vielleicht irgendwann mal mehr Programme mit denen man Vorteile aus mehreren Kernen holen kann.

Zitat von: erik
Klar kann es sein das der Pool mal ziemlich voll wird aber dann muss der eben etwas geleert werden. Es kommt letztendlich auf eine gute Pool-Verwaltungsstrategie an aber wenn ich die finden sollte werde ich wohl weniger Speicher verbrauchen als Dein Konzept.
Das klingt für mich wie nach dem Kontept eines Garbage-Collectors. Wird von allen als das Allheilmittel angepriesen (weil sich der dumme Programmierer nicht mehr um die Speicherverwaltung kümmern muss). Jedes Mal wenn ich das Wort Garbage-Collector höre, dreht sich bei mir im Magen alles um.
Meine Erfahrung von Programmen die in Java oder python oder anderen Sprachen mit einem Garbage-Collector geschrieben sind, der wird immer in einem bestimmten Zeit-Interall aufgerufen, wo er halt aufräumt und in der Zeit geht die Performance meistens in den Keller.
Sowas hört sich in der Theorie immer toll an, aber in der Praxis hapert es fast immer :(

Zitat von: erik
Bitte nicht übel nehmen aber das ist völliger Quatsch! Eine TCP-Verbindung wird über die 2 IP-Addressen und die 2 Port-Nummern (jeweils vom Client und vom Server) unterschieden und selbst wenn davon immer eine IP-Adresse und ein Port gleich sind (also die vom Server) so sind doch trotzdem mit IPv4 theoretisch bis zu 2^48 Verbindungen zu einem einzelnen Web-Server möglich (mit IPv6 entsprechend mehr).
Das klingt doch schon ganz anders und ist auch viel logischer!

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #47 am: 25. September 2010, 12:37 »
Zitat von: taljeth
Ich weiß ja nicht, wo du deine x86-Fälschung erworben hast, die das so macht, aber mein x86 sichert von sich aus bei einem Interrupt oder Trap Gate nur ss, esp, eflags, cs und eip auf den Stack. Der Rest landet nur dann auf dem Stack, wenn du das so programmierst. Bei einem Task Gate geht sogar alles direkt in eine andere Struktur (nämlich das TSS).
Naja, ich bin halt einfach davon ausgegangen das man alles auf dem Stack speichert, weil es das schnelleste und einfachste ist. Es dann nochmal umzukopieren wäre ja quatsch.
Ja, den Zustand irgendwohin zu kopieren, wo man ihn nicht benutzt, ist Quatsch. Auch wenn es noch so einfach ist. Aber das ist kein Argument - du kannst nicht einfach die eine Lösung zur Hälfte implementieren und dich dann beschweren, dass die andere Lösung damit nicht vernünftig zusammenspielt.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #48 am: 25. September 2010, 16:10 »
Hallo,


Ich wollte damit nur sagen, dass mein Konzept bei A-I/O nicht mehr funktioniert, aber diese Baustelle werde ich frühestens bearbeiten wenn alles andere läuft und bis dahin vergeht noch ne ganze Weile.
Ich sage es nur ungern aber falls Du dann später feststellen solltest das Dein Konzept für asynchrones I/O überhaupt nichts taugt wirst Du Dich ziemlich ärgern weil Du dann vor der Entscheidung stehst entweder ein zweites paralleles IPC-System zu implementieren oder alle Programme auf ein neues IPC-System umzustellen oder Dich mit dem Ärgernis irgendwie zu arrangieren oder es einfach ganz bleiben zu lassen. Ich hab es schon mal geschrieben und mache das gerne wieder: solche Arten von Entscheidungen sind ziemlich eklig.

Naja, du vergisst, dass auf dem Stack aber nicht nur die Register liegen, da liegen auch noch Funktionsargumente, lokale Variablen usw., das müsstest du dann alles mit in den Thread-Descriptor speichern und das artet dann mal schnell darin aus, das du den gesamten Stack woanders hinkopierst, nur um zu erreichen das du nur einen Stack im Kernel verwendest. Das einzigste was du dadurch erreichen würdest wäre, dass du pro CPU einen Stack mehr hast als normalerweise nötig.
Die Stack-Sachen des User-Mode-Thread liegen auf dem User-Mode-Stack und der wird natürlich mit einem Wechseln von SS:ESP komplett gewechselt. Die Sachen des Kernel-Mode-Stack liegen auf dem CPU-spezifischen-Kernel-Mode-Stack und sind nach dem IRET nicht mehr von Bedeutung. Von einem unterbrechbaren Kernel bin ich allerdings nicht ausgegangen, wenn Du sowas willst hast Du sicher noch ganz andere Probleme als nur die Zuordnung von Stacks. Wer hat den hier einen unterbrechbaren Kernel? Auf meiner Plattform gibt es keinen unterbrechbaren Kernel, wenn die CPU in einem System-Mode ist dann gehen keine weiteren Exceptions/Interrupts oder sonstwas (das würde zum Double-Error-Shutdown führen), aus dem System-Mode geht es nur raus wenn in den User-Mode zurück gewechselt wird.

Übrigens wiedersprichst du da deiner eigenen Philosophie des zero-copy (ich betrachte das Speichern auf dem Stack nicht als kopieren in dem Sinne, weil mind. 1 Register sowieso erstmal irgendwo zwischen gespeichert werden muss um den Rest dann irgendwo zu speichern).
Das mag auf x86 ja zutreffen aber auf viele andere CPUs trifft das nicht zu, z.B. ARM. Diese CPUs haben einen gewissen Teil der Register doppelt, einmal für den User-Mode und einmal für den System-Mode, und schalten beim Mode-Wechsel einfach um. Im System-Mode stehen einem gleich ein paar Register zur Verfügung die auch bereits sinnvolle Werte für den Kernel enthalten (z.B. einen Pointer zum Thread-Descriptor des gerade laufenden Threads) und das sichern der paar User-Mode-Register ist damit nicht viel mehr als ein einfacher Speicher-Zugriff. Schau Dir mal an wie ARM das macht, der einzigste Unterschied zu meiner CPU ist das ARM für jeden verschiedenen System-Mode ein extra Set an Schattenregistern hat so das auch innerhalb eines Syscall-Modes noch eine Page-Fault-Exception auftreten kann (ich will nur ein Schattenregister-Set implementieren und bin auch nicht der Meinung dass das ein Problem oder Nachteil ist).

Du hast, wie gesagt, das Szenario Syscall vergessen und das die Kernel-Funktionen ja auch den Stack verwenden.
Hä, Du schreibst wirr! Alles was vor dem Syscall im User-Mode passierte liegt im User-Mode-Thread und ist dort sicher. Alles was ab (einschließlich) dem Syscall passiert liegt auf dem Kernel-Mode-Stack und das sind in erster Linie die Register die die CPU automatisch dort hin legt und jene die Du dazu legst damit der Kernel selber erst mal ein paar Register zur Verfügung hat. Alles was im Syscall danach passiert ist doch für den User-Mode-Thread völlig ohne Bedeutung, solange seine paar Register wieder den richtigen Inhalt haben wenn dieser vom Scheduler wieder aktiviert wird. Auf meiner CPU wird es sogar so sein der der Kernel-Mode-Stack-Pointer weg ist sobald ein IRET gemacht wird, das bietet mir den Vorteil das ich nicht den ganzen Aufrufbaum wieder zurück gehen muss, ich kann also im Schduler (egal von wem er aufgerufen wurde) einfach die Register des neuen Threads laden und ein IRET machen und schon ist der Kernel-Mode-Stack verschwunden (die Daten im Speicher sind natürlich noch dort aber der Stack-Pointer ist weg, er wird nicht gesichert). Jedes mal wenn meine CPU in einen System-Mode wechselt hat sie einen jungfäulichen Kernel-Mode-Stack zur Verfügung.

Wie kommst du auf 10 Takte bei 128byte? Zumal es mehr als nur die 16 Register sind, da wäre noch das RIP und das RFlags Register.
Okay dann sind es eben 144 Bytes aber was glaubst Du wie lange das bisschen zu kopieren dauert wenn der Pfad in den L1-Cache 256 Bit breit ist? Auf heutigen x86-CPUs braucht ein "rep movsd" längst nicht mehr 1 Takt pro DWord wenn alles im L1-Cache ist (passendes Alignment usw. vorausgesetzt aber dem kann der Kernel-Programmierer ja nachhelfen). Nebst dessen das Du bei x86-64 ja nicht alle Register auf dem Stack in Sicherheit bringen musst um wenigstens etwas arbeiten zu können.

Ok, wenn du das so machen willst, darfst du mir nie mehr damit kommen, das man nicht alzu sehr von gewohnten Weg abweichen sollte ;)
Einverstanden. ;)
Wobei ich trotzdem der Meinung bin das mein Konzept (auf der libc-Ebene) weniger vom üblichen Weg abweicht als wenn read einen Pointer zurück gibt.

aber wenn es dann zu sachen kommen die normaler Weise nicht blockieren wenn keine Nachricht da ist, sondern weiter arbeiten, da stelle ich es mir dann schon schwierig vor (Bsp. Spiele, die warten ja auch nicht bis die Taste gedrückt wurde, sondern es wird geguckt, wurde eine gedrückt und dann wird der nächste Frame berechnet).
Hä, genau das ist doch der Vorteil meiner Methode, nichts muss blockiert oder unterbrochen werden nur weil eine Message empfangen wurde (es wird ja ein neuer Thread erstellt) oder eben keine Message kommt (es soll ja kein aktives Abholen geben).

Das klingt für mich wie nach dem Kontept eines Garbage-Collectors.
Was hat ein Speicher-Pool mit nem Garbage-Collector zu tun? Ich würde eher sagen das mein Pool einem Slab-Allocator etwas ähnelt. Und nur weil Du Das Prinzip des Garbage-Collectors nicht magst (oder möglicherweise gar nicht verstehst) heißt das nicht das es nicht funktioniert.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #49 am: 25. September 2010, 16:44 »
Zitat von: erik
Ich sage es nur ungern aber falls Du dann später feststellen solltest das Dein Konzept für asynchrones I/O überhaupt nichts taugt wirst Du Dich ziemlich ärgern weil Du dann vor der Entscheidung stehst entweder ein zweites paralleles IPC-System zu implementieren oder alle Programme auf ein neues IPC-System umzustellen oder Dich mit dem Ärgernis irgendwie zu arrangieren oder es einfach ganz bleiben zu lassen. Ich hab es schon mal geschrieben und mache das gerne wieder: solche Arten von Entscheidungen sind ziemlich eklig.
Das sehe ich noch nicht so. Denn ich könnte auch sagen, jede Nachricht wird von einem Thread abgeholt und dann wird ein neuer Thread erstellt der sie bearbeitet (worker Thread). Das ist dann deinen Popup-Threads sehr ähnlich nur das es keinen Pool gibt, den man aber auch schaffen könnte.
Also das Umstellen auf A-I/O sollte nicht das Problem darstellen.

Zitat von: erik
Von einem unterbrechbaren Kernel bin ich allerdings nicht ausgegangen, wenn Du sowas willst hast Du sicher noch ganz andere Probleme als nur die Zuordnung von Stacks. Wer hat den hier einen unterbrechbaren Kernel?
Welche anderen Probleme? Ich habe einen unterbrechbaren Kernel und jeder der nen Monolithischen Kernel schreibt, sollte bitte auch einen haben. Sicher ist das bei einem Mikrokernel nicht mehr so schlimm, aber da wären wir wieder bei dem Thema wie Mikro ein Kernel ist.

Zitat von: erik
Das mag auf x86 ja zutreffen aber auf viele andere CPUs trifft das nicht zu, z.B. ARM. Diese CPUs haben einen gewissen Teil der Register doppelt, einmal für den User-Mode und einmal für den System-Mode, und schalten beim Mode-Wechsel einfach um. Im System-Mode stehen einem gleich ein paar Register zur Verfügung die auch bereits sinnvolle Werte für den Kernel enthalten (z.B. einen Pointer zum Thread-Descriptor des gerade laufenden Threads) und das sichern der paar User-Mode-Register ist damit nicht viel mehr als ein einfacher Speicher-Zugriff.
Das ist z.B. ein Grund warum ich sage, das mein Kernel nicht portabel sein muss, sondern das ich dann für jede neue Architektur lieber einen neuen schreibe. Nur die meisten werden sich für den Anfang einfach auf x86 beschränken und ihn (Kernel) höchstens portabel halten.

Zitat von: erik
Alles was ab (einschließlich) dem Syscall passiert liegt auf dem Kernel-Mode-Stack und das sind in erster Linie die Register die die CPU automatisch dort hin legt und jene die Du dazu legst damit der Kernel selber erst mal ein paar Register zur Verfügung hat.
Naja, also zu erst hätte ich auf x86 gerne alle Register zur Verfügung (da sind so wenige und der Compiler will es so) und dann besteht der Kernel Code auch nur aus C-Code (oder welcher Sprache auch immer) und der benötigt einen Stack (den Kernel-Mode-Stack).
Da mein Kernel unterbrechbar ist, kann mitten in der Bearbeitung des Syscalls ein Interrupt kommen (kann auch ein Gerät sein) der den Scheduler aufruft, d.h. ich müsste jetzt den gesamten Stack in den Thread-Descriptor kopieren (weil ich die Daten die da stehen ja brauche um später den Thread fortsetzen zu können), aber warum sollte ich das tun, wenn die doch schon auf dem Stack liegen?

Was mir da gerade so auffällt ist wieso oft von Kernel-Threads und User-Threads gesprochen wird. Für mich war das immer das gleiche, aber es scheint wohl doch für viele einen Unterschied zu machen ob der Kernel im Kernel-Mode auch Threads haben kann (was für mich immer außer Frage stand).

Zitat von: erik
Wobei ich trotzdem der Meinung bin das mein Konzept (auf der libc-Ebene) weniger vom üblichen Weg abweicht als wenn read einen Pointer zurück gibt.
Irgendwie kommst du darüber nicht hinweg ;)

Was mir (und bestimmt auch uns was die Diskussion betrifft) weiterhelfen würde, wäre wenn jemand nen Mikrokernel kennt (der aber auch wirklich fast alles bis alles im UserSpace macht) wo der Source verfügbar ist. Damit man einfach mal gucken könnte wie andere sowas gelöst haben.

Zitat von: erik
Hä, genau das ist doch der Vorteil meiner Methode, nichts muss blockiert oder unterbrochen werden nur weil eine Message empfangen wurde (es wird ja ein neuer Thread erstellt) oder eben keine Message kommt (es soll ja kein aktives Abholen geben).
Das Problem sehe ich halt darin, das du also irgendetwas haben müsste wo du dann hinschreibst das eine Taste gedrückt wurde, wo dann der eigentliche worker Thread dann nachguckt (könnte z.B. ne Art Bitmap sein, wo du den KeyCode setzt oder resetest, was dann per cmpxchg passieren müsste/sollte).
Mir geht es nur darum, dass das auch nicht gerade der gewohnte Weg ist und man ganz schön umdenken muss, insbesondere in richtig Multithreading und Synchronisation.

Zitat von: erik
Was hat ein Speicher-Pool mit nem Garbage-Collector zu tun? Ich würde eher sagen das mein Pool einem Slab-Allocator etwas ähnelt. Und nur weil Du Das Prinzip des Garbage-Collectors nicht magst (oder möglicherweise gar nicht verstehst) heißt das nicht das es nicht funktioniert.
Ich habe an sich gegen den Garbage-Collector nichts, aber die Umsetzungen die ich bisher erlebt haben sprechen nicht gerade für ihn (das selbe Problem hat doch auch ein Mikrokernel).
Ich wollte damit nur sagen, das ein in der Theorie gutes Konzept erstmal in die Praxis vernünftig umgesetzt werden muss und genau daran hapert es meistens.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #50 am: 25. September 2010, 17:09 »
Minix ist ein Mikrokernel, Sourcen sind frei verfügbar. Allerdings gibt es (zumindest bei Minix 2) kein Threading, Minix 3 hab ich mich nie befasst - setzt 32-Bit-Maschinen voraus.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #51 am: 25. September 2010, 17:59 »
Was ich nach einer kurzen Lesetour durch die Dokumentation sagen kann, ist das Minix lieber einmal zu viel kopiert als einmal zu wenig.
Und das der VFS-Service einen SharedMemory-Bereich an den HDD-Treiber schickt wo er die Daten reinschreiben soll oder drauß lesen soll ;)

Dann werde ich das wohl doch so machen müssen. Damit würde dann ja auch der Sinn eines "Speicher verschicken"-Syscalls wegfallen (ein Glück das ich momentan eh nicht Programmieren kann ;) ).

Edit::

Den Vorteil meiner Methode sehe ich darin, das weniger Syscalls nötig wären:

Bei mir: Speicher allokieren, Speicher versenden
Ansonsten: Speicher allokieren, SharedMem-Bereich erstellen, SharedMem Erlaubnis an anderen Prozess geben, SharedMem beim Empfänger mappen, SharedMem beim Emfpänger unmappen

Das wäre sogar eine ganz schöne Ersparnis!

Edit::

Eigentlich erstellt man doch einen SharedMem-Bereich eh nur dazu, das man den Speicher mit anderen Prozessen "teilen" kann, oder?
Dann wäre es aus performace Sicht nämlich besser nen Syscall zu machen, wo man eine Nachricht verschickt, ein SharedMem Bereich erstellt wird und die ID in die Msg (nicht sichtbarer Teil) gespeichert wird und beim Empfangen wird der SharedMem gleich gemappt und die Adresse wird mit der Msg übergeben.

Wie klingt diese Idee?
« Letzte Änderung: 25. September 2010, 18:10 von FlashBurn »

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #52 am: 25. September 2010, 19:27 »
Minix stammt aus einer Zeit, als man ein billiges Unix für 8086/80286er brauchte. Zero-Copy war da noch kein Thema. ;-)

Du vergisst die Messages vom VFS-Treiber zum HDD-Treiber ("lies mal block xy") und umgekehrt ("bin fertig"). Wobei ich nicht weiß, ob/wie ich da Shared Memory einsetzen würde. Aber ich hab da auch keinen anderen (guten) Ansatz.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #53 am: 25. September 2010, 19:54 »
Zitat von: svenska
Minix stammt aus einer Zeit, als man ein billiges Unix für 8086/80286er brauchte. Zero-Copy war da noch kein Thema.
Ich habe mir Minix3 angesehen.

Zitat von: svenska
Du vergisst die Messages vom VFS-Treiber zum HDD-Treiber ("lies mal block xy") und umgekehrt ("bin fertig"). Wobei ich nicht weiß, ob/wie ich da Shared Memory einsetzen würde. Aber ich hab da auch keinen anderen (guten) Ansatz.
Das habe ich weggelassen, weil die sich nicht vermeiden lassen, die anderen Syscalls ist im Endeffekt das einzige wo du etwas verbessern kannst indem du sie einsparst, aber die Nachricht an sich kannst du ja schlecht einsparen ;)

Ich habe mir das so vorgestellt, das du Pages allokiert hast wo die Daten aus Block xy reinsollen und eine Anfrage an den HDD-Treiber sendest das er Block xy in diese Pages schreiben soll. Die Pages werden als SharedMem mit der Nachricht/Anfrage an den HDD-Treiber übergeben.
Dieser läd Block xy in die Pages und sendet eine Antwort zurück das alles i.O. war.

Ich wollte den Syscall so gestallten, das du über ein Flag entscheiden kannst, ob der SharedMem versendet werden soll (er bleibt auch bei dir), ob er geunmappt werden soll (weil du alles gemacht hast und ihn nicht mehr benötigst) oder ob du ihn ganz versenden willst (er wird bei dir geunmappt).

Damit sollte ich dann alle Fälle abgedeckt haben und ich spare mir Syscalls. Im Endeffekt könnte man sogar sagen, dass ich damit ne Art dynamic-size Msgs habe ;) Nur halt auf ein vielfaches der Pagegröße beschränkt.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #54 am: 26. September 2010, 11:04 »
Hallo,


Denn ich könnte auch sagen, jede Nachricht wird von einem Thread abgeholt und dann wird ein neuer Thread erstellt der sie bearbeitet (worker Thread). Das ist dann deinen Popup-Threads sehr ähnlich
Ja, die Ähnlichkeit ist verblüffend, aber im Punkt Effizienz kommt das noch nicht an mein Konzept ran. Bei Dir wären pro Message 2 Syscalls zum Abholen nötig, GetMyNextMessage und CreateThread, bei mir sind es 0 Syscalls zum Abholen einer Message. Durch das aktive Abholen hast Du aber wieder das Problem der unbekannten Message-Größe, falls Du dynamische Größen benutzen möchtest. Das mit dem Pool ist nur ne kleine Beschleunigung, das Konzept funktioniert auch ohne und im ersten Anlauf werde ich das sicher auch ohne Pool programmieren.

Also das Umstellen auf A-I/O sollte nicht das Problem darstellen.
Was macht Dich da so sicher?

Ich habe einen unterbrechbaren Kernel und jeder der nen Monolithischen Kernel schreibt, sollte bitte auch einen haben.
Du programmierst an einen monolithischen Kernel? Das ist ja eine völlig neue Information! Warum diskutieren wir dann so intensiv über Dein IPC-Konzept? Bei einem monolithischen Kernel reichen ganz normale Calls um vom VFS zum HDD-Treiber zu kommen und die User-Mode-Programme nutzen klassische Syscalls für alles was sie brauchen.

Sicher ist das bei einem Mikrokernel nicht mehr so schlimm, aber da wären wir wieder bei dem Thema wie Mikro ein Kernel ist.
Klar ist schon deswegen ein Micro-Kernel leichter zu programmieren weil er eben nicht unterbrechbar sein muss. Wenn in dem Kernel keine der normalen Dienste (wie VFS, TCP/UDP/IP, Treiber, Geräte-Management usw.) drin sind dann entsteht auch keinerlei Nachteil wenn er nicht unterbrechbar ist.

Zitat von: erik
Das mag auf x86 ja zutreffen aber auf viele andere CPUs trifft das nicht zu, z.B. ARM. Diese CPUs haben einen gewissen Teil der Register doppelt, einmal für den User-Mode und einmal für den System-Mode, und schalten beim Mode-Wechsel einfach um. Im System-Mode stehen einem gleich ein paar Register zur Verfügung die auch bereits sinnvolle Werte für den Kernel enthalten (z.B. einen Pointer zum Thread-Descriptor des gerade laufenden Threads) und das sichern der paar User-Mode-Register ist damit nicht viel mehr als ein einfacher Speicher-Zugriff.
Das ist z.B. ein Grund warum ich sage, das mein Kernel nicht portabel sein muss, sondern das ich dann für jede neue Architektur lieber einen neuen schreibe.
Was? Wegen solcher Kleinigkeiten (und das sichern der Register ist wirklich nur ein winziges Detail) willst Du gleich einen neuen Kernel programmieren? Und das auch noch bei einem Monolithen?

Was mir da gerade so auffällt ist wieso oft von Kernel-Threads und User-Threads gesprochen wird. Für mich war das immer das gleiche, aber es scheint wohl doch für viele einen Unterschied zu machen ob der Kernel im Kernel-Mode auch Threads haben kann (was für mich immer außer Frage stand).
Bei Kernel-Threads stellt sich eben das Problem in welchen Speicher-Kontext sie sind, eigentlich müssten sie ja in allen Kontexten drin sein, so wie der ganze Kernel ja auch. Außerdem müssen diese Threads auch vom Scheduler berücksichtigt werden und der muss beim reinspringen in so einen Kernel-Thread ja keinen Kontext-Switch machen sondern nur den Stack umschalten.

Zitat von: erik
als wenn read einen Pointer zurück gibt.
Irgendwie kommst du darüber nicht hinweg ;)
Ja, da komme ich wirklich nicht so einfach drüber hinweg. Es ist eben so das diese Variante nicht nur vom üblichen Weg abweicht sondern sie ist auch ziemlich unpraktisch (vor allem für normale Programme).

Was mir (und bestimmt auch uns was die Diskussion betrifft) weiterhelfen würde, wäre wenn jemand nen Mikrokernel kennt (der aber auch wirklich fast alles bis alles im UserSpace macht) wo der Source verfügbar ist. Damit man einfach mal gucken könnte wie andere sowas gelöst haben.
Da würde ich mal QNX empfehlen, die rühmen sich u.a. damit das der Kernel niemals ein Eigenleben entwickelt sondern nur per Kommando (da gibt es nur Syscalls, Exceptions und HW-Interrupts) aktiv wird und so schnell wie möglich wieder fertig ist. Klar gibt es unter den Syscals einige die länger dauern, z.B. sowas wie AllocPages oder CreateProcess, aber es ist bei QNX recht gut dokumentiert welche Syscalls in deterministischer Zeit fertig sind und welche nicht. Außerdem soll er POSIX-konform sein.

Zitat von: erik
Hä, genau das ist doch der Vorteil meiner Methode, nichts muss blockiert oder unterbrochen werden nur weil eine Message empfangen wurde (es wird ja ein neuer Thread erstellt) oder eben keine Message kommt (es soll ja kein aktives Abholen geben).
Das Problem sehe ich halt darin, das du also irgendetwas haben müsste wo du dann hinschreibst das eine Taste gedrückt wurde,
Wozu? Der PopUp-Thread kann doch gleich die passende Arbeit selber erledigen, da besteht doch kein Grund zur Eile, schließlich dürfen pro Message-Target auch mehrere PopUp-Threads aktiv sein.

und man ganz schön umdenken muss, insbesondere in richtig Multithreading und Synchronisation.
In diese Richtung müssen die Programmierer von Heute doch eh umdenken, schließlich wird man zukünftige CPUs kaum noch mit nem Single-Thread-Programm ausreizen können.


Eigentlich erstellt man doch einen SharedMem-Bereich eh nur dazu, das man den Speicher mit anderen Prozessen "teilen" kann, oder?
Dann wäre es aus performace Sicht nämlich besser nen Syscall zu machen, wo man eine Nachricht verschickt, ein SharedMem Bereich erstellt wird und die ID in die Msg (nicht sichtbarer Teil) gespeichert wird und beim Empfangen wird der SharedMem gleich gemappt und die Adresse wird mit der Msg übergeben.

Wie klingt diese Idee?
Die klingt so wie meine Idee! Wenn das weiter so geht muss ich noch Lizenz-Gebühren verlangen. ;)
Schau Dir noch mal meine Syscalls an http://forum.lowlevel.eu/index.php?topic=2433, das ist zwar nicht mehr ganz aktuell aber das Prinzip ist gut sichtbar.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #55 am: 26. September 2010, 11:52 »
Zitat von: erik
Was macht Dich da so sicher?
Weil ich noch keinerlei Erfahrung mit A-I/O habe  :wink:

Zitat von: erik
Du programmierst an einen monolithischen Kernel?
Um Gottes Willen, nein!!!!

Zitat von: erik
Klar ist schon deswegen ein Micro-Kernel leichter zu programmieren weil er eben nicht unterbrechbar sein muss. Wenn in dem Kernel keine der normalen Dienste (wie VFS, TCP/UDP/IP, Treiber, Geräte-Management usw.) drin sind dann entsteht auch keinerlei Nachteil wenn er nicht unterbrechbar ist.
Naja, der Kernel an sich ist leichter zu programmieren, aber der Rest des OS ist in vielen Sachen schwieriger (Huhn-Ei-Problem).
Wenn man, wie ich, die Speicherverwaltung im Kernel macht, dann sollte der Kernel schon unterbrechbar sein, zumal wo ist denn bitte das Problem damit? Wenn du nachher im UserMode dich eh mit Synchronisation befassen musst, kannst du das doch auch schon im Kernel machen, zumal ich finde das es dort einfacher ist.
Wenn ich jetzt mal von einem 4 CPU System ausgehe und der Prozess 4 Threads hat die alle zur gleichen Zeit laufen und alle dummerweise auch noch zur gleichen Zeit nen Syscall machen, dass sie mehr Speicher brauchen. Dann möchte ich (wie du das siehst weis ich ja ;) ) das mein Kernel unterbrechbar ist, weil ich nicht alle 4 Threads gleichzeitig in meinen Datenstrukturen rumspielen lassen und wenn dann der Kernel nicht unterbreachbar ist, dann kann das schon mal ne "Weile" dauern bis auch der letzte Thread seinen Speicher hat.

Zitat von: erik
Was? Wegen solcher Kleinigkeiten (und das sichern der Register ist wirklich nur ein winziges Detail) willst Du gleich einen neuen Kernel programmieren?
Das mit dem Monolithen habe ich mal weggelassen ;)

Es gibt bestimmt noch andere Unterschiede zw ARM und x86! Mir geht es darum, das man nicht immer alles versuchen sollte gleich zu machen, sondern jede Architektur sollte bestmöglichst ausgenutzt werden und gerade bei einem Mikrokernel sollte der Aufwand nicht so groß sein.
Ich meine mein PMM und VMM kann ich ja beibehalten, aber den Rest würde ich sehr großzügig neu schreiben wollen.

Zitat von: erik
Bei Kernel-Threads stellt sich eben das Problem in welchen Speicher-Kontext sie sind, eigentlich müssten sie ja in allen Kontexten drin sein, so wie der ganze Kernel ja auch. Außerdem müssen diese Threads auch vom Scheduler berücksichtigt werden und der muss beim reinspringen in so einen Kernel-Thread ja keinen Kontext-Switch machen sondern nur den Stack umschalten.
Naja, wenn ich einen reinen Kernel-Thread habe (der also auch nie in den UserMode geht) dann tausche ich bei mir auch nur den Stack aus. Ist es aber ein Thread der sich nur gerade im KernelMode befindet tausche ich auch noch CR3 aus.
Ich sehe da wie gesagt nicht so das Problem oder die Schwierigkeit.

Zitat von: erik
Da würde ich mal QNX empfehlen, die rühmen sich u.a. damit das der Kernel niemals ein Eigenleben entwickelt sondern nur per Kommando (da gibt es nur Syscalls, Exceptions und HW-Interrupts) aktiv wird und so schnell wie möglich wieder fertig ist. Klar gibt es unter den Syscals einige die länger dauern, z.B. sowas wie AllocPages oder CreateProcess, aber es ist bei QNX recht gut dokumentiert welche Syscalls in deterministischer Zeit fertig sind und welche nicht. Außerdem soll er POSIX-konform sein.
Der Source von QNX war ja mal verfügbar, aber ist er das immernoch wimre war da doch was, dass der nur noch für zahlende Kunden zu sehen ist?

Zitat von: erik
Wozu? Der PopUp-Thread kann doch gleich die passende Arbeit selber erledigen, da besteht doch kein Grund zur Eile, schließlich dürfen pro Message-Target auch mehrere PopUp-Threads aktiv sein.
Mit irgendwo hinschreiben meinte ich halt das was ich dann mit der Bitmap geschrieben habe, denn du kannst ja schlecht nen neuen Framen rendern, jedes Mal wenn ein Popup-Thread ne Nachricht bearbeitet, das wäre 1. bestimmt nicht einfach und 2. könnte es dann wohl ne ganze Weile dauern bis mal wieder ein neuer Frame berechnet wird, wenn der Nutzer nichts macht.

Zitat von: erik
Die klingt so wie meine Idee! Wenn das weiter so geht muss ich noch Lizenz-Gebühren verlangen.
Zum Glück ist es doch nicht das gleiche ;)

Was mir aber dann bei meinem Konzept wieder auffällt. Es ist egal ob der Client den Speicher bei der Anfrage mitsendet oder ob er ihn mit der Antwort erhält.
Im Code sollte das nicht viel anders aussehen. Man muss nicht mal großartig umdenken, deswegen frage ich mich halt wo das große Problem damit ist (das finde ich vielleicht dann wenn ich dann irgendwann mal Programme portiere oder schon bei meiner libc).

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #56 am: 26. September 2010, 20:52 »
Hallo,


Zitat von: erik
Du programmierst an einen monolithischen Kernel?
Um Gottes Willen, nein!!!!
Da bin ich aber beruhigt, ich befürchtete schon Du bist den Verlockungen der dunklen Seite erlegen. ;)

aber der Rest des OS ist in vielen Sachen schwieriger (Huhn-Ei-Problem).
Also da sehe ich kaum Probleme (kommt aber sicher noch wenn ich meine Ideen in die Realität überführen muss), es erfordert nur einiges an fertiger Infrastruktur bis ein printf("Hello Wordl\n"); korrekt über IPC funktioniert als nur mit nem simplen Syscall.

Wenn man, wie ich, die Speicherverwaltung im Kernel macht, dann sollte der Kernel schon unterbrechbar sein, zumal wo ist denn bitte das Problem damit?
Wieso muss der Kernel deswegen unterbrechbar sein? Klar gibt es Kollisionen wenn mehrere Prozesse gleichzeitig Syscalls aufrufen die sich gegenseitig behindern könnten aber wenn man die Stellen wo serialisiert werden muss auf ein Minimum beschränkt (und das ist IMHO auch beim Speicher-Management möglich) dürfte das kein Problem werden.

Wenn du nachher im UserMode dich eh mit Synchronisation befassen musst, kannst du das doch auch schon im Kernel machen, zumal ich finde das es dort einfacher ist.
Abgesehen davon das die Synchronisation unabhängig von CPU-Modus immer die selben Konzepte benutzt haben Synchronisation im Kernel und im User-Mode nichts miteinander zu tun. Das sind zwar identische Mechanismen die sich aber nicht berühren.

Wenn ich jetzt mal von einem 4 CPU System ausgehe und der Prozess 4 Threads hat die alle zur gleichen Zeit laufen und alle dummerweise auch noch zur gleichen Zeit nen Syscall machen, dass sie mehr Speicher brauchen.
Das Beispiel finde ich doof, die Threads in einem Prozess sollten schon in dem einem malloc serialisiert werden, aber ich verstehe was Du meinst. Trotzdem bin ich nicht der Meinung das dies ein ernstes Problem darstellt, es soll auch bei mir möglich sein das ein Thread zwar einen normalen Syscall macht aber dann blockiert wird und der Scheduler einen anderen Prozess aufruft und der Thread erst später fortgesetzt wird wenn sein Wunsch erfüllt werden konnte (das Anfordern von Speicher wird aber wohl nicht in diese Kategorie fallen).

Es gibt bestimmt noch andere Unterschiede zw ARM und x86!
Da gibt es nichts was der Compiler nicht wegabstrahieren könnte. Alle CPUs führen im wesentlichen Rechenbefehle aus und greifen auf eindimensionalen Speicher zu. ;)
Das einzigste was in einem Kernel für jede CPU individuell sein muss sind die paar unvermeidlichen Assembler-Stückchen und ein paar Strukturen (für allerlei Descriptoren und die gesicherten Register).

Mir geht es darum, das man nicht immer alles versuchen sollte gleich zu machen, sondern jede Architektur sollte bestmöglichst ausgenutzt werden und gerade bei einem Mikrokernel sollte der Aufwand nicht so groß sein.
Ich meine mein PMM und VMM kann ich ja beibehalten, aber den Rest würde ich sehr großzügig neu schreiben wollen.
Ich behaupte mal der erzielbare Performancevorteil dadurch liegt noch unterhalb der Messbarkeitsgrenze, dafür wäre mir der Aufwand einfach viel zu groß.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #57 am: 26. September 2010, 21:09 »
Zitat von: erik
Also da sehe ich kaum Probleme (kommt aber sicher noch wenn ich meine Ideen in die Realität überführen muss), es erfordert nur einiges an fertiger Infrastruktur bis ein printf("Hello Wordl\n"); korrekt über IPC funktioniert als nur mit nem simplen Syscall.
Mir ging es noch nicht mal um so ein Bsp., sondern darum dein OS erstmal lauffähig zu bekommen. Ich meine bei einem monolithen wo auch noch alle Treiber in den Kernel reinkompiliert sind, brauchst du nur den Kernel laden und alles läuft (vereinfacht). Bei nem Mikrokernel, müssen Treiber und Server samt Kernel geladen werden und dann gibt es auch noch Abhängigkeiten bzw. Sonderfälle, weil wie "läd" man eine Datei ohne das der nötige Server dafür geladen wurde (bzw. wie lädst du die Library die der VFS-Service benötigt)?
Sowas in der Richtig, nicht solch einfache Sache wie IPC ;)

Zitat von: erik
Wieso muss der Kernel deswegen unterbrechbar sein?
Wieso denn nicht? Ich weiß noch immer nicht, was daran so schlimm sein soll (oder schwierig).

Zitat von: erik
Abgesehen davon das die Synchronisation unabhängig von CPU-Modus immer die selben Konzepte benutzt haben Synchronisation im Kernel und im User-Mode nichts miteinander zu tun.
Mir ging es darum, das sich eine Semaphore (als Bsp.) effektiver/effizienter im KernelMode implementieren lässt, da man dort auch die Ints ausschalten kann (ist einfach effizienter als den Scheduler aufzurufen bzw. von diesem unterbrochen zu werden).

Zitat von: erik
Da gibt es nichts was der Compiler nicht wegabstrahieren könnte. Alle CPUs führen im wesentlichen Rechenbefehle aus und greifen auf eindimensionalen Speicher zu. Wink
Das einzigste was in einem Kernel für jede CPU individuell sein muss sind die paar unvermeidlichen Assembler-Stückchen und ein paar Strukturen (für allerlei Descriptoren und die gesicherten Register).
Da wären in meinem Fall die Timer, die FPU Behandlung (besonders das lazy Laden/Speichern des FPU Contextes wie ich es auf x86 mache, dürfte es so nicht auf ARM geben), IRQ-Management, Paging und mir fallen bestimmt noch mehr Sachen ein, wenn ich mich mal mit ARM beschäftigen würde.

Ich finde es auch einfach sauberer und übersichtlicher einen neuen Kernel zu schreiben als alles wegzuabstrahieren (der Code der trotzdem in allen Kernel verwendet wird ist natürlich ne andere Sache/Problem).

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #58 am: 27. September 2010, 12:05 »
Hallo,


sondern darum dein OS erstmal lauffähig zu bekommen. Ich meine bei einem monolithen wo auch noch alle Treiber in den Kernel reinkompiliert sind, brauchst du nur den Kernel laden und alles läuft (vereinfacht). Bei nem Mikrokernel, müssen Treiber und Server samt Kernel geladen werden und dann gibt es auch noch Abhängigkeiten bzw. Sonderfälle, weil wie "läd" man eine Datei ohne das der nötige Server dafür geladen wurde (bzw. wie lädst du die Library die der VFS-Service benötigt)?
Das ist schon ein kniffliges Problem aber das sollte auf dem PC ein guter Bootloader problemlos hinbekommen. Da hab ich es auf meiner Plattform schon deutlich schwieriger (ganz ohne BIOS o.ä.). Bei mir soll ein primitiver Initial-Boot-Loader einfach direkt den Code im Boot-Image des OS anspringen (der Boot-Flash wird direkt in den normalen Speicher-Adressraum eingeblendet). In diesem Boot-Image ist dann etwas Code, das Kernel-Image und 3 Executables (idle, exec und init) drin. Der Code initialisiert den RAM, entpackt den Kernel dort hinein und bereitet alle Datenstrukturen des Kernels vor, anschließend werden die 3 Executables ebenfalls in den RAM entpackt aber so das daraus 3 fertige Prozesse entstehen (bei idle werden sogar gleich mehrere Threads erstellt, so viele wie CPUs da sind) also mit allen zugehörigen Verwaltungsstrukturen im Kernel. Danach werden alle CPUs in Betrieb genommen und die springen alle gleichzeitig in den User-Mode in jeweils einen Thread von idle, bis hier hin wurde noch nicht ein Befehl des eigentlichen Kernel ausgeführt. Erst wenn die Zeitscheiben der idle-Threads abgelaufen sind kommt dann der Kernel an die Reihe, er ist zu diesem Zeitpunkt bereits voll einsatzfähig (das OS-Image macht quasi eine OS-Lebendgeburt) und der Bootvorgang ist aus seiner Sicht damit abgeschlossen. Erst der init-prozess führt dann den richtigen Boot-Vorgang durch, dazu soll init eine interne primitive ROM-Disk enthalten in der die Programme für PCI, VFS und der Driver-Loader (welcher seinerseits wieder ein paar Basis-Treiber wie SATA und ext2 enthält) drin sind. Erst der PCI-Prozess enumeriert das PCI-System und findet alle Devices, weist ihnen Ressourcen zu und holt vom Driver-Loader die zugehörigen Treiber (wobei sicher einige Devices erst mal leer ausgehen und der PCI-Prozess es später, wenn alle Dateisysteme gemountet sind und der Driver-Loader auch echten HDD-Zugriff benutzen kann, noch mal probiert). Bis init dann eine Shell startet ist einiges zu tun aber das sind aus Kernel-Sicht alles nur normale User-Mode-Aktivitäten, das eigentliche OS läuft also bereits bevor auch nur das erste PCI-Gerät gefunden ist. Der Kernel selber verwaltet nur RAM, CPUs und ein paar elementare Geräte wie den IRQ-Controller und den Timer. Dieser Vorgang ist sicher nicht ganz ohne aber ein Henne-Ei-Problem kann ich da nicht erkennen, das einzigste was etwas doof ist ist dass das OS-Image recht groß ausfällt weil ein Haufen Programme mit drin stecken müssen, aber ein Linux-Kernel-Image mit Init-RAM-Disk ist auch nicht besonders klein.

Ich weiß noch immer nicht, was daran so schlimm sein soll (oder schwierig).
[....]
Mir ging es darum, das sich eine Semaphore (als Bsp.) effektiver/effizienter im KernelMode implementieren lässt, da man dort auch die Ints ausschalten kann (ist einfach effizienter als den Scheduler aufzurufen bzw. von diesem unterbrochen zu werden).
Also erst mal muss man nicht immer gleich die IRQs abschalten, schon weil der IRQ-Handler in einem Micro-Kernel ja nichts weiter macht als eine Message an einen (unterbrechbaren) User-Mode-Treiber zu schicken. Und wenn doch mal ein Thread mitten in einer Critical-Section vom Scheduler unterbrochen werden sollte ist das bei User-Mode-Programmen nicht halb so kompliziert wie im Kernel. Wenn man den Kernel unterbrechbar macht dann muss man anfangen Dinge wie kmalloc dagegen abzusichern und das kann in ziemlich viel Arbeit ausarten. Frage ruhig mal taljeth wie viel Arbeit es war dafür zu sorgen das in einem tyndur-Programm auch ein Signal-Handler malloc u.ä. benutzen kann. Im User-Mode ist das alles noch mit reichlich Fleiß zu meistern aber im Kernel-Mode sind IMHO doch einige zusätzliche Schwierigkeiten zu erwarten. Aber ich will Dich nicht von Deinen Plänen abhalten, implementiere ruhig einen unterbrechbaren Kernel und erzähle uns hinterher wie einfach das war.

Wenn es Dir um das Abschalten der IRQs im User-Mode geht dann programmiere doch ein OS für eine CPU bei der das ohne Probleme möglich ist. Auf z.B. meiner CPU soll es möglich sein das ein User-Mode-Thread die IRQs für eine kurze/begrenzte Zeit abschalten kann (ohne Exception o.ä.) um zumindest kleine Code-Abschnitte, die atomar ausgeführt werden sollen, ohne externe Unterbrechungen durchführen zu können (selbst wenn die Zeitscheibe in diesem Abschnitt abläuft soll das ignoriert werden bis dieser Abschnitt fertig ist). Natürlich sollte der User-Mode-Code in so einem Abschnitt keine Exceptions verursachen oder Syscalls benutzen. Das wird mir zumindest ermöglichen viele kleine Critical-Sections sehr effizient und völlig ohne Hilfe des Kernels zu implementieren.


nicht solch einfache Sache wie IPC ;)
Seit wann ist IPC einfach? Bei den vielen Fragen und Unklarheiten die in diesem Forum dazu diskutiert werden (und das nicht nur von uns beiden) scheint das eine recht komplizierte Angelegenheit zu sein. ;)

Da wären in meinem Fall die Timer
Timer messen die Zeit und sind oft in der Lage zu bestimmten Zeitpunkten einen IRQ zu generieren. Wenn man das hinter einen vernünftigen/flexiblen Interface verbirgt dann gibt es da kein Problem.

die FPU Behandlung (besonders das lazy Laden/Speichern des FPU Contextes wie ich es auf x86 mache, dürfte es so nicht auf ARM geben)
Auch bei ARM ist die FPU ein Co-Prozessor aber ob es dort dieses lazy Sichern/Laden des FPU-Kontextes gibt weiß ich jetzt auch nicht. Aber trotzdem sehe ich hier kein Problem, man muss halt einen entsprechenden Exception-Handler implementieren den es auf anderen Plattformen einfach nicht gibt. Wie willst du das eigentlich für Kernel-Threads realisieren oder dürfen die keine FPU benutzen?

IRQ-Management
Was ist daran unterschiedlich? Alle IRQ-Controller bekommen IRQ-Signale und triggern einen zugehörigen IRQ-Handler an (wenn die CPU das gerade erlaubt), ob dabei der IRQ-Vector in einer IDT oder direkt im IRQ-Controller oder in SW gespeichert ist ist doch völlig unerheblich. Auch das ein EOI an den IRQ-Controller geschickt werden muss ist wimre überall gleich, der OS-Kernel muss einfach eine plattformspezifische EOI-Funktion anbieten die immer die gleiche Signatur haben kann.

Paging
Paging basiert auf allen mir bekannten CPUs auf Directorys (mit Page-Table-Walk in HW) oder der Page-Table-Walk muss gleich ganz in SW implementiert werden (was auch auf Directorys hinaus läuft). Die einzigste mir bekannte Ausnahme ist der Itanium und so sehr weicht der auch nicht von dem üblichen Weg ab.

und mir fallen bestimmt noch mehr Sachen ein, wenn ich mich mal mit ARM beschäftigen würde.
Nur zu, klar gibt es sehr viele Unterschiede aber das sind überwiegend Details. Die Grundkonzepte sind überall gleich, schon weil alle CPU-Hersteller (außer ich) wollen das die üblichen Betriebssysteme (Linux usw.) problemlos darauf laufen.

Ich finde es auch einfach sauberer und übersichtlicher einen neuen Kernel zu schreiben als alles wegzuabstrahieren (der Code der trotzdem in allen Kernel verwendet wird ist natürlich ne andere Sache/Problem).
Ja ist es nicht einfach nur eine Frage des Betrachtungsstandpunktes? Ob man nun von den Unterschieden ausgeht und die jeweils neu implementiert und dann das Gemeinsame hinzu fügt oder ob man vom Gemeinsamen ausgeht und jeweils die Unterschiede individuell hinzufügt kommt doch im Endeffekt auf das selbe Ergebnis hinaus. Die Frage ist wo Du die Grenze ziehst. Also in welchem prozentualem Verhältnis sollen der gemeinsame Code und der individuelle Code zueinander stehen?


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #59 am: 27. September 2010, 12:58 »
Zitat von: erik
... das eigentliche OS läuft also bereits bevor auch nur das erste PCI-Gerät gefunden ist.
Naja, das ist jetzt Definitionssache, was du als OS bezeichnest, was du vielleicht meinst ist der Kernel und das da weniger Aufwand als bei einem monolithen ist, dürfte klar sein (macht ja auch weniger).
Ich meine aber wenn du einen monolithen mit einem mikro vergleichst, dann kann man sagen dass das OS erst läuft wenn alle Server und Treiber laufen und bis dahin ist es dann doch um einiges komplexer bei einem Mikrokernel.

Zitat von: erik
Also erst mal muss man nicht immer gleich die IRQs abschalten, schon weil der IRQ-Handler in einem Micro-Kernel ja nichts weiter macht als eine Message an einen (unterbrechbaren) User-Mode-Treiber zu schicken.
Sollte er  :roll:

Ich muss mir bei mir nochwas schönes einfallen lassen, da ich auch Hardware habe (Timer) die ihr Arbeit nicht im UserMode sondern im KernelMode verrichten, aber eigentlich wollte ich das die richtigen Treiber dann wirklich nur ne Nachricht bekommen bzw. aufgeweckt werden.

Zitat von: erik
Aber ich will Dich nicht von Deinen Plänen abhalten, implementiere ruhig einen unterbrechbaren Kernel und erzähle uns hinterher wie einfach das war.
Also es war wirklich nicht schwierig ;) (mein Kernel war von Anfang an so geplant und das war mit das erste was ich implementiert habe).

Zitat von: erik
Wenn man den Kernel unterbrechbar macht dann muss man anfangen Dinge wie kmalloc dagegen abzusichern und das kann in ziemlich viel Arbeit ausarten.
Kann sein das ich da anders ticke, aber die Arbeit etwas Multithreading tauglich zu machen ist im Kernel- und UserMode eigentlich gleich. Denn du musst halt einfach die Datenstrukturen wo ein gleichzeitiger Zugriff nicht möglich ist durch irgendetwas schützen und das geht halt wesentlich effizienter wenn man dann die Ints abschalten kann (aber das hatten wir schonmal diskutiert).

Zitat von: erik
Wenn es Dir um das Abschalten der IRQs im User-Mode geht
Nein! Man sollte nichtmal daran denken, das ein UserMode Programm die Möglichkeit hat die Ints abzuschalten, das wäre genauso "dämlich" wie cooperatives-Multitasking. Sicher kann das auch funktionieren, aber einem warum sollte man es einem Programm so leicht machen den PC unbenutzbar zu machen!?

Zitat von: erik
Timer messen die Zeit und sind oft in der Lage zu bestimmten Zeitpunkten einen IRQ zu generieren. Wenn man das hinter einen vernünftigen/flexiblen Interface verbirgt dann gibt es da kein Problem.
Da mein Kernel schon einige Interfaces hat (weil er aus Modulen besteht die vom Loader zusammengefügt werden), weis ich das ein Interface zwar für einen Programmieren schön ist, aber wenn ich daran denke, wie viele sinnlose Funktionen (z.B. "uint32t foo(void bar) { return 0; }") ich dadurch habe. Dann kann ich gerne darauf verzichten.

Zitat von: erik
Wie willst du das eigentlich für Kernel-Threads realisieren oder dürfen die keine FPU benutzen?
Also erstens wenn man Kernel-Threads hat, dann kannst du die genauso benutzen wie User-Threads (es funktioniert bei mir also, theoretisch) und zweitens ist eine Regel von mir keine FPU im Kernel ;)

Zitat von: erik
Nur zu, klar gibt es sehr viele Unterschiede aber das sind überwiegend Details. Die Grundkonzepte sind überall gleich, schon weil alle CPU-Hersteller (außer ich) wollen das die üblichen Betriebssysteme (Linux usw.) problemlos darauf laufen.
Und genau damit habe ich ein Problem. Wenn man immer darauf bedacht ist kompatibel zu bleiben wird nichts richtiges neues mehr Entwickelt.
Wo wir wieder bei dem Henne-Ei-Problem wären. Wir halten an den ganzen alten Sachen fest und das hindert uns daran was richtig neues zu machen.

Zitat von: erik
Ja ist es nicht einfach nur eine Frage des Betrachtungsstandpunktes? Ob man nun von den Unterschieden ausgeht und die jeweils neu implementiert und dann das Gemeinsame hinzu fügt oder ob man vom Gemeinsamen ausgeht und jeweils die Unterschiede individuell hinzufügt kommt doch im Endeffekt auf das selbe Ergebnis hinaus. Die Frage ist wo Du die Grenze ziehst. Also in welchem prozentualem Verhältnis sollen der gemeinsame Code und der individuelle Code zueinander stehen?
Richtig! Und ich schreibe lieber meinen Kernel neu ;)

 

Einloggen