Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: FlashBurn am 03. October 2010, 16:46
-
Wie im Titel schon zu sehen, geht es mir darum mal darüber zu diskutieren wie man (am besten) einen neuen Prozess lädt und startet. Das ganze sollte aus Sicht eines Mikrokernel betrachtet werden, wo auch der VFS-Service nicht im Kernel ist.
Im besonderen geht es mir darum, wie man es am besten löst, das ja im Normalfall auch verschiedenste Libraries geladen werden müssen.
Ich habe mir über diesen Libraries-Kram noch gar keine richtigen Gedanken gemacht, aber im Moment kann bei mir nur ein neuer Prozess geladen werden, in dem man eine Funktion/Syscall CreateProcess() aufruft, der man nen Pointer zu der startenden Datei mitgibt, sprich die Datei muss schon im UserSpace geladen sein.
Das ist natürlich beim Booten ganz vorteilhaft, weil ich da alle als Module geladenen Dateien im Kernel eingeblendet habe und so bloß diese Funktion aufrufen muss.
Aber normalerweise übergibt man doch solch einer Funktion einen Dateinamen, oder?
Bei mir sieht es dann auch so aus, das ich einen neuen Prozess erstelle und dort einen neuen Kernelthread, welcher dann die ganze Arbeit übernimmt. Er (der KernelThread) mappt also die Datei rein und parst sie und erstellt dann nen UserModeThread mit der Main-Funktion.
Wie macht ihr das oder wie wird das im Normalfall gemacht?
Ich dachte daran, diese CreateProcess()-Funktion beizubehalten, aber ne Verbindung zum VFS-Service aufzubauen und dann die entsprechende Datei in den neu erstellten Prozess zu laden (vom Kernel aus) und dann erst zu parsen. Wie klingt das?
Wenn ich das so machen würde, dann könnte ich auch einfach über diese Verbindung (zum VFS-Service) die nötigen Libraries laden und einbinden.
So meine letzte Frage bezieht sich dann auf das Henne-Ei-Problem, das ja auch der VFS-Service erstmal gestartet werden muss. Wie macht man das ohne das Dateien geladen werden können (was jetzt nicht so schwierig sein sollte)? Selbst wenn man das geschafft hat, wie würde man dann erreichen das der VFS-Service selbst auch Libraries nutzen kann (im Moment linke ich die einfach mit zu der Executeable dazu)?
-
Hallo,
also ich habe vor dafür einen User-Mode-Prozess zu nehmen so das CreateProzess() erstmal auf IPC basiert. Dieser exec-Prozess lädt dann die Datei in den Speicher und macht daraus eine simple Fat-Binary (fürs erste ist sie das bereits), also wenn ich mal Librarys unterstützen sollte dann wird das alles noch vor dem Kernel gemacht. Der Kernel bietet dann einen speziellen Syscall dem die fertig gelinkte Executable in einem simplen Format bereits im Speicher übergeben werden muss. Der Kernel erstellt dann neue Segmente und kopiert den Inhalt der Executable aus dem User-Buffer vom exec-Prozess in die neuen Segmente, anschließend soll dann ein erster Thread erstellt werden der dann den Entry-Point ausführt. Damit ist dieser spezielle Syscall fertig. Diese Variante hat den Vorteil das der Kernel ein Micro bleibt und sich um nichts kümmern muss auch das laden und linken von Librarys wird vorher gemacht.
Der VFS selber gehört bei mir zum Boot-Image, ist also schon im RAM wenn der Kernel zum Leben erwacht. Die ganzen Dateisystemtreiber usw. kommen ebenfalls aus ner ROM-Disk die zusammen mit dem Kernel und den ersten Prozessen in den RAM kommt, auf einem PC würde ich das als Module die von GRUB geladen werden implementieren.
Grüße
Erik
-
Der VFS selber gehört bei mir zum Boot-Image, ist also schon im RAM wenn der Kernel zum Leben erwacht. Die ganzen Dateisystemtreiber usw. kommen ebenfalls aus ner ROM-Disk die zusammen mit dem Kernel und den ersten Prozessen in den RAM kommt, auf einem PC würde ich das als Module die von GRUB geladen werden implementieren.
Also wird dein VFS-Service auch alle Libraries die er benötigt schon statisch gelinkt haben.?
Mich hätte halt interessiert ob jemand eine Idee hat wie man das Lösen könnte ohne das man sich alzu sehr verrenken müsste?
also ich habe vor dafür einen User-Mode-Prozess zu nehmen so das CreateProzess() erstmal auf IPC basiert. Dieser exec-Prozess lädt dann die Datei in den Speicher und macht daraus eine simple Fat-Binary (fürs erste ist sie das bereits), also wenn ich mal Librarys unterstützen sollte dann wird das alles noch vor dem Kernel gemacht. Der Kernel bietet dann einen speziellen Syscall dem die fertig gelinkte Executable in einem simplen Format bereits im Speicher übergeben werden muss.
Das hätte natürlich den Vorteil das man sich nicht auf ein Dateiformat festlegen muss und es wäre die perfekte Anwendung für fork().
Also wenn ich es richtig verstanden habe, willst du per IPC nen Art runtimeloader aufrufen. Dieser läd die Datei erstellt nen Prozess-Image und könnte nen Fork machen. So umgeht man den Kernel fast ganz und macht das meiste im UserSpace.
Ist ne Variante, aber da müsste man dann sehen, wie man es macht das man warten kann bis der Prozess zu Ende ist und sowas.
Ich würde halt das Laden und das Parsen im Kernel machen, in einem neuen Prozess und KernelThread.
Edit::
Was ich noch vergessen habe, wie bekommt ihr die Parameter die dem Programm übergeben werden in das Programm?
-
Hallo,
dein VFS muss unabhängig von allem sein, was es zum Zeitpunkt des Starts deines VFS nicht gibt.
Konkret: Wenn du das VFS startest, dann darf es auf keinen nicht geladenen Libraries basieren, sofern du keine andere Möglichkeit hast, Libraries nachzuladen. Die könntest du vom Bootloader als Module laden lassen, müsstest sie halt nur entsprechend mit dem System verknüpfen. Ob das lohnt - keine Ahnung. Welche Libraries willst du denn deinem VFS anhängen?
Erik nimmt eine Lebendgeburt des Systems vor, d.h. der Bootloader lädt als freistehender Prozess die Libraries und baut die benötigten Datenstrukturen auf, ehe der Kernel läuft.
Unter Linux gibt es "binfmt" (man definiert vorher eine Liste mit Magic Numbers oder andere Kriterien) werden Dateien beim Versuch, sie auszuführen, an einen entsprechenden (ebenfalls definierten) Interpreter übergeben. Ist einsetzbar für Shellscripte, Java-Anwendungen und architekturfremde Dateien via Qemu, wurde aber auch für DOS-Anwendungen (via dosemu) verwendet. ELF-Dateien erhalten eine Sonderbehandlung, der ELF-Loader ist /lib/ld-linux.so.2 und wird immer (oder nur bei dynamischen ELFs?) mitgeladen.
Gruß,
Svenska
-
Hallo,
Also wird dein VFS-Service auch alle Libraries die er benötigt schon statisch gelinkt haben.?
Ja, in VFS ist ja nun auch wirklich nicht viel Library-Zeugs drin (der dürfte noch nicht mal ne halbwegs vollständige libc brauchen).
Mich hätte halt interessiert ob jemand eine Idee hat wie man das Lösen könnte ohne das man sich alzu sehr verrenken müsste?
Für die ersten paar Prozesse sollte man lieber auf solche Spielchen verzichten. Wie viel HDD-Platz kann man dadurch schon gewinnen? Sicher nicht so viel das sich dieser Aufwand lohnt.
Das hätte natürlich den Vorteil das man sich nicht auf ein Dateiformat festlegen muss
Der Kernel muss nur ein einziges und ganz simples Executable-Format unterstützen, alles andere bleibt im User-Mode. Bei mir werde ich aber erstmal dieses simple Format immer benutzen so das mein exec-Prozess nichts weiter machen muss außer die Datei in den Speicher zu laden.
und es wäre die perfekte Anwendung für fork().
Also fork() kann und will ich nicht unterstützen. Ich möchte schon dass das eigentliche Anlegen eines neuen Prozesses vom Kernel gemacht wird und dort auch ein neues Executable reingeladen wird.
Also wenn ich es richtig verstanden habe, willst du per IPC nen Art runtimeloader aufrufen. Dieser läd die Datei erstellt nen Prozess-Image und könnte nen Fork machen. So umgeht man den Kernel fast ganz und macht das meiste im UserSpace.
Ja, bis auf das fork hast Du mich richtig verstanden.
Ist ne Variante, aber da müsste man dann sehen, wie man es macht das man warten kann bis der Prozess zu Ende ist und sowas.
Mein exec-Prozess soll die PID an seinen (IPC-basierenden) Auftraggeber zurückmelden und was der mit der PID macht ist dann seine Angelegenheit.
Ich würde halt das Laden und das Parsen im Kernel machen, in einem neuen Prozess und KernelThread.
Ich las das laden lieber im User-Mode so das der Kernel nur das (simple) parsen machen muss.
Was ich noch vergessen habe, wie bekommt ihr die Parameter die dem Programm übergeben werden in das Programm?
Ich möchte das über einen Parameterblock machen der dem exec-Prozess gegeben wird. Dieser Block stamt von der CreateProzess()-Implementierung in der libc und der exec-Prozess gibt das dann dem Kernel mit welcher diesen Block hinten an die const-Section anhängt. Ein Pointer darauf ist dann das einzigste Parameter das der Entry-Point bekommt, dieser Code (auch aus der libc) zerlegt dann diesen Block in Umgebungsvariablen, geerbte Handles und die Parameter. Auf diese Weise bleibt das alles in der libc.
Grüße
Erik
-
Wenn du was POSIX-artiges machst, musst du übrigens noch viel mehr an den neuen Prozess weitergeben als nur Parameter. Beispielsweise Umgebungsvariablen oder Dateideskriptoren (wie auch immer die bei dir intern funktionieren).
-
Wenn du was POSIX-artiges machst, musst du übrigens noch viel mehr an den neuen Prozess weitergeben als nur Parameter. Beispielsweise Umgebungsvariablen oder Dateideskriptoren (wie auch immer die bei dir intern funktionieren).
Das wäre meine nächste Frage gewesen. Wie man z.B. das mit den Dateidiskriptoren lösen könnte.
Ich dachte daran, dass das der erzeugende Prozess selber machen sollte, so würde kein Sicherheitsproblem entstehen.
Er (der Prozess der einen neuen gestartet hat) schickt dem VFS-Service eine Nachricht das alle seine Dateidiskriptoren an einen bestimmten Prozess kopiert werden (oder was genau muss mit den Dateidiskriptoren gemacht werden, bei POSIX?).
Die Umgebungsvariablen würde ich im App-Server verwalten, aber auch da habe ich noch gar keine Vorstellung was das genau bedeutet, wozu die verwendet werden usw.
-
POSIX kopiert beim fork alle Deskriptoren, ja. Und beim exec schließt es wieder alle, die FD_CLOEXEC gesetzt haben. Kopieren heißt dabei das, was dup() macht. Es sind also zwei Deskriptoren, die sich aber beispielsweise einen gemeinsamen Zeiger für die Position in der Datei teilen. Lies dir am besten mal ein paar Manpages durch, vor allem von dup und fork.
-
Es sind also zwei Deskriptoren, die sich aber beispielsweise einen gemeinsamen Zeiger für die Position in der Datei teilen.
Du solltest mir eventuell erläutern was ein Dateidiskriptor ist, ich glaube ich habe das mit dem Dateihandle (einfach ne ID) verwechselt. Denn der Diksriptor ist doch der Client-Teil des Handles oder?
-
Also ein Dateideskriptor ist eigentlich schon das, was du wohl mit einem Handle meinst. Einfach ein Integer, den du z.B. von open() zurückkriegst. Ich bin mir jetzt nicht ganz sicher, was die Deskriptoren im Kernel adressieren. Ich glaube, es gibt ein paar lokale Eigenschaften, die jeder Deskriptor für sich hat (z.B. die Flags), und ein paar, die sie sich teilen (in der open file description).
Aber so ganz blicke ich da auch noch nicht durch. ;)
-
Also ein Dateideskriptor ist eigentlich schon das, was du wohl mit einem Handle meinst. Einfach ein Integer, den du z.B. von open() zurückkriegst.
Was mich verwirrt hat, war das du davon gesprochen hast das ein solcher Diskriptor die Position in der Datei speichert.
So wie ich mir das vorgestellt habe, wird das alles in der libc gemacht, sprich du sagst dem VFS-Service immer welchen 4kb Block du gerade brauchst und den Rest macht der libc Code.
Der VFS-Service soll bei mir eigentlich wirklich nur minimale Sachen machen und der Rest wird von den Libraries gemacht.
-
Hallo,
das mit den File-Descriptors finde ich auch etwas verwirrend. Ich möchte das auch gerne in der libc verbergen, z.B. die Verwaltung des aktuellen Datei-Pointers. Der Integerrückgabewert von open wäre dann einfach nur ein Index in eine Liste von FILE* und fopen liefert eben direkt diesen Pointer. Bei geerbten File-Descirptors hätte ich mir vorgestellt das der Eltern-Prozess als eine Art Vermittler fungiert und alles über ihn läuft. Auf diese Weise würde der VFS nicht mit solchen Dingen belästigt werden.
Für den VFS stelle ich mir vor das dieser nur create/delete/open/close/read/write/resize/rename anbietet und sonst nichts.
Bei den Umgebungsvariablen bin ich mir ebenfalls etwas unschlüssig. Die werden doch auch vererbt? Sonst ginge es ja nicht das 2 verschiedene Instanzen einer Shell unterschiedliche Umgebungsvariablen haben. Wimre wurden bereits unter DOS die Umgebungsvariablen vererbt.
Grüße
Erik
-
Problem mit Dateideskriptoren auf POSIX-Art ist, dass du die Dateideskriptoren an neue Prozesse vererbst. Du hast also hinterher zwei Deskriptoren in zwei unterschiedlichen Prozessen, die aber auf dieselbe open file description zeigen und sich damit z.B. den Dateizeiger teilen. Das in der libc zu verstecken, wird sehr schwer.
-
Sowas müsste man dann in einer "libposix" verstecken und die müssten beide miteinander kommunizieren.
Wozu braucht man sowas bzw. wo wird es angewendet?
-
Das merkst du dann, wenn du es nicht implementierst und ein Programm fällt auf die Schnauze. ;)
-
Das war nicht wirklich hilfreich ;)
Nee, aber mal ehrlich, in welcher Situation braucht man sowas? Ich meine das bedeutet ja auch, das wenn ein Prozess die Datei liest und der Pointer verändert wird, das dann der andere Prozess an der neuen Stelle liest oder?
Das klingt so als wenn man sich das hat einfallen lassen um Multithreading per Multitasking zu simulieren. Klingt also nach Unix ;)
-
Mag schon sein. Aber du machst POSIX, um Unix-Programme laufen zu lassen.
Dafür, dieselbe Datei in vielen Prozessen gleichzeitig offen zu haben, gibt es natürlich ein sehr einfaches Beispiel: Standardein- und -ausgabe. Jeder Prozess vererbt den Deskriptor für das Terminal einfach weiter. Das ist natürlich ein Character Device und insofern ein bisschen anders.
Aber jetzt denk mal weiter und leite die Standardausgabe in eine Datei um. Du willst, dass auch die Ausgabe der Kindprogramme mitgeloggt wird, und du willst nicht, dass sich die Ausgaben der verschiedenen Prozesse gegenseitig überschreiben, weil ihr Dateizeiger unabhängig ist.
-
Aber jetzt denk mal weiter und leite die Standardausgabe in eine Datei um. Du willst, dass auch die Ausgabe der Kindprogramme mitgeloggt wird, und du willst nicht, dass sich die Ausgaben der verschiedenen Prozesse gegenseitig überschreiben, weil ihr Dateizeiger unabhängig ist.
Wo wir wieder bei so einer Unix-Sache wären, ich hatte nämlich nicht vor die Standardausgabe als Datei anzulegen, sondern das muss ich emulieren. Na mal sehen wie ich das alles hinbekomme.
Die Kinderprozesse würden bei mir ihr eigenes "Fenster" bekommen.
-
Wie läuft das denn bei dir, wenn es keine Datei ist? Das ist übrigens keine Unix-Spezialität, sondern der C-Standard sieht so Sachen wie FILE* stdout vor...
-
Warum wurde C "erfunden" und woran hat sich der C-Standard orientiert? Unix, richtig ;)
Ich wollte das in eine Pipe schreiben (ist keine Datei) und es dann in einem "Konsolen"-Fenster ausgeben.
Musst du eigentlich irgendwo festlegen, das du willst, das alle Diskriptoren kopiert werden bzw. nicht kopiert werden? Oder musst du dann jedes Mal ne neue Standardausgabe öffnen wenn du nicht die alte nutzen willst?
-
Naja, Pascal hat auch input und output, wenn auch kein stderr, und das ist in Bezug auf Unix unverdächtig, oder? ;)
Was ist genau der Unterschied zwischen einer Pipe deiner Bauart und einer Datei? Im Zweifelsfall müssen sich die Prozesse dann halt eine Pipe statt einer Datei teilen. Und kann man bei dir die Ausgabe überhaupt nicht in eine "richtige" Datei umleiten?
-
Hallo,
Problem mit Dateideskriptoren auf POSIX-Art ist, dass du die Dateideskriptoren an neue Prozesse vererbst.
Okay, wobei trotzdem noch die Frage bleibt wo das verwendet wird, von stdin/stdout/stderr mal abgesehen.
Du hast also hinterher zwei Deskriptoren in zwei unterschiedlichen Prozessen, die aber auf dieselbe open file description zeigen und sich damit z.B. den Dateizeiger teilen.
Ich wollte das so machen das im erbenden Prozess eben für alles beim vererbenden Prozess nachgefragt wird. Das macht zwar eine Menge Kommunikationsoverhead aber wann wird das schon verwendet (für echte Dateien)? Bei Streams sieht die Sache wieder etwas anders (leichter) aus.
Was mir dann sicher etwas Kopfweh bereitet ist wenn der erbende Prozess seinerseits weiter vererbt, soll dann jeder Prozess direkt mit seinem unmittelbaren Vorfahren kommunizieren oder am besten mit dem Prozess der den original File-Descriptor hat. Was ist wenn ausgerechnet der Prozess mit dem original File-Descriptor diesen schließt nachdem er ihn schon an mehrere Kinder vererbt hat, wer wird dann der Chef über diesen File-Descriptor?
Das in der libc zu verstecken, wird sehr schwer.
Warum? Gehört open/close/read/.... nicht mehr zur libc? Und selbst wenn nicht dann hab ich eben eine libcOS die ein bisschen mehr enthält als nur das was der C-Standard vorgibt.
Grüße
Erik
-
Also mit DOS 2.0 wurden Unix-Pipes eingeführt und da kam auch eine Abstraktion von stdin/stdout/stderr mit rein. Sicher hat das Unix-Ursprünge, aber so schlecht kann das Konzept ja nun auch nicht gewesen sein... wenn es bereits damals übernommen wurde.
Übrigens: "Everything is a file" ist ein Unix-Konzept. Windows NT nutzt dieses auch, versteckt es aber vor dem Benutzer. Guck dir mal "dd" für Win32 an und rufe damit "dd --list" auf.
Warum? Gehört open/close/read/.... nicht mehr zur libc?
Bei Minix sind open/close/read Syscalls. Von der libc werden dann Wrapper zur Verfügung gestellt, die diese Syscalls auf POSIX-Standard abbilden.
Je näher du deine Syscalls an dieses Interface annäherst, desto kleiner (und schneller) wird dein Wrapper und desto weniger Aufwand hast du beim Portieren. Und sofern du jemals einen Webbrowser möchtest, wirst du wahrscheinlich lieber WebKit portieren wollen als selbst eine Javascript-Engine zu schreiben... mach dir Portierungen nicht sehr viel schwerer als nötig. Gleiches gilt für FlashBurn.
Grüße,
Sebastian
-
Problem mit Dateideskriptoren auf POSIX-Art ist, dass du die Dateideskriptoren an neue Prozesse vererbst.
Okay, wobei trotzdem noch die Frage bleibt wo das verwendet wird, von stdin/stdout/stderr mal abgesehen.
Ist stdin/out/err denn nicht schon Beispiel genug?
Aber das ist eigentlich die Methode, wie ein Unix-Prozess mit seinen Kindprozesses kommuniziert: fork(), pipe(), exec(),
das eine Ende selber behalten, das andere Ende ans Kind vererben und dann schließen.
Was mir dann sicher etwas Kopfweh bereitet ist wenn der erbende Prozess seinerseits weiter vererbt, soll dann jeder Prozess direkt mit seinem unmittelbaren Vorfahren kommunizieren oder am besten mit dem Prozess der den original File-Descriptor hat. Was ist wenn ausgerechnet der Prozess mit dem original File-Descriptor diesen schließt nachdem er ihn schon an mehrere Kinder vererbt hat, wer wird dann der Chef über diesen File-Descriptor?
Offensichtlich ist das Konzept also nicht ganz optimal. ;)
Ich schätze, um das vernünftig zu implementieren braucht man irgendeine zentrale Stelle.
Warum? Gehört open/close/read/.... nicht mehr zur libc? Und selbst wenn nicht dann hab ich eben eine libcOS die ein bisschen mehr enthält als nur das was der C-Standard vorgibt.
Ich meinte, komplett innerhalb der libc des Prozesses, ohne dass irgendein Service die ganze Sache verwaltet. Die libc kennt eben nur den Zustand eines Prozesses und ist wahrscheinlich nur bedingt geeignet, alles zu synchronisieren.
-
Hallo,
Ist stdin/out/err denn nicht schon Beispiel genug?
Jein, es sind eben keine echten Dateien. Ich wüste gerne ob sowas auch wirklich für richtige Dateien gemacht wird. Bei Output-Streams ist ein write eher ein append und die verschiedenen Zugriffe der Clients müssen zwar serialisiert werden aber die exakte Reihenfolge ist IMHO eher nachrangig. Über Input-Streams muss ich aber auch noch mal nachdenken.
Aber das ist eigentlich die Methode, wie ein Unix-Prozess mit seinen Kindprozesses kommuniziert: fork(), pipe(), exec(), das eine Ende selber behalten, das andere Ende ans Kind vererben und dann schließen.
Da ich weder fork noch exec habe muss ich dieses Code-Stückchen eh neu schreiben und ein Ende einer Pipe zu vererben ohne es selber zu behalten erschient mir jetzt nicht so extrem schwierig zu sein. Die größte Schwierigkeit sehe ich bei gemeinsam benutzten Ressourcen wo wirklich Kollisionen auftreten können (eben Dateien wegen dem File-Pointer).
Offensichtlich ist das Konzept also nicht ganz optimal. ;)
Ja, offensichtlich, aber das ist nicht mein dringendstes Problem
Ich schätze, um das vernünftig zu implementieren braucht man irgendeine zentrale Stelle.
Um die würde ich gerne rum kommen, vor allem weil Dein Beispiel mit der Pipe ja eher nur genau 2 Prozesse trifft. Ich hoffe das es reicht wenn die libc's der beteiligten Prozesse es schaffen sich zu einigen.
Die libc kennt eben nur den Zustand eines Prozesses und ist wahrscheinlich nur bedingt geeignet, alles zu synchronisieren.
Hm, weis nicht aber die embedded Variante von Qt macht das wimre auch so. Da ist jeder prinzipiell geeignet der Master zu sein aber nur einer wird es.
Übrigens: "Everything is a file" ist ein Unix-Konzept. Windows NT nutzt dieses auch, versteckt es aber vor dem Benutzer.
Ich weiß, aber so toll finde ich das ehrlich gesagt nicht. Das klingt für mich irgendwie danach unterschiedliche Dinge über einen Kamm zu scheren.
Von der libc werden dann Wrapper zur Verfügung gestellt, die diese Syscalls auf POSIX-Standard abbilden.
Bei mir wird das eben auf was anderes abgebildet.
Und sofern du jemals einen Webbrowser möchtest, wirst du wahrscheinlich lieber WebKit portieren wollen als selbst eine Javascript-Engine zu schreiben... mach dir Portierungen nicht sehr viel schwerer als nötig.
Davon träume ich noch nicht einmal. In meiner derzeitigen Planung hat meine Plattform nur RS232 als Verbindung zur Außenwelt, als nächstes sollen dann SATA (dafür wollte ich auf handlesübliche AHCI-basierte Controller zurückgreifen) und Ethernet (das würde ich gerne selber im FPGA implementieren) kommen. Danach kommt USB (auch gekaufte Controller) damit dann auch Tastatur und Maus möglich werden und danach kommt dann vielleicht mal ne Grafikkarte (wobei ich noch keine Vorstellung habe ob ich da was eigenes möchte oder was fertiges, PC kompatibel wird jedenfalls nichts nützen da ich keine I/O-Ports habe).
Grüße
Erik
-
Ist stdin/out/err denn nicht schon Beispiel genug?
Jein, es sind eben keine echten Dateien. Ich wüste gerne ob sowas auch wirklich für richtige Dateien gemacht wird.
Was macht eine "echte" Datei denn bei dir aus? Das einzige besondere an stdin/out/err ist, dass man rein sequenziell Zugriffe hat. Der Dateideskriptor dafür kann auf ein Terminal zeigen, aber auch auf eine Pipe oder eine Datei auf einem Dateisystem.
Bei Output-Streams ist ein write eher ein append und die verschiedenen Zugriffe der Clients müssen zwar serialisiert werden aber die exakte Reihenfolge ist IMHO eher nachrangig. Über Input-Streams muss ich aber auch noch mal nachdenken.
Zumindest brauchst du dann ein paar Synchronisationspunkte, z.B. das Beenden eines Prozesses. Wenn ein Prozess auf das Ende eines Kindprozesses wartet und danach eine Meldung ausgibt, erwartet jeder, dass diese Meldung selbstverständlich nach allen Meldungen des Kinds kommt.
Bei Ausgaben kannst du dich vermutlich noch irgendwie darum herummogeln, indem du die unterschiedlichen Ausgaben erst im Dateisystem-Server in irgendeiner Reihenfolge zusammenbastelst - die Clients interessieren sich dafür nicht. Aber wenn du eine Eingabeumleitung benutzt und alle Eingaben aus einer Datei liest, dann müssen Vater- und Kindprozess zwingend denselben Dateizeiger benutzen, sonst werden Dinge zweimal gelesen.
-
Hallo,
Was macht eine "echte" Datei denn bei dir aus?
Ich denke es ist gerade der File-Pointer, der bei allen Clients synchron sein muss, der ein Problem darstellt. Zumindest fällt mir da auf die schnelle keine elegante Lösung ein. Streams/Pipes (egal ob Input oder Output) haben das nicht in der Form.
Der Dateideskriptor dafür kann auf ein Terminal zeigen, aber auch auf eine Pipe oder eine Datei auf einem Dateisystem.
Ich denke selbst wenn stdout auf ne Datei zeigt muss das erst den Prozess interessieren der den original-Descriptor hat (für alle anderen bleibt das ein simpler Output-Stream).
Zumindest brauchst du dann ein paar Synchronisationspunkte, ...
Ja, es ist sicher nötig an ein paar Punkten ein flush zu benutzen.
Aber wenn du eine Eingabeumleitung benutzt und alle Eingaben aus einer Datei liest, dann müssen Vater- und Kindprozess zwingend denselben Dateizeiger benutzen, sonst werden Dinge zweimal gelesen.
Stimmt, hier muss man gut aufpassen. Bei Input-Streams werde ich wohl auf jeden Fall besser fahren wenn der erbende Prozess immer bei seinem direkten Vorfahren nachfragt, so das nichts was die libc im Elternprozess zwar schon eingelesen hat (in nen Buffer o.ä.) aber noch nicht verwendet wurde verloren geht.
Was ich mich aber noch frage ist: Was ist wenn in einer langen Kette an Vererbungen mittendrin ein Prozess gekillt wird?
Grüße
Erik
-
Damit bist du wieder bei deinem "nicht so dringenden" Problem, das ich oben schon angesprochen hatte. ;)
-
Das eigentliche Problem ist doch, das diese "alles ist eine Datei" Geschichte vorallem auf monolithische Kernel zugeschnitten ist und man so bei einem Mikrokernel einen IPC Overhead hat den man nicht haben müsste.
Das nächste Problem sehe ich darin wie man z.B. den Stdout implementiert. Ich meine wie soll ich mir das am lesenden Ende vorstellen und wo werden die Daten wie gespeichert?
Würde man das ganze als Pipe implementieren ist das schon wesentlich einfacher.
Eine Sache habe ich aber noch nicht ganz verstanden, File-Handles werden doch pro Prozess und nicht global rausgegeben, oder? Wie stellt man dann sicher das 2 Handles auch ein und die selbe Datei ansprechen?
Weil ansonsten könnte ich die Stout/-err/-in Geschichte einfach per Pipe lösen.
-
Hallo,
Das eigentliche Problem ist doch, das diese "alles ist eine Datei" Geschichte vorallem auf monolithische Kernel zugeschnitten ist und man so bei einem Mikrokernel einen IPC Overhead hat den man nicht haben müsste.
Das mag sein, aber es vereinfacht vor allem den Userspace extrem und stelle eine einheitliche, flexible Schnittstelle zur Verfügung. Alternativ musst du dir halt etwas ähnliches einfallen lassen oder aber für jedes Subsystem eine eigene Schnittstelle definieren, die du dann exportierst.
Der Vorteil ist, dass du die Schnittstelle zwischen Kernel und Treiber komplett ändern kannst und trotzdem irgendwie (read/write) kompatibel zum Userspace bleibst - es ändern sich nur ein paar Dateinamen - was du mit den reinen exportierten APIs nicht kannst. Die APIs kannst du natürlich direkt exportieren (z.B. DOS-basierte Windowse). Die Darstellung als Datei ist nur der nullte Wrapper.
Das nächste Problem sehe ich darin wie man z.B. den Stdout implementiert. Ich meine wie soll ich mir das am lesenden Ende vorstellen und wo werden die Daten wie gespeichert?
Würde man das ganze als Pipe implementieren ist das schon wesentlich einfacher.
Also DOS nutzt für seine Pipes temporäre Dateien. Daher auch die Begrenzung, dass maximal 2KB in einer Pipe sein können
Weil ansonsten könnte ich die Stout/-err/-in Geschichte einfach per Pipe lösen.
Kannst du, oder andersrum. Beispiel wieder DOS: Pipes sind Dateien.
Gruß
-
Kannst du, oder andersrum. Beispiel wieder DOS: Pipes sind Dateien.
Aber nur wenn du nach der "alles ist eine Datei" Philosophie handelst. Zumal das immernoch nicht meine Frage beantwortet wie das auf der lesenden Seite aussieht.
Was ich halt nicht ganz verstehe ist, man liest bis zum EOF, soweit so gut, aber was passiert wenn man ftell() aufrufen würde und liest man dann das nächste mal einfach wieder bis zum EOF und wie wird man benachrichtig das neue Daten vorhanden sind?
Im Endeffekt ist zwar nach außen alles eine Datei, aber dahinter muss man sich immer was spezielles einfallen lassen, damit es funktioniert. Warum dann nicht den Layer der Datei weglassen?
Der Vorteil ist, dass du die Schnittstelle zwischen Kernel und Treiber komplett ändern kannst und trotzdem irgendwie (read/write) kompatibel zum Userspace bleibst - es ändern sich nur ein paar Dateinamen - was du mit den reinen exportierten APIs nicht kannst. Die APIs kannst du natürlich direkt exportieren (z.B. DOS-basierte Windowse). Die Darstellung als Datei ist nur der nullte Wrapper.
Ich sehe da keinen Vorteil. Entweder es ändert sich die exportierte API und beide Seiten (Kernel und Treiber) müssen geändert werden oder es ändert sich das Protokoll welches hinter read/write kommt, dann muss genauso neugeschrieben werden.
Aber mal zum Punkt fexible API. Ich habe in letzter Zeit oft mit dem Gedanken gespielt das ne Text-API (nach XML Art) ziemlich flexible wäre.?
-
Hallo,
Damit bist du wieder bei deinem "nicht so dringenden" Problem, das ich oben schon angesprochen hatte. ;)
Allerdings, ich schreibe dann mal meine Gedanken dazu auf meine (bereits ziemlich große) ToDo-Liste.
Das eigentliche Problem ist doch, das diese "alles ist eine Datei" Geschichte vorallem auf monolithische Kernel zugeschnitten ist und man so bei einem Mikrokernel einen IPC Overhead hat den man nicht haben müsste.
Genau so sehe ich das auch.
Das nächste Problem sehe ich darin wie man z.B. den Stdout implementiert. Ich meine wie soll ich mir das am lesenden Ende vorstellen und wo werden die Daten wie gespeichert?
Würde man das ganze als Pipe implementieren ist das schon wesentlich einfacher.
stdout usw. müssen ja irgendwo enden und an dieser Stelle (also normalerweise im Shell-Prozess) muss eben etwas sitzen das diese Pipe abfragt und die gelesenen Daten in eine Datei schreibt oder auf ein anderes Stream-Device weiterleitet.
Alternativ musst du dir halt etwas ähnliches einfallen lassen oder aber für jedes Subsystem eine eigene Schnittstelle definieren, die du dann exportierst.
Ich hab mir vorgestellt das in der libc zu kapseln. Die libc bietet eine API für Dateien, eine API für Streams usw. und das normale open/read/write/... setzen da oben drauf auf (ich denke dieser Layer dürfte recht dünn sein). Die Datei-API benutzt ihrerseits den VFS als Back-End und für die Pipes kommunizieren die libc's in den beteiligten Prozessen direkt miteinander.
Die Darstellung als Datei ist nur der nullte Wrapper.
Das sehe ich anders. Wenn wir mal ein einfaches VFS betrachten wohinter sich verschiedene Dinge verbergen können dann muss eben hinter dem VFS die Logik stecken die aus den read/write/.... das macht was eigentlich gemeint ist (also Wrapper -1). Ich sehe da das Potential für Konvertierungsverluste und Zeitverschwendung. Bei einem Monolithen kann man das machen/rechtfertigen um das Syscall-API klein zu halten aber bei einem Micro-Kernel ist das IMHO der falsche Ansatz. Ich persönlich finde z.B. toll wie in Java die Streams für alles mögliche benutzt werden können (in/out/err oder TPC-Streams oder Pipes). Streams auf Dateien abbilden zu wollen halte ich für Quatsch.
Aber mal zum Punkt fexible API. Ich habe in letzter Zeit oft mit dem Gedanken gespielt das ne Text-API (nach XML Art) ziemlich flexible wäre.?
Das stelle ich mir sehr langsam vor. Die Prüfung auf Wohlgeformtheit ist bei XML nicht ohne und eine XML-Anfrage ist x-fach größer als ein Binär-Äquivalent.
Grüße
Erik
-
Das stelle ich mir sehr langsam vor. Die Prüfung auf Wohlgeformtheit ist bei XML nicht ohne und eine XML-Anfrage ist x-fach größer als ein Binär-Äquivalent.
Das wird auch der Grund sein, warum es noch nicht wirklich versucht wurde, aber wenn man den Overhead mal beiseite lässt wäre ne Text-API nicht schlecht.
-
Hallo,
Was ich halt nicht ganz verstehe ist, man liest bis zum EOF, soweit so gut, aber was passiert wenn man ftell() aufrufen würde und liest man dann das nächste mal einfach wieder bis zum EOF und wie wird man benachrichtig das neue Daten vorhanden sind?
Wie wäre es mit einer File Notification API?
Für solche Fälle kann man auch select() oder poll() oder epoll() oder kqueue nutzen. Du kannst ja nicht nur auf zu schreibende, sondern auch auf zu lesende Daten warten. Ursprünglich funktionierte das nur mit Sockets, inzwischen kannst du diese libc-Funktionen auch auf Dateihandles anwenden. Es sei denn, ich irre mich gerade gewaltig.
Im Endeffekt ist zwar nach außen alles eine Datei, aber dahinter muss man sich immer was spezielles einfallen lassen, damit es funktioniert. Warum dann nicht den Layer der Datei weglassen?
Wie gesagt, es vereinfacht den Userspace ungemein, wenn du nicht irgendwelche API-spezifischen Headerdateien einbinden musst, sondern es mit der Standard-libc erschlagen kannst.
Der Vorteil ist, dass du die Schnittstelle zwischen Kernel und Treiber komplett ändern kannst und trotzdem irgendwie (read/write) kompatibel zum Userspace bleibst - es ändern sich nur ein paar Dateinamen - was du mit den reinen exportierten APIs nicht kannst. Die APIs kannst du natürlich direkt exportieren (z.B. DOS-basierte Windowse). Die Darstellung als Datei ist nur der nullte Wrapper.
Ich sehe da keinen Vorteil. Entweder es ändert sich die exportierte API und beide Seiten (Kernel und Treiber) müssen geändert werden oder es ändert sich das Protokoll welches hinter read/write kommt, dann muss genauso neugeschrieben werden.
Falscher Ansatz. Kernel und Treiber musst du ohnehin komplett anpassen, aber die Anwendungsprogramme nicht. Für viele Fälle reicht da eine simple Implementation.
Beispielsweise muss ein MP3-Player einfach nur open() auf die Soundkarte und write() für die Daten machen; solange du keine optimale Latenz brauchst oder irgendwelche Kanäle aus einem Programm mixen willst, reicht das aus. Für Spezialfälle (Latenz, Mixing, Resampling, ...) stellst du dann z.B. deine interne, evtl. sogar instabile oder hardwareabhängige API bereit. Zugriffspunkt für die Kommunikation ist dann eleganterweise auch diese Datei, da eh vorhanden.
Gleiches gilt für die grafische Oberfläche, wenn du den Grafikspeicher einfach als Datei zur Verfügung stellst. Sonderfälle (wie 2D-Beschleunigung) kannst du nachträglich da einbauen, ohne die Kompatiblität zu gefährden und Anwendungen haben immer die Möglichkeit einer Rückfallstrategie.
Deine interne API den Treibern zur Verfügung zu stellen ist in Ordnung. Die Anwendungsprogramme sollten davon allerdings nichts mitkriegen und bestenfalls ein sauberes, schlankes Interface zur Verfügung haben.
Aber mal zum Punkt fexible API. Ich habe in letzter Zeit oft mit dem Gedanken gespielt das ne Text-API (nach XML Art) ziemlich flexible wäre.?
XML ist ein Gerüst der Hölle. So ziemlich jede XML-Implementation ist kaputt, anfällig für buffer overflows usw. Wer auf Anhieb eine XML-Implementation fehlerfrei hinkriegt, der ist entweder ein Genie oder ein Lügner.
Außerdem müsstest du deine XML-Strukturen ohnehin inkompatibel zum Rest der Welt standardisieren, da kannst du ein (wesentlich einfacher zu parsendes) binäres Format nutzen. Das ist zudem noch kleiner. Bleib mir bloß mit XML vom Leibe.
Gruß,
Sebastian
-
Für solche Fälle kann man auch select() oder poll() oder epoll() oder kqueue nutzen. Du kannst ja nicht nur auf zu schreibende, sondern auch auf zu lesende Daten warten. Ursprünglich funktionierte das nur mit Sockets, inzwischen kannst du diese libc-Funktionen auch auf Dateihandles anwenden. Es sei denn, ich irre mich gerade gewaltig.
Gut, das klingt so als wenn das nicht immer so war? Mir geht es darum, das diese "Dateien" im Kernel also speziell behandelt werden, das aber hinter einer Datei versteckt wird.
XML ist ein Gerüst der Hölle. So ziemlich jede XML-Implementation ist kaputt, anfällig für buffer overflows usw. Wer auf Anhieb eine XML-Implementation fehlerfrei hinkriegt, der ist entweder ein Genie oder ein Lügner.
Außerdem müsstest du deine XML-Strukturen ohnehin inkompatibel zum Rest der Welt standardisieren, da kannst du ein (wesentlich einfacher zu parsendes) binäres Format nutzen. Das ist zudem noch kleiner. Bleib mir bloß mit XML vom Leibe.
Ok, XML ist schlecht, aber ich meine sowas das man eine Anfrage in der Art macht:
OPEN name="foo.bar" flags="rb"
So wäre es z.B. egal in welcher Reihenfolge du Parameter übergibst und damit lassen sich dann bestimmt auch andere schöne Sachen machen.
Du könntest z.B. mehrere verschiedene Versionen dieses "call´s" haben mit verschiedenen Parametern und wenn ein altes Programm nicht alle Parameter übergibt, dann werden in die restlichen Standardwerte reingeschrieben.
-
Hallo,
Was leider immer noch keiner erklärt hat ist ob es tatsächlich Anwendungsfälle gibt in denen 2 Prozesse auf die selbe Datei über den selben File-Descriptor zugreifen. Das stelle ich mir immer ziemlich tricky vor.
Wie gesagt, es vereinfacht den Userspace ungemein, wenn du nicht irgendwelche API-spezifischen Headerdateien einbinden musst, sondern es mit der Standard-libc erschlagen kannst.
Das ist aber genauso gut möglich wenn die Verzweigung in die einzelnen APIs noch in der libc drin ist, solange die allgemeinen Headerdateien erstmal reichen sehe ich da keine Probleme.
XML ist ein Gerüst der Hölle.
Aha, dafür schwören aber ne Menge Leute auf XML.
Wer auf Anhieb eine XML-Implementation fehlerfrei hinkriegt, der ist entweder ein Genie oder ein Lügner.
oder er hat sich auf das wirklich benötigte beschränkt.
aber ich meine sowas das man eine Anfrage in der Art macht:OPEN name="foo.bar" flags="rb"
So wäre es z.B. egal in welcher Reihenfolge du Parameter übergibst und damit lassen sich dann bestimmt auch andere schöne Sachen machen.
Das geht binär genau so gut und ist leichter zu parsen/interpretieren.
Grüße
Erik
-
Was leider immer noch keiner erklärt hat ist ob es tatsächlich Anwendungsfälle gibt in denen 2 Prozesse auf die selbe Datei über den selben File-Descriptor zugreifen. Das stelle ich mir immer ziemlich tricky vor.
Wäre ich auch dran interessiert.
Das geht binär genau so gut und ist leichter zu parsen/interpretieren.
Aber, binär legst du auch die Reihenfolge fest und wie willst du es machen einen neuen Parameter hinzuzufügen ohne das es Probleme mit alten Programmen gibt. Auch wäre es so kein Problem wenn sich irgendwelche IDs ändern würden (z.B. bei Syscalls) weil man ja den direkten Namen nimmt.
-
Für solche Fälle kann man auch select() oder poll() oder epoll() oder kqueue nutzen. Du kannst ja nicht nur auf zu schreibende, sondern auch auf zu lesende Daten warten. Ursprünglich funktionierte das nur mit Sockets, inzwischen kannst du diese libc-Funktionen auch auf Dateihandles anwenden. Es sei denn, ich irre mich gerade gewaltig.
Gut, das klingt so als wenn das nicht immer so war? Mir geht es darum, das diese "Dateien" im Kernel also speziell behandelt werden, das aber hinter einer Datei versteckt wird.
Sockets sind keine Dateien. Diese API wurde anfangs nur für Sockets zugelassen, weil sie Teil des Netzwerklayers war. Inzwischen ist es möglich, diese API auch auf Dateien anzuwenden (select(2) gibt als Beispiel stdin an), einfach weil sie deinen Anwendungsfall (der recht häufig ist), gut lösen kann, gut getestet ist und vor allem für beides funktioniert.
Ansonsten müsstest du eine File Notification API erfinden und nutzen, was auch geht - und wenn ich mich recht entsinne, gibt es sowas unter POSIX zusätzlich auch. Die WinSock erlaubt select()/poll() nur für Sockets, nicht für Dateien.
Du könntest z.B. mehrere verschiedene Versionen dieses "call´s" haben mit verschiedenen Parametern und wenn ein altes Programm nicht alle Parameter übergibt, dann werden in die restlichen Standardwerte reingeschrieben.
Warum nicht einfach alle Felder "mandatory" machen und die Anwendung mit Standardwerten operieren lassen? :-)
Wie gesagt, es vereinfacht den Userspace ungemein, wenn du nicht irgendwelche API-spezifischen Headerdateien einbinden musst, sondern es mit der Standard-libc erschlagen kannst.
Das ist aber genauso gut möglich wenn die Verzweigung in die einzelnen APIs noch in der libc drin ist, solange die allgemeinen Headerdateien erstmal reichen sehe ich da keine Probleme.
Ich meinte ja auch die Schnittstelle zwischen Anwendungen und Kernel, wo diese Philosophie halt echt gut funktioniert, einfach und gleichzeitig mächtig ist.
Wie du die Schnittstelle zwischen Kernel und Treiber gestaltest, spielt (für mich) an der Stelle garkeine Rolle. Genausowenig, ob du diesen Wrapper (Kernel und Anwendung kommunizieren über Dateien) nun im Kernel/VFS oder in der libc versteckst.
XML ist ein Gerüst der Hölle.
Aha, dafür schwören aber ne Menge Leute auf XML.
Guck dir mal übliche, auf XML aufsetzende Netzwerkprotokolle an (MSN Messenger, ...). Da werden teilweise Binärdaten roh mitgeschickt, teilweise mit Base64 encodiert und dann hat man XML mit Base64 ENcodiert. Das sind grausame Protokolle, die da unterwegs sind. XML ist nur solange gut, wie man ein gut durchdachtes Protokoll dahinter ansetzt, wo man sich vorher Gedanken gemacht hat.
Und genau dann braucht man kein extensibles Textprotokoll wie XML mehr.
Wer auf Anhieb eine XML-Implementation fehlerfrei hinkriegt, der ist entweder ein Genie oder ein Lügner.
oder er hat sich auf das wirklich benötigte beschränkt.
Da hab ich aber schon andere Sachen gehört und gelesen...
Das geht binär genau so gut und ist leichter zu parsen/interpretieren.
Aber, binär legst du auch die Reihenfolge fest und wie willst du es machen einen neuen Parameter hinzuzufügen ohne das es Probleme mit alten Programmen gibt. Auch wäre es so kein Problem wenn sich irgendwelche IDs ändern würden (z.B. bei Syscalls) weil man ja den direkten Namen nimmt.
Ob dein Service nun "VFS_Handle_File_Open" oder "142" heißt, spielt keine Rolle. IDs ändern sich übrigens auch nicht von alleine, wenn du die vorher festlegst - genausowenig wie Namen.
Der Unterschied ist, dass dir bei Zahlen niemand den Text-Parser kaputtmachen muss - und dass ein Cast auf int einfach schneller ist, als ein Textparser, der am Ende eh nur ein Handle hinten rauswirft...
Gruß,
Svenska
-
Was leider immer noch keiner erklärt hat ist ob es tatsächlich Anwendungsfälle gibt in denen 2 Prozesse auf die selbe Datei über den selben File-Descriptor zugreifen. Das stelle ich mir immer ziemlich tricky vor.
Ich habe noch nicht genau verstanden, was dir am stdio-Beispiel nicht zusagt. Es ist kein Spezialfall und vermutlich der Haupteinsatzzweck der Deskriptorvererbung. Ein viel schöneres und praktischeres Beispiel kannst du doch gar nicht mehr bekommen?
-
Hallo,
Ich habe noch nicht genau verstanden, was dir am stdio-Beispiel nicht zusagt. Es ist kein Spezialfall und vermutlich der Haupteinsatzzweck der Deskriptorvererbung. Ein viel schöneres und praktischeres Beispiel kannst du doch gar nicht mehr bekommen?
Mir gefällt nicht das es ein Spezialfall ist, es geht dabei um das weitervererben eines Endes einer Pipe also um das weitervererben eines Input-Streams oder Output-Streams. Streams sind was völlig anderes als Dateien (ich denke für Streams hab ich ein funktionsfähiges Konzept in meine ToDo-Liste geschrieben). Wenn Descriptorvererbung nur für Streams/Pipes gemacht wird (was zweifelsohne der Haupteinsatzzweck ist) dann muss ich erst gar keine Vererbung von Dateien implementieren. Es geht mir darum ob es für diesen Nebeneinsatzzweck (vererben von Dateidescriptoren) auch reale Beispiele gibt an denen man sich orientieren könnte wie das ungefähr umzusetzen ist.
Ich meinte ja auch die Schnittstelle zwischen Anwendungen und Kernel, wo diese Philosophie halt echt gut funktioniert, einfach und gleichzeitig mächtig ist.
Aber gerade diese Schnittstelle wird doch von der libc repräsentiert (zumindest wenn wir Open-Source-SW betrachten und was anderes kann ich eh nicht auf meine Plattform portieren). Dass das üblicherweise zwischen Applikation und Kernel kommt liegt doch daran das übliche OSe Monolithen sind, bei einem Micro-Kernel-OS hat der Kernel überhaupt gar nichts damit zu tun da trifft das eher die Personality und die libc muss das passend verbergen so das auch dieses Konglomerat von Services wie ein OS aussieht.
Guck dir mal übliche, auf XML aufsetzende Netzwerkprotokolle an
Keine Angst, das schaue mich mir lieber erst gar nicht an. Ich bin nicht der Meinung das man XML für alles und jedes benutzen muss aber XML hat durchaus seine Stärken, ich möchte XML z.B. für mein linkbares Objekt-Datei-Format benutzen.
Wer auf Anhieb eine XML-Implementation fehlerfrei hinkriegt, der ist entweder ein Genie oder ein Lügner.
oder er hat sich auf das wirklich benötigte beschränkt.
Da hab ich aber schon andere Sachen gehört und gelesen...
Ich hab XML mal für eine ganz simple Konfigurationsdatei benutzt und ich bin mir sicher das dort kaum Fehler drin waren. Der Quell-Code fürs speichern und wieder einlesen war kaum über 20 kBytes und basierte im wesentlichen auf fprintf und strcmp (beim einlesen hab ich erst die komplette Datei in einen Puffer geladen und diesen dann durchgearbeitet).
Aber, binär legst du auch die Reihenfolge fest und wie willst du es machen einen neuen Parameter hinzuzufügen ohne das es Probleme mit alten Programmen gibt. Auch wäre es so kein Problem wenn sich irgendwelche IDs ändern würden (z.B. bei Syscalls) weil man ja den direkten Namen nimmt.
Ach was, das funktioniert wunderbar in Binär!
Du hast einen Header in Deiner Anfrage:uint commando_code; //Funktionsnummer für Deine Kommandos (2^32 sind erstmal genug)
uint data_length; //Anzahl der folgenden Bytes für dieses Kommando
Und danach kommen null, einer oder mehrere Parameter-Blöcke, jeder Parameterblock hat folgenden Aufbau:uint parameter_code; //hier kannst Du für jedes Kommando wieder bis zu 2^32 unterschiedliche Parameter definieren
uint parameter_length; //Anzahl der folgenden Bytes für diesen Parameter-Block
// (mit dieser Info kann Dein Service auch Parameter überspringen die er nicht kennt weil die libc neuer ist als der Service)
..... //Nutzdaten für dieses Parameter, hier könnte ein Dateiname mit variabler Länge stehen oder einfach nur ein einzelner Integer mit Flags
Diese Parameter-Blöcke müssen in keiner bestimmten Reihenfolge kommen und sind extrem flexibel. Der wichtigste Vorteil ist jedoch das Du keinen Text-Parser brauchst. (falls ich das jetzt nicht genau genug beschrieben hab dann frage ruhig noch mal)
Grüße
Erik
-
Ich habe noch nicht genau verstanden, was dir am stdio-Beispiel nicht zusagt. Es ist kein Spezialfall und vermutlich der Haupteinsatzzweck der Deskriptorvererbung. Ein viel schöneres und praktischeres Beispiel kannst du doch gar nicht mehr bekommen?
Mir gefällt nicht das es ein Spezialfall ist, es geht dabei um das weitervererben eines Endes einer Pipe also um das weitervererben eines Input-Streams oder Output-Streams. Streams sind was völlig anderes als Dateien (ich denke für Streams hab ich ein funktionsfähiges Konzept in meine ToDo-Liste geschrieben). Wenn Descriptorvererbung nur für Streams/Pipes gemacht wird (was zweifelsohne der Haupteinsatzzweck ist) dann muss ich erst gar keine Vererbung von Dateien implementieren. Es geht mir darum ob es für diesen Nebeneinsatzzweck (vererben von Dateidescriptoren) auch reale Beispiele gibt an denen man sich orientieren könnte wie das ungefähr umzusetzen ist.
Man kann unter Unix aber auch in/aus einer Datei pipen - va. kann man beim beim Pipen in eine Datei das geschriebene am Ende anhängen lassen (oder auch nicht), was natürlich voraussetzt, dass der Dateizeiger auch mitvererbt wird. Aber man könnte das eventuell durch eine zusätzliche Pipe an die Shell, die dann das eigentliche Schreiben in die Datei übernimmt, lösen, aber das ist zum einen weniger performant, zum anderen natürlich fricklig zu implementieren. Auf den ersten Blick sieht es auch so aus, als ob man das dann nicht (ohne Signale) in einem POSIX-Layer verbergen kann.
-
Hallo,
Man kann unter Unix aber auch in/aus einer Datei pipen
Genau das (das letzte Wort) macht den Unterschied zur echten Datei-Vererbung. Die Shell gibt an ihr Kind immer Streams für sdtin/stdout/stderr weiter, ob das die Streams sind die die Shell selber bekommen (geerbt) hat oder ob das Streams von einem Mechanismus sind der Dateien pipen kann ist völlig egal. Dieser Mechanismus muss innerhalb der Shell implementiert werden aber den stelle ich mir recht einfach vor (bei einem Output-Stream wird eben jedes write in ein fappend umgesetzt und bei einem Input-Stream wird jedes read in ein fread umgesetzt).
aber das ist zum einen weniger performant, zum anderen natürlich fricklig zu implementieren.
Wieso sollte das weniger performant als das schreiben in eine geerbte Pipe sein? Die Shell muss die gestreamten Daten weitergeben, entweder an den eigenen Parent oder ans VFS, ich sehe da keinen relevanten Unterschied. (bei stdin das ganze natürlich andersrum)
Auf den ersten Blick sieht es auch so aus, als ob man das dann nicht (ohne Signale) in einem POSIX-Layer verbergen kann.
Wo siehst Du da das Problem? Das weiterleiten von Streams wollte ich über IPC-basiertes RPC machen.
Grüße
Erik
-
aber das ist zum einen weniger performant, zum anderen natürlich fricklig zu implementieren.
Wieso sollte das weniger performant als das schreiben in eine geerbte Pipe sein? Die Shell muss die gestreamten Daten weitergeben, entweder an den eigenen Parent oder ans VFS, ich sehe da keinen relevanten Unterschied. (bei stdin das ganze natürlich andersrum)
Unter Unix geht das aber direkt in die Datei und nicht erst durch eine Pipe an die Shell (denke ich, aber da lass ich mich gerne eines besseren belehren), deswegen weniger performant und deswegen denke ich auch, dass das fricklig ist, da du über eine Pipe emulieren musst, was POSIX direkt machen könnte. Gut, wenn du deine eigene Shell schreibst ist das sowieso wurst, aber wenn du eine portierst nehme ich mal an, dass die davon ausgeht, dass sie einfach eine Datei als stdout/stdin "vererben" kann und die Sache hat sich für diese Shell gegessen (d.h. sie wird nicht versuchen eine Pipe zu vererben und dann da manuell die Datei rein/rauszupipen). Das müsstest du also im POSIX-Layer für die Shell übernehmen. Was aber bescheiden wird, weil es asynchron zur eigentlichen Befehlssequenz der Shell passieren muss.
-
Hallo,
Unter Unix geht das aber direkt in die Datei und nicht erst durch eine Pipe an die Shell
Bei einem Monolithen und der Prozesserstellung mit fork ist das sicher der beste Weg aber bei einem Micro-Kernel und ohne fork empfinde ich das vererben von Streams besser (das vererben richtiger Dateien erscheint mir irgendwie viel zu aufwendig). Wenn man kein durchreichen der Daten will dann benötigt man eine zentrale Stelle im System, da bietet sich zwar der VFS an aber IMHO ist das nicht seine Aufgabe, man würde ihm eigentlich fremde Arbeit aufbürden nur weil man versucht alles auf Dateien abzubilden.
aber wenn du eine portierst nehme ich mal an, dass die davon ausgeht, dass sie einfach eine Datei als stdout/stdin "vererben" kann und die Sache hat sich für diese Shell gegessen (d.h. sie wird nicht versuchen eine Pipe zu vererben und dann da manuell die Datei rein/rauszupipen). Das müsstest du also im POSIX-Layer für die Shell übernehmen. Was aber bescheiden wird, weil es asynchron zur eigentlichen Befehlssequenz der Shell passieren muss.
Ich wollte eigentlich schon versuchen eine Shell zu portieren und die soll für stdin/stdout/stderr immer Strems vererben, das bedeutet ich muss diesen Code eh anfassen (obwohl ich mir vorstellen könnte das wenn die Shell nur mit FILE* arbeitet das gar nicht so viel ist). Das die Shell damit als IPC-Service gegenüber ihren Kindern auftritt ist mir klar, das muss dann eben in speziellen Code gemacht werden. Ganz ohne Portierungsaufwand komme ich auf jeden Fall nicht zum Ziel.
Grüße
Erik
-
Waren Stdout/Stderr/Stdin nicht die ersten 3 Filehandles eines Prozesses?
Ich habe mir das dann so vorgestellt, das man einfach so als wenn man in die Datei schreiben würde den write-Aufruf macht und das VFS dann die Daten an meinen App-Server per Pipe weitersendet. So wird zwar unnötig kopiert aber das sollte mir einige Probleme vom Hals halten.
So kann ich auch ganz einfach das Vererben lösen.
Ich muss allerdings erik zustimmen, dass das eigentlich nicht die Aufgabe des VFS ist.
-
Ich wollte eigentlich schon versuchen eine Shell zu portieren und die soll für stdin/stdout/stderr immer Strems vererben, das bedeutet ich muss diesen Code eh anfassen (obwohl ich mir vorstellen könnte das wenn die Shell nur mit FILE* arbeitet das gar nicht so viel ist).
Welche Shell kennst du denn, die nur FILE* benutzt? ;)
-
Hallo,
Dass das üblicherweise zwischen Applikation und Kernel kommt liegt doch daran das übliche OSe Monolithen sind, bei einem Micro-Kernel-OS hat der Kernel überhaupt gar nichts damit zu tun da trifft das eher die Personality und die libc muss das passend verbergen so das auch dieses Konglomerat von Services wie ein OS aussieht.
Eben. Dem Anwendungsprogramm ist es völlig egal, mit wem es da eigentlich redet, im Endeffekt ist es "der Kernel". Die Abstraktion, mit wem da eigentlich geredet wird, verbirgst du in der libc. Mein Gedanke ist, wenn du die Schnittstelle von der libc im Kernel zumindest ansatzweise auch hast, bleibt diese Abstraktion sehr dünn.
Das macht die libc einfacher. Denn meine Frage ist: Wenn ich schon eine gut funktionierende API im Userspace habe - warum dann nicht die gleichen Konzepte auch im Kernel verwenden?
Prinzipiell musst du ja "nur" die IPC-Kommunikation über Sockets machen, die du miteinander verbinden kannst. Das heißt, dein Prozess redet via libc mit dem VFS, das VFS kriegt mit, dass der Plattentreiber gemeint ist und verbindet diese Deskriptoren quasi-miteinander, so dass die Kommunikation nicht mehr über das VFS läuft. Wenn der Plattentreiber nicht mehr kann oder will (z.B. nicht im HD-Treiber implementierte Funktion wie "cd"), gibt er den Deskriptor zurück an das VFS, was dann damit machen kann, was es will.
Kann dein Kernel das, ist die libc einfacher. Andersrum gilt entsprechendes.
Ich hab XML mal für eine ganz simple Konfigurationsdatei benutzt und ich bin mir sicher das dort kaum Fehler drin waren. Der Quell-Code fürs speichern und wieder einlesen war kaum über 20 kBytes und basierte im wesentlichen auf fprintf und strcmp (beim einlesen hab ich erst die komplette Datei in einen Puffer geladen und diesen dann durchgearbeitet).
Dann hast du gerade kein XML verwendet, sondern eine XML-ähnliche, eigene Markup-Language. Guck mal in die XML-Spec, was du für XML alles können musst!
Wenn man kein durchreichen der Daten will dann benötigt man eine zentrale Stelle im System, da bietet sich zwar der VFS an aber IMHO ist das nicht seine Aufgabe, man würde ihm eigentlich fremde Arbeit aufbürden nur weil man versucht alles auf Dateien abzubilden.
Richtig. Wenn du aber dem VFS die Möglichkeit gibst, allgemeingültig "alles" auf Dateien abzubilden, dann sparst du dir in anderen Layern sehr viel Arbeit, wenn du dieses Konzept gründlich umsetzt. Und dann ist es Kernaufgabe des VFS.
Waren Stdout/Stderr/Stdin nicht die ersten 3 Filehandles eines Prozesses?
Prinzipiell ja.
Ich habe mir das dann so vorgestellt, das man einfach so als wenn man in die Datei schreiben würde den write-Aufruf macht und das VFS dann die Daten an meinen App-Server per Pipe weitersendet. So wird zwar unnötig kopiert aber das sollte mir einige Probleme vom Hals halten.
So kann ich auch ganz einfach das Vererben lösen.
Siehe oben. Wenn es möglich ist, solltest du einen Mechanismus implementieren können, damit du dir das durchreichen sparst. Wie man das konkret lösen kann, weiß ich nicht und vermute, dass es stark vom IPC-Konzept abhängt.[/quote]
Ich muss allerdings erik zustimmen, dass das eigentlich nicht die Aufgabe des VFS ist.
Nun, wenn du alles auf Dateien abbildest, dann schon... dann ist es sogar die exakte Aufgabe des VFS (darum ist es ja "virtual"...)
Grüße.
Svenska
-
Hallo,
Welche Shell kennst du denn, die nur FILE* benutzt? ;)
Keine, ich hab noch nie in den Quell-Code einer Shell reingesehen. Ich hatte eben die Vermutung/Hoffnung das man das so macht, es wäre aus meiner naiven Sicht logisch. ;)
Dem Anwendungsprogramm ist es völlig egal, mit wem es da eigentlich redet, im Endeffekt ist es "der Kernel". Die Abstraktion, mit wem da eigentlich geredet wird, verbirgst du in der libc.
2x ACK
Mein Gedanke ist, wenn du die Schnittstelle von der libc im Kernel zumindest ansatzweise auch hast, bleibt diese Abstraktion sehr dünn.
Die Abstraktion in der libc bleibt IMHO so auch recht dünn, die Standard-Funktionen enthalten im wesentlichen ein switch(descriptor->type) und führen dann spezifische Funktionsaufrufe durch (das ist IMHO ziemlich dünn). Klar würde man im VFS nicht viel anderes implementieren aber dort hat man das Problem die Daten "weiterleiten" zu müssen (auch wenn das bei mir ohne kopieren geht so möchte ich das trotzdem vermeiden) wogegen die libc gleich direkt mit dem passenden Service kommunizieren kann. Wo genau diese Komplexität steckt hat IMHO keinen Einfluss auf den Programmieraufwand (obwohl der Code in der libc sich vielleicht leichter debuggen lässt als Code in einem Service oder gar im Kernel).
Denn meine Frage ist: Wenn ich schon eine gut funktionierende API im Userspace habe - warum dann nicht die gleichen Konzepte auch im Kernel verwenden?
Das klingt nach Monolith. Mein Frage ist: Geht es nicht besser/performanter wenn ich ein API benutze das auf die entsprechende Aufgabe passend zugeschnitten ist? Eben ein Stream-API für Streams oder ein File-API für Files oder ein Socket-API für Sockets (natürlich für TCP und UDP getrennt).
Prinzipiell musst du ja "nur" die IPC-Kommunikation über Sockets machen, die du miteinander verbinden kannst. Das heißt, dein Prozess redet via libc mit dem VFS, das VFS kriegt mit, dass der Plattentreiber gemeint ist und verbindet diese Deskriptoren quasi-miteinander, so dass die Kommunikation nicht mehr über das VFS läuft. Wenn der Plattentreiber nicht mehr kann oder will (z.B. nicht im HD-Treiber implementierte Funktion wie "cd"), gibt er den Deskriptor zurück an das VFS, was dann damit machen kann, was es will.
Ich hab zwar keine Ahnung was du da genau meinst aber das Ende eines Kommunikationskanals (Pipe o.ä.) durch die Gegend zu reichen stelle ich mir nicht sonderlich geschickt vor.
Kann dein Kernel das, ist die libc einfacher. Andersrum gilt entsprechendes.
Exact, kann meine libc das ist der Kernel einfacher und da ich nicht "den Kernel" habe ist es wohl eindeutig welcher Weg der bessere ist.
Wenn man kein durchreichen der Daten will dann benötigt man eine zentrale Stelle im System, da bietet sich zwar der VFS an aber IMHO ist das nicht seine Aufgabe, man würde ihm eigentlich fremde Arbeit aufbürden nur weil man versucht alles auf Dateien abzubilden.
Richtig.
Danke.
Wenn du aber dem VFS die Möglichkeit gibst, allgemeingültig "alles" auf Dateien abzubilden, dann sparst du dir in anderen Layern sehr viel Arbeit, wenn du dieses Konzept gründlich umsetzt.
Dafür habe ich diese Arbeit im VFS und der hat dann bei einem Micro-Kernel-OS das Problem für Nicht-Datei-Dienste alles weiter zu leiten. Ich versuche lieber jedes Ding möglichst simpel zu halten, der VFS kann nur Dateien, der TCP-Service nur TCP, der UDP-Service nur UDP, der RS232-Treiber stellt nur Streams bereit und die libc ist nur ein Verteiler der mit ein paar IPC-Stubs zusammen gelinkt wird.
Dann hast du gerade kein XML verwendet, sondern eine XML-ähnliche, eigene Markup-Language. Guck mal in die XML-Spec, was du für XML alles können musst!
Wieso soll das kein XML gewesen sein? (und selbst wenn nicht ist mir das auch egal) Wenn ich mir die Mühe gemacht hätte eine DTD zu schreiben hätte ich die Config-Dateien sicher auch validieren können. Ich habe nur meinen Code auf genau das beschränkt was ich auch tatsächlich benötigte (wimre 6 oder 7 Elemente mit jeweils 0 bis 2 Attributen und eben deren Inhalt).
Grüße
Erik
-
Ich habe mir mal den MINIX-Source und Windows-Aufrufe angeguckt und es läuft darauf hinaus, das der VFS-Service einen neuen Prozess startet.
Man richtet also die Anfrage, einen neuen Prozess zu starten, an den VFS-Service. Dieser kann dann (wenn nötig/gewollt) irgendwelche Handles an den Kindprozess weitervererben.
Soweit ich das bei Windows verstanden habe, würde eine Shell ein Konsolenfenster öffnen. Wenn die Shell dann ein Programm ausführt, erstellt es Pipes für Stdin/Stderr/Stdout und "vererbt" diese an den Kindprozess. Sprich wenn der Kindprozess wieder einen Prozess startet würde er auch wieder Pipes erstellen und diese "weitervererben". So entsteht unter Umständen ne tierisch lange Kette und es muss verdammt viel kopiert werden.
Was mir daran ein wenig komisch vorkommt, ist dass das auch bedeutet das jeder Prozess läuft und aus der vererbten Pipe liest und die Daten an seine Stdout Pipe weiterreicht. Das finde ich dann doch ein wenig umständlich/kompliziert.
Ich bin mir immernoch nicht sicher wie ich es denn nun umsetze. Allerdings scheinen wirklich alle OSs nur einen Handle für das Dateihandling zu nutzen und der Rest (Filepointer usw.) liegt beim VFS-Service.
Das heißt also das meine Idee so nicht klappen wird. Ich wollte es eigentlich so machen, das Dateien immer in 4K Blöcken in den Prozess gemappt werden und fread dann z.B. daraus in den Client-Buffer schreiben würde. Auch würde dann der VFS-Service sich nie um den Filepointer und solche Geschichten kümmern müssen und er hätte einfach immer nur irgendwelche Pages durch die Gegend geschoben.
Nur ist es bei dieser Idee schwierig das mit dem Vererben umzusetzen. Denn der Filepointer wäre ja beim Clienten.
Da stellt sich mir halt wieder die Frage, wo (außer bei Stdout/Stdin/Stderr) wird das Vererben angewendet? Denn wenn das wirklich nicht häufig und nur für spezielle Sachen verwendet wird, dann könnte man das auch anders lösen.
-
Dort, wo es dem Entwickler praktisch vorgekommen ist. Ich könnte mir vorstellen, dass bei fork ohne exec noch öfter in interessanten Fällen (also nicht stdio) darauf zurückgegriffen wird. Anders lösen kann man vieles, aber meistens bleibt dann die POSIX-Kompatibilität auf der Strecke, die du ja haben wolltest, wenn ich mich nicht täusche.
Damit du POSIX vollständig implementieren kannst, brauchst du übrigens auch noch eine Methode, zwischen laufenden Prozessen Dateideskriptoren zu verschicken.
-
Hallo,
Dort, wo es dem Entwickler praktisch vorgekommen ist.
Ich will ja nicht den Querulanten spielen aber dafür hätte ich schon gerne mal ein Beispiel.
Es fällt mir ziemlich schwer dafür eine sinnvolle Anwendungsmöglichkeit zu finden. Auf der selben Datei aus mehreren Threads (fork ohne exec ist ja so was ähnliches wie Threads, zumindest für die Datei-Handles) zu arbeiten wird auf jeden Fall zu Problemen führen. Was ist wenn einer ein seek und ein anderer ein write zur selben Zeit von verschiedenen CPUs aus aufrufen, da müsste auf jeden Fall synchronisiert werden aber für den Programmierer bleibt das Risiko das er die Reihenfolge dieser Vorgänge nicht vorhersagen kann und genau deswegen bin ich der Meinung das es für richtiges File-Descriptor-Sharing keine echte Anwendungsmöglichkeit gibt.
Anders sieht die Sache z.B. bei Log-Dateien aus, wo mehrere Threads oder Prozesse (wo möglicherweise jeder ein eigenes open gemacht hat, ganz ohne Vererbung) immer nur was hinten anhängen (also in Endeffekt das selbe wie bei stdout/stderr). Dabei spielt die Reihenfolge keine wichtige Rolle und das würde mit meinem Konzept auch wunderbar klappen (die einzelnen Prozesse müssten die Datei in einem append_only-Mode öffnen und dann müsste auch nichts geshared werden, es gibt dann in den lokalen Descriptoren keinen File-Pointer). Wenn die Reihenfolge doch eine Rolle spielt dann muss der Programmierer eh einen passenden Mechanismus selber vorsehen, genau das kann das POSIX-File-Descriptor-Sharing nicht gewährleisten.
Damit du POSIX vollständig implementieren kannst, brauchst du übrigens auch noch eine Methode, zwischen laufenden Prozessen Dateideskriptoren zu verschicken.
Da ist wieder die Frage was damit genau erreicht werden soll. Geht es um ne Art Pipe (also nur append bei Log-Dateien/stdout) dann sehe ich darin kein Problem. Sollen wirklich echte Dateien zum parallelen Zugriff benutzt werden müssten auch wieder die obigen Probleme auftreten.
Also ich bleibe dabei das mein VFS nur echte Dateien anbieten soll und das Sharing von Streams usw. in der libc gemacht wird. Für Log-Dateien soll mein VFS aber die Möglichkeit haben das mehrere Prozesse ein open auf eine Datei absetzen wenn das Flag append_only gesetzt ist, dann würde der VFS eben die append-Aufrufe der einzelnen Prozesse serialisieren (natürlich ohne auf eine bestimmte Reihenfolge zu achten). Für die Dateien soll mein VFS also 2 Möglichkeiten anbieten: einmal ein normales open (mit vollwertigen Zugriff) bei dem der File-Pointer in der libc liegt und die Datei eben nur von einem Prozess geöffnet werden kann oder ein open mit appen_only wo der Fie-Pointer (== File-Size) im VFS liegt (und nicht in der libc) und beliebig viele Prozesse die Datei öffnen dürfen (alle nur mit append_only). Zusätzliches open mit read_only soll aber immer gehen wobei dann keine File-Pointer geshared werden (wenn z.B. mehrere Compiler-Instanzen die selbe Header-Datei einlesen wollen). Echtes Vererben von richtigen Dateien möchte ich nicht unterstützen, ich glaube auch nicht das mir das zum Nachteil wird.
Grüße
Erik
-
Also ich stimme erik zu, solange wie kein "vernünftiges" Bsp. genannt wird sehe ich auch keinen Sinn darin einen Diskriptor zu vererben, halt wegen der genannten Probleme.
-
Mir fallen da Server ein, die nach einem accept() forken, wobei dann das neue Socket im neuen Prozess weiterverwendet wird, während der ursprüngliche Prozess wieder accept() aufruft.
-
Hallo,
Mir fallen da Server ein, die nach einem accept() forken ...
Das ist zwar richtig hat aber nichts mit unserem Thema zu tun. Wir diskutieren gerade über das sharing von richtigen Dateien. Bitte erkläre genauer was Du meinst.
Grüße
Erik
-
Das ist zwar richtig hat aber nichts mit unserem Thema zu tun.
Weil Sockets was anderes als Dateien sind und insbesondere nicht vom "VFS-Dienst" verwaltet werden?
-
Weil Sockets was anderes als Dateien sind und insbesondere nicht vom "VFS-Dienst" verwaltet werden?
Auch ;)
Aber wenn ich dein Bsp. richtig verstehe (wenn nicht gibt es die selben Probleme wie bei der gleichzeitigen Nutzung von einer Datei) dann wird der Socket ja "nur" weitergegeben, sprich der "Erstbesitzer" greift nicht mehr darauf zu? Dann gibt es auch keine Probleme.
Wir reden aber davon, das mehrere Threads/Prozesse durch ein und den selben Diskriptor auf eine Datei zugreifen und das glaube ich kaum das es wirklich verwendet wird.
Edit::
@erik
Ich glaube so langsam, das "unser" VFS-Service doch einige Sachen übernehmen muss, außer reines Dateihandling. Denn wie hast du es dir vorgestellt das Umleiten von Stdin/Stdout zu bewerkstelligen, wenn du es nicht auf Dateien abbildest?
@all
Etwas Offtopic, aber passt irgendwie doch hierher.
Wie funktioniert euer printf. Genauer bin ich daran interessiert ob ihr die Daten erst in einen "kleinen" (die Größe wäre auch interessant) Buffer schreibt und am Ende von printf einen flush() macht oder ob ihr jedes Zeichen einzeln sendet (was ich eher nicht glaube).
Ich frage das, weil ich auch schon von Problemen gehört habe, das unter Umständen die Ausgabe eines Programms erst sichtbar wird, wenn dieses beendet wird, weil erst dann ein flush() durchgeführt wird, aber jedes Zeichen einzeln zu senden ist ja auch quatsch.
-
stdio.h ist grundsätzlich gepuffert. Lies dir am besten mal die Doku zu setvbuf durch.
-
stdio.h ist grundsätzlich gepuffert. Lies dir am besten mal die Doku zu setvbuf durch.
Die Frage ist dann ja, wann wird der Buffer geflusht? Wird es zu oft gemacht, ist es nicht gut für die Performance, wird es zu wenig gemacht, kommen eventuell Meldungen auf der Konsole "zu spät" an.
Da sehe ich dann die ersten Nachteile, wenn man alles auf Dateien abbildet. Denn ich würde sagen, wenn man in Dateien schreibt, kann der Buffer ruhig etwas größer sein und sollte so wenig wie möglich geflusht werden, aber was Meldungen auf der Konsole betreffen, möchte ich die so schnell wie möglich sehen, werden die aber in eine Datei umgeleitet ist wieder ein großer Buffer besser.
-
Mal davon abgesehen, dass ich mir nicht sicher bin, dass das immer das ist was man will: So funktioniert C eben nicht.
Standardmäßig wird stdout am Zeilenende geflusht und stderr ist komplett ungepuffert. Das halte ich auch grundsätzlich für eine vernünftige Voreinstellung, wenn man nicht irgendwelche speziellen Sachen macht. Wenn ich dasselbe Programm einmal in eine Datei umleite und einmal auf der Konsole anzeigen lasse, dann will ich auf jeden Fall in beiden Fällen dasselbe Ergebnis sehen. Auch wenn das Programm nach irgendeiner Ausgabe, die möglicherweise gepuffert ist, abstürzt.
-
Hallo,
Ich glaube so langsam, das "unser" VFS-Service doch einige Sachen übernehmen muss, außer reines Dateihandling. Denn wie hast du es dir vorgestellt das Umleiten von Stdin/Stdout zu bewerkstelligen, wenn du es nicht auf Dateien abbildest?
Also stdin/stdout/stderr sind für mich Streams und die sollen bei mir von libc zu libc direkt gehen. Wenn die Shell ein Programm startet und diesem ihre Streams (die die Shell ja auch von ihrem Aufrufer bekommen hat) mitgibt dann kommuniziert der neue Prozess direkt mit dem original-Anbieter der Streams, zumindest bei den Output-Streams stdout/stderr ist das IMHO kein Problem solange immer ordentlich geflusht wird. Oder seht ihr da doch ein Problem?
Wenn in eine Datei gestreamt werden soll dann soll von libc zu libc nicht ein Handle o.ä. sondern ein absoluter Datei-Pfad weitergegeben werden und die libc im Kind-Prozess macht ein eigenes open mit append_only, das flushen muss hier natürlich auch passend gemacht werden.
Bei den Input-Streams ist das ein kleines bisschen komplizierter, ich habe mir das so vorgestellt das bei Streams immer die Daten-Quelle aktiv die Daten zum Ziel sendet (also per IPC weitergibt) und nicht das Ziel ständig bei der Quelle nachfragt (getch() usw. wären dann blockierende Funktionen in der libc und der blockierte Thread würde vom IPC-Stub aufgeweckt werden wenn Daten kommen). In meinem Konzept würde das bedeuten das wenn aus einer Datei gestreamt wird und der Kind-Prozess mit den ersten x kBytes angetriggert wurde dieser aber nur die ersten 50 Bytes entnommen hat und dann seinerseits ein Kind startet dann muss er antworten "ich habe nur 50 Bytes entnommen und jetzt schicke erst mal bis auf weiteres an XYZ". Daraufhin betrachtet die Quelle die ersten 50 Bytes als entnommen (der Lese-Pointer wird weiter gesetzt) und probiert es wieder mit x kBytes (ab Offset 50) beim neuen Abnehmer (dank meinem IPC geht das alles ohne das irgendwas kopiert werden musste). Wenn ein Kind beendet wird dann muss es der Quelle sagen das es nichts mehr haben will und die Quelle probiert es dann wieder beim vorherigem Ziel (LIFO). Wenn die Shell ein Kind startet und dessen stdin aus einer Datei kommen soll dann ist eben die Shell die Quelle, in der libc muss dafür ein Hintergrund-Thread gestartet werden der die aktive Quelle spielt (also aus Datei lesen und an Kind senden, auch hier muss nichts kopiert werden).
Wie ist das eigentlich wenn man stdin weiter vererbt, darf man dann trotzdem getch() machen? Eigentlich käme da ja unvorhersehbarer Quatsch raus. Ich bin daher der Meinung dass das nicht funktionieren muss. Was sagt POSIX dazu?
Ich denke mit diesem Weg schaffe ich eine hohe Performance weil keine langen Ketten zum durch reichen da sind, alles mit Zero-Copy läuft und habe trotzdem die Sicherheit das nichts verloren geht. Ich sehe auch kein Problem das in der libc zu verstecken.
Das printf wenigstens am Ende flusht finde ich auch gut, ich denke bei stdout und erst recht bei stderr geht Sicherheit vor Performance. Wer große Datenmengen schnell los werden will wird sich auch was passendes einfallen lassen.
@taljeth: Du bist uns immer noch ein Beispiel für echtes Datei-Descriptor-Sharing schuldig.
Grüße
Erik
-
Du akzeptierst also weder stdin/stdout/stderr, noch Sockets, noch FIFOs (mknod p) als valide Beispiele für Dateideskriptorsharing mit der Begründung, es handle sich ja nicht um "richtige" Dateien.
Gut, dann implementiert das halt alles nicht über das VFS und macht für jeden Teil ne eigene API auf. Kann nur gut zum Portieren vorhandener Software sein...
Mehr oder weniger sinnvolle Beispiele findet ihr bestimmt, wenn ihr Realwelt-Software benutzt, die das nutzt. Mir fiele noch die Kommunikation mit Plugins ein, aber solcherart "Dateien" werden dann ja sinnvollerweise wieder über Sockets verwendet...
-
Hallo,
Du akzeptierst also weder stdin/stdout/stderr, noch Sockets, noch FIFOs (mknod p) als valide Beispiele für Dateideskriptorsharing mit der Begründung, es handle sich ja nicht um "richtige" Dateien.
Ganz genau! Es geht um den File-Pointer der von mehreren Prozessen gleichzeitig benutzt werden soll und eben den gibt es bei Streams, Sockets, FIFOs usw. nicht, zumindest nicht in der Form wie bei echten Dateien.
Gut, dann implementiert das halt alles nicht über das VFS und macht für jeden Teil ne eigene API auf. Kann nur gut zum Portieren vorhandener Software sein...
Also ich sehe nur Dateien und Streams (TCP-Socket und Pipes/FIFOs sind IMHO welche), UDP-Sockets sind noch was eigenes da Paket-orientiert. Das hinter der libc-API zu verbergen stelle ich mir nicht so schwer vor (womit dann auch wieder das Portieren möglich ist), wenn du da Probleme siehst dann schreib die doch mal hier rein.
Mehr oder weniger sinnvolle Beispiele findet ihr bestimmt, wenn ihr Realwelt-Software benutzt, die das nutzt.
Also das es ein sinnvolles Beispiel für das gemeinsame benutzen echter Dateien aus mehreren Prozessen gibt bezweifle ich immer noch, die zu erwartenden Probleme hab ich doch gestern Nachmittag erläutert. Alles andere lässt sich IMHO auf Streams abbilden.
Grüße
Erik
-
Ich möchte mich nicht festlegen, wie sinnvoll das Beispiel sein wird, aber ich bin mir relativ sicher, dass du irgendwann beim Portieren irgendeiner Software die Hände über dem Kopf zusammenschlagen wirst, weil genau das benutzt wird. ;)
-
Sollen generell zwei Prozesse nicht die selbe Datei zum beliebigen Schreiben öffnen dürfen?
-
Naja, dafür findet man sicher viele Beispiele. Soweit ich das verstehe, geht es hier immer noch darum, dass zwei Prozesse nicht nur auf dieselbe Datei, sondern auch noch auf dieselbe Open File Description zugreifen, also denselben Dateizeiger haben. Da wird die Luft schon dünn, wenn man alles rausstreicht, was sequenziell ist...
-
Hallo,
Ich möchte mich nicht festlegen, wie sinnvoll das Beispiel sein wird, aber ich bin mir relativ sicher, dass du irgendwann beim Portieren irgendeiner Software die Hände über dem Kopf zusammenschlagen wirst, weil genau das benutzt wird. ;)
Entschuldige, aber das ist leeres geschreibsel. Wenn Du kein Beispiel kennst dann schreib das einfach.
Zur Not kannst Du Dir auch gerne ein fiktives Szenario ausdenken aber dann musst Du auch erklären wie die Probleme, die ich gestern Nachmittag erläutert habe, zu vermeiden sind.
Sollen generell zwei Prozesse nicht die selbe Datei zum beliebigen Schreiben öffnen dürfen?
Nicht zum beliebigen beschreiben mit einem gemeinsamen Datei-Pointer. Dafür kann ich mir wirklich keine sinnvolle Anwendung vorstellen (die Prozesse müssten sich einigen wer was wann macht sonst ist das Ergebnis in der Datei unvorhersehbar). Paralleles Schreiben in die selbe Datei aber mit unabhängigen Datei-Pointern kann ich mir zumindest theoretisch vorstellen aber unterstützen möchte ich das frühestens dann wenn ich das wirklich brauche (und das sehe ich noch nicht). Das mehrere Prozesse an eine Datei was anhängen (z.B. Log-Datei) ist ein sicher alltägliches Szenario das ich auch auf jeden Fall unterstützen möchte.
Grüße
Erik
-
Hallo,
Soweit ich das verstehe, geht es hier immer noch darum, dass zwei Prozesse nicht nur auf dieselbe Datei, sondern auch noch auf dieselbe Open File Description zugreifen, also denselben Dateizeiger haben. Da wird die Luft schon dünn, wenn man alles rausstreicht, was sequenziell ist...
Da dürfte die Luft noch weniger als nur dünn werden.
Wenn wir uns darauf einigen können das man den Datei-Pointer nicht in mehreren Prozessen gleichzeitig braucht so das dieser in der libc bleiben kann dann dürfte das Vererben in einem Micro-Kernel-OS schon deutlich einfacher werden.
Grüße
Erik
-
Entschuldige, aber das ist leeres geschreibsel. Wenn Du kein Beispiel kennst dann schreib das einfach.
Zur Not kannst Du Dir auch gerne ein fiktives Szenario ausdenken aber dann musst Du auch erklären wie die Probleme, die ich gestern Nachmittag erläutert habe, zu vermeiden sind.
Nachdem dir alle meine Beispiele nicht gut genug waren, kenne ich wohl keins mehr. Es wird schwer, noch ein sinnvolles Ergebnis zu produzieren, wenn man tatsächlich Seeks drin hat. Aber wie gesagt, ich würde mich wundern, wenn es nicht trotzdem irgendein Stück Software gäbe, das sowas benutzt. Ich persönlich bin nur noch nicht drübergestolpert.
-
Hallo,
Nachdem dir alle meine Beispiele nicht gut genug waren, kenne ich wohl keins mehr.
Deine Beispiele waren schon gut und wichtig, aber es waren eben keine Beispiele für echtes Datei-Sharing mit gemeinsamen Datei-Pointer für parallelen Zugriff und alles andere lässt sich über Streams leichter realisieren.
Es wird schwer, noch ein sinnvolles Ergebnis zu produzieren, wenn man tatsächlich Seeks drin hat.
Das schreibe ich doch nun schon seit mehreren Seiten in diesem Thread.
Aber wie gesagt, ich würde mich wundern, wenn es nicht trotzdem irgendein Stück Software gäbe, das sowas benutzt. Ich persönlich bin nur noch nicht drübergestolpert.
Klar, kein Blödsinn ist so blöd das den nicht doch mal irgendjemand macht. ;)
Im Ernst, logisch bist Du noch nicht über sowas gestolpert. Ich hab auch nicht wirklich damit gerechnet das Du da ein Beispiel nennen kannst. Ich wollte einfach nur das wir fürs Protokoll festhalten das man echte Dateien zwar vererben kann aber das die trotzdem immer nur von einem Prozess aktiv benutzt werden, genau das macht es aber möglich die Datei in der libc zu managen und den VFS simpel zu halten.
Du hast geschrieben das tyndur überhaupt gar keine Vererbung kann, dafür habt ihr aber schon ne Menge an POSIX-Programmen portiert. Also entweder wird das nicht so häufig benutzt wie Du uns weiß machen möchtest oder es liefert nicht die Basis-Funktionalität oder es ist nicht so schwer den Quell-Code von solcherlei gePOSIXe zu befreien.
Grüße
Erik
-
Zumindest bei Input-Streams sehe ich nicht, wie du noch sehr weit von der Behandlung allgemeiner Dateien entfernt wärst. Du hast dann ja schon eine gemeinsame Position. Das einzige, was du verbietest, sind Seeks, aber einen Vorteil im Sinn leichterer Implementierbarkeit bringt dir das nicht mehr. Und wenn du es für Input hast, ist der Sprung zu Output auch nicht mehr groß.
tyndur kann keine POSIX-Programme, die andere Prozesse starten (mangels fork), insofern wollen diese Programme natürlich auch keine Deskriptoren vererben. ;) Die einfachen Fälle kann man durch die nativen Funktionen ersetzen, aber wenn ich mich grad nicht täusche, hängt beispielsweise der Port von git an irgendsowas fest, weil er auch noch irgendwie mit dem Kindprozess kommunizieren will.
-
Hallo,
Zumindest bei Input-Streams sehe ich nicht, wie du noch sehr weit von der Behandlung allgemeiner Dateien entfernt wärst.
Ließt Du meine Beiträge auch oder immer nur den letzten? Vor fast genau 3 Stunden habe ich beschrieben wie ich mir das für Streams vorstelle, ich kann da keine Gemeinsamkeiten mit Dateien entdecken.
tyndur kann keine POSIX-Programme ....
Ich empfinde es trotzdem als respektable Leistung wie weit ihr gekommen seit.
Grüße
Erik
-
Und ich kann keine Unterschiede zu Dateien entdecken, außer eben dem fehlenden Seek. Statt dem Inhalt, der gerade in der Pipe ist (oder was auch immer du als Stream anerkennst), würde bei einer Datei die Quelle eben die komplette Datei bis zum EOF anbieten. Im Zweifelsfall kann die Datei während der Laufzeit auch wachsen, genau wie die Pipe auch. Wo genau soll denn jetzt der Unterschied sein, außer dass ich in der Pipe nicht seeken kann?
-
Ich habe mal ein wenig im INet gesucht und so wie ich das mit fork() und dem Vererben von z.B. Sockets bei nem Webserver verstanden habe funktioniert das so, dass der Parent sein Socket nach dem fork() schließt und dann nur noch das Kind alleine den Socket nutzt.
Wenn das bei annähernd 100% aller Fälle so ist (was ich nicht glaube) dann ist das Problem doch praktisch nicht vorhanden?!
Ich will darauf hinaus, das selbst bei anderen Sachen (wie z.B. Sockets) nie der Fall eintritt, dass mehrere Prozesse gleichzeitig (mit ein und dem selben Dateidiskriptor/-pointer) lesend zugreifen.
Wenn ich mich nicht irre, dann sind ja C-Library und POSIX zwei unterschiedliche Sachen oder?
Edit::
Nachdem was ich zu fork() und dup() gefunden habe, ist fork() der einzige Fall wo wirklich der Dateipointer auch geshared wird (und es wird auch gesagt, das gleichzeitiger Zugriff nicht sequentiell ist) und bei dup() wird zwar die Position übernommen, aber es ist ein eigenständiger Dateidiskriptor, sprich es ist ein "fork" (eine Momentaufnahme) des Dateidiskriptors.
-
Richtig, dein Socketbeispiel wird in der Regel so implementiert. Das geht allerdings nur solange gut, wie das Kind sich auch vollständig um das Socket kümmern kann.
Bei Sachen wie CGI/FastCGI mit Gefolge wie AJAX stelle ich mir das schon etwas schwierig vor, wenn die Kinder voneinander abhängen. Sie haben nämlich prinzipiell die gleichen Eltern, aber kennen sich nicht. In dem Fall kann ich es mir schon vorstellen, dass ein Kindprozess sagt "ich will nicht mehr", das Socket schließt und an den Elternprozess "zurückgibt". Letzterer kann das Socket ja auch solange offenlassen, bis das betreffende Kind gestorben ist...
POSIX ist ein Standard, der (u.a.) ein Interface beschreibt. Implementierst du dieses Interface in deiner libc, dann stellt deine libc eine POSIX-Implementation bereit. Andernfalls stellt deine libc eine native API (oder sogar ABI) bereit, und deine POSIX-Implementation liegt in einer libposix, die als Wrapper eingesetzt wird. Dann musst du zwei APIs pflegen.
Das eine ist ne Implementation, das andere nur eine Schnittstellenbeschreibung.
Gruß,
Svenska
-
POSIX ist ein Standard, der (u.a.) ein Interface beschreibt. Implementierst du dieses Interface in deiner libc, dann stellt deine libc eine POSIX-Implementation bereit. Andernfalls stellt deine libc eine native API (oder sogar ABI) bereit, und deine POSIX-Implementation liegt in einer libposix, die als Wrapper eingesetzt wird. Dann musst du zwei APIs pflegen.
Für mich ist beides ein Standard der bestimmte Sachen vorgibt (z.B. für C printf() und für POSIX ein fork()).
Bei Sachen wie CGI/FastCGI mit Gefolge wie AJAX stelle ich mir das schon etwas schwierig vor, wenn die Kinder voneinander abhängen. Sie haben nämlich prinzipiell die gleichen Eltern, aber kennen sich nicht. In dem Fall kann ich es mir schon vorstellen, dass ein Kindprozess sagt "ich will nicht mehr", das Socket schließt und an den Elternprozess "zurückgibt". Letzterer kann das Socket ja auch solange offenlassen, bis das betreffende Kind gestorben ist...
Also entweder nutzt der Elternprozess die "Datei" nicht während das Kind läuft oder es schließt sie sobald geforkt wurde. Wie soll das "zurückgeben" funktionieren?
Ich hatte die verrückte Idee (nur mal als Grobkonzept) fork() als threadCreate() und exec() als processCreate() umzusetzen.
Bei exec() sehe ich die wenigsten Probleme, da ich einfach bei meiner processCreate() eine Möglichkeit vorsehen kann, das man Dateidiskriptoren an den neuen Process weitergibt und dann wird einfach der laufende Thread beendet.
Bei fork() ist es etwas schwieriger. Erstmal müsste man für alle Dateidiskriptoren einen RefCount haben (wenn der ElternThread die Datei schließt, das der KindThread noch damit arbeiten kann) und ansonsten müsste die Implementation der C-Library ThreadSafe sein.
Welche Probleme würden euch sonst noch einfallen (was fork() als neuen Thread betrifft)?
-
Globale Variablen werden von allen Threads geteilt, während sie nach einem fork() maximal COW sind. Wenn ein Prozess schreibt, ändert sich also für den anderen nichts. Insofern ist threadCreate() nicht wirklich eine geeignete Implementierung für fork().
-
Hallo,
Und ich kann keine Unterschiede zu Dateien entdecken, außer eben dem fehlenden Seek. ....
Es gibt bei Dateien den wahlfreien Zugriff auf beliebige Bytes, eben das seek, und bei Streams/Pipes u.ä. gibt es das nicht. Dort bekommt das Ziel einfach die Daten der Reihe nach zugestellt und kann die entweder nehmen (read) oder ignorieren (skip) aber das Ziel hat keine Möglichkeit zu bestimmen welche Daten es wann gerne hätte. Mag sein das man das von der API her auf Dateien abbilden kann (so wie es auf der libc-API-Ebene ja auch sein soll) aber ich möchte Streams/Pipes u.ä. intern anders implementieren als Dateien, so wie ich das beschrieben hatte, und damit wären die Gemeinsamkeiten dann vorbei. Außerdem bleibt damit das VFS von solchen Sonderformen befreit, was einer meiner Hauptgründe ist.
Klar kann man alles als eins betrachten aber genau das will ich nicht, ich möchte 2 verschiedene Wege und jeden für seine Aufgabe passend optimieren und ich behaupte das ich damit in Summe sogar weniger Code haben werde weil beide Wege recht simpel bleiben können. Zusätzlich möchte ich mir gerne den Performancevorsprung durch passende Technik sichern.
Grüße
Erik
-
OT: "Vorsprung durch Technik" ;-)