Ich vermute mal die freigebenen IDs werden wieder recycled, ansonsten wären echte Langläufer wirklich kaum möglich.
Das ist halt nicht möglich, so wie ich den Standard verstanden habe. Das eine ID nicht gleichzeitig für 2 Prozesse/Threads verwendet wird, ist ja wohl logisch!
Genau da irrst Du, das ist eben der Punkt der mit NCQ geändert wurde. Siehe dazu http://de.wikipedia.org/wiki/NCQ. Damit kann man einer aktuellen SATA-HDD bis zu 32 Jobs geben die dann von der HDD in beliebiger Reihenfolge abgearbeitet werden dürfen und wenn ein paar der Jobs fertig sind darf man bereits neue Jobs nach schieben. Es dürften schon ein paar Szenarien existieren wo das tatsächlich etwas zusätzliche Performance bringt. Mit meinem PopUp-Thread-Konzept dürfte sich NCQ fast ohne Mehraufwand (im STAT-Treiber) nutzen lassen, ich muss nur darauf achten nicht mehr als 32 Jobs gleichzeitig an die HDD zu geben, ansonsten sehe ich keine konzeptionellen Beschränkungen.
Meine Aussage bleibt trotzdem richtig
Denn die Festplatte kann nur eine Anfrage zur gleichen Zeit entgegen nehmen, denn du hast ja nicht 32x Ports um die Festplatte anzusprechen!
Bei dem Fall würde es sich aber wirklich lohnen, wenn man 32 Threads hätte. Dann könnte jeder Thread warten bis neue Arbeit da ist und er könnte auch warten bis die Festplatte fertig ist (ich hoffe du verstehst wie ich das meine).
Das klingt nach nem dicken Haufen Arbeit wenn für jeden Kram erst ein Haufen verschiedener Kommunikationskanäle benutzt werden müssen, das müsstest Du mal etwas konkreter an einem Beispiel erklären. Wenn Du dann für das Leseende einer Pipe auch wieder einen Thread brauchst, der auf ankommende Daten wartet, dann werden es immer mehr Threads ohne das tatsächlich mehr gearbeitet wird.
Ich sehe es erstmal so, das die Initialisierung ruhig ein wenig komplexer/langsamer sein darf, wenn der Vorgang an sich dann schneller geht.
Ist doch ganz einfach
Gehen wir mal davon aus, das du weißt in welche Pipe du schreiben sollst und aus welcher du lesen sollst.
Dann holst du dir die Anfrage mit "pipeRead(&msg,sizeof(struct msg_t))" (lohnt sich vorallem bei größeren Msgs, wie z.B. wenn man eine Datei laden soll), arbeitest die Msg ab und schreibst deine Daten dann per pipeWrite(&data,dateLen)". Abschließend musst du dann noch ein "pipeFlush()" aufrufen, damit der Thread der auf die Daten wartet sie lesen kann.
Der Vorteil ist, das diese Pipe´s fast komplett im UserSpace laufen, du also nicht erst die Daten in den Kernel kopierst (Server) um sie dann wieder in den UserSpace zu kopieren (Client).
Mal vom "pipeFlush()" abgesehen, ist das doch nicht viel anders als ein "msgRecv(&msg)" und ein "msgSend(&data,dataLen)".
Also für mich ist eine Antwort auf eine Anfrage keine eigenständige Nachricht sondern nur eine weitere Phase eines synchronen IPC-Vorgangs. In meinem Kernel will ich auch konkret zwischen synchroner und asynchroner IPC unterscheiden und beides anbieten (wobei die asynchrone Variante wohl nur eine vereinfachte/abgekürzte Form der synchronen Variante ist, also nur wenig zusätzlicher Code erforderlich ist um beides zu unterstützen). Das meiste was in einem Mikro-Kernel-OS gemacht wird ist wohl synchrone IPC, eben RPC, nur wenige Dinge, wie z.B. Signale oder IRQs, müssen als asynchrones IPC behandelt werden.
Genau deswegen überlege ich auch ob nicht rein synchrones IPC doch besser ist (siehe L4), aber meine Momentane Lösung ist auch nicht so schlecht.
Obwohl wenn ich gerade mal genauer drüber nachdenke, dann muss ich sagen, dass ich es mir so wie ich es habe einfacher vorstelle.
Folgendes Szenario, ein Client sendet eine Nachricht und der Server kann sie gleich entgegen nehmen. Also kehrt der Client gleich wieder zurück ohne zu blockiert, dann wartet er auf eine Antwort, blockiert also. Aber in der Zwischenzeit sendet, aus welchem Grund auch immer, ein anderer Thread eine Nachricht an den Clienten und dieser wird aufgeweckt und bekommt nun eine Nachricht die er gar nicht haben will (im Moment) bzw. nicht erwartet.
Also müsstest du dort genauso etwas implementieren, das ein Thread auf eine Nachricht von einem bestimmten anderen Thread warten kann und du musst Queues implementieren wo du die Threads reinpacken kannst, falls sie nicht der gewünschte Thread sind.
Da wären wir ja aber praktisch schon wieder beim asynchronen IPC, nur das du halt anstatt Msgs die Threads in die Queue packst.
Wenn ich das von der Seite betrachte, ist asynchrones IPC mit aufgesetztem synchronen (das Warten auf eine Nachricht eines bestimmten Threads) doch besser. Da du dir die Möglichkeit der Asynchronität offen hälst.
Also gerade das weiterleiten der Nutzdaten stell ich mir damit nicht so einfach vor, ich schätze da wirst Du in jeder Ebene kopieren müssen. Oder willst du es ermöglichen das 2 Pipes miteinander verbunden werden?
Ich habe das alles noch nicht 100%ig zu Ende gedacht und wenn der Code dann endlich mal geschrieben wird, wird es auch bestimmt noch verbessert. Aber ansich ist das erstmal nur für den Weg Server->Client gedacht, wo die Daten eine dynamische Länge haben.
Wenn der Festplattentreiber Daten liest und diese an den Storage-Server weitergibt, dann würde ich das natürlich über SharedMem machen, viel einfacher und schneller. Denn da bekommst du ja (meistens) eh Blöcke die sich auf 4kb Pages aufteilen lassen und es muss nicht mehr kopiert werden.
Der Client will dann ja vielleicht nur ein paar Bytes haben und die kopierst du dann per Pipe. Das Einblenden (MemoryMapping) von Dateien wird dann ja eh wieder über SharedMem gemacht.
Du sollst ja auch nur einen try/catch Block benutzen. In den catch-Abschnit kommt das Aufräumen und in den try-Abschnitt kannst Du den Code packen der möglicherweise Fehler generiert, wenn dort ein Fehler auftritt (also eine Exception geworfen wird) wird sofort in den nächsten catch-Abschnitt gesprungen der sich für diese Exception zuständig fühlt (das throw kannst Du wie ein goto betrachten und das sogar über Funktionsebenen hinweg).
Genau dort liegt jetzt wieder das Problem (und der redundante Code). Stell dir vor du musst mehrmals Speicher anfordern, dann wird jedes Mal eine "NotEnoughMemoryException" geschmissen, aber da das Anfordern an verschieden Stellen stattfindet, musst du unterschiedlich Aufräumen, aber desto weiter du in einer Funktion bist, desto mehr musst du "gleich" machen!
Bsp:
try {
mem= memAlloc4kb();
} catch (NotEnoughMemoryException e) {
return 0;
}
try {
id= allocNewID();
} catch (NoFreeIDException e) {
memFree4kb(mem);
return 0;
}
try {
stack= memAlloc4kb();
} catch (NotEnoughMemoryException e) {
memFree4kb(mem);
freeID(id);
return 0;
}
... //das Spiel kannst du jetzt weiter treiben, indem noch mehr Ressourcen benötigt werden
Ich weiß das man den 1. und 2. try-Block zusammenfassen könnte, aber mir ist gerade die Syntax nicht eingefallen. Aber den 3. try-Block musst du doch vom 1. trennen, da du ja ansonsten nicht weißt welche Anforderung nicht geklappt hat und welchen Speicher du wieder freigeben musst.
Ich hoffe du siehst was ich meine.
Also zwischen einer Prozess-Sterbe-Nachricht und einer Veränderungsmitteilung ist IMHO schon ein gewisser Unterschied. Für die Veränderungsmitteilungen bei den Dateien wirst Du wohl einen zusätzlichen (aber ähnlichen) Mechanismus implementieren müssen.
Da ich einen Storage-Server habe, würdest du dich als Client bei dem registrieren, das du eine bestimmte Node beobachten möchtest und jedes Mal wenn sich was ändert, dann bekommst du eine Nachricht. Was in der Nachricht steht, darüber kann man jetzt diskutieren. Sprich ob da schon drin steht was sich genau geändert hat oder ob du das jetzt erst nachfragen musst.
Hier ist jetzt auch asynchrones IPC besser. Denn du willst einfach nur diese Nachricht loswerden und es interessiert dich nicht, ob der Client diese im Moment auch empfangen kann. Auch wäre hier noch ein Syscall nicht schlecht, wo du mehrere Nachrichten mit einmal versenden könntest!
Was den Unterschied betrifft, wenn du dem Unix-Prinzip treu bleiben willst "alles ist eine Datei". Dann bräuchtest du nur diesen Node-Monitor implementieren und alles andere läuft dann über das VFS.