Autor Thema: Flexibles und einfaches Prozess erzeugen  (Gelesen 27452 mal)

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #40 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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #41 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.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #42 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
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #43 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.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #44 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
Reality is that which, when you stop believing in it, doesn't go away.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #45 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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #46 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.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #47 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 (etwas höher hab ich auch ein paar Gedanken zu Log-Dateien geschrieben).


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #48 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.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #49 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
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #50 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.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #51 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)
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #52 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.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #53 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.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #54 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 (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
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #55 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.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #56 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
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #57 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 ;)

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #58 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
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #59 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?

 

Einloggen