Lowlevel

Lowlevel => OS-Design => Thema gestartet von: FlashBurn am 14. October 2011, 21:11

Titel: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 14. October 2011, 21:11
Ich wollte keine ewig alten Threads aus der Versenkung holen, deswegen mache ich mal nen neuen auf, auch wenn das Thema (Prozesserstellung) schon ein paar Mal diskutiert wurde.

Da ich bisher immer noch nicht so richtig weiß/wusste wie ich einen Runtime-Loader haben kann, der als Programm vorliegt, ist mir jetzt eine Idee für die Prozesserstellung gekommen, mit der ich vllt sogar fork() anbieten kann, ohne das auf Kernel-Ebene machen zu müssen.

Mein createTask() soll nur einen komplett leeren Task erstellen, nicht mehr und nicht weniger.

Der VFS-Server kann dann über eine private Kernel-API (die soll so geschützt sein, dass nur der VFS-Server sie aufrufen kann) Speicher in einen Prozess "injezieren" (im Endeffekt mappen) können und zwar an Adressen wo er das gerne hätte (soweit es da keine Überschneidungen gibt).

Damit ist es möglich einen Runtime-Loader, der als Programm vorliegt, in den leeren Prozess zu injezieren, einen neuen Thread zu erstellen und dieser Thread führt dann den Runtime-Loader aus, welcher sich die Daten (die ausführbare Datei und die nötigen Libs) vom VFS-Server holt und parst.
Ein weiterer Vorteil ist, dass ich dann auch einfach mehrere Runtime-Loader, für verschiedene Dateiformate, haben kann und vorallem bleiben diese im Prozess gemappt und der Prozess kann dann später noch Libs nachladen (Plug-Ins) und die Symbole auflösen lassen.
Der Runtime-Loader würde dann aus 2 Dateien bestehen, einmal aus dem eigentlichen Runtime-Loader (als Programm oder ne Art Programm, da bin ich mir noch nicht sicher) und einmal aus nem Add-On für den VFS-Server. Das Add-On wäre dafür da, dass der VFS-Server weiß welchen Runtime-Loader er für welche Datei nehmen muss (ne einfache Funktion, die nen Pointer auf z.B. die ersten 8kb von der Datei bekommt und den Header überprüft).
Ganz nebenbei könnte man so auch das Starten jeglicher Dateien, wo Programme für installiert sind, lösen. Sprich jedes Programm bringt für jeden Dateityp den sie unterstützt ein Add-On für den VFS-Server mit (z.B. ein Media-Player ein Add-on für jedes Container-Format oder so). Ist das Overkill? Ich meine man muss ja nicht, man könnte aber, die Funktionalität wäre gegeben.

Genauso kann ich es so ermöglichen das ein fork() über den VFS-Server stattfindet. In dem einfach der schon vorhanden Prozess per COW in den neuen Prozess injeziert wird.

Eine Frage zum fork(), wird da wirklich der komplette Adressraum geclont oder werden z.B. die Stacks weggelassen? Weil letzteres wäre dann schon wieder schwieriger umzusetzen (und wahrscheinlich nicht ohne die Hilfe des Kernels).
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 15. October 2011, 10:34
Das große Problem beim fork() ist nicht das Kopieren des Adressraums, sondern dass die beiden Prozesse hinterher gemeinsame Dateideskriptoren haben müssen.

Ob es so clever ist, jedes Programm eine zusätzliche Lib für den VFS-Server installieren zu lassen, weiß ich nicht. Das werden mit der Zeit ja doch ganz schön viele. Bist du sicher, dass du Binärcode brauchst, um Dateiformate zu erkennen und nicht eine einfachere Logik reicht, die fest eingebaut ist und auf eine Datenbank zugreift? Hast du dir z.B. mal angeschaut wie file funktioniert? Das hat meines Wissens auch eine Form von Datenbank und nicht Code für jedes einzelne Format.

Und warum ist ausgerechnet der VFS-Server für das Starten von Prozessen zuständig?
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 15. October 2011, 11:01
Zitat von: taljeth
Das große Problem beim fork() ist nicht das Kopieren des Adressraums, sondern dass die beiden Prozesse hinterher gemeinsame Dateideskriptoren haben müssen.
Naja, für mich ist eher das Kopieren des Adressraums ein Problem und nicht die Dateidiskriptoren. Wegen den FD´s (und aus einem anderen Grund) wird das ganze ja auch über den VFS-Server gemacht.

Zitat von: taljeth
Ob es so clever ist, jedes Programm eine zusätzliche Lib für den VFS-Server installieren zu lassen, weiß ich nicht.
Jap, ist mir auch schon aufgefallen, zumal es wahrscheinlich daran scheitern würde, dass es nicht reicht das Programm einfach zu "injezieren", weil fast alle Programme noch Libs hätten und dafür bräuchte man wieder den Runtime-Loader.
Sprich die ganze Sache mit dem in einen leeren Adressraum injezieren, bleibt den Runtime-Loadern vorbehalten.

Zitat von: taljeht
Und warum ist ausgerechnet der VFS-Server für das Starten von Prozessen zuständig?
Will man das Unix-Konzept "alles ist eine Datei" nutzen, ist das nun man die Stelle schlechthin dafür (ob ich das nutze, weiß ich noch nicht, wird wohl aber darauf hinauslaufen). Dann kommt noch hinzu, dass der Kernel nix von den Runtime-Loadern weiß, sondern nur der VFS-Server und auch nur diesem ist es erlaubt die leeren Prozesse mit Leben zu füllen.

Ein Problem wäre nur, wenn ich dann schon fork() habe, dann muss ich natürlich auch noch exec() umsetzen und das bereitet mir ein wenig Sorgen. Denn wenn ich dem VFS-Server auch noch die Möglichkeit gebe, den Adressraum eines vorhandenen Prozesse komplett zu löschen, haben alle Add-Ons ganz schön viel Rechte. Allerdings will ich der Einfachheit halber, dass ganze über Datei-Rechte lösen. Sprich man braucht root-Rechte um überhaupt ein Add-On an seinen Platz zu schaffen (die müssen in einem bestimmten Ordner liegen) und ich gehe einfach davon aus, dass das System wenn es installiert wird, sicher ist.
Selbst wenn ich Dateisystem nicht derartige Rechte unterstützt kann ich einfach bestimmte Ordner festlegen wo man für Schreibrechte (Leserechte sind da nicht so wichtig oder?) root-Rechte braucht. Das ganze lässt sich auch wunderbar im VFS-Server umsetzen.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 15. October 2011, 12:17
Naja, für mich ist eher das Kopieren des Adressraums ein Problem und nicht die Dateidiskriptoren. Wegen den FD´s (und aus einem anderen Grund) wird das ganze ja auch über den VFS-Server gemacht.
Das heißt, dass du den Dateizeiger im VFS-Server hast und die Programme alle Requests nicht direkt an den Treiber schicken, sondern an den VFS-Server, der die Requests dann entsprechend weiterleitet? Was bedeutet das für die Performance?

Zitat
Dann kommt noch hinzu, dass der Kernel nix von den Runtime-Loadern weiß, sondern nur der VFS-Server und auch nur diesem ist es erlaubt die leeren Prozesse mit Leben zu füllen.
Das müsste ja aber kein VFS-Server sein, sondern ließe sich mit einem anderen Server (sei es ein allgemeiner exec-Server oder ein spezieller ELF-Server) genauso umsetzen. Dass du die FDs nur gescheit hinkriegst, wenn es über den VFS-Server läuft, ist aber nachvollziehbar.

Zitat
Ein Problem wäre nur, wenn ich dann schon fork() habe, dann muss ich natürlich auch noch exec() umsetzen und das bereitet mir ein wenig Sorgen. Denn wenn ich dem VFS-Server auch noch die Möglichkeit gebe, den Adressraum eines vorhandenen Prozesse komplett zu löschen, haben alle Add-Ons ganz schön viel Rechte.
Vielleicht so: Ein Prozess hat ein Flag, das sagt, ob der VFS-Server im Adressraum rumpfuschen darf. Das erste, was der Prozess in _start macht, ist dieses Flag zu löschen und während exec setzt er das Flag halt wieder.

Zitat
Selbst wenn ich Dateisystem nicht derartige Rechte unterstützt kann ich einfach bestimmte Ordner festlegen wo man für Schreibrechte (Leserechte sind da nicht so wichtig oder?) root-Rechte braucht. Das ganze lässt sich auch wunderbar im VFS-Server umsetzen.
Warum willst du für die Systempartition etwas benutzen, das keine Rechte kann?
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 15. October 2011, 12:47
Zitat von: taljeth
Das heißt, dass du den Dateizeiger im VFS-Server hast und die Programme alle Requests nicht direkt an den Treiber schicken, sondern an den VFS-Server, der die Requests dann entsprechend weiterleitet? Was bedeutet das für die Performance?
Am liebsten wäre es mir wenn die Dateizeiger in der libc wären, aber das wäre ja noch umständlicher mit dem sharen. Das Thema hatten wir ja schon, wie das so ist mit den gemeinsamen Dateizeigern, ob da wirklich gleichzeitig geschrieben und/oder gelesen wird.

Gerade zwecks Performance sehe ich es als besser an das ganze Dateimanagement an einem Ort zu machen. Ich cache sozusagen die Dateien im VFS-Server (immer in vielfachem der Page-Größe bzw. "Cluster"-größe, je nach dem was größer ist).
Vorallem von welchem Treiber redest du? Der Medium-Treiber kennt keine Dateisystemstrukturen, die sind als Add-On im VFS-Server und der holt sich die Daten von den Treibern (wie genau ich das mit den Treibern und Bus-Treibern mache, muss ich mal sehen).

Zitat von: taljeth
Vielleicht so: Ein Prozess hat ein Flag, das sagt, ob der VFS-Server im Adressraum rumpfuschen darf. Das erste, was der Prozess in _start macht, ist dieses Flag zu löschen und während exec setzt er das Flag halt wieder.
Das ist eigentlich ne richtig gute Idee. So kann jeder Prozess selbst entscheiden ob er das zulässt bzw. halt nur exec().

Zitat von: taljeth
Warum willst du für die Systempartition etwas benutzen, das keine Rechte kann?
Ich bin Windows-User und der Einfachheit halber. Ich wollte mich aber eigentlich mal mit extfs2 auseinander setzen und das in meinem Bootloader implementieren (bisher habe ich nur FAT und ISO9660). Linux scheint das doch auch irgendwie hinzubekommen, mit nem Overlay oder so.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 15. October 2011, 13:21
Gerade zwecks Performance sehe ich es als besser an das ganze Dateimanagement an einem Ort zu machen. Ich cache sozusagen die Dateien im VFS-Server (immer in vielfachem der Page-Größe bzw. "Cluster"-größe, je nach dem was größer ist).
Das kann und sollte man natürlich machen, hilft aber erst beim zweiten Zugriff oder wenn die Zugriffe so klein sind, dass der VFS-Server sich wesentlich größere Bereich auf einmal in den Cache lädt als das Programm im Moment anfordert.

Zitat
Vorallem von welchem Treiber redest du? Der Medium-Treiber kennt keine Dateisystemstrukturen, die sind als Add-On im VFS-Server und der holt sich die Daten von den Treibern (wie genau ich das mit den Treibern und Bus-Treibern mache, muss ich mal sehen).
Du baust alle Dateisystemtreiber in den VFS-Server ein? Das ist aber kein sehr mikrokerneliger Ansatz.

Zitat von: taljeth
Ich bin Windows-User und der Einfachheit halber. Ich wollte mich aber eigentlich mal mit extfs2 auseinander setzen und das in meinem Bootloader implementieren (bisher habe ich nur FAT und ISO9660). Linux scheint das doch auch irgendwie hinzubekommen, mit nem Overlay oder so.
Windows benutzt NTFS, das ist also kein Argument. ;)

Linux hatte mal UMSDOS, das irgendwie zusätzliche Metadaten auf einem FAT-Dateisystem angelegt hat. War aber wohl immer relativ kaputt und ist soweit ich weiß mittlerweile auch rausgeflogen.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 15. October 2011, 14:39
Zitat von: taljeth
Du baust alle Dateisystemtreiber in den VFS-Server ein? Das ist aber kein sehr mikrokerneliger Ansatz.
Hmm, darüber lässt sich jetzt streiten ;)

Aus sicht einiger Vertreter habe ich sowieso keinen richtigen MikroKernel da ich ja den Speichermanager im Kernel habe. Allerdings wieso ist dem nicht so? Es läuft nicht im Kernel und so richtig als Treiber würde ich es nicht sehen. Ich habe einen Service (der VFS-Server) und den kann ich per Add-Ons erweitern (so wie Video- und Containerformate bei Mediaplayern).

Wenn ich die Sachen auch noch jeweils in einen extra Prozess lege, dann geht die Performance ja richtig in den Keller, es wird mehr Speicher verbraucht und macht die Sache ja noch komplexer (weil dadurch wieder neue Probleme auftauchen würden).

Zitat von: taljeth
inux hatte mal UMSDOS, das irgendwie zusätzliche Metadaten auf einem FAT-Dateisystem angelegt hat. War aber wohl immer relativ kaputt und ist soweit ich weiß mittlerweile auch rausgeflogen.
Hmm, ich weiß das Linux von nem FAT32 USB-Stick starten kann (obwohl es sein kann das da dann nen Image drauf ist, aber da waren so viel Dateien drauf) und von CD.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 15. October 2011, 14:52
Definiere "richtiger Mikrokernel". Nur, weil PMM und VMM im Kernel liegen, ist das noch lange kein Monolith.
Plugin-Technologien sind dagegen ein dynamischer, monolithischer Ansatz. Ein einzelner, riesiger, monolithischer Userspace-Prozess auf einem Mikrokernel führt das Mikrokernelkonzept ohnehin ad absurdum. Und ein Betriebssystem mit einem Mediaplayer zu vergleichen finde ich etwas abwegig. :roll:

Wenn ich die Sachen auch noch jeweils in einen extra Prozess lege, dann geht die Performance ja richtig in den Keller, es wird mehr Speicher verbraucht und macht die Sache ja noch komplexer (weil dadurch wieder neue Probleme auftauchen würden).
Warum baust du dann eigentlich keinen monolithischen Kernel? Da kannst du die Performance eh am feinsten steuern. :-)

UMSDOS war ein Linux-Dateisystem, bei dem die Rechtevergabe usw. in einer speziellen Datei --LINUX-.--- abgelegt waren, so dass FAT wenigstens virtuell um die wichtigsten Fähigkeiten erweitert wurde. Praktisch wurde es fast nie eingesetzt, weil Linux damit anfällig für DOS-Viren wurde und ext2 wesentlich schneller war. Und ja, es ist inzwischen aus dem Kernel rausgeflogen.

Was du meinst ist SYSLINUX. Den hatte ich hier schon öfter empfohlen. Der lädt für Linux einen Kernel und eine initiale Ramdisk von FAT12/16/32 in den Speicher und startet die. Das ist was völlig anderes. Die Initrd von Knoppix beispielsweise mountet dann alle Blockdevices, bis sie die KNOPPIX-Datei gefunden hat (das ist das Knoppix-Root-Dateisystem). Bei CDs wird das gleiche Programm für iso9660 benutzt, ISOLINUX. Der dritte Kollege heißt EXTLINUX und kann ext2/3/4.

Gruß,
Svenska
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 15. October 2011, 15:11
Zitat von: svenska
Definiere "richtiger Mikrokernel". Nur, weil PMM und VMM im Kernel liegen, ist das noch lange kein Monolith.
Das sehe ich ja genauso, nur je nach dem wen du fragst ist das dann schonmal kein MikroKernel mehr.

Zitat von: svenska
Plugin-Technologien sind dagegen ein dynamischer, monolithischer Ansatz. Ein einzelner, riesiger, monolithischer Userspace-Prozess auf einem Mikrokernel führt das Mikrokernelkonzept ohnehin ad absurdum.
Naja, was würdest du denn als einzelener monolithischen Userspace-Prozess bezeichnen?

Die Treiber werden wahrscheinlich alle in ihrem eigenem Prozess laufen und die Services sind auch getrennt. Nur sehe ich halt nen Dateisystem nicht als Treiber an.

Zitat von: svenska
Und ein Betriebssystem mit einem Mediaplayer zu vergleichen finde ich etwas abwegig.
Da ging es mir auch nur darum, dass die ganzen unterstützten Media- und Dateiformate dynamisch nachgeladen werden können, aber nicht in einem eigenen Prozess laufen (was ja wohl auch Overkill wäre, aber bestimmt einfacher umzusetzen als die Sache mit den Dateisystemen).

Zitat von: svenska
Warum baust du dann eigentlich keinen monolithischen Kernel? Da kannst du die Performance eh am feinsten steuern.
Was die Performance betrifft hast du da mMn recht. Allerdings mag ich das vieles physisch getrennt von anderen Sachen läuft (z.B. Treiber und die Services). Man kann halt auf nem MikroKernel viele Sachen einfacher dynamisch machen und vorallem leichter austauschen.

Nen schön modularen Monolithen gab es mit BeOS und gibt es wieder mit Haiku. Nur mit dem Linux-Kernel kann ich mich absolut nicht anfreunden (Windows kann ich nix zu sagen, da ich darüber zu wenig weiß).

Auf CDs würde ich gerne die Variante von BeOS nutzen. Da hat man mehrere Tracks auf der CD, der erste Track ist nen normales ISO9660 und die weiteren sind BeFS Images (einmal ein x86 und einmal ein PPC Image).
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 15. October 2011, 15:22
Zitat von: svenska
Plugin-Technologien sind dagegen ein dynamischer, monolithischer Ansatz. Ein einzelner, riesiger, monolithischer Userspace-Prozess auf einem Mikrokernel führt das Mikrokernelkonzept ohnehin ad absurdum.
Naja, was würdest du denn als einzelener monolithischen Userspace-Prozess bezeichnen?
Wenn dein VFS-Server sämtliche Dateisysteme beinhaltet (und sei es dynamisch nachladbar als Plugin), dann ist das schon monolithisch. :-)

Da ich das VFS zu den grundlegendsten Funktionalitäten zähle (neben Speicher- und Prozessverwaltung), darf es auch in einem Mikrokernel im Kernel selbst implementiert sein. Wenn alles Dateien sind, dann ist ein Kernel ohne VFS nicht mehr lebensfähig, kann sich also nicht selbst retten.

Die Treiber werden wahrscheinlich alle in ihrem eigenem Prozess laufen und die Services sind auch getrennt. Nur sehe ich halt nen Dateisystem nicht als Treiber an.
Da bin ich anderer Meinung, ist aber ne Definitionsfrage.

Nen schön modularen Monolithen gab es mit BeOS und gibt es wieder mit Haiku. Nur mit dem Linux-Kernel kann ich mich absolut nicht anfreunden (Windows kann ich nix zu sagen, da ich darüber zu wenig weiß).
Warum nicht?

Auf CDs würde ich gerne die Variante von BeOS nutzen. Da hat man mehrere Tracks auf der CD, der erste Track ist nen normales ISO9660 und die weiteren sind BeFS Images (einmal ein x86 und einmal ein PPC Image).
Naja, wer's braucht...
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 15. October 2011, 15:38
Zitat von: svenska
Wenn dein VFS-Server sämtliche Dateisysteme beinhaltet (und sei es dynamisch nachladbar als Plugin), dann ist das schon monolithisch.
Naja, dass sehe ich z.B. als KISS an. Wie gesagt, die Dateisysteme auch noch in eigene Prozesse auszulagern würde zu noch mehr Problemen führen.

Zitat von: svenska
Da ich das VFS zu den grundlegendsten Funktionalitäten zähle (neben Speicher- und Prozessverwaltung), darf es auch in einem Mikrokernel im Kernel selbst implementiert sein.
Wow, dann wäre der Haiku-Kernel ja für dich auch noch nen MikroKernel (ich möchte meinen die haben "nur" das VFS im Kernel, bin mir aber nicht sicher). So ist das halt mit den Auslegungen ;)

Zitat von: svenska
Warum nicht?
Zu unflexibel (in meinen Augen) und verwendet Konzepte die ich absolut nicht nachvollziehen kann (ich meine damit, dass ich sie verstehe, aber nicht verstehe warum sie genutzt werden). Mal ganz davon abgesehen das ich ihn inzwischen auch für bloated halte und wie man den nicht konfigurieren kann, damit er ja auch auf allen Architekturen läuft. Dann lieber vieles physisch voneinander trennen und den Kernel so klein wie möglich halten (da würde ich dann sogar nix gegen ein VFS im Kernel haben, ist ja wie du sagtest ein muss für das OS).
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 15. October 2011, 20:45
Hallo,


@FlashBurn:
Auch wenn ich grundsätzlich nix vom injizieren von Speicher halte so empfinde ich die von Dir ganz oben beschriebene Vorgehensweise durchaus als machbar, aber es gibt einige Nachteile: die enormen Rechte der Module Deines Loaders hast Du ja selber erkannt und der hohe und redundante Speicherverbrauch wurde auch schon erwähnt. Warum nicht den Executable-Loader in den Kernel packen? Wenn Du unbedingt AddOns haben willst dann lass die als Vorverarbeitung im User-Space laufen und erst das fertige Executable (das dann im simplen Format für den Kernel vorliegt) an den Kernel weitergeben. Vor allem wüste ich gerne mal was Du mit Modulen meinst. Darf ich mir das so wie DLL's unter Windows vorstellen die dann erst wenn sie wirklich benötigt werden per LoadDLL() geladen werden? Was ich mich da immer frage: was ist mit den Call-Befehlen im normalen Code der auf diese Funktionen zeigt? Wenn die noch nicht reloziert sind dann zeigen die ja ins Nirwana so das wenn das Programm so einen Call doch mal ausführt das es dann (kontrolliert) abstürzt. Normale Librarys werden zum Start des Programms eingebunden so das die durchaus mit einer Vorverarbeitung im User-Space zu erledigen wären.

Bei fork() muss übrigens auch der Stack komplett mit kopiert werden, und auch alle anderen Speicherbereiche (auch solche die erst zur Laufzeit angelegt wurden) ich glaube sogar Shared-Memory (bin mir da aber nicht sicher). Es sollte auf jeden Fall noch jeder Pointer funktionieren. Dazu kommen dann eben noch die ganzen File-Desciptoren und ich denke es gibt noch andere Sachen aber das sollte ein genauer Blick in die POSIX-Spezifikation klären können. Ich schätze so ganz ohne aktive Hilfe des Kernels lässt sich fork() nicht ohne Würgereiz (und dem Lieblings-K-attribut von taljeth, SCNR) umsetzen. Das einzigste was da noch bleiben würde ist die Variante von cygwin (http://cygwin.com/faq-nochunks.html#faq.api.fork) aber gerade die ließe sich ja mit Deiner ursprünglichen Idee mit dem Executable-Loader tatsächlich umsetzen.

Die Dateisysteme mit in den VFS-Prozess zu integrieren empfinde ich auch als etwas zu monolithisch, da hätte man ja wieder einen fetten Single-Point-of-Failure.
In meiner Fantasie ist der VFS vor allem eine Art Switch an den sich alle Block-Devices, File-System-Treiber (ohne eine konkrete Instanz) und auch alle fertigen Dateisysteme (z.B. NFS-Clients) anmelden können. Der VFS zerlegt dann z.B. die Block-Devices (falls diese partitioniert sind) und lässt alle File-System-Treiber prüfen (anhand der ersten 8 kB o.ä.) ob sie was damit anfangen können, falls ja weißt der VFS den File-System-Treiber an eine neue Instanz (innerhalb seines Prozesses) zu erstellen und teilt dabei mit auf welchen Block-Device in welcher Partition das liegt (damit der Datei-System-Treiber dann direkt mit dem entsprechenden Block-Device-Treiber kommunizieren kann). Das einzigste was mir dabei noch Kopfzerbrechen bereitet ist das Verwalten der Mount-Points, das VFS kann dazu zwar auf fstab für die bekannten Partitionen zurückgreifen aber für alles andere wird eine gescheite Automatik benötigt (wovon ich aber nicht so arg viel halte) oder der User muss das manuell machen (ist auch nicht toll).
Das Caching würde ich auch komplett im VFS machen und nicht den einzelnen File-Systemen überlassen (dann könnte der Kernel auch an VFS eine IPC-Message schicken das der Speicher knapp wird und VFS kann dann mal ein wenig aufräumen). Ich würde sogar soweit gehen das der VFS selbst das Anlegen von Dateien cached so das die z.B. erst dann an den eigentlichen Datei-System-Treiber weitergegeben werden (mit der endgültigen Größe usw.) wenn der User-Prozess die Datei längst geschlossen hat (falls er nicht vorher flushall() aufgerufen hat). Der VFS sollte auch beim Öffnen von Verzeichnissen diese immer komplett vom Datei-System-Treiber abfragen (mitsamt allen Meta-Informationen) und dann immer auf dieser internen Darstellung arbeiten. Auf diese Art besteht im Datei-System-Treiber (oder gar im Block-Device-Treiber) auch nicht die Notwendigkeit irgendetwas zu cachen.

Dass das VFS in den Micro-Kernel gehört würde ich persönlich dann doch eher verneinen, mein Micro-Kernel soll nicht mal wissen das es überhaupt Dateien gibt (wozu auch? mir fällt da absolut kein Grund ein), aber ich schätze mit dieser Meinung bin ich hier wohl eindeutig in der Unterzahl.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FreakyPenguin am 15. October 2011, 20:59
Dass das VFS in den Micro-Kernel gehört würde ich persönlich dann doch eher verneinen, mein Micro-Kernel soll nicht mal wissen das es überhaupt Dateien gibt (wozu auch? mir fällt da absolut kein Grund ein), aber ich schätze mit dieser Meinung bin ich hier wohl eindeutig in der Unterzahl.

Ich wollte nur rasch hierzu was sagen: Und zwar ist ja IPC eine zentrale Aufgabe des Mikrokernels. Somit finde ich das VFS im Kernel bei einem OS, das das VFS als primäre IPC-Methode benutzt nicht soo verkehrt.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 15. October 2011, 21:08
Zitat von: erik
und der hohe und redundante Speicherverbrauch wurde auch schon erwähnt.
Hier stehe ich irgendwie aufm Schlauch, welcher hohe und redundante Speicherverbrauch?

Zitat von: erik
Warum nicht den Executable-Loader in den Kernel packen? Wenn Du unbedingt AddOns haben willst dann lass die als Vorverarbeitung im User-Space laufen und erst das fertige Executable (das dann im simplen Format für den Kernel vorliegt) an den Kernel weitergeben.
Naja, keine halben Sachen, sprich entweder ganz oder gar nicht. Soll heißen, mit dem einfachen Format wäre es ja nicht getan, um zur Laufzeit Libs nachladen zu können, müsste der Kernel entweder die Formate kennen oder ich brauche was im UserSpace. Dann sagst du, dass das halt der UserSpace-Teil machen soll, aber wo sind dann die Symboltabellen?
Deswegen soll der Kernel gar nix davon wissen und der entsprechende Runtime-Loader ist immer und dauerhaft im Prozess gemappt (obwohl man da optmieren könnte und jeder Prozess könnte festlegen ob das wirklich sein muss). Damit ginge zwar auch "nur" das Libs nur im selben Format nachgeladen werden, wie das Hauptprogramm, aber ich denke mit der Einschränkung kann ich leben ;)

Zitat von: erik
Vor allem wüste ich gerne mal was Du mit Modulen meinst.
Ich habe alle meine Beiträge nochmal gelesen und da tauchen keine Module, aber sehr wohl Add-Ons auf. Das sollen ne Art Plug-Ins sein, die erst zur Laufzeit hinzugefügt werden.

Zitat von: erik
Bei fork() muss übrigens auch der Stack komplett mit kopiert werden, und auch alle anderen Speicherbereiche (auch solche die erst zur Laufzeit angelegt wurden) ich glaube sogar Shared-Memory (bin mir da aber nicht sicher). Es sollte auf jeden Fall noch jeder Pointer funktionieren.
Das macht die Sache doch einfacher, darum ging es mir eigentlich nur.

Zitat von: erik
Die Dateisysteme mit in den VFS-Prozess zu integrieren empfinde ich auch als etwas zu monolithisch, da hätte man ja wieder einen fetten Single-Point-of-Failure.
Das muss ich bei der konkreten Implementierung sehen. Im Moment gefällt mir das irgendwie nicht, dass jedes Dateisystem seinen eigenen Prozess bekommt. Das kostet wahrscheinlich richtig Performance und verursacht bestimmt auch neue Probleme.

Wenn ich natürlich sehe, dass die Dateisysteme nur an wenigen Bestimmten Stellen so aufgerufen werden, dass ich sie auch auslagern könnte, kann ich darüber ja nochmal nachdenken.

Zitat von: FreakyPenguin
Ich wollte nur rasch hierzu was sagen: Und zwar ist ja IPC eine zentrale Aufgabe des Mikrokernels. Somit finde ich das VFS im Kernel bei einem OS, das das VFS als primäre IPC-Methode benutzt nicht soo verkehrt.
Wie soll ich mir das vorstellen, das VFS als primäre IPC-Methode? Pipes und Dateien?
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 15. October 2011, 21:42
Für jedes Dateisystem einen eigenen Prozess zu benutzen ist nicht unbedingt langsam. Die meisten Dateisysteme arbeiten ohnehin mit realen Devices, deren Latenz wesentlich größer sein dürfte als der Overhead eines Kontextswitches (allein schon, weil es durch das PCI-Subsystem muss - und damit durch mindestens zwei weitere Kontextwechsel).

Das VFS als IPC-Methode ist ein Grund, an den ich nicht gedacht hatte. Mit virtuellen Dateisystemen (/proc, /sys) ist das durchaus möglich. Allerdings hätte ich IPC noch neben VFS, VMM und PMM (und evtl. vergessenen Dingen) gesehen. Im Hinterkopf habe ich dabei aber immer unixoide Systeme, da ist für ein fork() schon einiges an Hintergrundwissen über Dateien notwendig.

Gruß,
Svenska
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 15. October 2011, 21:51
Hallo,


Und zwar ist ja IPC eine zentrale Aufgabe des Mikrokernels. Somit finde ich das VFS im Kernel bei einem OS, das das VFS als primäre IPC-Methode benutzt nicht soo verkehrt.
Okay, diesen Einwand lass ich natürlich gelten. Obwohl ich schon der Meinung bin das ein anständiger Micro-Kernel sich bemüht ein effizientes IPC/RPC anzubieten und da sind z.B. Pipes im Dateisystem nicht so wirklich die optimale Wahl.


Hier stehe ich irgendwie aufm Schlauch, welcher hohe und redundante Speicherverbrauch?
Ich dachte Dein Loader ist dann in jedem Prozess mit enthalten damit er später eben AddOns nachladen kann usw., aber wenn der extern bleiben soll is auch gut, wobei das dann einige Dinge wohl ziemlich umständlich machen würde.

Naja, keine halben Sachen, sprich entweder ganz oder gar nicht.
Hm, okay.

aber wo sind dann die Symboltabellen?
Na die liegen noch im User-Space und zur Not können die auch noch mal aus dem Executable nachgeladen werden.

Ich habe alle meine Beiträge nochmal gelesen und da tauchen keine Module, aber sehr wohl Add-Ons auf. Das sollen ne Art Plug-Ins sein, die erst zur Laufzeit hinzugefügt werden.
Sorry, da hab ich mich wohl verlesen. Trotzdem wüste ich gerne wie das überhaupt funktionieren soll. Werden dann Reste in der Executable nachträglich reloziert?

Das kostet wahrscheinlich richtig Performance
Das hängt doch nur davon ab ob Du ein IPC mit geringem Overhead (wie z.B. Zero-Copy) hinbekommst. ;)

und verursacht bestimmt auch neue Probleme.
Welche denn? Solange Du das API zwischen VFS und den Dateisystemen klar strukturierst sollte es völlig egal sein ob da IPC dazwischen ist oder nicht.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 15. October 2011, 22:07
Zitat von: erik
Ich dachte Dein Loader ist dann in jedem Prozess mit enthalten damit er später eben AddOns nachladen kann usw., aber wenn der extern bleiben soll is auch gut, wobei das dann einige Dinge wohl ziemlich umständlich machen würde.
Das meintest du, ja dann hast du recht, aber bei Linux ist es doch genauso (dort ist es irgendeine ld.so oder so). Der Runtime-Loader liegt als ein fertiges Programm vor, dass nur noch gemappt werden muss (und damit in jedem Prozess die selben Pages nutzt).
Benutzt der Prozess keine Plug-Ins oder ähnliches zur Laufzeit, kann man diesen ja wieder unmappen bzw. würde es wahrscheinlich besser sein, einfach nach bestimmten Symbolen zu gucken (das wäre z.B. dlopen() und solche Sachen) und ihn dann nicht zu unmappen, ansonsten aber schon.

Zitat von: erik
Trotzdem wüste ich gerne wie das überhaupt funktionieren soll. Werden dann Reste in der Executable nachträglich reloziert?
Plug-Ins zur Laufzeit funktionieren so, dass du dir ne Liste von denen merkst und die ein ganz bestimmtes API implementieren. Im Endeffekt ruft das eigentliche Programm die Funktionen nur über eine Struktur mit Funktionspointern (siehe VFS bei Linux und bestimmt auch allen anderen OS´s) auf, aber im Plug-In werden Symbole reloziert, nicht aber im eigentlichen Programm.

Zitat von: erik
Das hängt doch nur davon ab ob Du ein IPC mit geringem Overhead (wie z.B. Zero-Copy) hinbekommst.
Wohl eher nicht bzw. hängt davon ab wie detailiert du es betrachtest. Das Senden und Empfangen von Daten sollte mehr oder weniger per Zero-Copy gehen, die eigentliche Kommunikation (welche Operation und sowas) wird durch kopieren passieren und die Implementierung in der libc wird definitiv nur über kopieren zu lösen sein (geht auch gar nicht anders ohne Segmente, irgendwo muss immer kopiert werden).

Zitat von: erik
Welche denn? Solange Du das API zwischen VFS und den Dateisystemen klar strukturierst sollte es völlig egal sein ob da IPC dazwischen ist oder nicht.
Wo würden dann die FD´s sein? Im VFS oder im Dateisystem-Programm?

Zitat von: svenska
Für jedes Dateisystem einen eigenen Prozess zu benutzen ist nicht unbedingt langsam. Die meisten Dateisysteme arbeiten ohnehin mit realen Devices, deren Latenz wesentlich größer sein dürfte als der Overhead eines Kontextswitches (allein schon, weil es durch das PCI-Subsystem muss - und damit durch mindestens zwei weitere Kontextwechsel).
Overhead ist da, aber er wäre so geringer.

Ich versuche mir das gerade vorzustellen. Das VFS müsste auf jeden Fall immer alle Pfade bis zu einem Mount-Point zwischenspeichern. Auch wäre es bestimmt nicht schlecht wenn Directories irgendwie gecacht werden.

Ansonsten wären das halt anstatt Funktionsaufrufen IPC-Requests. Möglich wäre es, nur die Frage wofür man optimiert? Wenn ich da an SSDs denke, könnte sich der Overhead bemerkbar machen oder? Bei normalen HDDs nicht.
Aber was wäre durch den Overhead gewonnen? Zumal es halt wesentlich mehr Speicher verschlingen würde und den Systemstart nicht einfacher macht. Ich wüsste jetzt auch nicht wie komplex solche "Programme" dann wären, sprich was sie an Libs benötigen würden.
Da muss ich nochmal ne Nacht drüber schlafen, aber ihr könnt ja versuchen es mir schmackhaft zu machen ;)
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 16. October 2011, 11:57
Hallo,


zu Deinem Loader fällt mir noch ein das Du es lösen musst den Speicher mit den richtigen Rechten (für den neuen Prozess) zu injizieren. Also z.B. Code sollte als Executable-Only gemappt sein und ASLR solltest Du auch (zumindest konzeptionell) vorsehen.

Plug-Ins zur Laufzeit funktionieren so, dass du dir ne Liste von denen merkst und die ein ganz bestimmtes API implementieren.
Hä? Soll die Liste ein API implementieren?

Im Endeffekt ruft das eigentliche Programm die Funktionen nur über eine Struktur mit Funktionspointern (siehe VFS bei Linux und bestimmt auch allen anderen OS´s) auf, aber im Plug-In werden Symbole reloziert, nicht aber im eigentlichen Programm.
Diese Strucktur-Member dann später (zur Laufzeit) auf den richtigen Code zeigen zu lassen ist auch relozieren. Auf was zeigen diese Funktions-Pointer vorher? Nebst dessen das solche Strukturen besser in rodata liegen damit das eigentliche Programm da nicht aus versehen was doofes reinschreibt, damit sollte Dein Loader umgehen können.

Das Senden und Empfangen von Daten sollte mehr oder weniger per Zero-Copy gehen
Das meinte ich auch, die Metadaten sind doch nur ein paar Bytes so das dort Kopieren immer billiger als Mappen sein sollte.

die Implementierung in der libc wird definitiv nur über kopieren zu lösen sein (geht auch gar nicht anders ohne Segmente, irgendwo muss immer kopiert werden).
Hm, ich wollte eher im VFS kopieren, dort muss man das doch eh tun wenn man anständigen Cache haben will. In der libc würde ich nur dann cachen wenn z.B. eine Datei per readln eingelesen wird aber wenn dort read(file,buffer,1000000) steht dann soll das VFS lieber direkt von seinem Cache in den eigentlich User-Buffer kopieren. Es ist IMHO auch unwahrscheinlich das ein Programm die selben Bytes aus einer Datei mehrfach hintereinander einließt so das es eher darum geht dass das VFS nicht für zu arg kleine Häppchen in Anspruch genommen wird was also eher für ein dezentes Read-Ahead und Write-Combining in der libc spricht (und für richtiges Datei-Caching ist die libc auf jeden Fall der falsche Ort).

Wo würden dann die FD´s sein? Im VFS oder im Dateisystem-Programm?
Auf jeden Fall nicht im Datei-System-Treiber, ich würde dort nicht mal irgendeine Art von Datei-Handle o.ä. haben wollen, eher würde ich dem VFS immer 2 oder 3 uint64_t Werte vom Datei-System-Treiber mitgeben die dann vom VFS wieder an den Datei-System-Treiber übergeben werden müssen damit diese z.B. zum schnelleren Finden der Datei benutzt werden können (bei FAT wäre das z.B. der Start-Cluster des Verzeichnisses und das Offset mit dem Verzeichniseintrag innerhalb des Verzeichnisses wenn es um die Metadaten geht und der Start-Clusster der eigentlichen Datei wenn es um die Datei selber geht, bei ext wohl eher eine INode-Nummer). Es ist auf jeden Fall Speicher-Effizienter wenn das Cache-Management nur einmal gemacht wird und da bietet sich das VFS geradezu an.

Wenn ich da an SSDs denke, könnte sich der Overhead bemerkbar machen oder?
Dann denk mal an eine PCIe-RAM-Disk (also ein HDD-Controller auf einer PCIe-Karte an dem direkt 4 oder mehr GB normaler DRAM hängen), damit kann man selbst bei zufälligem Lesen und Schreiben (es muss ja kein Wear-Leveling durchgeführt werden) von einzelnen Sektoren Commit-Zeiten von unter 1 µs erreichen. Da ist die SW-Schichtung im OS immer der Bremsklotz. Aber sowas ist eben auch für keinen von uns der normale Einsatzzweck, ich denke das 3 SW-Schichten im OS (VFS / Datei-System-Treiber / Block-Device-Treiber) ganz okay sind, bei normalen HDDs dürfte das nichtmal messbar sein.

Ich wüsste jetzt auch nicht wie komplex solche "Programme" dann wären, sprich was sie an Libs benötigen würden.
Ich vermute mal das so ein Datei-System-Treiber als eigenständiger Prozess fast gar nichts an libs braucht, außer malloc/free, nen printf für Fehlermeldungen und etwas Code zum umrechnen von Zeitwerten (Datei-System-Format <> UTC) fällt mir nicht viel ein.


noch etwas OT (ich finde jetzt auf die Schnelle nicht mehr den richtigen Thread):
Du hast doch mal gefragt ob man von einer virtuellen Adresse auf das zugehörige Objekt schließen können muss also ob man im VMM im Kernel auch die belegten Blöcke verwalten sollte: ich denke ja, weil spätestens wenn Du doch mal swappen willst must Du in der Lage sein zu wissen was Du gerade auslagerst wenn Dir die Page-Ersetzungsstrategie eine Page vorschlägt.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 16. October 2011, 12:14
Zitat von: erik
zu Deinem Loader fällt mir noch ein das Du es lösen musst den Speicher mit den richtigen Rechten (für den neuen Prozess) zu injizieren. Also z.B. Code sollte als Executable-Only gemappt sein und ASLR solltest Du auch (zumindest konzeptionell) vorsehen.
Das wäre alles kein Problem, da man sowas ja beim Mappen mit angeben kann. Auch ASLR kann wunderschön im Loader implementiert werden ohne das der Kernel geändert werden muss oder etwas darüber wissen muss.

Was Plug-Ins betrifft, ist kein Plug-In geladen ist auch kein Element in der Liste. Die Listeneinträge werden erst erstellt wenn ein Plug-In geladen ist und sehen z.B. so aus:
struct PlugInAPI {
 uint32 (*open)(const char* name);
 uint32 (*close)(uint32 handle);
};
Und jedes Plug-In muss mindestens die Symbole haben, die man für diese Struktur benötigt oder am besten noch, die Struktur selbst ist im Plug-In gespeichert und kann durch einen Funktionsaufruf geholt werden.

Zitat von: erik
Du hast doch mal gefragt ob man von einer virtuellen Adresse auf das zugehörige Objekt schließen können muss also ob man im VMM im Kernel auch die belegten Blöcke verwalten sollte: ich denke ja, weil spätestens wenn Du doch mal swappen willst must Du in der Lage sein zu wissen was Du gerade auslagerst wenn Dir die Page-Ersetzungsstrategie eine Page vorschlägt.
Ich kann dir gerade nicht ganz folgen was du genau meinst, aber ich habe ne art PageStruc Tabelle, wo für jede physisches Page gespeichert ist, wie oft sie gemappt ist und noch zusätzlich ein 4byte Wert gespeichert werden kann (wird im Mom nur vom SlabAllocator genutzt um auf die Slab-Struktur zu kommen).

Ansonsten hätte ich gesagt, kann man beim Speicheranfordern/Mappen sagen, ob der Speicher geswappt werden darf oder nicht.

Edit::

Was dynamisches Laden zur Laufzeit betrifft: http://en.wikipedia.org/wiki/Dynamic_loading (http://en.wikipedia.org/wiki/Dynamic_loading)
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 16. October 2011, 13:18
FlashBurn, eine Frage, die sich mir bei deinem ursprünglichen Modell stellt, ist, ob man in deinem OS auf Sachen wie Block und Character Devices mit den normalen Dateisystemoperationen zugreifen können soll. Habe ich also irgendwie sowas wie ein /dev/hda? Wenn ja, dann hast du schon Treiber vorgesehen, die außerhalb des VFS liegen und an die Requests einfach durchgereicht werden müssen. Warum nicht also konsistent das gleiche mit Dateisystemen machen?

Aber Erik hat auch noch ein schönes anderes Stichwort geliefert, nämlich NFS. Dort kann du kein Block-Backend abtrennen. Willst du also das ganze Netzwerkzeug in den VFS-Server einbauen? Wenn ich dein OS hacken sollte, wüsste ich dann jedenfalls schon, wo ich ansetzen würde - den VFS-Server unter seiner Kontrolle zu haben klingt nach einem guten Anfang. ;)

Okay, diesen Einwand lass ich natürlich gelten. Obwohl ich schon der Meinung bin das ein anständiger Micro-Kernel sich bemüht ein effizientes IPC/RPC anzubieten und da sind z.B. Pipes im Dateisystem nicht so wirklich die optimale Wahl.
Weil?

Was du in der Regel für IPC brauchst sind einige wenige Metadaten und je nach Request möglicherweise noch ein großer Haufen Nutzdaten. Die Metadaten in eine Pipe und die Nutzdaten eventuell über einen anderen Mechanismus wie Shared Memory klingt für mich eigentlich sehr sinnvoll (und deswegen ist das ja auch die Richtung, in die wir mit LIOv2 gehen).
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 16. October 2011, 13:38
Zitat von: taljeth
FlashBurn, eine Frage, die sich mir bei deinem ursprünglichen Modell stellt, ist, ob man in deinem OS auf Sachen wie Block und Character Devices mit den normalen Dateisystemoperationen zugreifen können soll. Habe ich also irgendwie sowas wie ein /dev/hda? Wenn ja, dann hast du schon Treiber vorgesehen, die außerhalb des VFS liegen und an die Requests einfach durchgereicht werden müssen. Warum nicht also konsistent das gleiche mit Dateisystemen machen?
Ansich würde ich schon sowas wie /dev/ haben wollen, nur habe ich noch keine Vorstellung wie das Interface dann aussehen soll und wie das ganze dann läuft. Ich dachte da irgendwie daran, das man Devices öffnen, schließen und lesen kann. In der "Datei" wären dann genau 4bytes gespeichert und das wäre der IPC-Port des Treibers.
Problem ist halt, ich habe sowas noch nie programmiert (Hardwarezugriff über solchen "Dateien").

Ansonsten hast du mir doch jetzt einen sehr guten Grund geliefert die Dateisysteme doch als eigenständige Prozesse zu implementieren.

Nur wie setzt man dann sowas (besser wo) um wie gleichzeitiger (unabhängiger) Lese-Zugriff und ein anderes Programm schreibt in diese Datei. Ich möchte etwas implementieren so dass ich bei Änderungen eine Benachrichtigung bekomme. Nur wird das im VFS oder im Dateisystem implementiert?

Zitat von: taljeth
Die Metadaten in eine Pipe und die Nutzdaten eventuell über einen anderen Mechanismus wie Shared Memory klingt für mich eigentlich sehr sinnvoll (und deswegen ist das ja auch die Richtung, in die wir mit LIOv2 gehen).
Klingt ähnlich wie mein IPC System, meine Nachrichten bestehen aus 32byte "Metadaten" und Shared Memory als Nutzdaten. Allerdings kopiert man dadurch natürlich viel wenn nicht direkt das IPC, sondern z.B. die libc genutzt wird.

So wir werden schonwieder ganz schön OT, für eine Diskussion zwecks VFS oder IPC sollte man vllt nen neuen Thread aufmachen ;)
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 16. October 2011, 14:52
In einer Device-Datei unter Unix stehen nur Major- und Minor-Nummern, also auch nur 2-4 Bytes. Allerdings sind das dort nicht die "Daten" der Datei, sondern spezielle Inode-Verweise. Es sind ja trotzdem Dateien. Die Größe könnte die Größe des Block-Devices (oder null) sein, der Inhalt ist genau das, was das Blockdevice rauswirft.

Da aber jeder Zugriff auf diese Datei durchs VFS hindurch muss, kann eben das VFS auch einfach ein paar Standard-Requests direkt an den Blockdevice-Treiber weiterreichen. Wenn der einen bestimmten Request nicht implementiert, geht der Fehler durch das VFS hindurch zum aufrufenden Programm.

Der Fall "fünf Prozesse lesen, während gleichzeitig ein Prozess in die Datei schreibt" muss nur soweit behandelt werden, dass die Daten konsistent sind, also entweder die kompletten neuen oder die kompletten alten Daten. Alles weitere ist egal.

Änderungen im Dateisystem gehen ebenfalls immer durch das VFS, also gehört die entsprechende Implementation auch dorthin. (Außerdem möchtest du nicht, dass sich verschiedene Dateisysteme unterschiedlich verhalten.) Das Dateisystem sollte aber in der Lage sein, so ein Ereignis zu triggern; bei Netzwerk-Dateisystemen könnte das nützlich sein.

Gruß,
Svenska
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 16. October 2011, 22:56
Hallo,


ist kein Plug-In geladen ist auch kein Element in der Liste. Die Listeneinträge werden erst erstellt wenn ein Plug-In geladen ist
Aha, da ist dann aber doppelte Indirektion weil der Code im Programm ja nicht direkt das Plug-In aufruft sondern dies immer über einen Vermittlungsmechanismus tut, sehe ich das richtig?

.... und kann durch einen Funktionsaufruf geholt werden.
Und der Loader findet diese Funktion anhand eines fest definierten Label-Namens?

Ich kann dir gerade nicht ganz folgen was du genau meinst ....
Ist okay, das trifft Dich beim Flat-Memory eh nicht weil Du ja für jeden Prozess ein eigenes PD hast und daher immer weißt wem der Speicher gehört den Du auslagern möchtest (das der Page-Ersetzungsalgorithmus keine Pages vorschlägt die gar nicht ausgelagert werden dürfen hab ich mal stillschweigend vorausgesetzt). Dieses Problem trifft nur mich weil ich für das gesamte System nur ein einziges PD habe und daher schon wissen muss wem der Speicher gehört und in welchem Segment er enthalten ist damit ich dann auch im richtigen Segment-Descriptor das Paging anschalten kann, sonst würde der Prozess auf Speicher zugreifen können der schon längst für was anderes benutzt wird.

Was dynamisches Laden zur Laufzeit betrifft: http://en.wikipedia.org/wiki/Dynamic_loading (http://en.wikipedia.org/wiki/Dynamic_loading)
Interessant aber erklärt auch nicht wie dieses dynamische Linken hinter den Kulissen genau funktioniert.
Dafür habe ich mal wieder etwas (für mich) neues über C gelernt, nämlich das man void-Pointer nicht offiziell in Funktions-Pointer umwandeln kann. ;)


Willst du also das ganze Netzwerkzeug in den VFS-Server einbauen?
Das VFS direkt per TCP/UDP von Außen erreichbar zu machen fällt IMHO in die Kategorie "grob fahrlässig". ;)

Was du in der Regel für IPC brauchst sind einige wenige Metadaten und je nach Request möglicherweise noch ein großer Haufen Nutzdaten.
Von den Daten her ist das richtig, trotzdem sollte nicht vergessen werden das die meisten IPC-Anwendungen eher RPC sind also auch immer eine zugehörige Antwort benötigen, deswegen würde ich jetzt mal vermuten das einfache Dateioperationen für effizientes IPC nicht die erste Wahl sind, als Antwort beim RPC wird man oft mehr benötigen als die Rückgabe-Werte von read()/write() leisten können.

Die Metadaten in eine Pipe und die Nutzdaten eventuell über einen anderen Mechanismus wie Shared Memory klingt für mich eigentlich sehr sinnvoll (und deswegen ist das ja auch die Richtung, in die wir mit LIOv2 gehen).
Solange ich nicht weiß wie der Shared Memory ins IPC eingebunden ist (ob integraler Bestandteil ober über zusätzliche Syscalls) kann ich dazu eigentlich nix sagen, von daher warte ich einfach mal ab wie das Ergebnis werden wird. Auch wäre es interessant zu wissen ob derart erhaltener Speicher auch per IPC wieder weitergereicht werden kann, damit das Zero-Copy auch über mehrere Ebenen hinweg funktioniert, und was mit den Rändern der Pages passieren soll, die keine erwünschten Nutzdaten enthalten. Oder soll Ausrichten Pflicht sein?


Ansich würde ich schon sowas wie /dev/ haben wollen ....
Dem will ich mich anschließen, ich hatte mir das so vorgestellt das bei einem open(/dev/hdb6) vom VFS an die libc eine spezielle Antwort kommt in der dann das entsprechende Device-Interface eindeutig benannt ist (zusammen mit der Device-Art) und die libc für read/write/... direkt den Device-Treiber benutzt, alternativ kann die Antwort bei open natürlich auch "Access denied" sein. Beim close meldet die libc wieder ans VFS das es jetzt dieses Device geschlossen hat so das Devices die nur exklusiv benutzt werden können (Block-Devices gehören IMHO in diese Kategorie) dann auch wieder für andere zur Verfügung stehen. Das einzigste Problem das ich sehe ist das die Zugangsdaten zum Device-Treiber ja noch immer in dem Programm vorhanden sein können und demzuvollge auch benutzt werden können, von daher wäre es nicht schlecht wenn das VFS bei jedem open auf ein Device dem Treiber ein weiteres Set an Zugangsdaten mitteilt die dann auch das Programm bekommt und diese Zugangsdaten beim close vom VFS beim Device-Treiber wieder gelöscht werden. Sicher is Sicher.

Nur wie setzt man dann sowas (besser wo) um wie gleichzeitiger (unabhängiger) Lese-Zugriff und ein anderes Programm schreibt in diese Datei.
Kleines Beispiel: Programm A schreibt in eine Datei in 4 * 100 Byte Häppchen neue Daten und Programm B ließt die Datei aber ein einem Stück (also 400 Bytes) ein dann müssen die Schreibzugriffe von Programm A in der getätigten Reihenfolge für Programm B sichtbar werden. Wenn also Programm A die 4 Häppchen in aufsteigender Reihenfolge geschrieben hat dann ist es zwar möglich das Programm B in den Häppchen 1 und 2 schon neue Daten findet und in den Häppchen 3 und 4 noch alte Daten aber es darf niemals passieren das Programm B in den Häppchen 1 und 3 die neuen Daten sieht und in den Häppchen 2 und 4 noch alte Daten. Äquivalent auch wenn Programm A alles mit einem mal schreibt und Programm B in 4 unabhängigen Häppchen liest. Das muss Dein VFS zusichern können, solange das erfüllt ist kannst Du frei Cachen wie Du willst.

Der Benchritigungs-Mechanismus gehört auch meiner Meinung nach ganz klar ins VFS, dort müssen alle wirksamen/sichtbaren Änderungen durch. Wobei das mit Devices die am VFS vorbei gehen wieder schwierig wird aber dort könnte das VFS ja auch die Einrichtung einer Benachrichtigungsfunktion verweigern, ich vermute mal dass das dort keiner vermissen wird.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 17. October 2011, 09:49
Von den Daten her ist das richtig, trotzdem sollte nicht vergessen werden das die meisten IPC-Anwendungen eher RPC sind also auch immer eine zugehörige Antwort benötigen, deswegen würde ich jetzt mal vermuten das einfache Dateioperationen für effizientes IPC nicht die erste Wahl sind, als Antwort beim RPC wird man oft mehr benötigen als die Rückgabe-Werte von read()/write() leisten können.
Also erstmal: Wenn man es richtig macht, sollte die Antwort auf die RPCs oft asynchron verarbeitet werden. Zum Beispiel könnte eine Implementierung von cp während des Schreibens auf das Ziel schonmal weitermachen und das nächste Stück aus der Quelle lesen. Es gibt keinen guten Grund, warum es warten und nichts tun sollte.

Du hast natürlich insofern recht als die meiste Software nicht so geschrieben ist. Die Frage ist, ob Software, der es nicht wert ist, darauf zu achten, wirklich perfekte Performance braucht. Ist halt ein Trade-off von Einfachheit gegen Perfomance.

Aber wenn du es wirklich darauf anlegst und sagst, dass erst ein read() und danach ein write() zwei Syscalls und damit viel zu langsam sind (wofür ich ehrlichgesagt keine Grundlage sehe), dann könnte man theoretisch auch einen read_and_write()-Syscall bauen, der halt beides auf einmal macht und damit hast du dein synchrones RPC wieder. Man dürfte natürlich immer nur einen Request gleichzeitig auf dieser Pipe laufen haben, aber das ist ja bei synchronem RPC sowieso so. Ich sehe nur keinen rechten Vorteil darin, es so zu machen.

Zitat
Solange ich nicht weiß wie der Shared Memory ins IPC eingebunden ist (ob integraler Bestandteil ober über zusätzliche Syscalls) kann ich dazu eigentlich nix sagen, von daher warte ich einfach mal ab wie das Ergebnis werden wird. Auch wäre es interessant zu wissen ob derart erhaltener Speicher auch per IPC wieder weitergereicht werden kann, damit das Zero-Copy auch über mehrere Ebenen hinweg funktioniert, und was mit den Rändern der Pages passieren soll, die keine erwünschten Nutzdaten enthalten. Oder soll Ausrichten Pflicht sein?
Also dass Zero Copy möglich ist, sollte Grundvoraussetzung für einen vernünftigen Mechanismus sein. Darin sehe ich aber auch keine größeren Designprobleme.

Im Moment haben wir für SHM eigene Syscalls und der Speicher ist immer alignt (man fordert halt eine bestimmte Größe an und kriegt dann frische Pages dafür). Ich könnte mir aber auch andere, ins VFS integrierte Methoden vorstellen, zum Beispiel eine gemmapte Datei in shm:/ oder so.

Eine interessante Frage ist auch, ob man bei jedem Request neuen SHM nimmt oder ob der nicht einmal beim open() der Pipe angelegt und wiederverwendet werden kann. Je nachdem, wie man diese Frage beantwortet, muss man darauf achten, ob das Einrichten/Öffnen von SHM optimiert werden muss oder nicht.

Zitat
Dem will ich mich anschließen, ich hatte mir das so vorgestellt das bei einem open(/dev/hdb6) vom VFS an die libc eine spezielle Antwort kommt in der dann das entsprechende Device-Interface eindeutig benannt ist (zusammen mit der Device-Art) und die libc für read/write/... direkt den Device-Treiber benutzt
Damit verliert FlashBurn dann seinen gemeinsamen Dateizeiger nach dem fork(), den wollte er ja im VFS implementieren.

Zitat
Kleines Beispiel: Programm A schreibt in eine Datei in 4 * 100 Byte Häppchen neue Daten und Programm B ließt die Datei aber ein einem Stück (also 400 Bytes) ein dann müssen die Schreibzugriffe von Programm A in der getätigten Reihenfolge für Programm B sichtbar werden.
Da kommen wir in ein interessantes Gebiet. :)

Was man muss oder nicht muss hängt natürlich in erster Linie von der API ab, die man implementieren will. Ich halte die Vorgabe, die du machst, grundsätzlich für sinnvoll, aber theoretisch könnte man verlangen, dass A flushen muss, wenn es eine Reihenfolge garantieren will.

Das macht an dieser Stelle wahrscheinlich wenig Sinn, weil man es vermutlich fast automatisch so macht, dass das sowieso passt. Aber das gleiche Problem hast du ja auch wieder mit Plattenzugriffen: Garantierst du, dass diese vier Writes auch in der richtigen Reihenfolge auf der Platte ankommen? Wie spielen Dateisystem-Metadaten mit rein? Was muss wann aktualisiert werden, und was geht automatisch und was muss das Programm explizit anfordern?
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 17. October 2011, 10:58
Zitat von: erik
Aha, da ist dann aber doppelte Indirektion weil der Code im Programm ja nicht direkt das Plug-In aufruft sondern dies immer über einen Vermittlungsmechanismus tut, sehe ich das richtig?
Ich weiß nicht was du mit doppelter Indirektion meinst, aber es ist nix anderes als Irgendwo Funktionspointer zu haben, zumal die Performance in dem Fall keine Rolle spielt.

Zitat von: erik
Und der Loader findet diese Funktion anhand eines fest definierten Label-Namens?
Jap, du suchst z.B. per dlsym("initFS") nach einem Symbol und kannst die Funktion dann aufrufen. Im Endeffekt implementiert jedes Modul mind. die nötigen API Symbole (init, deinit usw).

Zitat von: erik
Interessant aber erklärt auch nicht wie dieses dynamische Linken hinter den Kulissen genau funktioniert.
Wo genau hapert es bei dir? Ich meine du lädst halt das Modul, was nen shared-object ist und löst alles externen Symbole auf. Dazu brauchst du entweder weitere Libs, aber auf jeden Fall brauchst du die Symbole von dem Programm was das Modul geladen hat.
Die Symbole die du aus deinem Programm heraus dann im Modul nutzen willst, holst du dir halt über dlsym().

Zitat von: erik
Das einzigste Problem das ich sehe ist das die Zugangsdaten zum Device-Treiber ja noch immer in dem Programm vorhanden sein können und demzuvollge auch benutzt werden können, von daher wäre es nicht schlecht wenn das VFS bei jedem open auf ein Device dem Treiber ein weiteres Set an Zugangsdaten mitteilt die dann auch das Programm bekommt und diese Zugangsdaten beim close vom VFS beim Device-Treiber wieder gelöscht werden. Sicher is Sicher.
Das will ich direkt mit Hilfe des IPCs lösen. Bei meinen Ports kannst du die Lese- und Schreibrechte grob angeben, heißt lesen global, ein Thread oder ein Task und schreiben auch global, ein Thread oder ein Task.
Für die Treiber bräuchte man dann 2 Ports, einen für das VFS (wo auch niemand anders reinschreiben darf) und einen für den Nutzer. Wenn der Nutzer eine Gerätedatei öffnet, sagt das VFS dem Treiber wer das war und der Treiber ändert entsprechend die Schreibrechte für den anderen Port. Genauso wenn der Nutzer die Gerätedatei wieder schließt, dann bekommt der Treiber ne Nachricht und ändert die Rechte entsprechend.

Zitat von: erik
Der Benchritigungs-Mechanismus gehört auch meiner Meinung nach ganz klar ins VFS, dort müssen alle wirksamen/sichtbaren Änderungen durch.
D.h. aber das wirklich alle Sachen auch durchs VFS müssen und dass man z.B. die libc nicht direkt mit dem Treiber/Dateisystem kommunizieren lassen kann.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 17. October 2011, 18:37
Hallo,


Also erstmal: Wenn man es richtig macht, sollte die Antwort auf die RPCs oft asynchron verarbeitet werden.
Ja, dem muss ich unbedingt zustimmen. Das bedeutet aber das man eigentlich 3 Arten von IPC benötigt: einmal asynchrones IPC wo der Sender einfach eine Message an den Empfänger schickt und sofort weiter arbeitet ohne zu blockieren und der Empfänger diese Message irgendwann (möglichst zeitnah) empfängt und verarbeitet es aber keine zugehörige Antwort gibt (das ist z.B. für GUI-Events oder für IRQ-Messages passend). Dann das synchrone blockierende IPC (also simples RPC) wo der Caller solange blockiert bis der Service den Request fertig bearbeitet hat und die zugehörige Antwort abschickt (die dann der Caller gleich quasi als Rückgabewert des IPC-Calls bekommt). Und dann noch das synchrone nicht-blockierende IPC (also erweitertes RPC) wo der Caller nicht blockiert aber dafür eine Callback-Funktion benennt die dann vom Kernel (mit der zugehörigen Antwort) aufgerufen wird wenn der Service den Request fertig bearbeitet hat. Die zwei synchronen Varianten sind für den Service gleichwertig (der muss ja nicht mal unbedingt wissen ob der Caller in der Zeit was anderes erledigt oder nicht, wenngleich ich diese Info der Höflichkeit halber dann doch mitgeben würde).

Es gibt keinen guten Grund, warum es warten und nichts tun sollte.
Keinerlei Widerspruch meinerseits.

Also dass Zero Copy möglich ist, sollte Grundvoraussetzung für einen vernünftigen Mechanismus sein.
Das sehe ich auf jeden Fall ganz genau so. Aber ob das auch wirklich funktioniert wenn der IPC-Mechanismus dafür eigenen (extra allozierten) Speicher nutzen will und nicht direkt mit dem User-Buffer (der z.B. bei read() mitgegeben wird) arbeitet möchte ich dann doch bezweifeln. Bei mir soll es auf jeden Fall möglich sein das z.B. beim write()-Aufruft für eine TCP-Connection der Ethernet-Controller auf der Netzwerkkarte direkt die Daten aus dem User-Buffer der eigentlichen Anwendung holt. Wenn das VFS nicht cachen würde würde das bei Dateien und dem AHCI-Controller ebenso klappen, auch wenn die Dateien z.B. vom NFS-Client kommen soll der Ethernet-Controller direkt auf den Cache-Speicher vom VFS zugreifen. Dieses komplett durchgängige Zero-Copy ist IMHO für optimale Performance essentiell wichtig.

Damit verliert FlashBurn dann seinen gemeinsamen Dateizeiger nach dem fork(), den wollte er ja im VFS implementieren.
Hm, stimmt auffallend, da wird sich FlashBurn wohl oder übel einen Kompromiss einfallen lassen müssen.

Was man muss oder nicht muss hängt natürlich in erster Linie von der API ab, die man implementieren will.
Um das zu beurteilen weiß ich nicht genug darüber was POSIX oder auch die WinAPI an Kohärenz zusichern. Wie ich auch schon mal beim Thema mit dem gemeinsamen File-Pointer erklärt habe denke ich das es nur extrem wenige Szenarien gibt in denen tatsächlich mehrere Programme gleichzeitig auf einer Datei rumarbeiten. Das z.B. mehrere gcc-Instanzen gleichzeitig die selbe H-Datei einlesen ist sicher nichts ungewöhnliches aber das echt ein Programm eine Datei ändert und ein anderes diese Datei gleichzeitig ließt halte ich für ziemlich Praxisfern. Trotzdem muss das VFS hier natürlich irgendwelche Zusicherungen machen. Meine Vorgabe war wohl auch eher eine Art Diskussionsgrundlage und keine endgültige Festlegung, so oder so ähnlich würde ich persönlich das versuchen umzusetzen, einfach weil ich das so für sinnvoll erachte, ob das auch in der Praxis nötig oder überhaupt nützlich ist muss ich aber auch erst mal nachlesen.

Den Datei-System-Treiber oder gar den Block-Device-Treiber würde ich da aber eher nicht mit einbeziehen wollen (das Dateisystem wird schon eigene Ansprüche haben um z.B. immer einen einigermaßen konsistenten Zustand auf der HDD zu erreichen), ich denke hier ist das VFS alleine gefordert, alles was immer über das VFS geht kann das VFS schließlich auch alleine konsistent halten.


So langsam sollten wir dieses ganze IPC-Zeugs wirklich in einen eigenen Thread verlagern.


Jap, du suchst z.B. per dlsym("initFS") nach einem Symbol und kannst die Funktion dann aufrufen.
Okay, ich glaube so langsam hab ich das kapiert, Danke.

D.h. aber das wirklich alle Sachen auch durchs VFS müssen und dass man z.B. die libc nicht direkt mit dem Treiber/Dateisystem kommunizieren lassen kann.
Also direkt mit dem Dateisystem-Treiber würde ich eh keine beliebige Applikation kommunizieren lassen, was soll das auch bringen, echte Dateizugriffe müssen IMHO immer durch das VFS. Bei den Block-/Character-Devices sehe ich das anders aber gerade die sollten IMHO ja eh auf eigene Weise geshared werden bzw. die Block-Devices immer nur exklusiv geöffnet werden dürfen so das in beiden Fällen ein Benachrichtigungsmechanismus eh nur von geringem Nutzen oder sehr proprietär ist. Ich persönlich hätte zumindest kein Problem wenn das VFS beim Versuch Benachrichtigungen für Devices einzurichten immer "Service not available" antwortet.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 17. October 2011, 20:27
Das bedeutet aber das man eigentlich 3 Arten von IPC benötigt: [...]
Dazu will ich nur mal festhalten, dass wir offenbar eine unterschiedliche Terminologie benutzen, bevor das noch zu Schwierigkeiten führt. Bei synchron sind wir uns einig, aber was du "synchron nicht-blockierend" nennst, ist für mich asynchron (welche zwei Sachen sollten denn da synchron sein?). Das dritte ist bei mir einfach eine Funktion ohne Rückgabewert. ;)

Inhaltlich will ich dir nicht widersprechen, aber ich bin mir nicht sicher, ob die Unterscheidung besonders relevant ist. Ich kann z.B. mit der Pipe alle drei Varianten ohne weiteres abbilden.

Zitat
Aber ob das [Zero Copy] auch wirklich funktioniert wenn der IPC-Mechanismus dafür eigenen (extra allozierten) Speicher nutzen will und nicht direkt mit dem User-Buffer (der z.B. bei read() mitgegeben wird) arbeitet möchte ich dann doch bezweifeln. Bei mir soll es auf jeden Fall möglich sein das z.B. beim write()-Aufruft für eine TCP-Connection der Ethernet-Controller auf der Netzwerkkarte direkt die Daten aus dem User-Buffer der eigentlichen Anwendung holt.
An dieser Stelle bist du natürlich mit deinen bytegenauen Segmenten fein raus. Das gemeine Fußvolk hat nur Page-Granularität und damit geht das nicht ganz so uneingeschränkt. Ich sehe darin allerdings auch kein Problem: Wenn read()/write() kopieren, dann tun sie es eben. Das ist immer noch nur eine einzige Kopie und wird in den meisten Fällen nicht weh tun. Wenn ich sage, dass es die Möglichkeit geben muss, heißt das für mich, dass es auch akzeptabel ist, wenn das Programm dafür ein bisschen Aufwand treiben und meinetwegen ausgerichtete Puffer benutzen muss (mit O_DIRECT unter Linux muss man das ja auch, zum Beispiel).

Zitat
Was man muss oder nicht muss hängt natürlich in erster Linie von der API ab, die man implementieren will.
Um das zu beurteilen weiß ich nicht genug darüber was POSIX oder auch die WinAPI an Kohärenz zusichern.
Wieso musst du das dazu wissen? Du hast doch sicher eine eigene Meinung, was sinnvoll ist?

Um nur mal ein paar Varianten auf einem POSIX-System aufzuzählen: Normalerweise ist alles, was man mit read()/write() macht, sofort für andere Programme sichtbar, aber nicht zwingend auf der Platte. Mit fsync() zwingt man es auf die Platte, fdatasync() schreibt die Daten auf die Platte, aber aktualisiert die Metadaten des Dateisystems nicht. Mit O_SYNC oder O_DSYNC kann man automatisch das eine oder andere nach jedem write() machen lassen. Wenn man unter Linux O_DIRECT benutzt, wird der Pagecache umgangen, aber die Daten könnten noch in einem flüchtigen Schreibcache der Platte (oder ähnliches) hängen. Und dann gibt es noch die stdio.h-Funktionen, die in der Regel erstmal puffern und gar nichts ans OS weitergeben, so dass die Änderungen erst nach einem vollen Block bzw. einem Zeilenumbruch oder einem expliziten fflush() für andere Programme sichtbar sind.

Was ich sagen will: Es gibt da annähernd unendlich viele Möglichkeiten und man sollte sich schon Gedanken drüber machen, was man eigentlich haben will.

Zitat
Das z.B. mehrere gcc-Instanzen gleichzeitig die selbe H-Datei einlesen ist sicher nichts ungewöhnliches aber das echt ein Programm eine Datei ändert und ein anderes diese Datei gleichzeitig ließt halte ich für ziemlich Praxisfern.
Aber nicht unbedingt praxisfern, weil es nicht nützlich wäre, sondern vor allem deswegen, weil es nicht leicht ist, das korrekt zu implementieren. Da kommt man dann schnell in die Nähe von Clusterdateisystemen oder ähnlichem...

Zitat
Den Datei-System-Treiber oder gar den Block-Device-Treiber würde ich da aber eher nicht mit einbeziehen wollen (das Dateisystem wird schon eigene Ansprüche haben um z.B. immer einen einigermaßen konsistenten Zustand auf der HDD zu erreichen), ich denke hier ist das VFS alleine gefordert, alles was immer über das VFS geht kann das VFS schließlich auch alleine konsistent halten.
Das VFS weiß nicht, was nötig ist, um Anwendungsdaten konsistent zu halten, wenn die Anwendung es ihm nicht sagt. Du kannst natürlich immer deine Entsprechung von O_SYNC benutzen, dann ist es ganz bestimmt immer konsistent, aber dann ist auch deine Performance konsistent niedrig...

Und der Dateisystemtreiber muss dem Blocktreiber auch mitteilen können, was für eine Art von Konsistenz er gerade braucht.

Zitat
So langsam sollten wir dieses ganze IPC-Zeugs wirklich in einen eigenen Thread verlagern.
Tu dir keinen Zwang an. ;)
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 18. October 2011, 08:41
Hallo,


Dazu will ich nur mal festhalten, dass wir offenbar eine unterschiedliche Terminologie benutzen
Ja, das war mir schon während des Schreibens bewusst, mir ist nur keine bessere Benahmsung eingefallen (Du weißt ja dass das nicht gerade meine Stärke ist), "Funktion ohne Rückgabewert" klingt jedenfalls irgendwie doof gegenüber '"synchron" und "asynchron". Mach doch da Bitte mal einen geeigneten Vorschlag.

Die Unterscheidung zwischen diesen 3 Varianten halte ich für durchaus relevant da es ja Unterschiede bei der Dauer des Memory-Sharings gibt (zumindest wenn man das, so wie ich, auch tatsächlich über Memory-Sharing machen will). Gerade bei der ganz simplen Variante ("Funktion ohne Rückgabewert") ist es für den Caller durchaus relevant wann er den so gesharten Speicher wieder für andere Dinge nutzen kann. Bei mir hab ich vorgesehen das der Call in diesem Fall das Kopieren erzwingen kann (was ja gerade für kleine Events oder auch IRQ-Messages auch absolut kein Problem ist, 20 oder 30 Bytes zu kopieren dürfte meistens schneller sein als ein neues Mapping anzulegen).

Ich kann z.B. mit der Pipe alle drei Varianten ohne weiteres abbilden.
Das man das alles jeweils auch mit anderen Mitteln abbilden kann ist zwar richtig aber ob dabei dann auch noch eine anständige Performance rauskommt muss doch arg bezweifelt werden. Ich bin schon der Meinung das bei einem Mechanismus, der bei einem Micro-Kernel-OS von so zentraler Bedeutung ist wie eben IPC, nicht nur das "Ob" sondern auch das "Wie" zählt. Dass das flachspeichernde Fußvolk hier ein paar zusätzliche Nachteile zu tragen hat ringt mir aber nur sehr begrenzt Mitleid ab. ;)

(mit O_DIRECT unter Linux muss man das ja auch, zum Beispiel).
Aha, das wusste ich noch gar nicht, ich sollte mich damit wirklich mal etwas näher beschäftigen, schon um einfach mal ein Gefühl dafür zu bekommen wo ich gegenüber der Konkurenz eigentlich stehen könnte.

Wieso musst du das dazu wissen?
Es ist IMHO schon wichtig zu wissen was die anderen so bieten um auch abschätzen zu können was die typischen Programme so verlangen könnten. Man will ja schließlich kein unperformantes Over-Engineering oder untaugliches Under-Engineering abliefern.

Du hast doch sicher eine eigene Meinung, was sinnvoll ist?
Das ich zu fast allem eine eigene Meinung habe ist doch hier schon mehr als einmal unangenehm aufgefallen.

Um nur mal ein paar Varianten auf einem POSIX-System aufzuzählen: ....
Danke, das war doch mal sehr informativ.
Das alles was auch tatsächlich ins VFS geht sofort für andere sichtbar ist spricht dafür dass das VFS unter POXIS recht streng mit der Kohärenz ist (also vermutlich in der Nähe meines Vorschlages von vorgestern Abend liegt). Die libc wird intern sicher immer nur einzelne Blöcke Cachen (vermutlich eine Pagegröße o.ä.) um damit vor allem kleine fread()/fwrite()-Häppchen möglichst effizient ans OS übergeben zu können, das ist wichtig und sollte von einer guten libc auch angemessen unterstützt werden aber für die große Kohärenz im Gesamtsystem ist das IMHO nur von untergeordneter Bedeutung. Das die HDD die per SATA angelieferten Daten nicht sofort auf die Magnetscheiben wegschreibt ist ein bekanntes Problem, trotzdem ist die HDD von Außen betrachtet (wenn wir mal von plötzlichem Stromausfall u.ä. absehen) immer Konsistent (wenn ein Sektor gelesen wird der nur im HDD-Cache neu ist aber noch nicht auf den Magnetscheiben gespeichert wurde dann werden die aktuellen Daten aus dem Cache geliefert). Diese Vorgehensweise trifft in dem Stapel Applikation/VFS/Dateisystem/Block-Device auf jede Schicht zu. Wenn in einem Programm per fwrite() ein paar Bytes geschrieben wurden dann wird fread beim anschließenden Lesen der selben Bytes auch immer die aktuellen (gerade geschriebenen) Daten liefern egal ob diese Bytes bereits ans VFS durchgereicht wurden oder nicht. Bei einer SATA-HDD mit NCQ kann man aber bei jedem Commando mitteilen welches andere Commando (von den noch ausstehenden) abgeschlossen sein muss bevor dieses ausgeführt wird, damit kann z.B. erreicht werden das die Nutzdaten bereits geschrieben sind bevor die Metadaten aktualisiert werden (eine Methode die die besseren Dateisystem-Treiber im Linux-Kernel bestimmt nutzen, insbesondere jene die Journaling können), unbeteiligte andere Commandos kann die HDD dazu in jeder beliebigen Reihenfolge ausführen (wobei diese untereinander auch wieder Abhängigkeiten haben können).

Zitat
Das z.B. mehrere gcc-Instanzen gleichzeitig die selbe H-Datei einlesen ist sicher nichts ungewöhnliches aber das echt ein Programm eine Datei ändert und ein anderes diese Datei gleichzeitig ließt halte ich für ziemlich Praxisfern.
Aber nicht unbedingt praxisfern, weil es nicht nützlich wäre, sondern vor allem deswegen, weil es nicht leicht ist, das korrekt zu implementieren.
Doch, ich denke auch weil es tatsächlich nicht nützlich ist. Wenn Du da anderer Meinung bist nenne doch mal ein nützliches Beispiel wo eine Datei von einem Programm wahlfrei modifiziert wird während ein anderes Programm diese Datei einließt, mir fällt da absolut nichts ein. Das einzigste was wenigstens grob in die Nähe kommt sind Log-Dateien aber die werden nicht wahlfrei beschrieben sondern es wird (für gewöhnlich) ausschließlich angehängt und andere Programme sehen die neuen (angehängten) Log-Meldungen bereits oder eben noch nicht. In meinem VFS möchte ich es so machen das eine Datei die zum beliebigen Beschreiben geöffnet wurde nicht mehr einfach so von anderen Programmen geöffnet werden kann, bei Dateien die als "Append-Only" geöffnet wurden (also typischerweise Log-Dateien) ist das natürlich anders.

Zitat
So langsam sollten wir dieses ganze IPC-Zeugs wirklich in einen eigenen Thread verlagern.
Tu dir keinen Zwang an. ;)
Ich kann leider keine Admin-Werkzeuge in meiner Forumansicht sehen so das es mir leider nicht möglich ist die letzten Posts dieses Threads in einen neuen Thread zu verschieben. ;)


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 18. October 2011, 10:16
Ja, das war mir schon während des Schreibens bewusst, mir ist nur keine bessere Benahmsung eingefallen (Du weißt ja dass das nicht gerade meine Stärke ist), "Funktion ohne Rückgabewert" klingt jedenfalls irgendwie doof gegenüber '"synchron" und "asynchron". Mach doch da Bitte mal einen geeigneten Vorschlag.
Wie gesagt, ich glaube nicht, dass es einen entscheidenden Unterschied macht, deswegen brauche ich dafür auch selten ein Wort. Ich könnte dir höchstens eine pascalige Unterscheidung zwischen Funktion und Prozedur anbieten...

Zitat
Die Unterscheidung zwischen diesen 3 Varianten halte ich für durchaus relevant da es ja Unterschiede bei der Dauer des Memory-Sharings gibt
Davon bin ich nicht ganz überzeugt. Ja, es gibt Unterschiede in der Dauer des Memory Sharing. Die haben aber nicht zwingend was mit der Art des RPC zu tun, sondern damit, was der RPC mit dem übergebenen Speicher machen will. Wer sagt denn, dass der Empfänger den Speicher nicht weiterbenutzen will, nachdem er eine Antwort geschickt hat? (Eine Antwort in der Art "Ich hab den Puffer bekommen, der Inhalt sieht gültig aus, danke, ich kann jetzt allein damit weiterarbeiten")

Oder umgekehrt könnte es passieren, dass der Speicher eigentlich schon wieder unbenutzt ist, während noch andere Operationen durchgeführt werden müssen, bevor man eine Antwort schicken kann.

Von der formalen Art der Funktion auf ihr Verhalten zu schließen finde ich fragwürdig.

Zitat
Bei mir hab ich vorgesehen das der Call in diesem Fall das Kopieren erzwingen kann (was ja gerade für kleine Events oder auch IRQ-Messages auch absolut kein Problem ist, 20 oder 30 Bytes zu kopieren dürfte meistens schneller sein als ein neues Mapping anzulegen).
Ja. Kleine Sachen in die Pipe, größere in den SHM. ;)

Zitat
Das man das alles jeweils auch mit anderen Mitteln abbilden kann ist zwar richtig aber ob dabei dann auch noch eine anständige Performance rauskommt muss doch arg bezweifelt werden. Ich bin schon der Meinung das bei einem Mechanismus, der bei einem Micro-Kernel-OS von so zentraler Bedeutung ist wie eben IPC, nicht nur das "Ob" sondern auch das "Wie" zählt.
Dann erklär doch mal, wo ich bei meinem Ansatz die ganze Performance verliere. Oder welche der drei Arten ich nicht vernünftig, sondern nur irgendwie abbilden kann.

Zitat
Das alles was auch tatsächlich ins VFS geht sofort für andere sichtbar ist spricht dafür dass das VFS unter POXIS recht streng mit der Kohärenz ist (also vermutlich in der Nähe meines Vorschlages von vorgestern Abend liegt).
Ja, ich denke auch, dass deine Vorstellung relativ gut darauf passt. (An dieser Stelle muss man vielleicht noch erwähnen, dass unter Linux nicht alles POSIX-kompatibel ist, NFS bietet diese Kohärenz nicht unbedingt)

Zitat
Die libc wird intern sicher immer nur einzelne Blöcke Cachen (vermutlich eine Pagegröße o.ä.) um damit vor allem kleine fread()/fwrite()-Häppchen möglichst effizient ans OS übergeben zu können, das ist wichtig und sollte von einer guten libc auch angemessen unterstützt werden
Jede standardkonforme libc muss das unterstützen. Und mit setvbuf() kannst du den Puffer selber so einrichten, wie du es für richtig hältst.

Zitat
(wenn wir mal von plötzlichem Stromausfall u.ä. absehen)
Und genau das ist falsch!

Stromausfälle, Abstürze und so weiter sind kein theoretisches, sondern ein ganz reales Problem. Wenn nach einem Stromausfall die Datenbank oder sogar das ganze Dateisystem inkonsistent wird und mir womöglich komplett um die Ohren fliegt, dann bleibt mir beim besten Willen nichts anderes übrig als mal wieder das K-Attribut an den zuständigen Code zu verteilen.

Dateisysteme in ganz besonderem Maß, aber auch normale Anwendungen müssen sicherstellen, dass die Daten auf der Platte immer soweit konsistent sind, dass man bei einem Absturz keine Korruption, sondern maximal Datenverlust bekommt. Wenn sie das nicht tun, sind sie buggy.

Zitat
Doch, ich denke auch weil es tatsächlich nicht nützlich ist. Wenn Du da anderer Meinung bist nenne doch mal ein nützliches Beispiel wo eine Datei von einem Programm wahlfrei modifiziert wird während ein anderes Programm diese Datei einließt, mir fällt da absolut nichts ein.
Backup eines Snapshots im Image einer laufenden VM. Sonstige Zugriffe des Host auf das Image. Clusterdateisystem auf einem Image, das von zwei VMs geteilt wird.

Ich weiß, ein bisschen einseitig, aber das ist halt mein Arbeitsbereich. Andere können sicher andere Beispiele liefern. ;)

Zitat
Ich kann leider keine Admin-Werkzeuge in meiner Forumansicht sehen so das es mir leider nicht möglich ist die letzten Posts dieses Threads in einen neuen Thread zu verschieben. ;)
Hm, da wüsste ich sowieso nicht, wo anpacken, das war ja mal wieder ein fließender Übergang... Ich dachte einfach einen neuen Thread erstellen, wo wir weitermachen.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 18. October 2011, 13:10
Hallo,


Ich könnte dir höchstens eine pascalige Unterscheidung zwischen Funktion und Prozedur anbieten...
Hm, klingt irgendwie interessant, aber ich benutze bereits 'proc' als Abkürzung für Process so das mir da jetzt nichts geeignetes für Procedure einfällt. Ich hatte da noch an simple ('smpl') gedacht.

Die haben aber nicht zwingend was mit der Art des RPC zu tun, sondern damit, was der RPC mit dem übergebenen Speicher machen will. Wer sagt denn, dass der Empfänger den Speicher nicht weiterbenutzen will, nachdem er eine Antwort geschickt hat? (Eine Antwort in der Art "Ich hab den Puffer bekommen, der Inhalt sieht gültig aus, danke, ich kann jetzt allein damit weiterarbeiten")
Das hängt wohl auch davon ab ob das Sharing integraler Bestandteil des IPC ist (so wie bei mir) oder ob das Sharing außen herum gemanaged wird und das IPC nur dem Austausch von SHM-IDs + Requests/Responses dient (so wie ihr das wohl machen wollt und FlashBurn wohl auch, falls ich Euch da missverstanden habe so Bitte ich um konkrete Aufklärung). Bei ersterem muss das IPC-System natürlich klar angeben wann und wie der Speicher geshared wird damit sich die Applikation auch darauf verlassen kann und damit wird es dann eben erforderlich dass das IPC hier diese 3 Szenarien explizit anbietet (wenn man nicht mit einem Weg einen anderen Weg emulieren möchte), dafür komme ich mit einem Minimum an Modus-Wechseln und Syscalls aus (für die simple Variante und die blockierende synchrone Variante sind nur 4 Modus-Wechsel und für die nichtblockierende asynchrone Variante sind nur 6 Modus-Wechsel erforderlich, um Missverständnissen vorzubeugen: ein Modus-Wechsel ist jeweils zwischen User-Mode und Kernel-Mode oder anders herum). Vor allem hab ich bei meinen Segmenten das Problem das ich Speicher nicht so ohne weiteres von einem Prozess zum nächsten Migrieren kann (also das er im ersten Prozess komplett verschwindet und im zweiten Prozess dauerhaft enthalten bleibt), von daher ist bei mir das Sharing immer zeitlich limitiert (also an den IPC-Vorgang gebunden), ich denke für die überwiegende Mehrheit der IPC/RPC-Vorgänge ist das völlig in Ordnung. Für Dein Beispiel mit "ich mach dann mal alleine weiter" würde ich das so lösen das der eigentlich IPC-Vorgang so lange dauert bis der Service wirklich fertig ist (irgendwann muss er ja den PopUp-Thread wieder beenden), aber dieser Zwischenbericht über einen zusätzlichen simplen IPC-Call an den eigentlichen Client übermittelt wird, der Client muss dann in seinem Request per async/nonblocking IPC eben ein zusätzliches Message-Target/Port-Nummer/sonstwas mitgeben wo der Service diese Zwischenberichte unkompliziert abliefern kann. Der aus meiner Sicht wesentliche Vorteil meiner Methode ist der das bei mir immer noch der Postbote kommt und ein passend verpacktes Packet abliefert und niemand zur Post hin muss um dort etwas abzuholen und dabei auch noch die Schwierigkeit hat immer eine passende Verpackung dabei zu haben.

Oder umgekehrt könnte es passieren, dass der Speicher eigentlich schon wieder unbenutzt ist, während noch andere Operationen durchgeführt werden müssen, bevor man eine Antwort schicken kann.
Dazu könnte der Service aber auch einen weiteren Workerthread erstellen und den IPC-Vorgang sofort beenden.

Von der formalen Art der Funktion auf ihr Verhalten zu schließen finde ich fragwürdig.
Naja, ich betrachte das lieber anders herum, ich schaue mir die wesentlichen Verhaltensmuster an und biete dafür passende Unterstützung. Solange ich den selteneren Verhaltensmustern (die ich nicht direkt unterstütze) nicht unnötig viele bzw. teure Steine in den Weg lege ist das aus meiner Sicht auch Okay. Bis jetzt hast Du zumindest noch kein Szenario genannt das ich nicht relativ performant und unkompliziert unterstützen kann.

Dann erklär doch mal, wo ich bei meinem Ansatz die ganze Performance verliere. Oder welche der drei Arten ich nicht vernünftig, sondern nur irgendwie abbilden kann.
Also ich denke das einer der wesentlichen Performanceverluste dadurch kommt das Du nicht direkt mit den User-Buffern der Applikation arbeitest sondern für das IPC extra SHM erstellst so das Du oft (wenn auch sicher nicht immer) kopieren musst und dann vermute ich mal das für das SHM oft (wenn auch sicher nicht immer) zusätzliche Syscalls erforderlich sind. Alternativ könntest Du wegen den Syscalls natürlich einen IPC-Mega-Syscall anbieten der alles optional unterstützt (ob das dann wieder dem KISS-Pinzip dient sei jetzt mal egal).

NFS bietet diese Kohärenz nicht unbedingt
Davon bin ich wiederum nicht so wirklich überzeugt, ich denke das hängt davon ab wo man die vereinheitlichende Linie zieht. Wenn man nur das betrachtet was der NFS-Client beim NFS-Server wirklich abliefert bzw. frisch holt dann dürfte das wohl schon Kohärent sein, aber da der NFS-Client intern sicher etwas aufwendigeren Cache betreibt als z.B. die libc dürfte das etwas stärker von den Anwendungen, die auf den verschiedenen Client-Computern laufen, entkoppelt sein. Das setvbuf() aus der libc gibt auch immer nur einen einzelnen Buffer vor der immer nur einen einzelnen zusammenhängenden Abschnitt der Datei puffern kann, das ist also ein Cache mit nur einer einzigen Cache-Line, die kann zwar recht groß sein und muss wohl auch nicht unbedingt eine Cache-Line-Boundary einhalten aber es bleibt eine einzelne Cache-Line die auch immer komplett geleert werden muss bevor neue Daten rein können. Mit einer einzelnen Cache-Line ist die Kohärenz eigentlich immer gewahrt. Innerhalb des VFS, oder auch eines NFC-Clients, kommen wohl für gewöhnlich deutlich komplexere Cache-Mechanismen zum Einsatz.

Zitat
(wenn wir mal von plötzlichem Stromausfall u.ä. absehen)
Und genau das ist falsch!
Natürlich gibt es in der Realität Stromausfall und andere Katastrophen die das Dateisystem auf der HDD bedrohen, ich wollte das auch nur für meine Betrachtungen bezüglich der Kohärenz die das VFS den Anwendungen bietet mal außen vor lassen, das ist eine Sache die der Dateisystem-Treiber über den Block-Device-Treiber direkt mit der HDD regelt (z.B. mit dem von mir angesprochenen SATA-Mechanismus der voneinander anhängigen SATA-Kommandos). Ein System das hierbei nicht zumindest grundlegende Vorkehrungen trifft würde sich Deines beliebten K-Attributes auch mehr als nur würdig erweisen.

Backup eines Snapshots im Image einer laufenden VM. Sonstige Zugriffe des Host auf das Image. Clusterdateisystem auf einem Image, das von zwei VMs geteilt wird.
Das mit den Snap-Shots ist natürlich ein wichtiger Punkt (den muss ich mir gleich mal notieren) aber das ist eigentlich auch eine Aufgabe die das VFS erledigen können sollte, alle Änderungen an einer Datei die vor dem Snap-Shot-Kommando im VFS ankommen sind noch enthalten und alles was danach kommt eben nicht mehr. Hier könnte man für die Anwendungen noch eine höfliche Benachrichtigung generieren damit diese auch alle ihre Änderungen in einem konsistenten Zustand in den Snap-Shot bekommen.

Andere können sicher andere Beispiele liefern. ;)
Das wäre sehr schön, den Snap-Shots sind nicht unbedingt das was ich meinte.

Hm, da wüsste ich sowieso nicht, wo anpacken, das war ja mal wieder ein fließender Übergang... Ich dachte einfach einen neuen Thread erstellen, wo wir weitermachen.
Tja, da müsste mal einer den ersten Schritt tun, aber eigentlich driften wir auch schon die ganze Zeit zwischen zwei Themen hin und her. Ich bin fast der Meinung das wir unser bisheriges Konversationsschema beibehalten können. Wozu überhaupt Threads in diesem Forum? ;)


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 18. October 2011, 14:45
Hm, klingt irgendwie interessant, aber ich benutze bereits 'proc' als Abkürzung für Process so das mir da jetzt nichts geeignetes für Procedure einfällt. Ich hatte da noch an simple ('smpl') gedacht.
sync/async/detached?

Zitat
Das hängt wohl auch davon ab ob das Sharing integraler Bestandteil des IPC ist (so wie bei mir) oder ob das Sharing außen herum gemanaged wird und das IPC nur dem Austausch von SHM-IDs + Requests/Responses dient (so wie ihr das wohl machen wollt und FlashBurn wohl auch, falls ich Euch da missverstanden habe so Bitte ich um konkrete Aufklärung).
Umgekehrt wird ein Schuh draus: Wenn das IPC-Design es unmöglich macht, dass ein Prozess Speicher weiterbenutzen kann nachdem er die Antwort geschickt hat, dann muss man sich überlegen, ob Memory Sharing fest daran zu koppeln wirklich eine gute Idee ist.

Der Workaround, den du beschreibst, hinterlässt bei mir den Eindruck, dass das nicht wirklich mit deinem Design zusammenpasst, sondern du dir mit irgendwelchen Zwischennachrichten behilfst, die eigentlich nicht vorgesehen sind und dein einfaches System komplizierter machen (gibt es dann einen zusätzlichen Typ "RPC mit zwei synchronen und einer asynchronen Zwischennachricht und einer asynchronen Endnachricht"?) Dasselbe gilt für den Workerthread, den man irgendwo in der Mitte des Handlers starten soll, wenn man den IPC-Speicher nicht mehr braucht.

Oder mit deiner eigenen Aussage von oben: Man kann das zwar alles irgendwie abbilden, aber ob es dann so toll ist, ist die andere Frage.

Zitat
Vor allem hab ich bei meinen Segmenten das Problem das ich Speicher nicht so ohne weiteres von einem Prozess zum nächsten Migrieren kann (also das er im ersten Prozess komplett verschwindet und im zweiten Prozess dauerhaft enthalten bleibt), von daher ist bei mir das Sharing immer zeitlich limitiert (also an den IPC-Vorgang gebunden), ich denke für die überwiegende Mehrheit der IPC/RPC-Vorgänge ist das völlig in Ordnung.
Schön ist es nicht und kann manche Fälle auf Senderseite etwas umständlicher machen, weil er seine Speicherfreigabe verzögern muss, bis der RPC fertig ist. Interessant könnte der Fall werden, wenn der Empfänger das alleinige Schreibrecht auf den erhaltenen Puffer haben will (und das will er in aller Regel nachdem er den Puffer geprüft hat, sonst kriegst du oft genug ein sicherheitskritisches Race). Dazu müsste er in deinem Konzept wahrscheinlich kopieren.

Zitat
Der aus meiner Sicht wesentliche Vorteil meiner Methode ist der das bei mir immer noch der Postbote kommt und ein passend verpacktes Packet abliefert und niemand zur Post hin muss um dort etwas abzuholen und dabei auch noch die Schwierigkeit hat immer eine passende Verpackung dabei zu haben.
Naja, damit kommen wir direkt wieder zur Diskussion von Popup-Threads. Ich halte es nach wie vor übertrieben, von allen Programmen zu verlangen, dass sie threadsafe sind.

Zitat
Also ich denke das einer der wesentlichen Performanceverluste dadurch kommt das Du nicht direkt mit den User-Buffern der Applikation arbeitest sondern für das IPC extra SHM erstellst so das Du oft (wenn auch sicher nicht immer) kopieren musst und dann vermute ich mal das für das SHM oft (wenn auch sicher nicht immer) zusätzliche Syscalls erforderlich sind. Alternativ könntest Du wegen den Syscalls natürlich einen IPC-Mega-Syscall anbieten der alles optional unterstützt (ob das dann wieder dem KISS-Pinzip dient sei jetzt mal egal).
Naja, ein IPC-Mega-Syscall ist ja genau das was du machst. Der ist wohl unabhängig davon, ob am Ende Pipes oder Popup-Threads stehen.

Was die Puffer angeht, führt bei einem x86-OS mit flachem Speichermodell nichts dran vorbei, das ist keine IPC-Designentscheidung.

Zitat
NFS bietet diese Kohärenz nicht unbedingt
Davon bin ich wiederum nicht so wirklich überzeugt, ich denke das hängt davon ab wo man die vereinheitlichende Linie zieht. Wenn man nur das betrachtet was der NFS-Client beim NFS-Server wirklich abliefert bzw. frisch holt dann dürfte das wohl schon Kohärent sein, aber da der NFS-Client intern sicher etwas aufwendigeren Cache betreibt als z.B. die libc dürfte das etwas stärker von den Anwendungen, die auf den verschiedenen Client-Computern laufen, entkoppelt sein.
Änderungen an einer Datei auf NFS müssen erst dann auf dem Server sichtbar sein, wenn die Datei geschlossen wird.

Zitat
Natürlich gibt es in der Realität Stromausfall und andere Katastrophen die das Dateisystem auf der HDD bedrohen, ich wollte das auch nur für meine Betrachtungen bezüglich der Kohärenz die das VFS den Anwendungen bietet mal außen vor lassen, das ist eine Sache die der Dateisystem-Treiber über den Block-Device-Treiber direkt mit der HDD regelt (z.B. mit dem von mir angesprochenen SATA-Mechanismus der voneinander anhängigen SATA-Kommandos). Ein System das hierbei nicht zumindest grundlegende Vorkehrungen trifft würde sich Deines beliebten K-Attributes auch mehr als nur würdig erweisen.
Es ist aber eben gerade keine Sache, die das Dateisystem mit dem Blocktreiber allein ausmachen kann. Wenn du nur Dateisystem-Metadaten konsistent halten willst, dann reicht das. Aber auch Anwendungen haben für ihre Dateien Anforderungen in Sachen Konsistenz, und das heißt, dass die Anwendungen dem VFS sagen müssen können, was Sache ist. Nur dann kann das VFS dem Dateisystem erklären, wo die Abhängigkeiten sind, so dass das dem Blocktreiber wiederum erklären kann, wo es besser mal ein FLUSH runterschickt. Wenn irgendwo zwischen Anwendungscode und Platte eine Schicht nicht ordentlich mitspielt, wird es schwierig.

Es ist also schon die Frage, welches Interface das VFS dafür zur Verfügung stellen sollte. Nur einen Holzhammer fsync(fd); oder irgendwelche feineren Operationen?

Zitat
Das mit den Snap-Shots ist natürlich ein wichtiger Punkt (den muss ich mir gleich mal notieren) aber das ist eigentlich auch eine Aufgabe die das VFS erledigen können sollte, alle Änderungen an einer Datei die vor dem Snap-Shot-Kommando im VFS ankommen sind noch enthalten und alles was danach kommt eben nicht mehr. Hier könnte man für die Anwendungen noch eine höfliche Benachrichtigung generieren damit diese auch alle ihre Änderungen in einem konsistenten Zustand in den Snap-Shot bekommen.
Hm, ich würde sagen, dass dazu das Dateisystem mitspielen sollte. Ansonsten musst du ja alles im Speicher machen.

Der Fall mit zwei VMs und einem Clusterdateisystem gefällt dir nicht, wenn du noch was anderes als Snapshots willst?
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 18. October 2011, 18:03
Zitat von: erik
und FlashBurn wohl auch, falls ich Euch da missverstanden habe so Bitte ich um konkrete Aufklärung
Bei mir gibt es SHM nur in Verbindung mit IPC. Ich mache es daher mehr oder weniger so wie du. Du kannst beim Senden einer Nachricht angeben, dass du nen gewussen Speicherbereich mitsenden willst (was dann halt SHM wird). Ich möchte den Syscall sogar so allgemein machen, dass du nur durch ein Flag auch den Speicher versenden (Speicher wird auf Senderseite ungemappt) kannst.

Einen Nachteil den ich darin sehe ist, dass man für SHM also immer nen Port braucht (der die Nachricht/den SHM bekommt), aber das ist nicht wirklich schlimm. Denn wie bekommt man die SHM-ID in einen anderen Prozess, wenn nicht über IPC, sprich der Port muss eh vorhanden sein.

Zitat von: taljeth
Naja, damit kommen wir direkt wieder zur Diskussion von Popup-Threads. Ich halte es nach wie vor übertrieben, von allen Programmen zu verlangen, dass sie threadsafe sind.
Das trifft nur Programme die Nachrichten auch empfangen können (und eine Antwort ist bei mir nicht eine Nachricht zu empfangen) und bei denen sehe ich nicht so das Problem.

Was für Programme und Probleme schweben dir denn dort genau vor?

Zitat von: taljeth
Was die Puffer angeht, führt bei einem x86-OS mit flachem Speichermodell nichts dran vorbei, das ist keine IPC-Designentscheidung.
Das stimmt so nicht. Es wäre möglich, indem einfach alle Puffer an 4kb ausgerichtet wären oder man es mit der Sicherheit nicht so genau nimmt, nicht schön, aber möglich.

Vorallem sollte man auch immer das Verhältnis betrachten, von was für Puffern sprechen wir hier? Alles was größer als 4kb ist, kann man doch auch ruhig an 4kb ausrichten und alles andere müsste halt kopiert werden, aber da ja niemand hier mit solch "alter" Hardware wie ich plant, sollte das auch kein Problem darstellen.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 18. October 2011, 19:27
Hallo,


sync/async/detached?
Okay, gekauft, Zahlung erfolgt später. ;)
Ich nutze diese Terminologie dann gleich mal.

Umgekehrt wird ein Schuh draus: Wenn das IPC-Design es unmöglich macht, dass ein Prozess Speicher weiterbenutzen kann nachdem er die Antwort geschickt hat, dann muss man sich überlegen, ob Memory Sharing fest daran zu koppeln wirklich eine gute Idee ist.
Also da alle wichtigen Dinge (also read()/write(), bis einschließlich dem asynchronen File-I/O) den User-Buffer nur so lange ausleihen bis die Aktion abgeschlossen ist sehe ich darin erst einmal keine Beschränkung, solltest Du oder jemand anders ein praxisrelevantes Szenario kennen in dem das anders ist dann würde ich das sehr gerne lesen. Ansonsten steht auf meinem System ja nichts im Wege auch dediziertes Memory-Sharing mit extra erstellten Segmenten zu machen so das dann per IPC nur noch die Kommandos ausgetauscht werden müssen, es ist bei meinem IPC-Konzept nicht Pflicht das Memory-Sharing immer in vollem Umfang zu nutzen. Da mein IPC-Konzept aber eben komplett durchgängiges Zero-Copy (und zwar wirklich vom Hardware-Gerät bis zur eigentlichen Applikation) ermöglicht, und Du hast ja selber geschrieben dass das wichtig ist, kann ich durchaus damit leben das mein IPC-Konzept nur die normalen Dinge ohne Verrenkungen ermöglich und für die exotischen Dinge eben das Shared-Memory extra gemanaged werden muss (so wie bei Euch immer). Das heißt für mich das ich in über 99% aller Fälle mit maximaler Performance arbeiten kann und beim Rest eben auf das (niedrige) Niveau der Konkurrenz zurückfalle. Das gezielte abkoppeln des geteilten Speichers durch den Callee habe ich nur für detached IPC vorgesehen, wobei auch gerade da es sich lohnt wenn der Caller angibt das der Kernel in jedem Fall kopieren soll (egal wie viele Daten es sind) damit der Caller eben wirklich unabhängig bleibt.

Der Workaround, den du beschreibst, hinterlässt bei mir den Eindruck, dass das nicht wirklich mit deinem Design zusammenpasst, ...
Ja, da hast Du recht. Deine beiden Szenarien, mit dem allein Weiterarbeiten und dem Nacharbeiten, sind so ohne weiteres in meinem IPC-Konzept nicht vorgesehen, da beides nicht der Regelfall ist (oder siehst Du das anders?) habe ich damit auch absolut kein Problem. Wenn man das normale IPC trotzdem dafür nutzen möchte muss man eben einen Workaround nutzen oder man entscheidet sich besser dafür das Memory-Sharing und das Übermitteln der Kommandos/Antworten zu trennen (ersteres über dedizierte Memory-Sharing-Funktionen des OS-Kernels und zweites z.B. per detached IPC).

Interessant könnte der Fall werden, wenn der Empfänger das alleinige Schreibrecht auf den erhaltenen Puffer haben will (und das will er in aller Regel nachdem er den Puffer geprüft hat, sonst kriegst du oft genug ein sicherheitskritisches Race). Dazu müsste er in deinem Konzept wahrscheinlich kopieren.
Ich verstehe nicht ganz was Du meinst. In meinem Konzept bleibt der geteilte Speicher beim IPC-Caller immer so erhalten wie er ist und der IPC-Callee bekommt entweder Leserechte oder Schreibrechte (aber nie beides gleichzeitig) auf die bis zu 4 übergebenen Speicherbereiche (davon maximal 2 Lesen für Kommando und write-Nutzdaten und maximal 2 Schreiben für Antwort und read-Nutzdaten). Klar kann es damit passieren das der Caller z.B. im Kommando rumfuscht obwohl der IPC-Call noch nicht beendet ist (bei Multithreading im Caller ja durchaus möglich) aber wenn der Callee dann das Kommando nicht korrekt verarbeitet und Blödsinn zurück schickt ist das ein Problem des Callers. Das einzigste was natürlich nicht passieren darf ist das damit z.B. ein Service gestört wird oder gar abstürzt, da die kleinen Kommandos eh meistens vom Kernel kopiert werden (bei Speicherbereichen unterhalb einer gewissen Größe soll der Kernel grundsätzlich kopieren und nicht Sharen, das wird für jeden der bis zu 4 Speicherbereiche individuell entschieden) und der Callee auch prüfen kann ob wirklich kopiert wurde und falls nicht das notfalls selber nachholen kann sehe ich da keine große Gefahr. Auch wenn das bei Eurem Shared-Memory wohl eher nur die Nutzdaten betrifft so ist diese Gefahr ja grundsätzlich ebenfalls vorhanden.

Naja, damit kommen wir direkt wieder zur Diskussion von Popup-Threads. Ich halte es nach wie vor übertrieben, von allen Programmen zu verlangen, dass sie threadsafe sind.
Da hast Du grundsätzlich recht, deswegen kommt bei mir ein reiner IPC-Caller der nur synchrounus und detached IPC nutzt auch mit einem einzelnen Thread aus, das dürfte die meisten einfachen Programme (die kein asynchrones File-I/O u.ä. nutzen) betreffen, ich würde sogar soweit gehen zu behaupten das die meisten Programme die auf tyndur laufen auch auf meinem System als simple singlethreaded Applikationen laufen müssten. Erst komplexere Programme, die z.B. für asynchrones File-I/O eben auch asynchrones IPC nutzen, oder richtige Services müssen bei mir zwangsläufig multithreadingtauglich sein. Meine libc möchte ich grundsätzlich nur multithreadingtauglich anbieten.

Naja, ein IPC-Mega-Syscall ist ja genau das was du machst. Der ist wohl unabhängig davon, ob am Ende Pipes oder Popup-Threads stehen.
Hä, nein, einen Mega-IPC-Sycall hab ich nicht, ich habe in meinem aktuellen Konzept 19 Syscalls für IPC vorgesehen. Es gibt auch für sync/async/detached jeweils einen eigenen Call-Syscall. Natürlich werden die intern für viele Dinge die selben Unterfunktionen nutzen (z.B. für das Vererben/Kopieren des Speichers oder für das Erstellen des PopUp-Threads), weswegen ich auch nicht davon ausgehe das diese doch recht große Anzahl an Syscalls nur fürs IPC meinen Kernel unangemessen komplex werden lässt, aber nach außen sind das unabhängige Syscalls.

Was die Puffer angeht, führt bei einem x86-OS mit flachem Speichermodell nichts dran vorbei, das ist keine IPC-Designentscheidung.
Ist mir bekannt.

.... Wenn irgendwo zwischen Anwendungscode und Platte eine Schicht nicht ordentlich mitspielt, wird es schwierig.
Natürlich, mit all dem hast Du uneingeschränkt recht. Es ging mir doch erst mal nur um die Kohärenz die die Anwendungen auf jeden Fall sehen müssen, das es da noch mehr gibt ist mir auch klar.

Es ist also schon die Frage, welches Interface das VFS dafür zur Verfügung stellen sollte. Nur einen Holzhammer fsync(fd); oder irgendwelche feineren Operationen?
Wenn ich Dich vorhin richtig verstanden hab dann bietet doch eine konforme POSIX-Implementierung schon so einige Mitelchen an, ich denke das ist zumindest eine gute Orientierung was sinnvoll ist und was auch von existierenden Programmen so alles benutzt wird.

Hm, ich würde sagen, dass dazu das Dateisystem mitspielen sollte. Ansonsten musst du ja alles im Speicher machen.
Ja, ein berechtigter Einwand. Ich denke mal das btrfs und ZFS sowas von Hause aus bieten, NTFS wimre auch (zumindest sein kurzer Zeit) und ob da auch die ext?-Familie was zu bieten hat weiß ich gar nicht.

Der Fall mit zwei VMs und einem Clusterdateisystem gefällt dir nicht, wenn du noch was anderes als Snapshots willst?
Ah zwei VMs (das hatte ich heute Mittag offensichtlich überlesen), das macht die Sache schon wesentlich interessanter. Ja da müsste man wirklich überlegen welche Konsistenzen das VFS zusichern muss und welche Mechanismen die VMs nutzen wollen um das zu gewährleisten. Ich schätze mal da hat POSIX auch was hübsches anzubieten.
Trotzdem muss ich ganz ehrlich sagen das ich noch gerne ein etwas alltäglicheres Beispiel lesen würde. Clusterdateisysteme und mehrere VMs sind IMHO nicht so das typische Anwendungsszenario für ein Hobby-OS.


Ich möchte den Syscall sogar so allgemein machen, dass du nur durch ein Flag auch den Speicher versenden (Speicher wird auf Senderseite ungemappt) kannst.
Da ich eher versuchen möchte möglichst immer direkt den eh vorhandenen User-Buffer für das IPC zu nutzen (wegen dem lieben Zero-Copy) ist das für mich sowieso kein interessantes Ziel, nebst dessen das ich aus meinen Segmenten nicht einfach Speicher mitten raus klauen kann.

Denn wie bekommt man die SHM-ID in einen anderen Prozess, wenn nicht über IPC, sprich der Port muss eh vorhanden sein.
Also da kann ich mir so einiges vorstellen: z.B. ein Programm könnte den SHM erstellen und dann weitere Kind-Prozesse starten und diesen die nötigen Zugriffsinformationen gleich als Command-Line-Parameter o.ä. mitgeben.

aber da ja niemand hier mit solch "alter" Hardware wie ich plant, sollte das auch kein Problem darstellen.
Der Nutzwert von Zero-Copy ist IMHO unabhängig vom Alter des Systems, Kopieren kostet immer unnütz CPU-Zeit.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 18. October 2011, 19:53
Zitat von: erik
Also da kann ich mir so einiges vorstellen: z.B. ein Programm könnte den SHM erstellen und dann weitere Kind-Prozesse starten und diesen die nötigen Zugriffsinformationen gleich als Command-Line-Parameter o.ä. mitgeben.
Das stimmt wohl, irgendwie vergesse ich immer die Command-Line-Parameter (auch bei meinen Überlegungen zwecks Prozesserstellung und sowas). Hast du dafür mal nen richtiges (in Form einer Anwendung, real existent) Bsp?
Michwürde halt interessieren, ob das eher selten ist oder ob sogar ein Service mit reinspielt und ob es sich lohnt das ganze noch anders umzusetzen.

Zitat von: erik
Der Nutzwert von Zero-Copy ist IMHO unabhängig vom Alter des Systems, Kopieren kostet immer unnütz CPU-Zeit.
Ist erstmal richtig, aber wenn man sich dann die Zeit anguckt, die gespart werden kann, kann man den ganzen Spaß auch vernachläßigen (vorallem wenn man daran denkt, das die meisten Rechner die meiste Zeit eh nix tun).

Zumal man auch den Overhead durchs Mappen nicht vergessen darf, ich denke mal bis zu einer gewissen Menge an Speicher ist kopieren sogar schneller als Mappen. Allerding bleibt es auch so, das die Zeit die absolut dafür gebraucht wird, bei vielen Sachen inzwischen zu gering ist als das sie auffallen würde.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 18. October 2011, 20:52
Ein Beispiel wäre übrigens eine Datenbank... vierzig lesende Threads und vier schreibender Threads, wobei die interne Datenbank-Kohärenz in jedem Fall gewahrt bleiben muss. Schon muss das VFS fein gestückeltes Locking und eine garantierte Abarbeitungsreihenfolge bieten, die vollständig von der Anwendung gesteuert werden kann.

An IPC halte ich synchron (Sender wartet auf Antwort) und asynchron (Sender wartet nicht auf Antwort) für ausreichend. Shared Memory ist erst ab einer gewissen Nutzdatenmenge sinnvoll und gilt bei synchronem IPC bis zum Erhalt der Antwort, bei asynchronem IPC bis unendlich (wenn der vorletzte Prozess den Speicherbereich freigibt, ist es kein SHM mehr).

Am Ende spielt die interne IPC-Implementation - abgesehen von der Performance - eh keine Rolle, weil die Anwendungen davon nichts mitkriegen. Die libc muss das IPC nur möglichst effizient nutzen können.

Die Command Line als Träger wichtiger Informationen zu missbrauchen halte ich für unelegant.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 18. October 2011, 21:50
Das trifft nur Programme die Nachrichten auch empfangen können (und eine Antwort ist bei mir nicht eine Nachricht zu empfangen) und bei denen sehe ich nicht so das Problem.
Wenn eine Antwort was anderes ist, wie funktionieren denn bei dir asynchrone Antworten?

Zitat
Das stimmt so nicht. Es wäre möglich, indem einfach alle Puffer an 4kb ausgerichtet wären oder man es mit der Sicherheit nicht so genau nimmt, nicht schön, aber möglich.
Also mit der Sicherheit nicht genau nehmen ist keine Option, Korrektheit steht immer noch an erster Stelle. Ich sehe kein größeres Problem darin, Puffer einfach richtig auszurichten, aber Erik scheint das nicht zu gefallen.

sync/async/detached?
Okay, gekauft, Zahlung erfolgt später. ;)
Ich nutze diese Terminologie dann gleich mal.
Ansonsten hätte ich auch noch uni-/bidirektional im Angebot. Ist vielleicht einfacher verständlich, wenn man den Begriff in diesem Zusammenhang nicht kennt.

Zitat
Also da alle wichtigen Dinge (also read()/write(), bis einschließlich dem asynchronen File-I/O) den User-Buffer nur so lange ausleihen bis die Aktion abgeschlossen ist sehe ich darin erst einmal keine Beschränkung, solltest Du oder jemand anders ein praxisrelevantes Szenario kennen in dem das anders ist dann würde ich das sehr gerne lesen.
Wie funktioniert das eigentlich bei detached/unidirektionalen IPCs? Gibt es da doch eine Antwort, nur dass sie eben keine zusätzliche Information außer "bin fertig, kannst den Speicher freigeben" enthält?

Interessant könnte der Fall werden, wenn der Empfänger das alleinige Schreibrecht auf den erhaltenen Puffer haben will (und das will er in aller Regel nachdem er den Puffer geprüft hat, sonst kriegst du oft genug ein sicherheitskritisches Race). Dazu müsste er in deinem Konzept wahrscheinlich kopieren.
Ich verstehe nicht ganz was Du meinst. In meinem Konzept bleibt der geteilte Speicher beim IPC-Caller immer so erhalten wie er ist und der IPC-Callee bekommt entweder Leserechte oder Schreibrechte (aber nie beides gleichzeitig) auf die bis zu 4 übergebenen Speicherbereiche (davon maximal 2 Lesen für Kommando und write-Nutzdaten und maximal 2 Schreiben für Antwort und read-Nutzdaten). Klar kann es damit passieren das der Caller z.B. im Kommando rumfuscht obwohl der IPC-Call noch nicht beendet ist (bei Multithreading im Caller ja durchaus möglich) aber wenn der Callee dann das Kommando nicht korrekt verarbeitet und Blödsinn zurück schickt ist das ein Problem des Callers.[/quote]
Und wenn der Callee dann abstürzt, weil er erst Datensatz A validiert hat und anschließend auf Datensatz B arbeitet, dann ist das ein Bug, der durch das IPC-Design begünstigt worden ist. Ich glaube, es wäre ein nützliches Feature, wenn beim Übergeben des Speichers der Caller (ggf. vorübergehend) die Schreibrechte auf den Speicherbereich verlieren würde.

Zitat
Hä, nein, einen Mega-IPC-Sycall hab ich nicht, ich habe in meinem aktuellen Konzept 19 Syscalls für IPC vorgesehen. Es gibt auch für sync/async/detached jeweils einen eigenen Call-Syscall. Natürlich werden die intern für viele Dinge die selben Unterfunktionen nutzen (z.B. für das Vererben/Kopieren des Speichers oder für das Erstellen des PopUp-Threads), weswegen ich auch nicht davon ausgehe das diese doch recht große Anzahl an Syscalls nur fürs IPC meinen Kernel unangemessen komplex werden lässt, aber nach außen sind das unabhängige Syscalls.
Was ich damit meinte, ist, dass du einen Syscall hast, der gleichzeitig einen Haufen Speicherverwaltung macht, wenn ich das richtig verstanden habe. Also nicht erst den Speicher zuweisen und dann gesondert die RPC-Anfrage absetzen und dann gesondern die Antwort holen, sondern alles in einem.

Zitat
Es ist also schon die Frage, welches Interface das VFS dafür zur Verfügung stellen sollte. Nur einen Holzhammer fsync(fd); oder irgendwelche feineren Operationen?
Wenn ich Dich vorhin richtig verstanden hab dann bietet doch eine konforme POSIX-Implementierung schon so einige Mitelchen an, ich denke das ist zumindest eine gute Orientierung was sinnvoll ist und was auch von existierenden Programmen so alles benutzt wird.
Ich habe schon die Erfahrung gemacht, dass das, was vorhanden ist, zwar genug unterschiedliche Varianten hat um zu verwirren, aber dass es schon noch einige Wünsche offen lässt. Zum Beispiel ein fsync nicht für die ganze Datei, sondern nur für einen Bereich ist nicht vorgesehen. Abhängigkeiten zwischen Requests definieren wollen vermutlich die wenigsten Anwendungen, aber auch das wäre denkbar und könnte von Anwendungen, bei denen es sich lohnt, auch sinnvoll eingesetzt werden.

Zitat
Ja, ein berechtigter Einwand. Ich denke mal das btrfs und ZFS sowas von Hause aus bieten, NTFS wimre auch (zumindest sein kurzer Zeit) und ob da auch die ext?-Familie was zu bieten hat weiß ich gar nicht.
Nein, ext4 kann kein COW.

Ein Beispiel wäre übrigens eine Datenbank... vierzig lesende Threads und vier schreibender Threads, wobei die interne Datenbank-Kohärenz in jedem Fall gewahrt bleiben muss. Schon muss das VFS fein gestückeltes Locking und eine garantierte Abarbeitungsreihenfolge bieten, die vollständig von der Anwendung gesteuert werden kann.
Stimmt, Multithreading ist ein guter Einwand. Damit kriegt man solche Szenarien fast schon automatisch.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 18. October 2011, 22:50
Hallo,


ein richtig gutes Beispiel für allgemeinen Shared-Memory, inklusive der Verteilung der Zugangsdaten (was z.B. auch über stdin beim Kindprozess passieren kann falls die Commad-Line-Parameter nicht sicher genug sind), kenne ich leider auch nicht, von den akademischen Beispielen zum Grundverständnis mal abgesehen.

Ist erstmal richtig, aber wenn man sich dann die Zeit anguckt, die gespart werden kann, kann man den ganzen Spaß auch vernachläßigen
Klar kann eine moderne CPU 1000 mal schneller kopieren als eine von vor 20 Jahren aber sie kann eben auch 1000 mal mehr sinnvolle Arbeit in der selben Zeit verrichten! Das Arbeitsäquivalent für die Zeit in der eine CPU X Bytes kopiert dürfte sich in den letzten 20 Jahren nur wenig verändert haben, genau weiß ich das aber nicht, so das man davon ausgehen kann das Kopieren in Software immer etwa gleich teuer ist (relativ unabhängig von der absoluten Performance der CPU). Meiner persönlichen Meinung nach ist Kopieren einer der wesentlichen CPU-Verschwender in modernen Software-Architekturen und genau deswegen versuche ich so energisch dies zu vermeiden.

Natürlich ist auch das Erstellen eines Mappings nicht kostenlos, bei meinen Segmenten könnte eventuell das Erstellen eines neuen Segments sogar ein bisschen mehr kosten als zwei oder drei Pages bei Flat-Memory, so das man auf jeden Fall mal ausmessen sollte wie viele Bytes man in der selben Zeit kopieren könnte um zu wissen aber welcher Menge an Nutzdaten sich das Mapping überhaupt lohnt.


Ein Beispiel wäre übrigens eine Datenbank... vierzig lesende Threads und vier schreibender Threads, wobei die interne Datenbank-Kohärenz in jedem Fall gewahrt bleiben muss. Schon muss das VFS fein gestückeltes Locking und eine garantierte Abarbeitungsreihenfolge bieten, die vollständig von der Anwendung gesteuert werden kann.
Okay, das ist auch ein gutes Beispiel (wenn auch sicher ebenfalls nicht das typische Anwendungsszenario für ein Hobby-OS). Das VFS muss also auf jeden Fall anständige Kohärenz und auch ein zugehöriges API bieten.

An IPC halte ich synchron (Sender wartet auf Antwort) und asynchron (Sender wartet nicht auf Antwort) für ausreichend. Shared Memory ist erst ab einer gewissen Nutzdatenmenge sinnvoll und gilt bei synchronem IPC bis zum Erhalt der Antwort, bei asynchronem IPC bis unendlich (wenn der vorletzte Prozess den Speicherbereich freigibt, ist es kein SHM mehr).
Das finde ich reicht nicht aus, bei Deinem asynchronem IPC wird ja effektiv der Speicher dem Caller weggenommen: er weiß einfach nicht ab wann er diesen Speicher wieder benutzen kann also bleibt ihm nichts anderes übrig als ihn frei zu geben. Das mag für kleine Messages/Signale und ähnlichen Kleinkram (wo dann wohl eh meistens nur so wenige Bytes im Spiel sind das sowieso gleich kopiert werden kann) reichen aber für z.B. asynchrones File-I/O, wo der User-Buffer nach dem Transfer weiter benutzt werden soll/muss ist das nicht ausreichend.

Am Ende spielt die interne IPC-Implementation - abgesehen von der Performance - eh keine Rolle, weil die Anwendungen davon nichts mitkriegen.
Einverstanden.

Die libc muss das IPC nur möglichst effizient nutzen können.
Und genau hier sind wir beim entscheidenden Knackpunkt. Als die libc entworfen wurde waren monolithische OS-Kernel das Mass der Dinge und bei einem Monolithen kann man einfach einen Pointer auf den User-Buffer mitgeben und fertig (der Kernel kommt an den User-Buffer ja problemlos ran). Von daher bin ich der Meinung wenn man effizientes IPC für existierende Programme haben will dann darf man genau dieses Paradigma nicht aus den Augen verlieren.


aber Erik scheint das nicht zu gefallen.
Ganz recht, das gefällt mir absolut nicht. Ich möchte nicht das mir die libc oder irgendeine andere Library vorschreibt was ich für Speicher benutzen soll, ich möchte auch einfach mal ein paar Bytes in einen Puffer holen können der als lokale Variable auf dem Stack liegt und ich möchte auch nicht dem malloc sagen müssen das ich nur ausgerichteten Speicher haben will (vor allem dann nicht wenn mein Speicherbedarf selber auch kein exaktes Vielfache einer Pagegröße ist). In meiner Welt gibt es nur eine Art von Speicher und der geht für alles, fertig. Ich will auch bei DMA nicht zwischen "hohem" und "normalen" Speicher unterscheiden so das bei mir ein HW-Gerät immer den gesamten physischen Adressraum unterstützen muss wenn es DMA nutzen will, und das soll es tun weil ich ja Zero-Copy will.

Ansonsten hätte ich auch noch uni-/bidirektional im Angebot. Ist vielleicht einfacher verständlich, wenn man den Begriff in diesem Zusammenhang nicht kennt.
Hm, ne das gefällt mir nicht so sehr wie Dein letzter Vorschlag.

Wie funktioniert das eigentlich bei detached/unidirektionalen IPCs? Gibt es da doch eine Antwort, nur dass sie eben keine zusätzliche Information außer "bin fertig, kannst den Speicher freigeben" enthält?
Nein, bei dem detached IPC gibt es keine Antwort, auch der Rückgabewert des Call-Syscalls kann nicht durch den Callee beeinflusst werden (der sagt nur ob das IPC grundsätzlich geklappt hat oder nicht). Alles was ich vorgesehen hab ist ein Parameter für diesen Call-Syscall das sagt ob der Caller solange blockiert werden möchte bis der Callee den Speicher freigegeben hat (bei recht großen Datenmengen wo das Kopieren unangenehm wäre könnte das eventuell nützlich sein). Der Callee kann den Speicher entweder manuell freigeben oder der Speicher wird automatisch am Ende vom PopUp-Thread freigegeben. Da ich aber davon ausgehe das detached IPC üblicherweise nur mit kleinen Datenmengen benutzt wird (wo der Kernel eh kopiert) bzw. der Caller zur Sicherheit das Kopieren immer explizit anfordert dürfte das wohl keine echte Relevanz haben.

Ich glaube, es wäre ein nützliches Feature, wenn beim Übergeben des Speichers der Caller (ggf. vorübergehend) die Schreibrechte auf den Speicherbereich verlieren würde.
Da gebe ich Dir zwar uneingeschränkt recht aber das kann ich mit Segmenten nicht bieten, eben weil ich ja möchte das ich jeden beliebigen Speicher für IPC nutzen kann. Ich denke das man im Service mit etwas Umsicht die Gefahr für ernste Fehler und Abstürze auch erheblich reduzieren kann, nebst dessen das ja gerade die Kommandos wohl eh meistens kopiert werden (wegen geringer Größe) und wenn nicht der Service das auch selber erledigen kann. Habt Ihr den vor in tyndur entsprechende Mechanismen vor zusehen?

Was ich damit meinte, ist, dass du einen Syscall hast, der gleichzeitig einen Haufen Speicherverwaltung macht, wenn ich das richtig verstanden habe.
Ja. Der Call-Syscall erstellt Alias-Segmente (eine Funktionalität die ich eventuell gar nicht einzeln anbiete, außer für Debugger fällt mir auch keine sinnvolle Anwendung dafür ein) oder kopiert, erstellt einen neuen Thread im Ziel-Prozess und blockiert dann noch eventuell den Caller-Thread oder hängt ihn an die runnable-Liste des Schedulers an (damit erst mal der neue PopUp-Thread an die CPU kann). Zusammen mit dem eigentlichen IPC-Kram und allen Sicherheitsprüfungen ist das schon so einiges, aber nichts davon ist wirklich teuer so das ich davon ausgehe trotzdem eine hohe Performance erreichen zu können. Der Ende-Syscall der vom PopUp-Thread aufgerufen wird löscht die Alias-Segmente und gibt die Antwort entweder dem blockierten Caller oder einer asynchronen Callback-Funktion (was auch immer das ist auf jeden Fall als nächstes auf der aktuellen CPU dran, hier wird also der Scheduler gar nicht benötigt).

Das Problem mit der Kohärenz im VFS eines Hobby-OS ist sicher auch das man erst mal überlegen muss was man von diesem ganzen Zeugs wirklich alles echt implementieren muss damit zumindest die gewünschten Anwendungen sauber laufen. Ich schätze das wird man selbst mit intensiven Code-Studium nicht immer vollständig heraus bekommen weil sich manche Dinge wohl erst indirekt ergeben.

Multithreading ist ein guter Einwand. Damit kriegt man solche Szenarien fast schon automatisch.
Wobei dann wieder die Frage aufkommt ob die alle über das selbe Datei-Handle gehen oder die Datei mehrfach geöffnet wird. Wird letzteres in der libc eventuell abgefangen? Falls nein, was passiert wenn man das doch tut? (aber den Datei-Pointer getrennt lässt)


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 19. October 2011, 09:14
Zitat von: taljeth
Wenn eine Antwort was anderes ist, wie funktionieren denn bei dir asynchrone Antworten?
Man muss bei mir zw. RPC (wozu auf der Sender-Seite kein Port notwendig ist) und asynchronem IPC unterscheiden (hier ist auf der Sender und Empfänger-Seite ein Port notwendig).

Aber ihr habt mir mit dem Callback schon neue Ideen geliefert. Ich überlege, ob man darüber nicht Signale (in Form eines Signal-Servers) anbieten könnte. Allerdings hieße das, das man dann immer "Nachrichten" an diesem Callback bekommen könnte, sprich da wäre kein Anfrage-Antwort-Prinzip.

Könnte denn mal jemand ein Bsp. aus der Praxis bringen, wo asynchrones IPC genutzt wird, weil mir fällt so nur asynch-I/O ein, aber da wartet man doch auch nicht auf eine Antwort (das habe ich sowieso nicht ganz verstanden wie das genau funktioniert)?

Zitat von: erik
Das Arbeitsäquivalent für die Zeit in der eine CPU X Bytes kopiert dürfte sich in den letzten 20 Jahren nur wenig verändert haben, genau weiß ich das aber nicht, so das man davon ausgehen kann das Kopieren in Software immer etwa gleich teuer ist (relativ unabhängig von der absoluten Performance der CPU).
Das ist halt Situationsabhängig. Der Flaschenhals bleibt im Endeffekt der Speicherbus. Desto schneller die CPUs werden, desto mehr Takte müssen sie auch warten bis der Speicher dann "verfügbar" ist. Das kann negativ für das Kopieren sein, aber auch fürs Mappen.

Zitat von: erik
Natürlich ist auch das Erstellen eines Mappings nicht kostenlos, bei meinen Segmenten könnte eventuell das Erstellen eines neuen Segments sogar ein bisschen mehr kosten als zwei oder drei Pages bei Flat-Memory, so das man auf jeden Fall mal ausmessen sollte wie viele Bytes man in der selben Zeit kopieren könnte um zu wissen aber welcher Menge an Nutzdaten sich das Mapping überhaupt lohnt.
Man könnte davon ausgehen, das die Daten schon im Cache sind und von daher, sollte das Kopieren von 2 oder 3 Pages wirklich kein Problem darstellen (vorallem nicht bei den heutigen Caches).
Zumal ich nicht von mehreren MBs rede, sondern von wenigen KBs, das sollte sich wirklich vernachlässigen lassen.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 19. October 2011, 09:53
Könnte denn mal jemand ein Bsp. aus der Praxis bringen, wo asynchrones IPC genutzt wird, weil mir fällt so nur asynch-I/O ein, aber da wartet man doch auch nicht auf eine Antwort (das habe ich sowieso nicht ganz verstanden wie das genau funktioniert)?
Naja, überall da, wo man noch was anderes zu tun hat und das Ergebnis nicht zwingend sofort braucht, um was sinnvolles zu tun. Und natürlich, wo einem der Durchsatz wichtig genug ist, dass man nicht einfach aus Bequemlichkeit wartet. Asynchron programmieren ist immer schwieriger als synchron.

AIO für Dateien ist wahrscheinlich einer der wichtigstens Anwendungsfälle, wobei das sicher auch damit zusammenhängt, wie stark man "alles ist eine Datei" macht. Und natürlich will man bei AIO eine Antwort haben, die einem wenigstens sagt, ob der Request erfolgreich war oder was für ein Fehler aufgetreten ist.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 19. October 2011, 13:25
Hallo,


asynchronem IPC unterscheiden (hier ist auf der Sender und Empfänger-Seite ein Port notwendig).
Immer? Auch wenn es sich nur um eine unidirektionale Kommunikation handelt? Falls ja empfände ich das als etwas übertrieben.

Ich überlege, ob man darüber nicht Signale (in Form eines Signal-Servers) anbieten könnte. Allerdings hieße das, das man dann immer "Nachrichten" an diesem Callback bekommen könnte, sprich da wäre kein Anfrage-Antwort-Prinzip.
Dann scheint Dein Call-Back in etwa das zu sein was ich als detached IPC bezeichne. Bei mir gibt es Call-Back nur beim asynchronen IPC um anzuzeigen das der IPC-Vorgang abgeschlossen ist und um das Ergebnis zu übermitteln, beim synchronen IPC würde ja der Caller solange blockieren bis der IPC-Vorgang abgeschlossen ist und beim asynchronen IPC tut er das nicht also ist am Ende ein simpler Call-Back erforderlich.

(das habe ich sowieso nicht ganz verstanden wie das genau funktioniert)
Asynchrones File-I/O (was auch Character-Devices und Stream-Sockets usw. mit einschließt) funktioniert in etwas so das der aio_read()/aio_write()-Aufruft nicht so lange bis alles erledigt ist intern blockiert sondern das der Caller (fast) sofort aus der aio_read()/aio_write()-Funktion zurückkommt und weiterarbeiten kann, intern (im VFS) wird dann dieser Job eigenständig abgearbeitet (und, zumindest bei einem Monolithen, auch direkt mit dem bei aio_read()/aio_write() übergebenen User-Buffer gearbeitet) so das dann am Ende oder bei einem Fehler der zum Abbruch führt (z.B. EOF) dann ein Call-Back o.ä. an die Applikation durchgeführt werden kann der das Ergebnis signalisiert (ab hier kann die Applikation auch die Buffer die bei aio_read()/aio_write() mitgegeben wurden wieder normal nutzen, eventuelles Memory-Sharing muss hier also beendet werden).

Man könnte davon ausgehen, das die Daten schon im Cache sind und von daher, sollte das Kopieren von 2 oder 3 Pages wirklich kein Problem darstellen (vorallem nicht bei den heutigen Caches).
Es können aber auch die Daten für die nächste sinnvolle Arbeit bereits im Cache liegen so das diese von der CPU auch sehr effizient abgearbeitet werden könnte. Cache ist bei dieser Betrachtung völlig irrelevant, wichtig ist das in der Zeit wo die CPU händisch kopiert eben keine sinnvolle Arbeit von dieser CPU verrichtet werden kann und das die Kosten fürs kopieren wohl immer so einigermaßen konstant sind (in Relation zur Arbeitsmenge die die CPU anstatt dem Kopieren erledigen könnte).

Zumal ich nicht von mehreren MBs rede, sondern von wenigen KBs, das sollte sich wirklich vernachlässigen lassen.
Auch die Datenmenge ist ersteinmal irrelevant, hier ist eben nur zu beachten dass das Anlegen eines Mappings eben auch gewisse Grundkosten hat die von der Anzahl der Pages unabhängig sind, wenn die nicht wären würde sich Mapping (fast) immer lohnen (außer wenn die Daten so wenig sind das nicht mal eine einzige Page voll wird).


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 19. October 2011, 13:54
Hallo,

An IPC halte ich synchron (Sender wartet auf Antwort) und asynchron (Sender wartet nicht auf Antwort) für ausreichend. Shared Memory ist erst ab einer gewissen Nutzdatenmenge sinnvoll und gilt bei synchronem IPC bis zum Erhalt der Antwort, bei asynchronem IPC bis unendlich (wenn der vorletzte Prozess den Speicherbereich freigibt, ist es kein SHM mehr).
Das finde ich reicht nicht aus, bei Deinem asynchronem IPC wird ja effektiv der Speicher dem Caller weggenommen: er weiß einfach nicht ab wann er diesen Speicher wieder benutzen kann also bleibt ihm nichts anderes übrig als ihn frei zu geben.
Niemand hindert den Callee daran, den Speicher selbst freizugeben und den Caller per asynchronem IPC darüber zu informieren.

Als die libc entworfen wurde waren monolithische OS-Kernel das Mass der Dinge und bei einem Monolithen kann man einfach einen Pointer auf den User-Buffer mitgeben und fertig (der Kernel kommt an den User-Buffer ja problemlos ran). Von daher bin ich der Meinung wenn man effizientes IPC für existierende Programme haben will dann darf man genau dieses Paradigma nicht aus den Augen verlieren.
Willst du etwa eine vorhandene libc portieren? Betrachte das lieber als Einheit und stimme beides aufeinander ab.

Ich will auch bei DMA nicht zwischen "hohem" und "normalen" Speicher unterscheiden so das bei mir ein HW-Gerät immer den gesamten physischen Adressraum unterstützen muss wenn es DMA nutzen will, und das soll es tun weil ich ja Zero-Copy will.
Solange Kompatiblität keine Rolle spielt, kannst du das tun. Kann jedes PCI-Gerät überhaupt 64-Bit-DMA benutzen? Würde ich jedenfalls wundern.

Multithreading ist ein guter Einwand. Damit kriegt man solche Szenarien fast schon automatisch.
Wobei dann wieder die Frage aufkommt ob die alle über das selbe Datei-Handle gehen oder die Datei mehrfach geöffnet wird. Wird letzteres in der libc eventuell abgefangen? Falls nein, was passiert wenn man das doch tut? (aber den Datei-Pointer getrennt lässt)
Das VFS sollte mit beiden Varianten klarkommen, von daher spielt das keine Rolle.

Gruß,
Svenska
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 19. October 2011, 17:02
Zitat von: erik
Immer? Auch wenn es sich nur um eine unidirektionale Kommunikation handelt? Falls ja empfände ich das als etwas übertrieben.
Praktisch bisher ja, theoretisch muss aber auf jeden Fall auf Empfängerseite immer ein Port vorhanden sein, nicht aber auf Senderseite, aber dann kann man auch nicht auf eine Nachricht antworten.

Zitat von: erik
Asynchrones File-I/O (was auch Character-Devices und Stream-Sockets usw. mit einschließt) funktioniert in etwas so das der aio_read()/aio_write()-Aufruft nicht so lange bis alles erledigt ist intern blockiert sondern das der Caller (fast) sofort aus der aio_read()/aio_write()-Funktion zurückkommt und weiterarbeiten kann, intern (im VFS) wird dann dieser Job eigenständig abgearbeitet (und, zumindest bei einem Monolithen, auch direkt mit dem bei aio_read()/aio_write() übergebenen User-Buffer gearbeitet) so das dann am Ende oder bei einem Fehler der zum Abbruch führt (z.B. EOF) dann ein Call-Back o.ä. an die Applikation durchgeführt werden kann der das Ergebnis signalisiert (ab hier kann die Applikation auch die Buffer die bei aio_read()/aio_write() mitgegeben wurden wieder normal nutzen, eventuelles Memory-Sharing muss hier also beendet werden).
Hmm, spontan würde ich sagen, einfach durch aio_read()/aio_write() nen neuen Thread erstellen, der dann normales RPC macht, aber der Caller (der den Thread erstellt hat) kann sofort danach (Threaderstellung) weitermachen.

Nur das mit dem Callback verstehe ich noch nicht ganz bzw. leuchtet mir noch nicht ein, was der genau bewirkt. Ich meine am Bsp. aio_read() (das ich nicht wüsste was eine Anwendung weiter machen sollte, wenn sie noch keine Daten hat, ist erstmal egal), was ändert dieser Callback, dass die Anwendung miteinmal die Daten, die jetzt vorhanden sind, nutzt? Wird der Code der die Daten verarbeitet direkt in dem Callback ausgeführt? Wird für den Callback der laufende Thread unterbrochen (wenn ja, welcher Thread, bei Multithreading) oder wird dafür ein neuer erstellt?

Zitat von: erik
wenn die nicht wären würde sich Mapping (fast) immer lohnen (außer wenn die Daten so wenig sind das nicht mal eine einzige Page voll wird).
Also wahrscheinlich für verdammt viele Sachen in einem Mikrokernel!? Selbst viele (die meisten?) read()´s und write()´s dürften darunter fallen. Womit wir dabei wären, kopieren ist meistens schneller als Mappen. Denn ich gehe davon aus, dass das meiste was per IPC kommuniziert wird unter 4kb liegt.

Soweit Theorie. Ein Bsp. fällt mir ein wo mapping schneller wäre, anstatt die Daten die man per read() anfordert ständig in einzelne Msgs zu kopieren, könnte man immer eine oder zwei Pages der Datei per Mapping in den Prozess gemappt haben und die read()´s lesen direkt daraus. Das verzögerte Dateipointer aktualisieren dürfte doch kein Problem sein (wenn man mal davon ausgeht, das nicht der gleiche Dateipointer von mehreren Prozessen zum Lesen verwendet wird, wofür ich kein vernünftiges Bsp kenne).
Selbst für (zumindest exklusives) das Schreiben kann man so vorgehen. Es wird immer direkt in die Memory gemappte Datei geschrieben und nur bei der Anforderung eines neuen Bereiches der Datei oder dem expliziten fseek() muss auch der Dateipointer im VFS aktualisiert werden. Das ganze würde natürlich nicht für das Schreiben von Daten von verschiedenen Prozessen aus in die gleiche Datei funktionieren.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 19. October 2011, 19:56
Hallo,


Niemand hindert den Callee daran, den Speicher selbst freizugeben und den Caller per asynchronem IPC darüber zu informieren.
Das ist zwar richtig aber es erscheint mir irgendwie umständlich, aus meiner Sicht ist das ganze ein kompletter IPC/RPC-Vorgang den ich auch so als asynchrones IPC anbieten möchte. Bei mir wird zuerst das Memory-Sharing eingerichtet und dann beim Callee ein PopUp-Thread injiziert aber der Caller blockiert nicht bis der Callee fertig ist sondern kann weiter arbeiten, wenn der Callee fertig ist wird das Memory-Sharing beendet und beim Caller auch ein PopUp-Thread injiziert der dann den Call-Back ausführt.

Willst du etwa eine vorhandene libc portieren?
Nein, das will ich nicht, was ich meinte war eher dass das API der libc eben auch auf dieses Paradigma setzt. Das bedeutet z.B. das read die gelesenen Daten nicht in einem neuen Speicherbereich als Rückgabewert liefert sondern das der Speicherbereich in den die Daten rein sollen schon vorgegeben wird.

Kann jedes PCI-Gerät überhaupt 64-Bit-DMA benutzen? Würde ich jedenfalls wundern.
Bei nativen PCI-Express-Geräten ist es vorgeschrieben, bei legacy PCI-Express-Geräten ist das freiwillig. Bei xHCI und ACHI müssen alle Geräte das unterstützen, GBit-Ethernet-Controller wohl auch (zumindest die aus gutem Hause). Echtes Profi-Equipment (SAS / 10GBit-Ethernet / Infiniband / ....) der letzten 10 Jahre sollte das ebenfalls können. Was als Unsicherheit bleibt ist irgendwelcher Billig-Ramsch und all jenes Zeugs das man unter "Sonstiges" einsortiert, aber selbst dort denke ich das in den letzten 5 Jahren die Mehrheit 64Bit-DMA können sollte. Bis ich wirklich echte Hardware zum Ausprobieren habe wird die Einschränkung auf 64Bit-DMA jedenfalls keine Einschränkung mehr sein, wäre es auch Heute schon nicht.

Das VFS sollte mit beiden Varianten klarkommen, von daher spielt das keine Rolle.
Aus Sicht des VFS spielt das IMHO schon eine Rolle, wenn sämtliche Dateioperationen über ein einziges Handle laufen dann ist die Kohärenz doch schon innerhalb der Anwendung erledigt, egal wie viele Threads die Anwendung hat.


nicht aber auf Senderseite, aber dann kann man auch nicht auf eine Nachricht antworten.
Also wenn man für synchrones IPC (also alles was eine zugehörige Antwort hat) bereits einen eigenen Port benötigt um auch nur als Client funktionieren zu können ist das IMHO schon eine Einschränkung, die meisten (einfachen) Funktionen aus der libc sind synchron (und blockierend). Wie viele libc-Funktionen kennst Du die keinen Rückgabewert haben?

Hmm, spontan würde ich sagen, einfach durch aio_read()/aio_write() nen neuen Thread erstellen, der dann normales RPC macht, aber der Caller (der den Thread erstellt hat) kann sofort danach (Threaderstellung) weitermachen.
Kann man so machen aber ist natürlich zusätzlicher Overhead. Wenn man schon beim Callee einen neuen Thread erzeugt ist es IMHO Unsinn auch noch beim Caller einen neuen Thread zu erzeugen der eigentlich nur dazu dient untätig zu blockieren bis der Callee fertig ist.

(das ich nicht wüsste was eine Anwendung weiter machen sollte, wenn sie noch keine Daten hat, ist erstmal egal)
Mit asynchronem I/O kann man z.B. einen HTTP-Server als Single-Thread-Applikation bauen, dieser eine Thread managed dann alle aktiven Verbindungen. Auf diese Art spart man sich ne Menge Kontext-Switches. Dafür benötigt man natürlich entsprechende (komplexe) Mechanismen mit denen man erkennen kann bei welcher der vielen Verbindungen als nächstes Daten nachgeschoben werden können. Auch wenn man jede Verbindung mit einem eigenen Thread behandelt kann asynchrones I/O helfen, indem man z.B. Double-Buffering betreibt, während die Daten aus dem einen Buffer per TCP verschickt werden können in den anderen Buffer die nächsten Daten eingelesen werden (falls die Daten z.B. von PHP kommen ist es sicher von Vorteil wenn zumindest das Senden per TCP bequem im Hintergrund läuft) und wenn beides fertig ist wird getauscht.

was ändert dieser Callback, dass die Anwendung miteinmal die Daten, die jetzt vorhanden sind, nutzt?
Ja, genau darum gehts.

Wird der Code der die Daten verarbeitet direkt in dem Callback ausgeführt?
Kann man machen muss man aber nicht, das hängt davon ab wie man sein Programm entwirft. In dem Beispiel mit dem Double-Buffering (mit asynchronem File-read und asynchronem TCP-write) würde ich im Call-Back-Handler nur jeweils ein Flag setzen und den eigentlichen Arbeits-Thread aufwecken (falls das andere Flag auch gesetzt ist). Man kann aber auch die Daten gleich verarbeiten und gegebenenfalls den nächsten asynchronen IPC-Vorgang anstoßen, falls dann der Call-Back-Handler fertig ist bevor der nächste IPC-Vorgang beendet wird (und auch wieder einen Call-Back-Handler anstößt) kann es sogar sein das für diese konkrete Aufgabe gar kein Thread in dem Programm existiert (nicht mal ein blockierter).

Wird für den Callback der laufende Thread unterbrochen (wenn ja, welcher Thread, bei Multithreading) oder wird dafür ein neuer erstellt?
Das ist auch wieder davon abhängig wie Du das implementierst. In meinem System soll ein neuer Thread injiziert werden (genauso wie der PopUp-Thread beim Callee).

Denn ich gehe davon aus, dass das meiste was per IPC kommuniziert wird unter 4kb liegt.
Davon bin ich nicht unbedingt überzeugt, aber ich schätze zumindest für Dinge wie stdin/stdout/stderr dürfte das zutreffen.

Soweit Theorie. Ein Bsp. fällt mir ein wo mapping schneller wäre, anstatt die Daten die man per read() anfordert ständig in einzelne Msgs zu kopieren, könnte man immer eine oder zwei Pages der Datei per Mapping in den Prozess gemappt haben ....
Tolle Idee, aber dann mach doch gleich richtiges Memory-Mapped-File-I/O.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 19. October 2011, 20:43
Zitat von: erik
Also wenn man für synchrones IPC (also alles was eine zugehörige Antwort hat) bereits einen eigenen Port benötigt um auch nur als Client funktionieren zu können ist das IMHO schon eine Einschränkung, die meisten (einfachen) Funktionen aus der libc sind synchron (und blockierend).
Ich habe doch geschrieben, dass man auf Senderseite keinen Port benötigt und bei asynch kann man dann keine Antwort bekommen, aber bei synch blockiert ja der Sender (und wartet auf Daten) und somit kann ihm auch die Nachricht zugestellt werden.

Der Grund warum das so "komisch" ist, weil entweder ich lasse das IPC per Thread machen (jeder Thread hat eine Messagebox) oder ich erstelle Ports dafür. Das mit jeder Thread hat ne Msgbox gefällt mir irgendwie nicht und deswegen halt die Ports.

Zitat von: erik
Kann man so machen aber ist natürlich zusätzlicher Overhead. Wenn man schon beim Callee einen neuen Thread erzeugt ist es IMHO Unsinn auch noch beim Caller einen neuen Thread zu erzeugen der eigentlich nur dazu dient untätig zu blockieren bis der Callee fertig ist.
Hast du ja recht, aber würde halt die Sache mit dem Callback (ohne das man das zusätzlich über den Kernel implementieren müsste) einfacher machen.

Würde das eigentlich auch für Signale so funktionieren oder muss das zwingend an einen bestimmten Thread und nicht nur Task gehen? Ich überlege immernoch ob man Signale über IPC (was dann einen Port pro Anwendung die Signale nutzen möchte) oder über nen Art Callback macht.
Wobei mit obigen Callback hätte die IPC Variante weniger Overhead.

Zitat von: erik
Davon bin ich nicht unbedingt überzeugt, aber ich schätze zumindest für Dinge wie stdin/stdout/stderr dürfte das zutreffen.
Dann sag mir doch mal ein paar Sachen die immer/meistens über 4kb liegen, aber nicht gleich mehrere MBs sind, so dass Mapping sich auf jeden Fall lohnt.

Zitat von: erik
Tolle Idee, aber dann mach doch gleich richtiges Memory-Mapped-File-I/O.
Ich hatte das immer so verstanden, dass da einfach ein, gewünschter, Teil der Datei in den User-Prozess gemappt wird!? Und damit man den User-Prozess nicht zuspamt dachte ich halt an ein oder zwei Pages. Denn Dateien die größer sind als der Adressraum, können sowieso nicht vollständig eingeblendet werden.

Das mit dem Datei einblenden ist auch eine Sache die dafür Spricht fork() nicht anzubieten. Denn so spare ich mir das ich auf jeden Fall FD´s haben muss, die gesharet werden können.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 19. October 2011, 21:35
Hallo,


Ich habe doch geschrieben
Sorry, da hab ich wohl nicht so ganz aufmerksam mitgedacht.

Der Grund warum das so "komisch" ist, weil entweder ich lasse das IPC per Thread machen (jeder Thread hat eine Messagebox) oder ich erstelle Ports dafür. Das mit jeder Thread hat ne Msgbox gefällt mir irgendwie nicht und deswegen halt die Ports.
Was meist Du denn mit "jeder Thread hat eine Messagebox"? Das man direkt an die Thread-ID (anstatt an einen Port) eine Message schicken kann? Wird das dann innerhalb dieses Threads verarbeitet (also wird der so unterbrochen wie bei einem klassischen Signal) oder muss der Thread die Message selber aktiv abholen?
Bei mir hab ich vorgesehen das jeder Prozess ein Prozess-Message-Target kreieren kann so das man wirklich an eine Prozess-ID eine simple Message (per detached IPC) schicken kann, aber das muss der Prozess aktiv einrichten und es wird auch hier ein PopUp-Thread injiziert. Das kann z.B. der Kernel nutzen um an einen Prozess eine Message zu schicken (sowas wie OOM-Killer kommt gleicht oder Pagefault-Exceptions usw.), aber das geht auch für Signale und ähnliches Zeugs.

Hast du ja recht, aber würde halt die Sache mit dem Callback (ohne das man das zusätzlich über den Kernel implementieren müsste) einfacher machen.
Ich verstehe was Du meinst, aber mit dieser kleinen Vereinfachung des Kernels holst Du Dir ne Menge Overhead und etwas zusätzlichen Speicherverbrauch, ich würde eher dazu raten diese kleine zusätzliche Komplexität in den Kernel zu holen (man kann ja den selben Mechanismus nutzen wie beim Zustellen der eigentlichen Anfrage nur mit anderen Parametern).

Würde das eigentlich auch für Signale so funktionieren oder muss das zwingend an einen bestimmten Thread und nicht nur Task gehen? Ich überlege immernoch ob man Signale über IPC (was dann einen Port pro Anwendung die Signale nutzen möchte) oder über nen Art Callback macht.
Signale an einen Prozess möchte ich ebenfalls über das Prozess-Message-Target laufen lassen. Einen Thread zu unterbrechen (so wie bei klassischen Signalen) hab ich grundsätzlich nicht vorgesehen (sowas will ich überhaupt nicht haben), das bringt IMHO nur Ärger mit Funktionen wie malloc die man besser nicht unterbrechen sollte und in tyndur-Manier vor und hinter jeder kritischen Aktion das Zustellen von Signalen ab und wieder an zu schalten ist meiner persönlichen Meinung nach auch keine Lösung.

Dann sag mir doch mal ein paar Sachen die immer/meistens über 4kb liegen, aber nicht gleich mehrere MBs sind, so dass Mapping sich auf jeden Fall lohnt.
Ich kann mir vorstellen das die meisten Programme die Dateien am Stück einlesen/speichern dafür auch größere read()/write()-Aktionen nutzen. Aus meiner Sicht wären das z.B. Text-Editoren u.ä., es gibt durchaus auch einige Anwendungen wo Dateien nicht wie ein Stream verarbeitet werden.
Wenn Du kopieren möchtest, wo willst Du die Daten beim Callee den hinkopieren? Wenn man für den IPC-Vorgang immer einen frischen Thread nimmt hat man auch immer einen frischen Stack auf den schon mal ein paar Daten drauf können (dann wäre der Stack-Pointer beim Einsprung in die Handler-Funktion eben schon etwas weiter gerutscht).

Das mit dem Datei einblenden ist auch eine Sache die dafür Spricht fork() nicht anzubieten. Denn so spare ich mir das ich auf jeden Fall FD´s haben muss, die gesharet werden können.
Über das Sharen von FDs bei echten Dateien hatten wir hier doch schon mal ausführlich diskutiert und ich erinnere mich nicht daran das da jemand ein reelles Beispiel genannt hat, von daher bleibe ich bei meiner Meinung das sich das bei echten Dateien eh nicht lohnt. Anders ist das natürlich bei Character-Devices wie stdin/stdout/stderr wenn man diese weitervererben möchte aber das sind eben keine echten Dateien so das man den Datei-Pointer hier eh anders behandeln muss.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 19. October 2011, 21:37
Hallo,

Niemand hindert den Callee daran, den Speicher selbst freizugeben und den Caller per asynchronem IPC darüber zu informieren.
Das ist zwar richtig aber es erscheint mir irgendwie umständlich, aus meiner Sicht ist das ganze ein kompletter IPC/RPC-Vorgang den ich auch so als asynchrones IPC anbieten möchte.
Für mich ist das ein Spezialfall, der ebensogut von der allgemeinen IPC-API abgedeckt werden kann.

Bei mir wird zuerst das Memory-Sharing eingerichtet und dann beim Callee ein PopUp-Thread injiziert aber der Caller blockiert nicht bis der Callee fertig ist sondern kann weiter arbeiten, wenn der Callee fertig ist wird das Memory-Sharing beendet und beim Caller auch ein PopUp-Thread injiziert der dann den Call-Back ausführt.
Ich halte nicht viel von Popup-Threads. Bei meinem Vorschlag blockiert der Caller auch nicht, wenn er asynchrones IPC benutzt und sowohl Caller als auch Callee können das Sharing beenden.

Nein, das will ich nicht, was ich meinte war eher dass das API der libc eben auch auf dieses Paradigma setzt. Das bedeutet z.B. das read die gelesenen Daten nicht in einem neuen Speicherbereich als Rückgabewert liefert sondern das der Speicherbereich in den die Daten rein sollen schon vorgegeben wird.
Stört mich persönlich nicht, dann muss halt kopiert werden. Allerdings nur bei nicht ausgerichteten Puffern und nur Anfang und/oder Ende.

Das VFS sollte mit beiden Varianten klarkommen, von daher spielt das keine Rolle.
Aus Sicht des VFS spielt das IMHO schon eine Rolle, wenn sämtliche Dateioperationen über ein einziges Handle laufen dann ist die Kohärenz doch schon innerhalb der Anwendung erledigt, egal wie viele Threads die Anwendung hat.
Das ist richtig, betrifft aber nur den Fall mehrerer Threads innerhalb eines Prozesses auf einer einzelnen Maschine. Für verteilte Systeme oder mehrere Prozesse in einem System hilft das nichts. Von daher sollte das VFS entsprechende Garantien anbieten.

Gruß,
Svenska
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 19. October 2011, 21:58
Zitat von: erik
Was meist Du denn mit "jeder Thread hat eine Messagebox"? Das man direkt an die Thread-ID (anstatt an einen Port) eine Message schicken kann? Wird das dann innerhalb dieses Threads verarbeitet (also wird der so unterbrochen wie bei einem klassischen Signal) oder muss der Thread die Message selber aktiv abholen?
Genau, die Thread-ID wäre dann gleich der Port-ID und wir wären wieder bei akivem Abholen.

Zitat von: erik
Bei mir hab ich vorgesehen das jeder Prozess ein Prozess-Message-Target kreieren kann so das man wirklich an eine Prozess-ID eine simple Message (per detached IPC) schicken kann, aber das muss der Prozess aktiv einrichten und es wird auch hier ein PopUp-Thread injiziert.
Klingt für mich nach dem selben Prinzip wie dein IPC, hat nur nen anderen Namen ;) Sprich du hättest also auch ein Port pro Prozess (wenn der es denn will) für Signale und ähnliche Sachen.

Zitat von: erik
Ich kann mir vorstellen das die meisten Programme die Dateien am Stück einlesen/speichern dafür auch größere read()/write()-Aktionen nutzen. Aus meiner Sicht wären das z.B. Text-Editoren u.ä., es gibt durchaus auch einige Anwendungen wo Dateien nicht wie ein Stream verarbeitet werden.
Naja, das meinte ich ja, da reden wir dann aber schon von wesentlich mehr Daten als nur ein oder zwei Pages.

Daher könnte man einfach sagen, alles < 4kb muss nicht ausgerichtet sein und wird kopiert und alles >= 4kb muss ausgerichtet sein und wird nicht kopiert, sondern per SHM gelöst.
Man könnte dann sogar soweit gehen, dass alles >= 4kb per Message-IPC gemacht wird und alles < 4kb per Pipe (die auch nur ne Einwege-Kommunikation wäre die in beide Prozesse gemappt ist).

Zitat von: erik
Über das Sharen von FDs bei echten Dateien hatten wir hier doch schon mal ausführlich diskutiert und ich erinnere mich nicht daran das da jemand ein reelles Beispiel genannt hat, von daher bleibe ich bei meiner Meinung das sich das bei echten Dateien eh nicht lohnt.
Richtig, das hätte vorallem den Vorteil das man die FD´s wunderbar in der libc lassen kann und ich brauche auch keinen read()-"Syscall" mehr anbieten, sondern nur noch Memory-Mapped-File.

Allerdings werden dadurch so Sachen wie Log-Dateien schwierig. Wo vllt mehrere Prozesse reinschreiben (ist das wirklich so?). Das mehrere Threads den gleichen FD nutzen um in eine Datei zu schreiben, wäre ja kein Problem und könnte per Locking gelöst werden.

Zitat von: erik
Anders ist das natürlich bei Character-Devices wie stdin/stdout/stderr wenn man diese weitervererben möchte aber das sind eben keine echten Dateien so das man den Datei-Pointer hier eh anders behandeln muss.
Ist meine Annahme richtig, das diese Character-Devices eh keinen ftell() und fseek() anbieten? Dann bräuchte man sich dort auch nicht um sowas kümmern. Das würde ich dann per Pipe lösen wollen, die auch in mehrere Prozesse gemappt sein kann und das Schreiben könnte dann z.B. per cmpxchg passieren.

Die Frage wäre dann nur, ist bei einem Character-Device ein String eine Einheit oder ein Character? Ich frage, weil ob es passieren kann das wenn 2 Prozesse da reinschreiben (einer "HELLO" und einer "WORLD"), dass dabei dann "HWEOLRLLOD" rauskommen kann (rein theoretisch)?

Was ich eher unglücklich finden würde (und das wolltest du >erik< doch machen wimre), wenn eine Pipe in Prozess A führt (stdin) die dann stdin weiter an Prozess B vererbt und dafür ne neue Pipe erstellt wird und jedes mal wenn etwas auf stdin geschrieben wird, wird erst Prozess A geweckt, holt die Daten ab und schreibt sie auf die Pipe an Prozess B.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 20. October 2011, 10:07
Hallo,


Für mich ist das ein Spezialfall, der ebensogut von der allgemeinen IPC-API abgedeckt werden kann.
So gesehen gibt es nur Spezialfälle, die Frage ist welche davon bietet man nativ an und lassen sich die übrigen Fälle damit effizient nachbilden. Ich hab mir für mein IPC-API 3 Spezialfälle rausgesucht, die aus meiner Sicht >99% aller IPC-Vorgänge abdecken, und denke dass das reichen wird, auch weil sich die restlichen Szenarien damit IMHO recht effizient nachbilden lassen.

sowohl Caller als auch Callee können das Sharing beenden.
Warum sollte der Caller das Sharing beenden wollen? Das bedeutet ja das er den Speicher verliert (quasi frei gibt) und das dürfte bei den üblichen Anwendungsmöglichkeiten wie read()/write() oder aio_read()/aio_write() eher nicht gewünscht sein, nebst dessen dass das nur mit ausgerichtetem Speicher funktioniert (man kann ja keine halben Pages frei geben).

Stört mich persönlich nicht, dann muss halt kopiert werden.
So leicht kann man verschiedener Meinung sein, mich stört das Kopieren erheblich. ;)
Das Kopieren zu vermeiden ist für mich ein wesentliches Designziel.

Das ist richtig, betrifft aber nur den Fall mehrerer Threads innerhalb eines Prozesses auf einer einzelnen Maschine.
Aber gerade darum ging es doch, ihr habt doch gesagt das man bei Multithreading-Applikationen sehr schnell an einem Punkt ist wo das VFS saubere Kohärenz bieten muss.

Für verteilte Systeme oder mehrere Prozesse in einem System hilft das nichts.
Das ist richtig aber eben wieder kein Alltagsszenario für ein Hobby-OS. Genau deswegen hätte ich ja gerne mal ein etwas alltäglicheres Beispiel.


Genau, die Thread-ID wäre dann gleich der Port-ID und wir wären wieder bei akivem Abholen.
Also für jeden einzenen Thread möchte ich sowas nicht haben, mir reicht das einmal pro Prozess. Zum aktiven Abholen hatte ich ja nun auch schon mehrmals geschrieben.

Klingt für mich nach dem selben Prinzip wie dein IPC, hat nur nen anderen Namen ;) Sprich du hättest also auch ein Port pro Prozess (wenn der es denn will) für Signale und ähnliche Sachen.
Das klingt nicht nur nach meinem normalen IPC sondern es ist mein normales IPC, der einzigste Unterschied ist das eine Prozess-ID als Ziel benutzt wird und dass das auch nur mit detached (unidirektional) IPC funktioniert (für die komplexeren/bidirektionalen IPC-Mechanismen (sync/async) muss man immer ein dediziertes IPC-Target (was bei Dir dann ein Port ist) einrichten).

... und alles < 4kb per Pipe (die auch nur ne Einwege-Kommunikation wäre die in beide Prozesse gemappt ist).
Also Pipes sind da IMHO eher weniger geeignet, schon weil die oft doppeltes Kopieren erfordern (Sender>>Kernel und dann Kernel>>Empfänger). Nebst dessen das dann Dein VFS ne Menge offene Pipes verwalten muss, mein VFS soll genau ein einziges (bidirektionales) IPC-Target anbieten über das mehrere (theoretisch unbegrenzt viele) IPC-Vorgänge gleichzeitig laufen können (jeder IPC-Vorgang injiziert einen eigenen PopUp-Thread in den VFS-Prozess), das VFS merkt auch nicht ob die Clients dafür synchrones oder asynchrones IPC benutzen (das lässt sich bei einem bidirektionalem IPC-Target beliebig mischen) und es spielt auch keine Rolle ob alle IPC-Vorgänge vom selben Prozess kommen oder von vielen verschiedenen Prozessen, das einzigste was das VFS gewährleisten muss ist Kohärenz wenn mehrere IPC-Vorgänge die selbe Datei betreffen (auch hierbei ist es wieder egal ob das alles vom selben Prozess kommt oder von mehreren Prozessen).

Allerdings werden dadurch so Sachen wie Log-Dateien schwierig. Wo vllt mehrere Prozesse reinschreiben (ist das wirklich so?).
Log-Dateien sollten mit APPEND_ONLY geöffnet und dann von der libc so ähnlich wie Streams behandelt werden, dann liegt der File-Pointer aber doch im VFS (hier muss dann das VFS dafür sorgen das die einzelnen append()-Aufrufe den File-Pointer atomar weiterrücken).

Ich frage, weil ob es passieren kann das wenn 2 Prozesse da reinschreiben (einer "HELLO" und einer "WORLD"), dass dabei dann "HWEOLRLLOD" rauskommen kann (rein theoretisch)?
Ja, das kann theoretisch passieren, falls beide Prozesse jeweils einzelne Buchstaben schicken. Wenn immer nur nach printf() geflusht wird dann sollte zumindest so eine extreme Verzahnung nicht passieren (ich denke mal das die einzelnen Datentransfers jeweils atomar abgearbeitet werden).

Was ich eher unglücklich finden würde (und das wolltest du >erik< doch machen wimre), wenn eine Pipe in Prozess A führt (stdin) die dann stdin weiter an Prozess B vererbt und dafür ne neue Pipe erstellt wird und jedes mal wenn etwas auf stdin geschrieben wird, wird erst Prozess A geweckt, holt die Daten ab und schreibt sie auf die Pipe an Prozess B.
Das Vererben von Streams wollte ich etwas anders lösen: http://forum.lowlevel.eu/index.php?topic=2622.msg29758#msg29758 (http://forum.lowlevel.eu/index.php?topic=2622.msg29758#msg29758) (etwas höher hab ich auch ein paar Gedanken zu Log-Dateien geschrieben).


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 20. October 2011, 10:37
Zitat von: erik
Also Pipes sind da IMHO eher weniger geeignet, schon weil die oft doppeltes Kopieren erfordern (Sender>>Kernel und dann Kernel>>Empfänger).
Also meine Pipes laufen fast komplett im UserSpace (nur das eventuell ein Writer oder ein Reader aufgeweckt werden muss, dass passiert im/über den Kernel). Von daher kein doppeltes Kopieren.

Ich dachte mir das dann so, dass die ersten 3 FD´s immer Pipe´s sind und von der Libc gesondert behandelt werden. Über das VFS holt man sich dann die Pipe´s ab und kann sie nutzen. Dadurch wäre es auch möglich, dass eine Datei am anderen Ende stecken kann, das wird dann aber im VFS gemacht. Wenn z.B. die Ausgabe von Prozess A auf die Eingabe von Prozess B umgeleitet werden soll, dann geht das so, dass das VFS das weiß und Prozess B mappt die Pipe von Prozess A. So ist das VFS auch nicht mehr involviert.

Zitat von: erik
ich denke mal das die einzelnen Datentransfers jeweils atomar abgearbeitet werden
Dafür müsste ich sagen, wenn eine Datei zum exklusiven Schreiben geöffnet wird, dann kann gemappt werden und ansonsten wird über Pipe´s gearbeitet und das VFS muss dann zusehen, wie es die Sachen halbwegs atomar aus den Pipe´s in die Datei bekommt.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 20. October 2011, 17:29
Hallo,


Also meine Pipes laufen fast komplett im UserSpace (nur das eventuell ein Writer oder ein Reader aufgeweckt werden muss, dass passiert im/über den Kernel). Von daher kein doppeltes Kopieren.
Da bin ich jetzt aber neugierig wie Du das umsetzen möchtest? Per Mapping? Wenn man bei einem Datentransfer zwischen 2 unabhängigen Flat-Memory-Prozessen weniger als 2 mal kopieren möchte wird IMHO auf jeden Fall Mapping benötigt, selbst der Kernel kann nicht ohne ein (temporäres) Mapping auf 2 virtuelle Adressräume gleichzeitig zugreifen.

Ich dachte mir das dann so, dass die ersten 3 FD´s immer Pipe´s sind und von der Libc gesondert behandelt werden.
Ich denke dass das nicht nur die ersten 3 FDs pro Prozess trifft sondern eine beliebige Anzahl, die libc sollte zu jeden FD wissen was für ein Typ das ist. Die Schwierigkeit bei den ersten 3 FDs ist eher das sie noch vor dem Aufruf von main() vorhanden sein müssen und da ist zu überlegen ob die libc sich diese 3 FDs irgendwo holt (indem bei irgendeiner zentralen Stelle die libc anfragt "ich bin Prozess XYZ und will meine 3 Standard-FDs") oder ob sie dem Prozess explizit mitgegeben werden (so ähnlich wie die Command-Line-Parameter). Das ist IMHO ein wesentlicher Aspekt bei der Erstellung neuer Prozesse (womit wir auch wieder beim Thema dieses Threads sind). Auch sehe ich ein Problem beim weitervererben von stdin, hier muss IMHO bei jedem ProcessCreate() eindeutig geklärt werden ob stdin vererbt werden soll (dann sollte der aktuelle Prozess nicht mehr auf stdin zugreifen können) oder ob der neue Prozess ein eigenes stdin bekommt (und der aktuelle Prozess sein stdin behalten kann). Auch wäre es eine Überlegung wert ob Hintergrundprozesse überhaupt ein stdin bekommen, da müsste dann gegebenenfalls die libc immer nur EOF o.ä. zurück liefern.

Dafür müsste ich sagen, wenn eine Datei zum exklusiven Schreiben geöffnet wird, dann kann gemappt werden und ansonsten wird über Pipe´s gearbeitet und das VFS muss dann zusehen, wie es die Sachen halbwegs atomar aus den Pipe´s in die Datei bekommt.
Also ich muss ganz ehrlich sagen das mir persönlich dieses Umherreichen von Pipes ziemlich suspekt ist, ich hätte da Angst das da mal was verloren geht (weil einer der Prozesse genau in dem Moment aufgrund irgendeiner Exception in einem anderen Thread plötzlich gekillt wird o.ä.) und dann gar nix mehr geht. Da solltest Du IMHO auf jeden Fall noch mal gründlich drüber nachdenken.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 20. October 2011, 19:16
Zitat von: erik
Da bin ich jetzt aber neugierig wie Du das umsetzen möchtest? Per Mapping?
Jap, per Mapping. Ich hatte das sogar schon umgesetzt, aber irgendwo war noch der Wurm drin und die Pipe war auf 4byte Blöcke beschränkt (schöner wären 1byte Blöcke).

Man hat nen Read-Ptr, nen Write-Ptr und nen Flag-Feld (jeweils 4byte groß). Eine Pipe besteht dann aus einer Page und der restliche Speicher der Page (4084bytes) wird als Ring-Buffer genutzt.

Problem daran ist das Thread-aufwecken und schlafen-legen. Es gibt da so ein paar Randsituationen die ich noch nicht im Griff habe und auch keine gute Lösung für weiß.

Zitat von: erik
Ich denke dass das nicht nur die ersten 3 FDs pro Prozess trifft sondern eine beliebige Anzahl, die libc sollte zu jeden FD wissen was für ein Typ das ist.
Ja, dachte ich mir auch schon. Damit würde ich dann beide Fälle, einmal den FD in der libc und einmal im VFS haben (jenachdem ob Pipe oder Mapping).

Zitat von: erik
Die Schwierigkeit bei den ersten 3 FDs ist eher das sie noch vor dem Aufruf von main() vorhanden sein müssen und da ist zu überlegen ob die libc sich diese 3 FDs irgendwo holt (indem bei irgendeiner zentralen Stelle die libc anfragt "ich bin Prozess XYZ und will meine 3 Standard-FDs") oder ob sie dem Prozess explizit mitgegeben werden (so ähnlich wie die Command-Line-Parameter).
Ich würde das vor main() so machen, dass die libc, einfach den VFS-Server nach den ersten 3 FD´s fragt und entsprechend einrichtet.

Zitat von: erik
Auch sehe ich ein Problem beim weitervererben von stdin, hier muss IMHO bei jedem ProcessCreate() eindeutig geklärt werden ob stdin vererbt werden soll (dann sollte der aktuelle Prozess nicht mehr auf stdin zugreifen können) oder ob der neue Prozess ein eigenes stdin bekommt (und der aktuelle Prozess sein stdin behalten kann).
Dafür wäre dann die Elter-Kind-Beziehung gut. Denn so könnte ich sagen, beim Weitervererben wird die Pipe (stdin) beim Eltern-Prozess geunmappt und im Kind-Prozess gemappt, so kann der Eltern-Prozess erst wieder auf stdin zugreifen, wenn das Kind beendet wurde oder stdin geschlossen hat.

Zitat von: erik
Auch wäre es eine Überlegung wert ob Hintergrundprozesse überhaupt ein stdin bekommen, da müsste dann gegebenenfalls die libc immer nur EOF o.ä. zurück liefern.
Dafür gibt es ja "/dev/null" und sowas würde ich auf jeden Fall in der libc haben wollen. Damit für solch einfache Sachen kein IPC notwendig ist (was ja auch quatsch wäre, wenn wahrscheinlich in dem konkreten Fall nicht weiter schlimm).

Zitat von: erik
Also ich muss ganz ehrlich sagen das mir persönlich dieses Umherreichen von Pipes ziemlich suspekt ist, ich hätte da Angst das da mal was verloren geht (weil einer der Prozesse genau in dem Moment aufgrund irgendeiner Exception in einem anderen Thread plötzlich gekillt wird o.ä.) und dann gar nix mehr geht. Da solltest Du IMHO auf jeden Fall noch mal gründlich drüber nachdenken.
So richtig perfekt ist das mit den Pipes nicht, das weiß ich aber. Das Problem was du beschreibst hatte ich so gelöst, dass der Writer/Reader bei einem Zugriff auf die Pipe nen Fehler zurück bekommt (kann durch die Flags geschehen), dass das Gegenüber nicht mehr existiert.
Ein Problem bei meinen Pipe´s war, dass sie auf Threads geeicht waren und nicht auf ganze Prozesse (war wegen dem Aufwachen und Schlafenlegen).

Ein anderes Problem ist wenn der Writer z.B. der Meinung ist er müsse die Flags und die Pointer ändern. Dann liest die andere Seite natürlich Müll, aber das Problem hätte man auch bei Zero-Copy-IPC (wenn man dem Caller nicht die Schreibrechte für den Bereich nimmt) und Multithreading.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 21. October 2011, 12:12
Hallo,


Man hat nen Read-Ptr, nen Write-Ptr und nen Flag-Feld (jeweils 4byte groß). Eine Pipe besteht dann aus einer Page und der restliche Speicher der Page (4084bytes) wird als Ring-Buffer genutzt.
Hm, das klingt nach einem ziemlichen Sicherheitsleck. Ich nehme mal an das beide auf diese eine Page Schreibzugriff haben (sonst könnten ja nicht beide ihren reweiligen Pointer aktualisieren). Was passiert den wenn einer von beiden den jeweils anderen Pointer manipuliert? Oder die Flags auf etwas ungültiges/unerwünschtes setzt? Bei den Pointern kann man sich so behelfen des jede Seite ihren Pointer in einer lokalen Variable verwaltet und den Pointer in dieser shared Page nur schreibt um den Anderen zu informieren aber niemals selber von dort ließt. Mit den Flags könnte man das ähnlich machen, wenn man 2 getrennte Flags hat. Trotzdem finde ich dass das immer noch ein tolles Einfallstor für DoS-Angriffe u.ä. darstellt.

Es gibt da so ein paar Randsituationen die ich noch nicht im Griff habe und auch keine gute Lösung für weiß.
Du kennst doch da ganz bestimmt ein gutes deutschsprachiges Forum wo man sich über OS-Entwicklung und ähnlichen Quatsch austauscht? Falls nicht wird Dir hier sicher jemand eines empfehlen können.  SCNR

Damit würde ich dann beide Fälle, einmal den FD in der libc und einmal im VFS haben (jenachdem ob Pipe oder Mapping).
Also ich hätte den FD nur dann im VFS wenn eine echte Datei im Append-Only-Mode geöffnet ist (und selbst da noch nicht mal richtig weil ja der File-Pointer immer gleich der File-Size ist), alle anderen Fälle managed entweder schon die libc intern oder die gehen gleich komplett am VFS vorbei.

Ich würde das vor main() so machen, dass die libc, einfach den VFS-Server nach den ersten 3 FD´s fragt und entsprechend einrichtet.
Und wieso ausgerechnet das VFS fragen? Und was ist wenn ein Prozess stdin/stdout/stderr für sein Kind komplett selber anbieten will (um z.B. ein anders Programm selber interaktiv zu steuern)? Mit so einer zentralen Stelle sehe ich eine Menge an Problemen. Ich würde es eher so machen das beim Erstellen eines neuen Prozesses diesem eine Art Parameter-Block mitgegeben wird (der beim Ersteller innerhalb der libc aufgebaut wird, so das der Kernel da fein draußen bleiben kann und maximal die Größe dieses Parameter-Blocks wissen muss) und den die libc dann vor main() wieder intern zerlegt um dort u.a. die Command-Line-Parameter, die Umgebungsvariablen und eben auch die 3 Default-FDs zu extrahieren. Dazu ist nur notwendig das die libc des Elternprozesses mit der libc im Kindprozess kompatibel ist (wenn die dynamisch eingebunden wird sollte das von selber gewährleistet sein) aber auch das kann man mit einem flexiblen Format (muss ja nicht gleich XML sein aber eben was geeignetes) ganz gut erledigen. Das ist zumindest der Weg den ich gehen möchte.

Dafür wäre dann die Elter-Kind-Beziehung gut. ....
Hast Du auch an Fälle gedacht wo der Elternprozess in der Zwischenzeit beendet wird und stdin vom Kindprozess an dessen Großelternprozess (oder noch höher) zurückgegeben werden muss?

Zitat von: erik
Auch wäre es eine Überlegung wert ob Hintergrundprozesse überhaupt ein stdin bekommen, da müsste dann gegebenenfalls die libc immer nur EOF o.ä. zurück liefern.
Dafür gibt es ja "/dev/null"
Also für ein leeres stdin ist das IMHO ganz und gar nicht geeignet, da kommen ja Bytes raus und ich glaube nicht das es im Sinne der Prozesse ist wenn diese von stdin unbegrenzt viele 0x00-Bytes lesen können.

und sowas würde ich auf jeden Fall in der libc haben wollen.
Ja, solche Dinge möchte ich auch gleich direkt in der libc abfangen. Ob das schlimm ist wenn /dev/null nicht so performant ist kann ich nicht wirklich beurteilen. In der c't war vor etlichen (>10) Jahren mal ein Performance-Vergleichstest der Null-Deives von verschiedenen OSen (wimre DOS, Windows 95, Windows NT und Linux) drin, damals hat Linux mit gutem Abstand gewonnen (sicher auch weil Linux eben ein Monolith ist, NT eher ein Micro-Kernel sein wollte aber MS dafür keine echte OS-Dev-Kompetenz besitzt und Win95 + DOS eh Schei.. sind).

Ein Problem bei meinen Pipe´s war, dass sie auf Threads geeicht waren
Womit aktives Abholen dann auch Pflicht wird, aber dazu kennst Du meine Meinung ja bereits.

Ein anderes Problem ist wenn der Writer z.B. der Meinung ist er müsse die Flags und die Pointer ändern. Dann liest die andere Seite natürlich Müll, aber das Problem hätte man auch bei Zero-Copy-IPC (wenn man dem Caller nicht die Schreibrechte für den Bereich nimmt) und Multithreading.
Ja, diese Problem hat man grundsätzlich, selbst wenn (eventuell gar im Kernel) kopiert wird könnte während dessen ein anderer Thread immer noch in den Daten rumfuschen. Ich denke das kann man nur dann zufriedenstellend lösen wenn zumindest die Kommandos und Rückgabewerte grundsätzlich kopiert und vor der Benutzung gründlich auf Plausibilität getestet werden, bei den Nutzdaten wird man sich wohl damit abfinden müssen. Solange die Stabilität des System durch dieses Problem nicht beeinträchtigt wird ist es aber IMHO akzeptabel.


Grüße
Erik


PS.: die oben erwähnte c't war übrigens eine April-Ausgabe (damals kam die c't auch noch monatlich)
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 21. October 2011, 15:35
Zitat von: erik
Hm, das klingt nach einem ziemlichen Sicherheitsleck. Ich nehme mal an das beide auf diese eine Page Schreibzugriff haben (sonst könnten ja nicht beide ihren reweiligen Pointer aktualisieren). Was passiert den wenn einer von beiden den jeweils anderen Pointer manipuliert? Oder die Flags auf etwas ungültiges/unerwünschtes setzt? Bei den Pointern kann man sich so behelfen des jede Seite ihren Pointer in einer lokalen Variable verwaltet und den Pointer in dieser shared Page nur schreibt um den Anderen zu informieren aber niemals selber von dort ließt. Mit den Flags könnte man das ähnlich machen, wenn man 2 getrennte Flags hat. Trotzdem finde ich dass das immer noch ein tolles Einfallstor für DoS-Angriffe u.ä. darstellt.
Ich weiß dass das alles Probleme sind und mir sind diese auch bekannt, aber ist das nicht im Endeffekt das gleiche, als wenn die Daten einer Msg verändert werden (z.B. von nem anderen Thread)?

Zitat von: erik
Und wieso ausgerechnet das VFS fragen? Und was ist wenn ein Prozess stdin/stdout/stderr für sein Kind komplett selber anbieten will (um z.B. ein anders Programm selber interaktiv zu steuern)?
Wird mit fork() oder der durch spezielles Markieren der FD´s als shared (oder so) in Windows gemacht. Fork() ist ja so ne Sache ;), aber wie du es schon selbst beschreibt, wollte ich das dann irgendwie so machen, dass diese 3 FD´s bei einer Prozesserstellung mit übergeben werden können. Der Prozess halt sich diese dann vom VFS und damit müssten die lib´s auch nicht kompatibel sein. Was ist denn wenn du z.B. mehrere Sprachen mit unterschiedlichen libs hast?

Ich sehe das halt so, dass das VFS sowieso vorhanden sein muss, die libc aber nicht. Deswegen soll es darüber gelöst werden.

Gibt es praktisches Fälle wo nicht nur die ersten 3 sondern auch andere "normale" Dateien an das Kind weitervererbt werden (obwohl das ja wiederrum nur bei fork() Sinn machen würde, weil man sonst die FD Nummer gar nicht wüsste)?

Zitat von: erik
Hast Du auch an Fälle gedacht wo der Elternprozess in der Zwischenzeit beendet wird und stdin vom Kindprozess an dessen Großelternprozess (oder noch höher) zurückgegeben werden muss?
Sollte das nicht "einfach" gehen, indem man den Elternzeiger dann auf den Großeltern-Prozess zeigen lässt?

Zitat von: erik
Also für ein leeres stdin ist das IMHO ganz und gar nicht geeignet, da kommen ja Bytes raus und ich glaube nicht das es im Sinne der Prozesse ist wenn diese von stdin unbegrenzt viele 0x00-Bytes lesen können.
Jap, das ist wohl war, also EOF.

Zitat von: erik
Womit aktives Abholen dann auch Pflicht wird, aber dazu kennst Du meine Meinung ja bereits.
Gerade nicht oder was meinst du überhaupt ;)

Man hat anhand der Pointer und der Flags sehen können ob der Writer gerade schläft oder nicht und wenn ja, hat man ihn aufgeweckt und sich selber schlafen gelegt. Der Writer schreibt dann Daten und weckt gegebenenfalls (falls der Reader schlafen sollte) den Reader auf.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 21. October 2011, 15:43
Zitat
Dafür gibt es ja "/dev/null"
Also für ein leeres stdin ist das IMHO ganz und gar nicht geeignet, da kommen ja Bytes raus und ich glaube nicht das es im Sinne der Prozesse ist wenn diese von stdin unbegrenzt viele 0x00-Bytes lesen können.
Du hast ein komisches /dev/null. Bei mir kommen nur aus /dev/zero Nullen raus.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 21. October 2011, 17:43
Hallo,


Du hast ein komisches /dev/null. Bei mir kommen nur aus /dev/zero Nullen raus.
Äh, Mist, da hab ich mal wieder voll das Fettnäpfchen erwischt. :roll:


aber ist das nicht im Endeffekt das gleiche, als wenn die Daten einer Msg verändert werden (z.B. von nem anderen Thread)?
Ja, dieses Problem existiert grundsätzlich auf jedem SMP System bei allen Daten die nicht atomar gelesen werden können (also alles was ein einzelnes natives Word übersteigt, es gibt auf vielen CPUs auch Spezialbefehle mit dem das Doppelte der nativen Word-Größe atomar gelesen werden kann aber dann ist auf jeden Fall Schluss). Komplexere Messages (und auch die Nutzdaten) unterliegen immer der Gefahr das diese sich ändern können während sie verarbeitet oder auch kopiert werden. Solange wir SMP wollen müssen wir auch damit leben, wir können uns nur bemühen zumindest die Konsequenzen in Grenzen halten. Ob ich z.B. in meinem VFS wirklich das Kopieren der Befehls-Messages tatsächlich erzwinge weiß ich noch nicht, das hängt wohl dann auch davon ab ob ich es schaffe den Befehl nur ein einziges mal zu lesen und auch gründlich auf Plausibilität zu prüfen. Bei den Rückgabewerten ist mir das egal, die sind für den Service eh uninteressant da er sie nur schreiben und nicht lesen kann.
Wie taljeth mal geschrieben hat: "ganz kostenlos bekommt man Korrektheit meistens nicht".

wollte ich das dann irgendwie so machen, dass diese 3 FD´s bei einer Prozesserstellung mit übergeben werden können. Der Prozess halt sich diese dann vom VFS und damit müssten die lib´s auch nicht kompatibel sein.
Hä, was denn nun, übergeben oder abholen? Beides geht IMHO nicht.

Was ist denn wenn du z.B. mehrere Sprachen mit unterschiedlichen libs hast?
Dann muss zumindest dieser Aspekt bei allen Libraries identisch implementiert sein oder die anderen Libraries binden die libc mit ein (zumindest diese Teile). Ich schätze mal das ich diesen Aspekt dann bei der OS-Dokumentation mit definiere, dann gehört das eben mit zur API/ABI vom OS auch wenn das OS selber damit eigentlich nichts zu tun hat (außer das der exec-Service und der Kernel eben diesen Prozess-Parameter-Block durchreichen), obwohl ja die Personality auch aus Prozessen besteht die eben die normale libc einbinden und damit eben auch genau diesen Mechanismus genau so benutzen.

Ich sehe das halt so, dass das VFS sowieso vorhanden sein muss, die libc aber nicht. Deswegen soll es darüber gelöst werden.
Dann hat das Programm eben eine andere Library die die OS-Konzepte in die entsprechende Programmiersprache überführt. Die einzigste Ausnahme sind wohl reine Assemblerprogramme die gar keine Library einbinden und hier muss eben der Assembler-Programmierer selber Code erstellen wenn er die 3 Basis-FDs, Umgebungsvariablen usw. nutzen möchte. Das sollte auch kein so großes Problem sein weil ich das Format nicht allzu komplex machen will.

Gibt es praktisches Fälle wo nicht nur die ersten 3 sondern auch andere "normale" Dateien an das Kind weitervererbt werden (obwohl das ja wiederrum nur bei fork() Sinn machen würde, weil man sonst die FD Nummer gar nicht wüsste)?
Mit den Mitteln von POSIX fällt mir eigentlich auch nur fork() ein wenn man ein Datei-Handle an sein Kind geben will, oder eben http://de.wikipedia.org/wiki/POSIX_local_inter-process_communication_socket (http://de.wikipedia.org/wiki/POSIX_local_inter-process_communication_socket) (damit kann man auch Datei-Handles übertragen).

Sollte das nicht "einfach" gehen, indem man den Elternzeiger dann auf den Großeltern-Prozess zeigen lässt?
Dazu muss dieser Prozess aber eventuell darüber informiert werden das er andere/neue Kinder hat (und das nicht mit 9 Monaten Vorwarnzeit). Ich bin auch am überlegen wie umfangreich ich die entsprechenden Verwaltungsinformationen im Kernel eigentlich machen will, ich schätze da reicht nicht nur eine PID in jedem Process-Descriptor sondern da wird auch immer eine vollständige Liste mit allen (lebenden) Kindern vorhanden sein müssen (suchen wäre auf jeden Fall zu aufwendig). Diese Kinder-Liste (falls nicht leer) wird dann beim Tod immer an den jeweiligen Elternprozess hoch gegeben werden müssen und bei jedem Kind wird auch immer die PID geändert werden müssen.

Zitat von: erik
Womit aktives Abholen dann auch Pflicht wird, aber dazu kennst Du meine Meinung ja bereits.
Gerade nicht oder was meinst du überhaupt ;)
Ich meine das Du eigentlich weißt das ich von aktivem Abholen gor nix halte.

Man hat anhand der Pointer und der Flags sehen können ob der Writer gerade schläft oder nicht und wenn ja, hat man ihn aufgeweckt und sich selber schlafen gelegt. Der Writer schreibt dann Daten und weckt gegebenenfalls (falls der Reader schlafen sollte) den Reader auf.
Das klingt für mich so als ob da eine Reihe von Race-Conditions drin sein könnten und verschiedene andere Probleme auch. Das Potential für DoS-Angriffe hatte ich ja schon erwähnt. Das ist zwar nur so ein Bauchgefühl von mir aber in der Hinsicht hat mich mein Bauch bisher nur selten enttäuscht.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 21. October 2011, 18:02
Zitat von: erik
Hä, was denn nun, übergeben oder abholen? Beides geht IMHO nicht.
Der Ersteller ruft createProcess(strImage,fd0,fd1,fd2) auf. Damit weiß das VFS wie die ersten 3 FD´s im neuen Prozess aussehen sollen. Der neue Prozess muss diese 3 FD´s dann mappen und dazu fragt er das VFS welche Pipe´s (oder halt Dateien) das sind.

Zitat von: erik
Mit den Mitteln von POSIX fällt mir eigentlich auch nur fork() ein wenn man ein Datei-Handle an sein Kind geben will
Sprich fork() schafft sowieso mehr Probleme als es löst (welche das auch immer seien).

Zitat von: erik
Dazu muss dieser Prozess aber eventuell darüber informiert werden das er andere/neue Kinder hat
Hmm, wozu?

Zitat von: erik
Ich meine das Du eigentlich weißt das ich von aktivem Abholen gor nix halte.
Da reden wir mal wieder aneinander vorbei. Mir ist nicht ganz klar was du unter aktivem Abholen bei einer Pipe meinst.

Zitat von: erik
Das klingt für mich so als ob da eine Reihe von Race-Conditions drin sein könnten und verschiedene andere Probleme auch. Das Potential für DoS-Angriffe hatte ich ja schon erwähnt.
Race-Conditions auf jeden Fall, die hängen aber "nur" mit dem Schlafenlegen und Aufwecken von Threads zusammen. Was du dir unter DoS-Angriff vorstellst ist mir aber noch nicht klar.

Man könnte eine Pipe auch so gestalten, das die nur die Daten in die "Pipe" kommen (ist immer ne 4kb Page die in beide Prozesse gemappt ist) und der Empfänger wird per IPC benachrichtigt das Daten vorhanden sind und er kann diese dann auch gleich verarbeiten.
Damit dürften sich die Fälle <= 4kb und > 4kb (zumindest bei Dateioperationen wüsste ich wie ich das machen würde) ganz gut lösen. Zwar könnten die Daten wieder in einem ungünstigen Moment geändert werden, aber die Pipe wäre nicht mehr Thread, sondern Prozess "fixiert" und man hätte keine Race-Conditions mehr und die eigentliche Arbeitsgrundlage (Pointer und Flags) sind auch sicher.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 21. October 2011, 18:33
Hallo,


Der Ersteller ruft createProcess(strImage,fd0,fd1,fd2) auf. Damit weiß das VFS wie die ersten 3 FD´s im neuen Prozess aussehen sollen.
Und wenn der Elternprozess keine vorhandene Datei sondern was neues/eigenes mitgeben möchte? Oder wenn diese Handles gar nicht über das VFS laufen? (ich hatte Dich schon so verstanden das auch bei Dir nicht alles über das VFS geht)

Sprich fork() schafft sowieso mehr Probleme als es löst (welche das auch immer seien).
Ganz meine Rede. ;)

Hmm, wozu?
Naja, so aus ernst gemeinter Höflichkeit eben. Oder vielleicht damit der Prozess weiß woher er sich im Notfall stdin/... zurückholen muss. Und natürlich damit der Kernel weiß bei welchen Prozessen er alles die PID ändern muss wenn ein Prozess stirbt.

Race-Conditions auf jeden Fall, die hängen aber "nur" mit dem Schlafenlegen und Aufwecken von Threads zusammen.
Na das ist doch schlimm genug, oder?

Was du dir unter DoS-Angriff vorstellst ist mir aber noch nicht klar.
Damit meine ich das ein böser Prozess unter Umständen mit minimalem Einsatz an CPU-Zeit einen Service massiv beschäftigen kann wenn er diese ständig weckt und ihnen vor gaukelt das die Pipe voll ist und die dann auch noch ständig irgendwelchen Müll verarbeiten.

Man könnte eine Pipe auch so gestalten ....
Jaja, warum einfach wenn es auch kompliziert geht?


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 21. October 2011, 19:08
Zitat von: erik
Und wenn der Elternprozess keine vorhandene Datei sondern was neues/eigenes mitgeben möchte? Oder wenn diese Handles gar nicht über das VFS laufen? (ich hatte Dich schon so verstanden das auch bei Dir nicht alles über das VFS geht)
Was meinst du mit neues/eigenes? Auch wenn der FD nicht im VFS ist, so weiß das VFS doch welcher Prozess welche Datei von welchen Prozessen geöffnet ist.

Zitat von: erik
Jaja, warum einfach wenn es auch kompliziert geht?
Jetzt bin ich verwirrt. Wie würdest du es denn performant (und ohne Segmente ;)) machen? Meine erste Variante hat halt so ihre Probleme und die jetzt beschriebene ist da schon robuster und umgeht halt das Kopieren in und aus dem Kernel und es muss nicht ständig neugemappt werden und ich denke das ist schon ein Vorteil. Aber wo siehst du denn die Nachteile?

Zumal sowas wie ASLR und andere "Sicherheitskonzepte" sind auch nicht gerade einfach oder KISS ;)
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 21. October 2011, 20:13
Hallo,


Was meinst du mit neues/eigenes?
Naja, eigentlich meinte ich damit das der Prozess einen neuen Stream (für stdin) oder z.B. eine Log-Datei (für stdout/stderr) erstellt, aber wenn bei Dir alles (zumindest die Verwaltung) über das VFS geht dann ist es auch okay wenn der neue Prozess sich vom VFS die nötigen Daten holt. Aber zumindest eindeutige IDs musst Du trotz allem in den neuen Prozess bekommen.

welcher Prozess welche Datei von welchen Prozessen geöffnet ist.
Das is ja mal tolles Deutsch. Sorry aber damit kann ich echt nüschtz anfangen.

Wie würdest du es denn performant (und ohne Segmente ;)) machen?
Hm, ohne Segmente wird das aber komplizierter, las mal überlegen.
Ich denke ich würde so eine Pipe auf jeden Fall unidirektional machen und die Verwaltung der Schreib/Lese-Pointer dem Kernel überlassen. Also beim Erstellen so einer Pipe kann der Ersteller immer auch die Größe in Pages (1..n) angeben. Wenn der Writer diese Pipe öffnet bekommt er einen Schreib-Pointer und eine maximale Message-Größe (sollte nur die Hälfte der Pipe-Größe betragen) zurück, an der Stelle muss er dann die nächste (erste) Message rein packen. Beim Senden gibt der Writer dann die tatsächliche Message-Größe an (der Kernel prüft natürlich das die zuvor gemeldete maximale Größe eingehalten wurde) und bekommt möglichst schnell einen neuen Schreib-Pointer und eine neue maximale Message-Größe zurück (deswegen pro Message auch maximal die Hälfte der Pipe-Größe damit Double-Buffering gut läuft, die Pipe-Größe kann ja passend großzügig gewählt werden). Beim Empfäger wird dann ein PopUp-Thread injiziert (ja ich finde PopUp-Threads extrem cool und sehr nützlich + effizient) der dann als Parameter das Handle der Pipe (damit er weiß von wo er die Daten holen muss) und einen Lese-Pointer + tatsächliche Größe der Message damit das dann verarbeitet werden kann. Wenn der PopUp-Thread beendet ist weiß der Kernel das der entsprechende Teil der Pipe wieder verfügbar ist. Falls der Writer die Pipe voll gemacht hat und beim letzten Sende-Syscall eine unbefriedigende Maximal-Größe für die nächste Message bekommen hat könnte er einen weiteren Syscall benutzen um zu blockieren bis mindestens X Bytes frei sind und bekommt dann als Rückgabewert wieder einen Schreib-Pointer und eine maximale Größe (die jetzt >= X sein sollte). Das ließe sich IMHO sogar von mehreren Threads im Writer-Prozess parallel nutzen, der Kernel darf nur nie durcheinander geraten welche Teilabschnitte er einem Writer zugesichert hat, ebenso wie er auch nie vergessen darf welche Teilabschnitte von den PopUp-Threads abgearbeitet wurden da diese ja nicht zwangsläufig in der selben Reihenfolge beenden wie sie gestartet wurden. Wenn ich mich jetzt nicht verzählt hab macht das 8 Syscalls (Create/Kill/PopUp-Ende/Open-for-Writer/Send/Wait-for-Space/Close-for-Writer/Status). Die ersten 3 sind für den Empfänger und die nächsten 4 für den Sender. Die Status-Abfrage ist für beide Seiten um erfahren zu können was Sache ist, sowas kann immer mal nützlich sein. Das sollte IMHO recht simpel zu implementieren sein und das Mapping wird in den beteiligten Prozessen auch nur beim Create/Open eingerichtet bzw. beim Kill/Close entfernt. Theoretisch könnte diese Pipe sogar von mehreren Prozessen als Sender geöffnet und benutzt werden (würde den Kernel wohl nur unwesentlich komplexer machen) aber das wäre dann definitiv ein Sicherheitsrisiko wenn diese Prozesse ihre Messages gegenseitig beeinflussen können. Beim Lesen und Schreiben aus/in diese Pipe muss es natürlich immer ein Wrap-Around bei der Pipe-Größe geben und die Pointer sind auch nur Offsets innerhalb der Pipe-Größe, aber das versteht sich wohl von selbst.

Was denkst Du darüber? Falls irgendwas nicht so toll ist dann ruhig ansprechen, ich hab mir das gerade innerhalb von 10 Minuten ausgedacht, es ist also durchaus möglich das ich irgendein winziges Detail übersehen hab (obwohl mein Bauch nix auszusetzen hat ;)).

Zumal sowas wie ASLR und andere "Sicherheitskonzepte" sind auch nicht gerade einfach oder KISS ;)
Also wie komplex ASLR ist hängt auch davon ab wo das überall mit drin hängt und ob z.B. der Code positionunabhängig ist und ob die Daten relozierbar sind. In meinem System kann ich z.B. nur den Code verschieben aber die Daten müssen die Offsets innerhalb der Segmente behalten die der Linker vorgegeben hat, das liegt daran das ich das im Code nicht so einfach relozieren kann, dafür ist mein Code immer positionsunabhängig.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 21. October 2011, 20:38
Zitat von: erik
Das is ja mal tolles Deutsch. Sorry aber damit kann ich echt nüschtz anfangen.
Sorry, ich weiß auch nicht warum ich gerade sowas beim Nochmal-Drüber-Lesen immer übersehe. Sollte heißen, dass das VFS weiß welche Dateien ein Prozess alles geöffnet hat.

Zu deiner Pipe. Wow ;) Gutes Bsp. wie sehr es vom Betrachter abhängt, ob etwas einfach oder kompliziert ist.

Zitat von: erik
Ich denke ich würde so eine Pipe auf jeden Fall unidirektional machen und die Verwaltung der Schreib/Lese-Pointer dem Kernel überlassen.
Die Lese-/Schreibpointer hätte ich aber schon den Prozessen überlassen, weil nen Sicherheitsgewinn hast du dadurch das du es in den Kernel packst auch nicht, weil der Prozess sich ja nicht dran halten muss.
Wenn er anfängt und mehr als die Pipe-Größe schreiben möchte, muss er die Pipe-Flushen und würde dann schlafen gelegt werden bis der Reader entsprechend mitgeteilt hat, dass er die Pipe vollständig ausgelesen hat. Denn das eine Pipe voll ist geht ja nur, wenn ohne einen Flush Daten mit einer Größer der Pipe-größe geschrieben wurden.
Das Flushen würde dann einen Popup-Thread injezieren und wenn dieser Fertig ist, wird der Writer wieder aufgeweckt. Problem wäre dabei nur, dass es entweder nicht asynch wäre oder man einen weiteren Syscall bräuchte damit der Writer weiß ob der Reader schon fertig ist (und wenn nicht wird er schlafen gelegt).

Zitat von: erik
Also beim Erstellen so einer Pipe kann der Ersteller immer auch die Größe in Pages (1..n) angeben.
Jap, würde ich so auch mitgehen.

Zitat von: erik
deswegen pro Message auch maximal die Hälfte der Pipe-Größe damit Double-Buffering gut läuft, die Pipe-Größe kann ja passend großzügig gewählt werden
Da komme ich nicht mit, wieso und wofür Double-Buffering?
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 21. October 2011, 21:05
Hallo,


Zu deiner Pipe. Wow ;)
Danke.

Gutes Bsp. wie sehr es vom Betrachter abhängt, ob etwas einfach oder kompliziert ist.
Wieso, was genau empfindest Du an meiner Idee kompliziert?

Die Lese-/Schreibpointer hätte ich aber schon den Prozessen überlassen, weil nen Sicherheitsgewinn hast du dadurch das du es in den Kernel packst auch nicht, weil der Prozess sich ja nicht dran halten muss.
Es geht dabei auch nicht um Sicherheit sondern darum das der Kernel dem Writer quasie einen Bereich in der Pipe zusichert und der Writer diesen Bereich ungestört füllen kann ohne auf andere Threads im selben Prozess Rücksicht nehmen zu müssen (es bedarf dafür keiner weiteren Synchronisation o.ä. und trotzdem können mehrere Threads parallel Messages in diese Pipe stellen). Das der Kernel in meinem Vorschlag immer gleich die halbe Pipe zusichert ist dafür aber etwas doof, hier wäre es eventuell geschickter wenn der Empfänger beim Create-Syscall auch die maximale Message-Größe vorgibt so das auch mehr als 2 Threads im Writer die Pipe befüllen können (falls das Verhältnis Pipe-Größe:maximale Message-Größe mehr als 2:1 beträgt). Diese Zusicherung gilt immer solange bis sie durch einen Send-Syscall benutzt wird (das übrig bleibende Stück kann dann wieder neu zugesichert werden) oder beim Wait-Syscall (da hier ja die vorangegangene Zusicherung durch den Kernel nicht groß genug war). Dem Wait-Syscall kann auch NULL übergeben werden so das nur eine neue Zusicherung mit einer gewissen Mindestgröße angefordert wird (damit ein weiterer Thread im Writer-Prozess an diesem Spiel mitmachen kann), die Mindestgröße könnte aber auch 0 sein so das nur eine vorhandene Zusicherung zurückgegeben wird. Die Zusicherung die vom Wait-Syscall kommt sollte auch nicht größer sein als die angeforderte Größe (der entsprechende Writer-Thread fordert ja schließlich X Bytes weil er genau weiß was er senden möchte) um die Pipe möglichst effizient nutzen zu können.

Einen expliziten Flush empfinde ich als unnötig weil ja jede geschriebene Message (durch den Send-Syscall) auch möglichst schnell zugestellt wird, der Kernel wartet nicht bis eine Menge X in der Pipe ist, das ergibt keinen Sinn.

Da komme ich nicht mit, wieso und wofür Double-Buffering?
Wenn Du eine Pipe immer ganz voll machst dann kann immer nur entweder der Writer oder der Reader arbeiten aber nie beide gleichzeitig. Deswegen würde ich die maximale Message-Größe deutlich kleiner ansetzen als die Größe der Pipe. Wenn beide parallel (auf unterschiedlichen CPUs) arbeiten können dann muss im Idealfall nie einer blockieren. Diese Idee ist eigentlich von Dir und die erscheint mir auch sehr nützlich in so einem Szenario.


Grüße
Erik
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 21. October 2011, 21:25
Zitat von: erik
Es geht dabei auch nicht um Sicherheit sondern darum das der Kernel dem Writer quasie einen Bereich in der Pipe zusichert und der Writer diesen Bereich ungestört füllen kann ohne auf andere Threads im selben Prozess Rücksicht nehmen zu müssen
Achso. Bei einer Pipe denke ich immer nur an eine 1:1 Beziehung und nicht an 1:n oder m:n. Ich denke es sollte auch nicht so schlimm sein, wenn meine nur eine 1:1 Beziehung zulässt. Ist halt ne Beschränkung die aber die Sache einfacher machen kann.

Zitat von: erik
Einen expliziten Flush empfinde ich als unnötig weil ja jede geschriebene Message (durch den Send-Syscall) auch möglichst schnell zugestellt wird, der Kernel wartet nicht bis eine Menge X in der Pipe ist, das ergibt keinen Sinn.
Da habe ich mich dann wieder unglücklich ausgedrückt. Mit Flush war eigentlich das Senden (und damit den Reader "erstellen") gemeint.

Nur fällt mir auf, dass ich bei meiner Variante das Problem hätte, dass ich gar nicht mitbekomme wieviel nun in der Pipe noch frei ist, sprich ob der Reader auch schon fertig ist.
Diese ganzen Probleme hat man mit in den Kernel kopieren und wieder rauskopieren natürlich nicht ;)

Ich überlege gerade, ob man die Lese-/Schreibpointer nicht einfach dem Reader und Writer überlässt, aber die ersten 4byte der Pipe sind ne UserSpace Semaphore. Der Writer würde die Semaphore inkrementieren (und wenn diese am Größenlimit ist, würde er in den Kernel gehen und blockieren) und der Reader würde sie dekrementieren (und wenn sie kleiner 0 ist, würde er blockieren).
Der Reader würde immer dann in den Kernel gehen und den eventuell blockierten Writer wecken, wenn die Semaphore vom Maximum um eins dekrementiert wird und der Writer würde immer dann in den Kernel gehen und den eventuell blockierten Reader wecken, wenn die Semaphore 0 wird. Dadurch würde man sich einige Syscalls sparen und man müsste weniger in den Kernel.

Wo genau siehst du denn ein Problem wenn der Writer den Wert der Semaphore beeinflussen kann? Ich meine sowas gibt es ja auch für Linux, also kann es nicht so schlimm sein ;) Oder hängt es damit zusammen, weil man dadurch eventuell einen Service irgendwie schlecht beeinflussen kann?

Bei dieser Variante hätte man zwar keine Popup-Threads, aber ich würde meiner ursprünglichen Idee, dass 2 Threads parallel daran arbeiten können wieder näher bzw. hätte sie erreicht.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: erik.vikinger am 21. October 2011, 21:47
Hallo,


Ich denke es sollte auch nicht so schlimm sein, wenn meine nur eine 1:1 Beziehung zulässt. Ist halt ne Beschränkung die aber die Sache einfacher machen kann.
Kann man machen, ja, aber ich glaube nicht das der dadurch erzielbare Gewinn an Einfachheit wirklich nennenswert ist, insofern würde ich das als netten Zusatznutzen betrachten der nur wenig kostet.

Diese ganzen Probleme hat man mit in den Kernel kopieren und wieder rauskopieren natürlich nicht ;)
Also bei meiner Idee würde der Kernel auch nichts kopieren, die Pipe (eigentlich ja nur ein bestimmter Bereich simplen Speichers) ist in beiden Prozessen identisch vorhanden (natürlich an unterschiedlichen virtuellen Adressen).

Ich überlege gerade, ob man die Lese-/Schreibpointer nicht einfach dem Reader und Writer überlässt, aber die ersten 4byte der Pipe sind ne UserSpace Semaphore....
Und da sind wir wieder an dem Punkt wo Du versuchst alles so kompliziert wie irgend möglich zu machen. Bei meiner Idee ist selbst bei mehreren Writer-Threads keinerlei zusätzliche Synchronisation im User-Space erforderlich und zwischen den zwei Prozessen erst recht nicht. Nebst dessen das wenn die zwei Prozesse sich per Semaphore o.ä. nur im User-Space synchronisieren das dann wieder die Möglichkeit für ein DoS besteht weil einer den anderen dauerhaft blockieren kann, auch soetwas ist bei meiner Idee grundsätzlich ausgeschlossen.

Dadurch würde man sich einige Syscalls sparen und man müsste weniger in den Kernel.
Wo würde man was sparen? Mit meiner Idee ist pro Message nur ein Syscall im Writer und nur ein Syscall im Reader erforderlich (also insgesamt nur 4 Ring-Wechsel), billiger wirst Du das bestimmt nicht bekommen.


Grüße
Erik


PS.: Linux benutzt ja auch das Konzept eines monolithischen Kernels, also kann das nicht so schlimm sein.  SCNR
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 21. October 2011, 22:12
Zitat von: erik
Also bei meiner Idee würde der Kernel auch nichts kopieren, die Pipe (eigentlich ja nur ein bestimmter Bereich simplen Speichers) ist in beiden Prozessen identisch vorhanden (natürlich an unterschiedlichen virtuellen Adressen).
Mit Problemen meinte ich dann auch sowas wie einfach ignorieren was der Kernel sagt und die alten Daten einfach überschreiben auch bei Multithreading zugriff auf eine Pipe würde sowas nicht möglich sein. Diese Variante wäre halt einfach sicherer, aber langsamer.

Zitat von: erik
Und da sind wir wieder an dem Punkt wo Du versuchst alles so kompliziert wie irgend möglich zu machen.
Und ich sehe das halt anders ;) Für mich ist diese Variante einfacher als das irgendwie im Kernel zu machen und irgendwelche Lücken zu berechnen und sowas. Du hast einfach nen Counter wieviele Bytes in der Pipe sind und dadurch weißt du ob weiter reinschreiben/rauslesen kannst oder nicht.

Zitat von: erik
Nebst dessen das wenn die zwei Prozesse sich per Semaphore o.ä. nur im User-Space synchronisieren das dann wieder die Möglichkeit für ein DoS besteht weil einer den anderen dauerhaft blockieren kann, auch soetwas ist bei meiner Idee grundsätzlich ausgeschlossen.
Mal davon abgesehen das man sowas mit nem Timeout lösen kann (was ich aber grundsätzlich nicht mag), bin ich mir gerade nicht sicher wie das genau in Linux umgesetzt ist. Das werde ich mir morgen mal im Source-Code angucken. Ansonsten hast du recht.

Zitat von: erik
Wo würde man was sparen? Mit meiner Idee ist pro Message nur ein Syscall im Writer und nur ein Syscall im Reader erforderlich (also insgesamt nur 4 Ring-Wechsel), billiger wirst Du das bestimmt nicht bekommen.
Wenn ich nun aber mehrere Msgs da reinschreibe besteht bei meiner Variante die Möglichkeit das gar nicht in den Kernel gegangen werden muss und der Reader fängt muss nicht erst anfangen wenn die Msg komplett geschrieben ist (könnte man aber trotzdem so machen).
Es könnte ja die Situation geben, dass der Writer Daten reinschreibt, den Reader aufweckt und noch während der Reader die Daten ausliest/mit ihnen arbeitet, kann er schon neue Daten reinschreiben ohne das er den Reader benachrichtigen muss.

Der Vorteil deiner Variante sind die Popup-Threads, ansonsten finde ich das mit den "Lücken" verwalten wesentlich komplizierter als ne normale Pipe. Eigentlich wäre deine Pipe mehr nen SlabAllocator malloc() mit Hilfe des Kernels. Denn so wie ich es verstanden habe, bekommt der Writer den Pointer ja vom Kernel und es kann durchaus sein, dass davor und danach noch Bereiche sind die schon älter sind.
Bei einer klassischen Pipe liest/schreibt man immer nacheinander rein ohne das da Lücken entstehen oder man aufpassen müsste wo man jetzt hinschreiben kann und wie groß der Bereich ist. Das ist also schon wesentlich komplizierter als es sein müsste.

Zitat von: erik
PS.: Linux benutzt ja auch das Konzept eines monolithischen Kernels, also kann das nicht so schlimm sein. SCNR
Mal davon abgesehen, dass ein MikroKernel schöner vom Design her ist und theoretisch sicherer (wenn man denn ne IOMMU hat), aber auf jeden Fall stabiler (weil ein falscher Pointer im Treiber nicht im Kernel ist und das ganze System mitreißen muss) ist, kann man auch "schöne" Monolithen/Hybriden schreiben.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 22. October 2011, 01:05
sowohl Caller als auch Callee können das Sharing beenden.
Warum sollte der Caller das Sharing beenden wollen? Das bedeutet ja das er den Speicher verliert (quasi frei gibt) und das dürfte bei den üblichen Anwendungsmöglichkeiten wie read()/write() oder aio_read()/aio_write() eher nicht gewünscht sein, nebst dessen dass das nur mit ausgerichtetem Speicher funktioniert (man kann ja keine halben Pages frei geben).
Denk z.B. an die Schnittstelle zw. HW-Treiber und nächster Ebene. Da ist das Alignment unkritisch und auf deiner Arch. eh kein Thema.
Das ist richtig, betrifft aber nur den Fall mehrerer Threads innerhalb eines Prozesses auf einer einzelnen Maschine.
Aber gerade darum ging es doch, ihr habt doch gesagt das man bei Multithreading-Applikationen sehr schnell an einem Punkt ist wo das VFS saubere Kohärenz bieten muss.
Ein FD = muss Anwendung sich drum kümmern, nicht das VFS, da Daten bereits vorserialisiert, Threading irrelevant. Mehrere FDs werden interessant, egal ob eine oder mehrere Anwendungen. Darum ging es mir.
Für verteilte Systeme oder mehrere Prozesse in einem System hilft das nichts.
Das ist richtig aber eben wieder kein Alltagsszenario für ein Hobby-OS. Genau deswegen hätte ich ja gerne mal ein etwas alltäglicheres Beispiel.
Logfiles, wo mehrere reinschreiben und lesen können? NFS-Server? Definiere, was ein Hobby-OS NICHT kann. Können soll?

Gruß,
gesendet mit Bildschirmtastatur, daher kurz.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 24. October 2011, 13:49
Zitat von: svenska
Logfiles, wo mehrere reinschreiben und lesen können?
Was ist eigentlich das Problem, dass mehrere aus einer Datei lesen können (aber jeder sein eignen FD hat)? Ich sehe da erstmal keins. Was das Schreiben betrifft, muss das ganze im VFS serialisiert werden (und damit ist kein Mapping mehr möglich, sondern die zu schreibenden Daten müssen zuerst ins VFS).

Das 2 (oder mehr) Anwendungen den gleichen FD "sharen", damit z.B. immer am Ende der Datei geschrieben wird, könnte man doch dadurch "emulieren", dass man halt ne Art "APPEND_WRITE" Modus hat, der immer an das Dateiende anhängt, egal wo der Datei-Zeiger gerade ist, oder?

Was ich mir aber kaum vorstellen kann, ist dass es ein Szenario gibt, wo 2 (oder mehr) Anwendungen den gleichen FD "sharen" und aus der Datei lesen, weil da kommt ja nur Müll bei raus.
Das mehrere Threads daraus lesen kann ich mir noch vorstellen, aber da wäre ja nur ein FD in der libc (und die würde das serialisieren). Die Anwendung könnte z.B. mehrere Threads haben und alles lesen immer einen Block aus der Datei und verarbeiten den.

Sicherlich könnte man das Szenario auch auf mehrere Anwendungen ausdehnen, aber was soll das bringen, nur um die Ergebnisse dann wieder in eine neue Datei mit "APPEND_WRITE" zu schreiben?

Was Pipe´s betrifft, denke ich das sie so oder so ein Sicherheitsrisiko darstellen (genauso wie Semaphoren). Denn wenn der Reader blockiert und auf neue Daten wartet, braucht ja der Writer einfach keine mehr schicken und schon kann man nen Service "ärgern". Auch geht das ganze umgedreht, der Service will dem Clienten was schicken, der Client blockiert und wartet auf Daten und der Service schickt einfach keine.
Das ist aber nen Datenstruktur-Problem.

Ich musste dann auch feststellen, dass Futex´s nur für Intra-Process-Communication und nicht für Inter-Process-Communication sind und damit für meine Pipe´s eher ungeeignet. Bin mir nur nicht sicher, woran das liegt, ob daran das es so vllt einfacher ist oder zwecks irgendwelcher Sicherheitsprobleme.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 24. October 2011, 14:02
Zitat von: svenska
Logfiles, wo mehrere reinschreiben und lesen können?
Was ist eigentlich das Problem, dass mehrere aus einer Datei lesen können (aber jeder sein eignen FD hat)?
Bitte den Satz nochmal lesen. "Wo mehrere reinschreiben .... können."

Das Problem entsteht, wenn mehrere aus einer Datei lesen und die Datei gleichzeitig verändert wird. Möglicherweise von verschiedenen Prozessen.

Wenn es nur einen FD gibt, dann spielt jede Form von Multithreading keine Rolle, weil die Anwendung sich um die Serialisierung kümmern muss, um den FD konsistent zu halten. Interessant für das VFS sind nur die Fälle, in denen mit mehreren FDs gleichzeitig auf die gleiche Datei zugegriffen wird und zwar nicht nur lesend.

Man kann das zwar verbieten, wie Windows das tut (eine zum Schreiben geöffnete Datei ist exklusiv geöffnet), aber "schön" ist das dann nicht.

Gruß,
Svenska
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 24. October 2011, 14:55
Zitat von: svenska
Bitte den Satz nochmal lesen. "Wo mehrere reinschreiben .... können."
Ich habe den Satz schon richtig gelesen, da steht aber auch was von mehreren die lesen und da ist es doch egal ob da nun einer und mehrere in die Datei schreiben, dass sollte immer kein Problem darstellen (es sei denn, mehrere die gleichzeitig mit dem selben FD, in verschiedenen Anwendungen, lesen).

Zitat von: svenska
Man kann das zwar verbieten, wie Windows das tut (eine zum Schreiben geöffnete Datei ist exklusiv geöffnet), aber "schön" ist das dann nicht.
Scheint aber zu funktionieren ;)

Aber die Frage bleibt, abgesehen von einem "APPEND_WRITE", wo wird das noch verwendet (Schreiben oder auch Lesen mit dem selben FD in unterschiedlichen Anwendungen)?
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 24. October 2011, 16:16
Identische FDs in verschiedenen Anwendungen ist relativ schwierig hinzukriegen... und wenn du es schaffst, ist es außerdem kaputt.
Und wenn du die Hälfte von einem Satz weglässt, verschwindet gelegentlich auch das Problem. :roll:

Wenn mehrere Prozesse bestimmte Daten aus einer Datei lesen, während mehrere andere Prozesse die gleichen Daten in die Datei reinschreiben - absolut gleichzeitig - wie verhält sich dein VFS dann? Werden die geschriebenen Daten in der endgültigen Datei schön durchmischt oder überlebt nur eine geschriebene Version, oder garkeine? Bekommen die Lesenden mal die eine, mal die andere Fassung der Daten oder bleibt für diese die Welt konsistent? Gelten die Daten als geschrieben, wenn die Datei geschlossen wurde, sie mit fsync() synchronisiert wurde, die Daten mit write() geschrieben wurden oder nach fünf Sekunden? Liest jeder Lesethread etwas anderes oder lesen alle immer das gleiche?

Oder kannst du Dateien prinzipiell nicht verändern, wenn sie schon geöffnet sind?

Probleme gibt es da schon genug, man muss sie nur suchen.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 24. October 2011, 16:35
Zitat von: svenska
Identische FDs in verschiedenen Anwendungen ist relativ schwierig hinzukriegen... und wenn du es schaffst, ist es außerdem kaputt.
Es ging z.B. um die Situation die bei einem fork() passiert und soweit ich es verstanden habe, nutzen dann Eltern und Kind-Prozess den gleichen FD mit dem gleichen Datei-Zeiger.

Zitat von: svenska
Wenn mehrere Prozesse bestimmte Daten aus einer Datei lesen, während mehrere andere Prozesse die gleichen Daten in die Datei reinschreiben - absolut gleichzeitig - wie verhält sich dein VFS dann? Werden die geschriebenen Daten in der endgültigen Datei schön durchmischt oder überlebt nur eine geschriebene Version, oder garkeine? Bekommen die Lesenden mal die eine, mal die andere Fassung der Daten oder bleibt für diese die Welt konsistent? Gelten die Daten als geschrieben, wenn die Datei geschlossen wurde, sie mit fsync() synchronisiert wurde, die Daten mit write() geschrieben wurden oder nach fünf Sekunden? Liest jeder Lesethread etwas anderes oder lesen alle immer das gleiche?
Sind alles durchaus Probleme, aber die eigentliche Frage ist doch, muss es wirklich möglich sein, dass mehrere Anwendungen in ein und die selbe Datei (mit dem gesharten Datei-Zeiger) schreiben können? Wo passiert das in der Praxis (bitte keine Log-Dateien)?

Selbst wenn mehrere Anwendung in ein und die selbe Datei schreiben (mit ihren eignen FDs), ist das irgendwo nötig? Zumal ich da ja sowieso sage, jedes write() gilt als eine atomare Einheit.
Wann die Daten wirklich in die Datei kommen, liegt doch am Programmierer. Denn in der libc wird ja gepuffert (wenn man es nicht abstellt).

Was das Lesen betrifft, kann ein vermischen alter und neuer Daten nur vermieden werden, wenn alles im VFS abläuft. Bei Memory-Mapped-Files wird es durchaus vorkommen, dass eine Anwendung einen Mix aus alten und neuen Daten liest.

Der einfachste Weg ist immernoch, nur exklusives Schreiben zuzulassen (mit der Ausnahme "APPEND_WRITE", obwohl sich auch diese Fälle auch mit exklusivem Schreiben lösen lassen). Denn mir erschließt sich immernoch nicht der Sinn hinter dem zeitgleichen Schreiben in eine Datei, das bringt doch nur Probleme mit sich.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: Svenska am 24. October 2011, 23:27
Zitat von: svenska
Identische FDs in verschiedenen Anwendungen ist relativ schwierig hinzukriegen... und wenn du es schaffst, ist es außerdem kaputt.
Es ging z.B. um die Situation die bei einem fork() passiert und soweit ich es verstanden habe, nutzen dann Eltern und Kind-Prozess den gleichen FD mit dem gleichen Datei-Zeiger.
Hmm. Meine Erfahrung mit (socket-)FDs ist, dass man den FD zweimal schließen muss, einmal im Elternteil und einmal im Kind, damit das Socket wirklich geschlossen ist.
Das klingt für mich mehr wie eine direkte Kopie des FDs, weniger wie der selbe. Jetzt bräuche ich jemanden, der die Details kennt. :mrgreen:

Sind alles durchaus Probleme, aber die eigentliche Frage ist doch, muss es wirklich möglich sein, dass mehrere Anwendungen in ein und die selbe Datei (mit dem gesharten Datei-Zeiger) schreiben können?
Ich bezweifle, dass mit einem identischen FD mehrere Anwendungen gleichzeitig in eine Datei schreiben.

Selbst wenn mehrere Anwendung in ein und die selbe Datei schreiben (mit ihren eignen FDs), ist das irgendwo nötig? Zumal ich da ja sowieso sage, jedes write() gilt als eine atomare Einheit.
Beispiele findest du weiter oben. Wenn du, wie erik, der Meinung bist, dass sowas für ein Hobby-OS nicht nötig sei, dann solltest du das dokumentieren und ignorieren. Ansonsten behandeln. Wie, ist deine Sache. :-)

Wann die Daten wirklich in die Datei kommen, liegt doch am Programmierer. Denn in der libc wird ja gepuffert (wenn man es nicht abstellt).
Die Frage ist, ob die Welt für jedes Programm konsistent bleibt oder eben nicht. Dazu gesellt sich dann die Frage, wie man diese Konsistenz definiert.

Gruß,
Svenska
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 25. October 2011, 10:10
Hmm. Meine Erfahrung mit (socket-)FDs ist, dass man den FD zweimal schließen muss, einmal im Elternteil und einmal im Kind, damit das Socket wirklich geschlossen ist.
Das klingt für mich mehr wie eine direkte Kopie des FDs, weniger wie der selbe. Jetzt bräuche ich jemanden, der die Details kennt. :mrgreen:
Es ist ein einziger, mehrfach referenzierter Dateizeiger. Du hast wohl unsere ganzen Diskussionen über fork() nicht gelesen? ;)

"The  child  process  shall  have its own copy of the parent's file descriptors.  Each of the child's file descriptors shall refer to the same open file description with the corresponding file descriptor of the parent."

Zitat
Ich bezweifle, dass mit einem identischen FD mehrere Anwendungen gleichzeitig in eine Datei schreiben.
Ich glaube, dafür suchen wir auch seit längerem erfolglos einen sinnvollen Anwendungsfall, aber POSIX erlaubt es halt.

Selbst wenn mehrere Anwendung in ein und die selbe Datei schreiben (mit ihren eignen FDs), ist das irgendwo nötig? Zumal ich da ja sowieso sage, jedes write() gilt als eine atomare Einheit.
Ja, dafür gibt es Anwendungsfälle. Ob du die in deinem OS unterstützt oder nicht, ist deine Sache.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: FlashBurn am 25. October 2011, 11:21
Zitat von: taljeth
Ich glaube, dafür suchen wir auch seit längerem erfolglos einen sinnvollen Anwendungsfall, aber POSIX erlaubt es halt.
Sprich wir könnten und also langsam mal darauf einigen das es eigentlich nicht nötig ist (sofern man nicht volle POSIX Kompatibilität anstrebt).

Zitat von: taljeth
Ja, dafür gibt es Anwendungsfälle. Ob du die in deinem OS unterstützt oder nicht, ist deine Sache.
Ok, das ist doch mal ein Wort. Wissen das dann die Anwendungen alle, dass da noch mehr zur gleichen Zeit drin rumpfuschen?

Wenn dem so ist, würde ich grundsätzlich sagen, jede Datei wird fürs Schreiben exklusiv geöffnet, will man das mehrere Anwendungen in eine Datei schreiben können, muss man das explizit angeben. Ich denke das ist der beste Kompromiss.

Zitat von: taljeth
"The  child  process  shall  have its own copy of the parent's file descriptors.  Each of the child's file descriptors shall refer to the same open file description with the corresponding file descriptor of the parent."
Ich tue mich schon immer mit Gesetzestexten schwer, aber wiederspricht sich das nicht?

Ich verstehe das so, das jedes Kind seine eigene Kopie == eigener FD mit Startwerten des originalen FDs. Der zweite Satz sagt dann aber wiederrum, dass die FD´s der Kinder auf die FD´s der Eltern zeigen sollen, was ja keine Kopie wäre. Was denn nun?
Zumal würde das nicht auch ne Kette geben der man folgen müsste, weil der FD der Eltern kann ja wieder auf den FD der Großeltern zeigen usw.
Titel: Re: Flexibles und einfaches Prozess erzeugen
Beitrag von: kevin am 25. October 2011, 12:15
Eine Open File Description ist klassischerweise eine Struktur im Kernel, die eine geöffnete Datei beschreibt und zum Beispiel den Dateizeiger enthält. Der Userspace kommt damit nicht in Kontakt. Der File Descriptor ist ein Integer, der auf eine Open File Description gemappt ist. Das bekommt der Userspace mit, und davon hat jeder Prozess seinen eigenen Satz. Bei einem dup() ist es da gleiche, da kriegst du auch einen neuen FD für die Open File Description im Kernel.

Deine Fragen, was Programme im allgemeinen tun und wissen und so weiter kann man einfach nicht beantworten. Programme tun irgendwas und wenn es ein beabsichtigtes oder unbeabsichtigtes Feature gibt, werden Programme es früher oder später in einer mehr oder weniger kriminellen Weise ausnutzen.