Autor Thema: Funktionsaufruf Kernel -> Modul  (Gelesen 30765 mal)

Programm Noob

  • Gast
Gespeichert
« Antwort #40 am: 25. August 2010, 07:04 »
Moin

Da es zum Topic passt schreibe ich es mal hier rein.
Wie Komuniziert CDI mit den Treibern? Also über welche Methode.

Programm Noob

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #41 am: 25. August 2010, 09:16 »
CDI ist eine Bibliothek, insofern über ganz normale Funktionsaufrufe.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #42 am: 25. August 2010, 16:13 »
Hallo,


CDI ist eine Bibliothek, insofern über ganz normale Funktionsaufrufe.
Ich denke mal das ist allen klar, ich vermute die Frage zielt eher darauf ab was dahinter steckt also wie diese Funktionsaufrufe bei einem Mikro-Kernel-OS von einem User-Mode-Prozess zum nächsten User-Mode-Prozess kommen.
Ich persönlich vermute mal das da überall ein paar Stubs drin stecken die das dann auf das OS-spezifische IPC umsetzen und wieder entgegennehmen.

@taljeth: Falls diese Vermutung richtig ist und sie auch auf tyndur zutrifft wäre es schön wenn Du uns dazu mal etwas detailliertere Infos geben könntest.


Zitat von: erik
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.
Also wenn Du den Standard richtig verstanden hast dann ist das schon ziemlich mysteriös. Im Prinzip verbraucht ja jedes fork eine ID und wenn die endlich sein sollten dürften so manche Web-Server (solche die für jede ankommende TCP-Verbindung einen fork machen) nicht besonders lange laufen. Ich vermute mal die OSe halten sich in diesem Punkt nicht an den Standard, anders kann ich mir das nicht erklären.

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!
Nein! Man kann einer modernen SATA-HDD, wenn sie an einem Host-Controller hängt der im AHCI-Modus arbeitet (und nicht mehr im klassischen IDE-Modus), wirklich mehrere Jobs geben ohne warten zu müssen bis die vorangegangenen Jobs fertig sind. SATA arbeitet paket-orientiert und da ist es wirklich möglich mehrere Aufträge hintereinander an die Platte zu schicken, ließ Dir am besten mal die SATA-Spezifikation und die AHCI-Spezifikation durch.

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).
Eben genau das meinte ich, wenn Du nur dann Anfragen entgegennehmen kannst wenn ein Thread darauf wartet dann muss es möglich sein das mehrere Threads gleichzeitig an einem Deiner Message-Ports/Pipes/was-auch-immer warten.


Ich sehe es erstmal so, das die Initialisierung ruhig ein wenig komplexer/langsamer sein darf, wenn der Vorgang an sich dann schneller geht.
Was ist mit einfachem Code in der Art:int randValue;
{
FILE* randFile = fopen("/dev/random",ro);
fread(randFile,&randValue,sizeof(int));
fclose(randFile);
}
oder etwas anderem wo nur von einer (oder vielen) Datei(en) die Größe ermittelt werden soll. Da würdest Du einen Haufen Arbeit machen nur um wenige Infos zu bekommen, das erscheint mir unnötig kompliziert (und auch nicht schneller). Nebst dessen das Du in Deinem Kernel eine Menge Zeugs implementieren musst und ja theoretisch jeder Teil davon Fehler enthalten kann, zumindest generierst Du so eine Menge Code für den Kernel und steigerst damit die Wahrscheinlichkeit für Fehler (ohne nennenswert mehr Funktionalität zu bekommen).

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)
Woher weiß Dein Dateisystemtreiber wie groß dieser Auftrag ist wenn er die Länge des Dateinamens noch nicht kennt? Byte-orientierte Pipes sind für solche Dinge extrem ungeeignet weil der Empfänger niemals die Synchronisation auf die Message-Grenzen verlieren darf.

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.
Das sieht nach ner Menge Syscalls aus. 1. der Client für das Abschicken der Anfrage. 2. der Service für das Abholen der Anfrage (hier eventuell noch ein weiterer weil er erst mal nur die Länge der eigentlichen Anfrage erfahren wollte). 3. der Service für das Schreiben der Antwort (das Flush zähle ich mal nicht extra, das ist ein Flag beim Schreiben). 4. der Client zum Abholen der Antwort. Macht 4 Syscalls mit insgesamt 8 CPU-Mode-Wechseln (+ zusätzliches zur Vorbereitung/Aufräumen). Bei mir sind es nur 2 Syscalls (zum einen macht der Client seine Anfrage und zum anderen meldet der PopUp-Thread das er fertig ist und gekillt werden kann) mit insgesamt 4 CPU-Mode-Wechseln (ohne das irgendwelche Shared-Memorys o.ä. extra vorbereitet/aufgeräumt werden müssen).

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).
In meinem Konzept wird auch nichts kopiert sondern der Client stellt alle Speicherbereiche zur Verfügung und diese werden in den Service eingeblendet. Falls der Service seinerseits auch wieder die Dienste eines weiteren Service benötigt werden die geerbten Speicherbereiche einfach weitergereicht so das auch der letzte Service direkt mit dem Speicher des ursprünglichen Clients arbeitet. Ein HDD-Treiber würde also direkt in den Speicher der Applikation schreiben (bzw. den HDD-Host-Controller schreiben lassen) obwohl da noch das VFS und ein Datei-System-Treiber dazwischen hängen, das heißt das bevor die Applikation die Daten verarbeitet (nach dem fread) keines der Nutzdatenbytes überhaupt von einer CPU angefasst wurde. Der HDD-Controller schreibt die Daten (als Bus-Master) in den Haupt-Speicher und erst die Applikation (nach dem ganzen IPC-Vorgang) greift tatsächlich auf diese Daten zu.

Das fread in meiner libc würde etwa so aussehen (ich beziehe mich auf die IPC-Syscalls von http://forum.lowlevel.eu/index.php?topic=2433):int fread(FILE* const handle,void* const data,const ulong length)
{
  ulong fread_length; //hier kommt die Anzahl der tatsächlich gelesenen Bytes rein

  {
  File_Read_Request_t request;
  //'request' passend befüllen mit Infos von 'handle'

  File_Read_Responce_t responce;
  ulong responce_length = ~0;

  //ein blockierender Syscall (synchrones IPC) :
  const long retval = syscall_msg_sync_send(vfs_msg_id,&request,sizeof(request),NULL,0,&responce,&responce_length,sizeof(responce),data,&fread_length,length);
  //den Erfolg des Syscalls ansich auswerten :
  if (retval != KERNEL_SYSCALL_OK_Code)
   { /* IPC ansich ist fehlgeschlagen */ return ???; }

  //die Antwort vom aufgerufenen Service (RPC-Handler) auswerten :
  if (responce_length != sizeof(responce))
   { /* die Antwort vom RPC-Handler hat nicht die passende Größe */ return ???; }
  if (responce != ????)
   { /* Fehler beim eigentlichen fread im VFS oder tiefer */ return ???; }
  assert(fread_length <= length); //nur zur Sicherheit
  }

  return fread_length; //Anzahl der tatsächlich gelesenen Bytes zurückmelden
}
Ich finde das recht einfach und übersichtlich und es ist völlig egal ob 5 Bytes oder 500Mbytes gelesen werden sollen. Als einzigste Vorbereitung muss 'vfs_msg_id' von der libc einmal beim Prozessstart ermittelt werden, damit man weiß wohin mit den IPC-Anfragen.

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.
Das sind schon 2 Syscalls für den Client obwohl IMHO einer reicht.

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.
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Das simple Programm ist doch darauf gar nicht ausgelegt. Ich persönlich sehe es als enormes Risiko an wenn man Prozessen etwas geben kann womit sie nichts anfangen können bzw. was sie gar nicht explizit erlaubt haben. Einem PC im Internet kann man doch auch nur auf die TCP/UDP-Ports was schicken die dieser PCs explizit geöffnet hat (um eben was von außen empfangen zu können).
Was ist eigentlich wenn ein böser Prozess in genau diesem Moment dem Client eine fingierte Antwort schickt? Ich persönlich bin der Meinung das der OS-Kernel es garantieren muss das der Client genau seine Antwort erhält und nichts anderes.

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.
Ich bleibe lieber dabei das ich beides anbiete, der zusätzliche Code im Kernel dürfte sehr überschaubar bleiben und den Performance-Verlust um aus etwas Asynchronem etwas Synchrones zu machen will ich nicht (eben weil das Synchrone das signifikant Häufigere und deutlich Wichtigere ist).

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.
Wenn Du erst für jeden kurzen Datei-Lesevorgang dann ein paar Ebenen tiefer Shared-Memory vorbereiten musst stelle ich mir das nicht besonders performant vor.


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! ...
Das soll man ja auch anders machen:try
{ //Ebene 1
  mem = memAlloc4kb();

  try
  { //Ebene 2
    id = allocNewID();

    try
    { //Ebene 3
      stack = memAlloc4kb();

      ... //das Spiel kannst du jetzt weiter treiben, indem noch mehr Ressourcen benötigt werden

    } //Ebene 3
    catch (NotEnoughMemoryException e)
    {
      freeID(id);
      throw(NoFreeIDException); //noch den Aufräum-Code von Ebene 2 ansteuern
    }
  } //Ebene 2
  catch (NoFreeIDException e)
  {
    memFree4kb(mem);
    throw(NotEnoughMemoryException); //noch den Aufräum-Code von Ebene 1 ansteuern
  }
} //Ebene 1
catch (NotEnoughMemoryException e)
{
  return 0;
}
Man räumt quasi von innen nach außen auf. Jeden Aufräum-Code hast Du nur ein einziges mal (keine Redundanzen), Du musst immer nur darauf achten das beim von innen nach außen gehen keine Ebene ausgelassen wird.
In diesem konkreten Fall (wo es nur um malloc u.ä. geht) würde ich das aber schon etwas anders (einfacher) machen:try
{
  mem = memAlloc4kb();
  id = allocNewID();
  stack = memAlloc4kb();

  //arbeiten ....

  memFree4kb(stack);
  freeID(id);
  memFree4kb(mem);
  return OK_Code;
}
catch (KernelException e) //KernelExpection ist Parent von NotEnoughMemoryException und NoFreeIDException
{
  if (stack != NULL) { memFree4kb(stack); }
  if (id != NULL) { freeID(id); }
  if (mem != NULL) { memFree4kb(mem); }
  return Fehler_Code;
}


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #43 am: 25. August 2010, 19:13 »
Zitat von: erik
Also wenn Du den Standard richtig verstanden hast dann ist das schon ziemlich mysteriös. Im Prinzip verbraucht ja jedes fork eine ID und wenn die endlich sein sollten dürften so manche Web-Server (solche die für jede ankommende TCP-Verbindung einen fork machen) nicht besonders lange laufen. Ich vermute mal die OSe halten sich in diesem Punkt nicht an den Standard, anders kann ich mir das nicht erklären.
Es gibt Situationen wo das Sinn macht, aber im Großen und Ganzen glaube ich eher das ich den Standard da nicht richtig verstanden habe.

Zitat von: erik
Nein! Man kann einer modernen SATA-HDD, wenn sie an einem Host-Controller hängt der im AHCI-Modus arbeitet (und nicht mehr im klassischen IDE-Modus), wirklich mehrere Jobs geben ohne warten zu müssen bis die vorangegangenen Jobs fertig sind. SATA arbeitet paket-orientiert und da ist es wirklich möglich mehrere Aufträge hintereinander an die Platte zu schicken, ließ Dir am besten mal die SATA-Spezifikation und die AHCI-Spezifikation durch.
Was ich meinte war, das du immer nur einen Job zur gleichen Zeit an die Festplatte geben kannst und dass das nicht parallel, auf mehreren CPUs, gleichzeitig läuft.

Zitat von: erik
oder etwas anderem wo nur von einer (oder vielen) Datei(en) die Größe ermittelt werden soll. Da würdest Du einen Haufen Arbeit machen nur um wenige Infos zu bekommen, das erscheint mir unnötig kompliziert (und auch nicht schneller). Nebst dessen das Du in Deinem Kernel eine Menge Zeugs implementieren musst und ja theoretisch jeder Teil davon Fehler enthalten kann, zumindest generierst Du so eine Menge Code für den Kernel und steigerst damit die Wahrscheinlichkeit für Fehler (ohne nennenswert mehr Funktionalität zu bekommen).
Also die Initialisierung, würde ja nur einmal ablaufen und das würde dann auch von meiner Libc gemacht werden.

Was meinst du genau, was ich da alles im Kernel implementieren müsste?

Zitat von: erik
Das sieht nach ner Menge Syscalls aus. 1. der Client für das Abschicken der Anfrage. 2. der Service für das Abholen der Anfrage (hier eventuell noch ein weiterer weil er erst mal nur die Länge der eigentlichen Anfrage erfahren wollte). 3. der Service für das Schreiben der Antwort (das Flush zähle ich mal nicht extra, das ist ein Flag beim Schreiben). 4. der Client zum Abholen der Antwort. Macht 4 Syscalls mit insgesamt 8 CPU-Mode-Wechseln (+ zusätzliches zur Vorbereitung/Aufräumen). Bei mir sind es nur 2 Syscalls (zum einen macht der Client seine Anfrage und zum anderen meldet der PopUp-Thread das er fertig ist und gekillt werden kann) mit insgesamt 4 CPU-Mode-Wechseln (ohne das irgendwelche Shared-Memorys o.ä. extra vorbereitet/aufgeräumt werden müssen).
Als erstes, wie willst du es denn bei Msgs dynamischer Länge machen? Ich meine an sich müsstes du erstmal fragen wie groß die Msg ist, dann nen Buffer allokieren und dann die Msg abholen, oder?

Dann noch was zu meinen Pipe´s, die sind fast ausschließlich im UserSpace, um daraus was zu lesen musst du halt nicht in den Kernel, das ist ja der ganze Sinn und Zweck der Sache. Du musst nur in den Kernel, wenn du lesen willst und keine Daten vorhanden sind oder du schreiben willst und kein "freier" Speicher mehr da ist.

Zitat von: erik
Woher weiß Dein Dateisystemtreiber wie groß dieser Auftrag ist wenn er die Länge des Dateinamens noch nicht kennt? Byte-orientierte Pipes sind für solche Dinge extrem ungeeignet weil der Empfänger niemals die Synchronisation auf die Message-Grenzen verlieren darf.
Deswegen war es nur ein einfaches Bsp.!

Es gibt die Möglichkeit das ich eine Msg mit nem festgelegten Header habe und der String danach kommt (wobei die Länge gegeben werden kann oder einfach gelesen wird bis "\0" kommt, ich weiß fehleranfällig).

Was du mit der Synchronisation meinst ist mir gerade auch nicht klar.

Zitat von: erik
Ich finde das recht einfach und übersichtlich und es ist völlig egal ob 5 Bytes oder 500Mbytes gelesen werden sollen. Als einzigste Vorbereitung muss 'vfs_msg_id' von der libc einmal beim Prozessstart ermittelt werden, damit man weiß wohin mit den IPC-Anfragen.
D.h. ja das du dich um alles im Clienten kümmern willst, sprich das wenn du 5bytes an der Position 100 lesen willst, dann musst du "wissen" wieviel du von der Datei laden musst und dann musst du die richtigen Daten noch rumkopieren (das Rumkopieren muss ich auch machen).

Ich weiß nicht wie du das mit dem SharedMem machst, aber ich stelle es mir schwierig vor, da dann nen vernünftiges VFS mit Caching aufzusetzen.

Zitat von: erik
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Das simple Programm ist doch darauf gar nicht ausgelegt. Ich persönlich sehe es als enormes Risiko an wenn man Prozessen etwas geben kann womit sie nichts anfangen können bzw. was sie gar nicht explizit erlaubt haben. Einem PC im Internet kann man doch auch nur auf die TCP/UDP-Ports was schicken die dieser PCs explizit geöffnet hat (um eben was von außen empfangen zu können).
Was ist eigentlich wenn ein böser Prozess in genau diesem Moment dem Client eine fingierte Antwort schickt? Ich persönlich bin der Meinung das der OS-Kernel es garantieren muss das der Client genau seine Antwort erhält und nichts anderes.
Also bei mir kannst du beim Erstellen des Ports festlegen, ob ein bestimmter Prozess oder alle Prozesse an diesen Port Nachrichten schicken dürfen. Willst du aber als Clienten nur einen Port (was vollkommen ausreichend sein sollte) öffnen, dann musst du alle Prozesse senden lassen.
Ich will ja nicht noch eine Liste an den Port hängen welche Prozesse etwas senden dürfen. (das selbe gilt für das Abholen der Msgs)

Zitat von: erik
Ich bleibe lieber dabei das ich beides anbiete, der zusätzliche Code im Kernel dürfte sehr überschaubar bleiben und den Performance-Verlust um aus etwas Asynchronem etwas Synchrones zu machen will ich nicht (eben weil das Synchrone das signifikant Häufigere und deutlich Wichtigere ist).
Welcher Performanceverlust?

Zitat von: erik
Wenn Du erst für jeden kurzen Datei-Lesevorgang dann ein paar Ebenen tiefer Shared-Memory vorbereiten musst stelle ich mir das nicht besonders performant vor.
Also ich habe schon vor irgendwelches Caching zu nutzen (so dass z.B. der Storage-Server die Daten schon hat und den Festplattentreiber gar nicht erst danach fragen muss, da wird dann auch nichts mit SharedMem initialisiert) und das was du beschrieben hast, ist doch auch nur eine Form des SharedMem!?

Was deine Lösung (die 1.) für die Exceptions betrifft. Das findest du lesbarer und schöner (mal abgesehen von dem ganzen Overhead und das du es im Kernel nicht nutzen kannst)?

Also da bleibe ich lieber bei meinem goto ;)

Wie machst du es eigentlich, wenn eine Funktion zwar einen Fehler zurückgibt, du aber aus dieser Ebene nichts mehr freigeben musst, nur halt aus den äußeren, einen leeren catch-Block?

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #44 am: 25. August 2010, 19:54 »
Ich denke mal das ist allen klar, ich vermute die Frage zielt eher darauf ab was dahinter steckt also wie diese Funktionsaufrufe bei einem Mikro-Kernel-OS von einem User-Mode-Prozess zum nächsten User-Mode-Prozess kommen.
Ich persönlich vermute mal das da überall ein paar Stubs drin stecken die das dann auf das OS-spezifische IPC umsetzen und wieder entgegennehmen.

@taljeth: Falls diese Vermutung richtig ist und sie auch auf tyndur zutrifft wäre es schön wenn Du uns dazu mal etwas detailliertere Infos geben könntest.
Richtig, der standardisierte Teil von CDI ist das Interface. Damit haben die Treiber etwas einheitliches, gegen das sie programmieren können. Die Implementierung, also die eigentliche Lib, ist völlig OS-spezifisch. Manche Teile kann man von anderen Systemen übernehmen, aber die meisten sind einfach genau die Abbildung auf die nativen Schnittstellen des Systems. Wenn man CDI implementiert, geht man also beispielsweise her und implementiert Funktionen wie cdi_net_receive_packet. Der Netzwerktreiber ruft diese Funktion auf, damit das OS was damit machen kann, aber wohin man das Paket am Ende hinliefert, ist CDI völlig egal.

Insofern ist auch die Implementierung unter tyndur nicht außergewöhnlich spannend. Meistens wird einfach eine Dateisystem-Schnittstelle als Wrapper um die CDI-Funktionen gebastelt, unter Umständen aber auch direkt in RPC umgesetzt.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #45 am: 26. August 2010, 12:33 »
Hallo,


Wenn man CDI implementiert, geht man also beispielsweise her und implementiert Funktionen wie cdi_net_receive_packet. Der Netzwerktreiber ruft diese Funktion auf, damit das OS was damit machen kann, aber wohin man das Paket am Ende hinliefert, ist CDI völlig egal.
Ich hätte jetzt vermutet dass das Netzwerkpaket zum IP-Stack (auch wieder ein User-Mode-Prozess) muss (nicht zum OS selber) welcher das Paket entpackt und dann an TCP (oder UDP oder was anderes) weiterleitet. Ich bin davon ausgegangen das Netzwerk-Treiber und IP-Stack beides Clients von CDI sind und sich CDI darum kümmert (mit einem OS-spezifischen Mechanismus) das die zueinander finden.

Insofern ist auch die Implementierung unter tyndur nicht außergewöhnlich spannend. Meistens wird einfach eine Dateisystem-Schnittstelle als Wrapper um die CDI-Funktionen gebastelt, unter Umständen aber auch direkt in RPC umgesetzt.
Ich weiß zwar theoretisch wie das mit den Stubs ungefähr funktioniert aber es wäre trotzdem sehr nett da mal ein praktisches Beispiel sehen zu können, eventuell garniert mit ein paar erklärenden Sätzen.


Was ich meinte war, das du immer nur einen Job zur gleichen Zeit an die Festplatte geben kannst und dass das nicht parallel, auf mehreren CPUs, gleichzeitig läuft.
Das übergeben eines neuen Jobs an den AHCI-Host-Controller läuft auf das einhängen einer Struktur in eine Art verkettete Liste hinaus. Der kritische Code-Abschnitt (welcher serialisiert werden muss) dürfte nur sehr wenige Assemblerbefehle lang sein, in so fern sollte sich das extrem gut parallelisieren lassen. So wie ich die AHCI-Spezifikation verstanden habe ist AHCI ein gutes Beispiel dafür wie man eine HW-Komponente entwerfen muss damit sie möglichst vielen parallelen Jobs gleichzeitig nachgehen kann. Ein AHCI-Controller darf sogar richtig viele verschiedene Interrupts (dann per MSI/MSI-X) haben und man kann jedem Job sagen welcher davon nach Erledigung ausgelöst werden soll, wenn man jeden Interrupt einer anderen CPU zuweist und für jeden Job den richtigen Interrupt wählt kann man damit erreichen das alle Arbeiten an einem Job immer von der selben CPU erledigt werden (wegen Cache-Effizienz usw.).


Also die Initialisierung, würde ja nur einmal ablaufen
Einmal pro was? In meinem Konzept brauch ich nur die Message-Target-ID des VFS und schon kann ich ohne weitere Vorbereitungen ein (oder beliebig viele) fopen machen. Diese ID soll dem Prozess bei seiner Erstellung gleich mitgegeben werden so das in der Init-Funktion der libc diese ID nur noch in die richtige Variable kommt (noch bevor main aufgerufen wird).

Was meinst du genau, was ich da alles im Kernel implementieren müsste?
Kann ich nicht genau sagen, nur Du kennst Dein Konzept gut genug um das sagen zu können. Bis jetzt hast Du von Messages und Ports, von Pipes und von Shared-Memory geschrieben. Für all das musst Du zumindest Verwaltungsfunktionen in Deinen Kernel implementieren.

Als erstes, wie willst du es denn bei Msgs dynamischer Länge machen? Ich meine an sich müsstes du erstmal fragen wie groß die Msg ist, dann nen Buffer allokieren und dann die Msg abholen, oder?
In meinem Code-Beispiel für fread gab es doch  File_Read_Request_t request;
  //'request' passend befüllen mit Infos von 'handle'
und später beim IPC-Syscall gibt es das als Parameter,&request,sizeof(request),Der Request liegt also beim Client im Speicher (hier im Stack-Frame der fread-Funktion) und der Client kennt auch seine Größe. Der Kernel blendet diesen Speicher einfach in den Kontext des aufgerufenen Service ein (und da ich Segmente benutze ist es egal ob dieser Speicherbereich nur 3 Bytes oder gleich 3 GBytes groß ist, das kostet nicht einen Assemblerbefehl mehr), der Service muss den Request also nicht in einen eigenen Buffer holen sondern bekommt direkt den Original-Buffer seines Clients übergeben (zusammen mit der Info wie Groß dieser ist). Des selbe trifft auch für die Antwort zu, der Client hat einen Speicherbereich wo die Antwort rein soll und der Service bekommt quasi einen Pointer in diesen Speicher und die Info wie lang seine Antwort maximal sein darf (dank der Segmente kann die Antwort auch nicht länger werden, sonst löst der Service eine Segmentation-Fault-Exception aus).
Diese Structur (für fread) könnte diesen Aufbau haben:typedef File_Read_Request_t{
  ulong  job_code;     //muss immer an dieser Stelle liegen damit der IPC-Handler im VFS weiß was zu tun ist (wird mit einer Funktionsnummer belegt)
  ulong  file_handle;  //dürfte klar sein (fread holt das aus 'FILE* handle')
  ulong  file_offset;  //das ist der aktuelle File-Pointer (welcher auch aus 'FILE* handle' kommt)
  ulong  read_length;  //dürfte auch klar sein
}File_Read_Request_t;
Der Handler sieht dann etwa so aus:void vfs_file_handler(const void* const param,
                      const long sender_id,
                      const void* const question1,
                      const ulong question1_length,
                      const void* const question2,
                      const ulong question2_length,
                      void* const answer1,
                      const ulong answer1_max_length,
                      void* const answer2,
                      const ulong answer2_max_length)
{
  switch(*((const ulong* const)question1)) //Job-Code auswerten (deswegen muss der immer an Offset 0 im Request liegen)
   {
     case VFS_IPC_FUNCTION_FREAD:
       {
       const File_Read_Request_t* const request = (const File_Read_Request_t* const)question1;

       //den Rest vom request auswerten und Job erledigen und gelesene Daten nach '*answer2' schreiben ...
       ulong real_read_length = ???; //mit der Anzahl der tatsächlich gelesenen Bytes befüllen

       File_Read_Response_t* const response = (File_Read_Responce_t* const)answer1;
       //response mit dem Job-Ergebnis befüllen (also Okay oder passender Fehler-Code)

       //IPC-Handler ist fertig, die IPC-Ende-Funktion kommt nicht zurück, dieser PopUp-Thread wird damit vom Kernel gekillt
       syscall_msg_sync_handler_end(sizeof(response),real_read_length); //die beiden Parameter melden die tatsächliche Größe der beiden Antworten zurück
                                                                        //(müssen natürlich kleiner oder gleich sein als die Max-Werte)
       }
   }
}

Dann noch was zu meinen Pipe´s, die sind fast ausschließlich im UserSpace, um daraus was zu lesen musst du halt nicht in den Kernel, das ist ja der ganze Sinn und Zweck der Sache. Du musst nur in den Kernel, wenn du lesen willst und keine Daten vorhanden sind oder du schreiben willst und kein "freier" Speicher mehr da ist.
Ich verstehe zwar nicht was Du damit meinst das die Pipes "fast ausschließlich im User-Space" sind aber zumindest muss der Sender den Empfänger irgendwie (wohl per Syscall) darüber benachrichtigen wenn er neue Daten reingelegt hat, dauerndes Pollen des Empfängers willst du bestimmt nicht. Das selbe noch in die andere Richtung wenn der Buffer voll ist und der Empfänger dem (wartenden weil sendewilligen) Sender mitteilen muss das wieder Platz geschaffen wurde.

Es gibt die Möglichkeit das ich eine Msg mit nem festgelegten Header habe und der String danach kommt (wobei die Länge gegeben werden kann oder einfach gelesen wird bis "\0" kommt, ich weiß fehleranfällig).
Eben genau diese Fehleranfälligkeit meinte ich damit das der Empfänger niemals eine Nachricht fehlerhaft decodieren darf, dann erkennt er nämlich nicht mehr die nachfolgenden Nachrichten (er weiß nicht mehr wo diese Anfangen) und die Pipe ist erledigt.

Ich weiß nicht wie du das mit dem SharedMem machst, aber ich stelle es mir schwierig vor, da dann nen vernünftiges VFS mit Caching aufzusetzen.
Das mit dem Caching ist allerdings ein kleines Problem, im Endeffekt bedeutet dass das die von der HDD gelesenen Daten in zwei verschiedene Speicherbereiche rein sollen, einmal in den Speicherbereich der fread übergeben wurde (in der eigentlichen Applikation) und zusätzlich in den Cache (im VFS). Da muss ich auf jeden Fall noch mal gründlich drüber nachdenken aber ich schätze einmal muss ich dafür die Daten dann doch kopieren (das ist aber auch in allen anderen Konzepten so). Ein Lesezugriff auf den Cache läuft ja auch auf einen Kopiervorgang hinaus.

Zitat von: erik
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Das simple Programm ist doch darauf gar nicht ausgelegt. Ich persönlich sehe es als enormes Risiko an wenn man Prozessen etwas geben kann womit sie nichts anfangen können bzw. was sie gar nicht explizit erlaubt haben. Einem PC im Internet kann man doch auch nur auf die TCP/UDP-Ports was schicken die dieser PCs explizit geöffnet hat (um eben was von außen empfangen zu können).
Was ist eigentlich wenn ein böser Prozess in genau diesem Moment dem Client eine fingierte Antwort schickt? Ich persönlich bin der Meinung das der OS-Kernel es garantieren muss das der Client genau seine Antwort erhält und nichts anderes.
Also bei mir kannst du beim Erstellen des Ports festlegen, ob ein bestimmter Prozess oder alle Prozesse an diesen Port Nachrichten schicken dürfen. Willst du aber als Clienten nur einen Port (was vollkommen ausreichend sein sollte) öffnen, dann musst du alle Prozesse senden lassen.
Du hast meine Frage(n) nicht beantwortet! Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Diese potentielle Gefahr empfinde ich als groben Design-Fehler.
Dieses Warten auf bestimmte Antworten macht IMHO nur den Kernel-Code noch komplizierter.

Zitat von: erik
und den Performance-Verlust um aus etwas Asynchronem etwas Synchrones zu machen will ich nicht (eben weil das Synchrone das signifikant Häufigere und deutlich Wichtigere ist).
Welcher Performanceverlust?
Beim asynchronen IPC muss der Client 2 Syscalls machen um das Verhalten von synchronem IPC zu erzielen (einmal Anfrage schicken und einmal auf Antwort warten/abholen).
Wann läuft der Service-Thread (der den Job bearbeitet) eigentlich los? Wird der Client gleich bei der Anfrage geschedult und die CPU dem Service-Thread übergeben so das der Job möglichst schnell anfangen kann (was bedeutet das der Client erst später dazu kommt seine Antwort abzuholen)? Oder wird der Service-Thread nur auf "runnable" gesetzt und läuft erst dann los wenn der Scheduler mal wieder vorbei schaut.


Was deine Lösung (die 1.) für die Exceptions betrifft. Das findest du lesbarer und schöner (mal abgesehen von dem ganzen Overhead und das du es im Kernel nicht nutzen kannst)?
Natürlich ist Beispiel 1 kein Vorbild im Punkt Lesbarkeit, ich wollte nur mal demonstrieren wie man mit Exceptions mehrfachen Aufräum-Code vermeiden kann, es gibt aber noch viele andere Wege die zum Teil auch schöner zu lesen sind.

Wie machst du es eigentlich, wenn eine Funktion zwar einen Fehler zurückgibt, du aber aus dieser Ebene nichts mehr freigeben musst, nur halt aus den äußeren, einen leeren catch-Block?
Dann kann man das try/catch wohl auch ganz weglassen, Du musst nur sicherstellen dass das nächst äußere catch auch diese Art von Exception fängt (siehst Du in meinem 2. Beispiel).


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #46 am: 26. August 2010, 13:06 »
Zitat von: erik
Einmal pro was? In meinem Konzept brauch ich nur die Message-Target-ID des VFS und schon kann ich ohne weitere Vorbereitungen ein (oder beliebig viele) fopen machen. Diese ID soll dem Prozess bei seiner Erstellung gleich mitgegeben werden so das in der Init-Funktion der libc diese ID nur noch in die richtige Variable kommt (noch bevor main aufgerufen wird).
Das ist der Punkt der noch ein wenig schwammig ist ;) Ich weiß nicht ob die Initialisierung einmal pro Prozess (was das arbeiten von mehreren Threads aus wieder unschön macht) oder pro Thread (mehr "Aufwand" bzw. MemoryOverhead).

Ich wäre ja dafür einen neuen Thread aufzumachen, wo man sich mal über verschiedene IPC Methoden und eine effiziente Implementierung unterhalten kann!

Zitat von: erik
Kann ich nicht genau sagen, nur Du kennst Dein Konzept gut genug um das sagen zu können. Bis jetzt hast Du von Messages und Ports, von Pipes und von Shared-Memory geschrieben. Für all das musst Du zumindest Verwaltungsfunktionen in Deinen Kernel implementieren.
Hab ich schon, aber (mal die Pipes ausgenommen) müsstest du doch auch haben, oder?

Was die dynamischen (die Größe) Msgs betrifft. Dann gehst du aber immer davon aus das an einem "Port" (oder was du als Abstraktion nimmst) immer nur eine Art von Msg ankommen kann. Sprich die müssen immer die selbe Länge haben.

Zitat von: erik
Ich verstehe zwar nicht was Du damit meinst das die Pipes "fast ausschließlich im User-Space" sind aber zumindest muss der Sender den Empfänger irgendwie (wohl per Syscall) darüber benachrichtigen wenn er neue Daten reingelegt hat, dauerndes Pollen des Empfängers willst du bestimmt nicht. Das selbe noch in die andere Richtung wenn der Buffer voll ist und der Empfänger dem (wartenden weil sendewilligen) Sender mitteilen muss das wieder Platz geschaffen wurde.
Im Moment (da ich gerade nen schönen Artikel über IPC gefunden habe und überlege diese Konzept zu nutzen) kannst du dir meine Pipes wie SharedMem vorstellen. Du schreibst/liest in diesem SharedMem. Wie gesagt, du musst dann halt in den Kernel um dem eventuell wartenden Reader/Writer "bescheid" zu geben.

Zitat von: erik
Eben genau diese Fehleranfälligkeit meinte ich damit das der Empfänger niemals eine Nachricht fehlerhaft decodieren darf, dann erkennt er nämlich nicht mehr die nachfolgenden Nachrichten (er weiß nicht mehr wo diese Anfangen) und die Pipe ist erledigt.
Da wollte ich dann "Dummheit wird sofort bestraft" ;) anwenden. Sprich macht der Client etwas was er nicht durfte/sollte, dann wird die Pipe halt dicht gemacht und die Kommunikation wird abgebrochen.

Zitat von: erik
Du hast meine Frage(n) nicht beantwortet! Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken? Diese potentielle Gefahr empfinde ich als groben Design-Fehler.
Dieses Warten auf bestimmte Antworten macht IMHO nur den Kernel-Code noch komplizierter.
Eine Tastatureingabe, wäre z.B. auch ne Msg oder dass das Fenster verkleinert wurde und und und ... Also es gibt genug Gründe wieso man einer Anwendung Msgs schicken kann.
Dass das alles bei einer Konsolenanwendung keinen Sinn macht ist klar. Aber als Gegenfrage würde ich stellen, wieso sollte dir (als PC) jemand ein schädliches Netzwerkpaket schicken?

Mein Code ist deswegen nicht wirklich komplizierter geworden, ich meine es wird einfach jedes Mal wenn eine neue Msg in die Queue kommt, überprüft ob der Sender gleich der ID ist auf die man wartet und wenn dem so ist, wird der wartende Thread aufgeweckt.

Zitat von: erik
Beim asynchronen IPC muss der Client 2 Syscalls machen um das Verhalten von synchronem IPC zu erzielen (einmal Anfrage schicken und einmal auf Antwort warten/abholen).
Also ich würde sagen, das hat nichts mit synchron oder asynchron zu tun! Synchron heißt doch ansich nur, das solange wie der Empfänger die Nachricht nicht entgegen nimmt/kann, blockiert der Sender.
Außerdem habe ich genau für die Situation doch meinen "send+recv"-Syscall.

Zitat von: erik
Wann läuft der Service-Thread (der den Job bearbeitet) eigentlich los? Wird der Client gleich bei der Anfrage geschedult und die CPU dem Service-Thread übergeben so das der Job möglichst schnell anfangen kann (was bedeutet das der Client erst später dazu kommt seine Antwort abzuholen)? Oder wird der Service-Thread nur auf "runnable" gesetzt und läuft erst dann los wenn der Scheduler mal wieder vorbei schaut.
Also wenn ein Thread auf eine Antwort wartet und diese bekommt wird er wieder in die Ready-Queue des Schedulers gepackt (wann der Scheduler den Thread laufen lässt, kommt auf die Strategie und auf die anderen laufenden Threads an).

Was die Exceptions betrifft, ich bleibe bei meinem goto ;)
Aber es gibt durchaus Sachen wo die sehr hilfreich sein können, in der Libc wird sowas ja mit "errno" gelöst, sprich alle Werte die die Funktion zurückgibt sind "gültig", du musst halt nur in "errno" nachgucken, ob ein Fehler und was für ein Fehler aufgetreten ist.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #47 am: 26. August 2010, 14:47 »
Ich hätte jetzt vermutet dass das Netzwerkpaket zum IP-Stack (auch wieder ein User-Mode-Prozess) muss (nicht zum OS selber) welcher das Paket entpackt und dann an TCP (oder UDP oder was anderes) weiterleitet. Ich bin davon ausgegangen das Netzwerk-Treiber und IP-Stack beides Clients von CDI sind und sich CDI darum kümmert (mit einem OS-spezifischen Mechanismus) das die zueinander finden.
Ich denke, das ist vor allem ein Problem der Terminologie. Unter OS verstehe ich das große Ganze, selbstverständlich einschließlich IP-Stack. Ich meine nicht den Kernel damit.

Ich weiß zwar theoretisch wie das mit den Stubs ungefähr funktioniert aber es wäre trotzdem sehr nett da mal ein praktisches Beispiel sehen zu können, eventuell garniert mit ein paar erklärenden Sätzen.
Okay, nehmen wir als einfachstes Beispiel Netzwerk: tyndur-Code

Oben sind ein paar Initialisierungsfunktionen, die einen RPC-Handler für das Senden von Paketen definieren und solche Dinge. Das eigentliche Interface fängt bei cdi_net_receive() an. Das ist eine Funktion, die vom Treiber aufgerufen wird, wenn ein Paket angekommen ist. Was sie macht ist das Paket einfach an alle registrierten Empfänger per send_message() weiterzuleiten.

rpc_send_packet() ist das Gegenteil, nämlich ein RPC-Handler, der aufgerufen wird, wenn der TCP/IP-Stack ein Paket senden möchte. Der Wrapper holt sich die passende Struktur, die das Gerät beschreibt und ruft den Funktionspointer dev->send_packet() auf, der in den Treiber zeigt.

Ist das ungefähr, was du sehen wolltest?
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #48 am: 26. August 2010, 21:46 »
Hallo,


Ist das ungefähr, was du sehen wolltest?
Ja, recht herzlichen Dank dafür!


Ich wäre ja dafür einen neuen Thread aufzumachen, wo man sich mal über verschiedene IPC Methoden und eine effiziente Implementierung unterhalten kann!
Ja gerne.

Hab ich schon, aber (mal die Pipes ausgenommen) müsstest du doch auch haben, oder?
Noch hab ich gar nichts programmiert, sind bis jetzt alles nur Design-Überlegungen die ich zum großen Teil in Textform habe.

Was die dynamischen (die Größe) Msgs betrifft. Dann gehst du aber immer davon aus das an einem "Port" (oder was du als Abstraktion nimmst) immer nur eine Art von Msg ankommen kann. Sprich die müssen immer die selbe Länge haben.
Wie kommst Du denn da drauf, ich hab doch eindeutig geschrieben das dem Handler die Größe der Speicherbereiche (die er vom Client geerbt bekommt) erfährt. Schau Dir Bitte noch mal die letzten 8 Parameter im Signal-Handler an, mein letztes Code-Beispiel von heute Mittag, da sind 4 Pointer mit je einer Längenangabe. Der Pointer zeigt direkt in den Speicher des Clients (per Shared-Memory) und die Längenangabe kommt ebenfalls vom Client. Auf diese Art kann der Handler mit jeder beliebigen Größe umgehen, er muss ja nichts dafür vorbereiten, das macht alles der Kernel.

(da ich gerade nen schönen Artikel über IPC gefunden habe und überlege diese Konzept zu nutzen)
Wenn der Artikel wirklich schön ist wäre es toll wenn Du da mal einen Link geben könntest.

Zitat von: erik
Eben genau diese Fehleranfälligkeit meinte ich damit das der Empfänger niemals eine Nachricht fehlerhaft decodieren darf, dann erkennt er nämlich nicht mehr die nachfolgenden Nachrichten (er weiß nicht mehr wo diese Anfangen) und die Pipe ist erledigt.
Da wollte ich dann "Dummheit wird sofort bestraft" ;) anwenden. Sprich macht der Client etwas was er nicht durfte/sollte, dann wird die Pipe halt dicht gemacht und die Kommunikation wird abgebrochen.
Ich meinte eher dass das Risiko besteht das der Service durcheinander gerät (trotz dessen das der Client alles richtig macht).

Eine Tastatureingabe, wäre z.B. auch ne Msg
Die hat das Programm aber auch angefordert, indem aus cin gelesen wird. Wenn das Programm aber nicht mal cin öffnet dann darf es IMHO auch nicht mit Tastatureingaben belästigt werden.

oder dass das Fenster verkleinert wurde und und und ... Also es gibt genug Gründe wieso man einer Anwendung Msgs schicken kann.
Das sind alles Dinge wo das Programm vorher explizit sagen muss das es soetwas überhaupt verarbeiten kann/möchte.

Aber als Gegenfrage würde ich stellen, wieso sollte dir (als PC) jemand ein schädliches Netzwerkpaket schicken?
Meinst Du diese Frage ernst? Hast Du etwa zu lange für unsere letzte Familienministerin gearbeitet?

Zitat von: erik
Wieso kann man eigentlich überhaupt einem reinen Client (z.B. ein simples Hello-World-Programm) eine Message schicken?
Hierauf hab ich immer noch keine plausible Antwort.

Zitat von: erik
Beim asynchronen IPC muss der Client 2 Syscalls machen um das Verhalten von synchronem IPC zu erzielen (einmal Anfrage schicken und einmal auf Antwort warten/abholen).
Also ich würde sagen, das hat nichts mit synchron oder asynchron zu tun! Synchron heißt doch ansich nur, das solange wie der Empfänger die Nachricht nicht entgegen nimmt/kann, blockiert der Sender.
Beim synchronen Vorgang blockiert der Client so lange bis er eine Antwort erhält, was natürlich das zustellen der Anfrage beim Service mit einschließt. Beim asynchronen Vorgang kann man es zwar so implementieren das der Sender immer sofort weiter machen kann aber das bedeutet das die gesamte Message irgendwo (im Kernel) gequeuet werden muss (falls der Empfänger gerade nicht bereit ist) und das kostet Speicher. Ich werde es in meinem OS wahrscheinlich so einrichten dass das queuen nur für kleine Nachrichten möglich ist die der Kernel selber senden will, denn kann man ja eben nicht blockieren. Wenn ein User-Mode-Prozess eine große asynchrone Nachricht verschicken will und der Empfänger gerade nicht bereit ist, weil z.B. kein zusätzlicher PopUp-Thread mehr erstellt werden darf, dann wird er eben blockiert, ich sehe da keine negativen Folgen.

Also wenn ein Thread auf eine Antwort wartet und diese bekommt wird er wieder in die Ready-Queue des Schedulers gepackt (wann der Scheduler den Thread laufen lässt, kommt auf die Strategie und auf die anderen laufenden Threads an).
Das klingt irgendwie ungünstig langsam. Für eine simple RPC-Anfrage, z.B. die Größe einer Datei ermitteln, wird 2 mal auf den Scheduler gewartet. Ein ls in einem Verzeichnis mit 10000 Dateien dürfte da eine echt harte Geduldsprobe werden.


Was die Exceptions betrifft, ich bleibe bei meinem goto
Nur zu. Ich empfehle Dir trotzdem irgendwann einmal eine mächtige Programmiersprache, wie z.B. C++, zu erlernen und damit auch größere Programm zu entwickeln. Das eröffnet einem auch neue Sichtweisen für viele Dinge.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #49 am: 26. August 2010, 22:07 »
Zitat von: erik
Wie kommst Du denn da drauf, ich hab doch eindeutig geschrieben das dem Handler die Größe der Speicherbereiche (die er vom Client geerbt bekommt) erfährt. Schau Dir Bitte noch mal die letzten 8 Parameter im Signal-Handler an, mein letztes Code-Beispiel von heute Mittag, da sind 4 Pointer mit je einer Längenangabe. Der Pointer zeigt direkt in den Speicher des Clients (per Shared-Memory) und die Längenangabe kommt ebenfalls vom Client. Auf diese Art kann der Handler mit jeder beliebigen Größe umgehen, er muss ja nichts dafür vorbereiten, das macht alles der Kernel.
Mir sind deine Parameter schon klar, aber d.h. auch das du davon ausgehst, dass du immer weißt wann was für eine Nachricht kommt.

Zitat von: erik
Die hat das Programm aber auch angefordert, indem aus cin gelesen wird. Wenn das Programm aber nicht mal cin öffnet dann darf es IMHO auch nicht mit Tastatureingaben belästigt werden.
Ich muss ehrlich sagen, das ich nicht genau weiß wie das z.B. unter Windows läuft, aber ich dachte du bekommst halt ständig Nachrichten und die die du verarbeiten willst machst du halt und alle anderen werden gleich wieder verworfen.

Bzw. ich stelle es mir so auch einfacher vor, du sendest dem Programm halt das die Mouse gerade da und da geklickt hat und wenn das Programm daraus was machen will dann macht es was drauß und wenn nicht, dann wird die Nachricht halt gelöscht.

Zitat von: erik
Meinst Du diese Frage ernst? Hast Du etwa zu lange für unsere letzte Familienministerin gearbeitet?
Es ging mir darum, das du immer mit allem rechnen musst. Ansonsten fällt das dann unter die Kategorie Sicherheitslücke/Bug was irgendwie ausgenutzt werden könnte.

Zitat von: erik
Beim synchronen Vorgang blockiert der Client so lange bis er eine Antwort erhält, was natürlich das zustellen der Anfrage beim Service mit einschließt. Beim asynchronen Vorgang kann man es zwar so implementieren das der Sender immer sofort weiter machen kann aber das bedeutet das die gesamte Message irgendwo (im Kernel) gequeuet werden muss (falls der Empfänger gerade nicht bereit ist) und das kostet Speicher. Ich werde es in meinem OS wahrscheinlich so einrichten dass das queuen nur für kleine Nachrichten möglich ist die der Kernel selber senden will, denn kann man ja eben nicht blockieren. Wenn ein User-Mode-Prozess eine große asynchrone Nachricht verschicken will und der Empfänger gerade nicht bereit ist, weil z.B. kein zusätzlicher PopUp-Thread mehr erstellt werden darf, dann wird er eben blockiert, ich sehe da keine negativen Folgen.
Ich sehe da auch erstmal keine negativen Folgen. Ich wollte halt nur darauf hinaus das du synchron damit verbindest das du automatisch auch gleich eine Antwort bekommst und das hat damit nichts zu tun. Denn du musst ja auch die Möglichkeit haben einfach eine Msg zu senden ohne eine Antwort zu wollen (Bsp. Server) und da macht sich dann asynchron einfach besser. Ansonsten ist natürlich aus Programmierersicht synchron viel sympatischer.

Zitat von: erik
Das klingt irgendwie ungünstig langsam. Für eine simple RPC-Anfrage, z.B. die Größe einer Datei ermitteln, wird 2 mal auf den Scheduler gewartet. Ein ls in einem Verzeichnis mit 10000 Dateien dürfte da eine echt harte Geduldsprobe werden.
Ich weiß jetzt nicht wie du das machen willst, aber wenn ein anderer Thread aufgerufen wird (was ja praktisch beim RPC passiert), dann kommt es auf die Scheduling-Strategie an, wann er läuft. Sprich wenn ein anderer Thread eine höhere Priorität hat als der Service-Thread dann darf natürlich erstmal der andere Thread laufen.

Ich weiß nicht was du sonst mit warten auf den Scheduler meinen könntest, kannst das ja mal genauer an einem Bsp erklären.

Zitat von: erik
Nur zu. Ich empfehle Dir trotzdem irgendwann einmal eine mächtige Programmiersprache, wie z.B. C++, zu erlernen und damit auch größere Programm zu entwickeln. Das eröffnet einem auch neue Sichtweisen für viele Dinge.
Ich denke durchaus daran C++ für meine Server zu nutzen, nur im Kernel fällt es halt aus. Im Moment scheitert es noch am mangelnden Wissen und das ich weder eine Libc noch eine Libc++ habe.

edit::

Zitat von: erik
Wenn der Artikel wirklich schön ist wäre es toll wenn Du da mal einen Link geben könntest.
Link kommt sofort ;) http://www.ddj.com/showArticle.jhtml?articleID=189401457
« Letzte Änderung: 26. August 2010, 22:08 von FlashBurn »

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #50 am: 27. August 2010, 12:49 »
Hallo,


Mir sind deine Parameter schon klar, aber d.h. auch das du davon ausgehst, dass du immer weißt wann was für eine Nachricht kommt.
Nein, den Typ der Nachricht erkennt der Handler am Inhalt der Nachricht. Das hatte ich wohl nicht deutlich genug veranschaulicht, sorry.
In der Header-Datei vom VFS ist etwas das:#define VFS_IPC_FUNCTION_OPEN  1
#define VFS_IPC_FUNCTION_READ  2
#define VFS_IPC_FUNCTION_SEEK  3
....

typedef VFS_File_Open_Request_t{
  ulong  job_code;     //hier kommt die richtige Nummer (VFS_IPC_FUNCTION_OPEN) rein
  ulong  attributes;   //dürfte wohl klar sein
  ulong  file_name_length;  //zur Sicherheit noch mal die Länge das Dateinamens welcher als Request 2 (Nutzdaten) übergeben wird
}File_Read_Request_t;

typedef VFS_File_Read_Request_t{
  ulong  job_code;     //hier kommt die richtige Nummer (VFS_IPC_FUNCTION_READ) rein
  ulong  file_handle;  //dürfte klar sein (fread holt das aus 'FILE* handle')
  ulong  file_offset;  //das ist der aktuelle File-Pointer (welcher auch aus 'FILE* handle' kommt)
  ulong  read_length;  //dürfte auch klar sein
}File_Read_Request_t;

typedef VFS_File_Seek_Request_t{
  ulong  job_code;     //hier kommt die richtige Nummer (VFS_IPC_FUNCTION_SEEK) rein
  ulong  file_handle;  //dürfte klar sein (fseek holt das aus 'FILE* handle')
  ulong  file_offset;  //das ist der neue File-Pointer
}VFS_File_Seek_Request_t;
Im Handler sieht das switch dann etwa so aus:void vfs_file_handler(const void* const param,
                      const long sender_id,
                      const void* const question1,
                      const ulong question1_length,
                      const void* const question2,
                      const ulong question2_length,
                      void* const answer1,
                      const ulong answer1_max_length,
                      void* const answer2,
                      const ulong answer2_max_length)
{
  switch(*((const ulong* const)question1)) //Job-Code auswerten (deswegen muss der immer an Offset 0 im Request liegen)
   {
     case VFS_IPC_FUNCTION_OPEN:
       {
       //Open-Job erledigen
       }
     case VFS_IPC_FUNCTION_READ:
       {
       //Lese-Job erledigen
       }
     case VFS_IPC_FUNCTION_SEEK:
       {
       //Seek-Job erledigen
       }
     case ....:
   }
}
Der Handler wertet immer das erste Element vom Request aus das immer eine passende Nummer enthalten muss.
Ich hab auch schon mal darüber nachgedacht beim Sende-Syscall für synchrones IPC zusätzlich einen Wert direkt zu übergeben und auch einen Wert direkt zurückgeben zu lassen. Das macht aber noch 2 Parameter (also eigentlich 1 Parameter und 1 Rückgabewert) mehr und der Sende-Syscall für synchrones IPC hat jetzt schon 11 Parameter und 3 Rückgabewerte (so das es dann 12 Parameter und 4 Rückgabewerte wären). Register hab ich genug dafür also eigentlich ist das ne gute Idee. Bei den Syscalls gibt man ja auch immer ne Nummer mit (auf x86 meist in EAX) damit der Kernel weiß was er zu tun hat, das wäre bei den IPC-Handlern auch keine schlechte Idee, damit erspare ich mir die Anforderung das man dem Request selber entnehmen können muss welcher Request genau vorliegt (also jeweils der erste Member in den Strukturen oben kann damit entfallen).

Ich muss ehrlich sagen, das ich nicht genau weiß wie das z.B. unter Windows läuft, ....
So weit ich das weiß bindet eine WIN32-Konsolen-Applikation eine passende DLL ein welche dann das Fenster generiert, dafür einen Message-Handler registriert (ich glaube unter Windows kann man Messages nur an Fenster schicken, würde ja auch Sinn ergeben) und der libc dann cin, cout und cerr zur Verfügung stellt.

Es ging mir darum, das du immer mit allem rechnen musst. Ansonsten fällt das dann unter die Kategorie Sicherheitslücke/Bug was irgendwie ausgenutzt werden könnte.
Eben deshalb halte ich es für Sinnvoll einer Applikation keine Messages zu schicken die diese gar nicht explizit angefordert hat, man kann doch nicht von einem Hello-World-Programm erwarten mit allem fertig zu werden. Daher ist es mir wichtig das ein Client eben nicht auch zwangsläufig Service sein muss nur um seine Antwort erhalten zu können.
Um noch mal auf das Beispiel mit den TCP-Ports zurück zu kommen, wenn Dein Browser eine HTTP-Verbindung (per TCP) zu irgendeinem Server aufbaut dann sagt er dem TCP-Stack "gib mir eine Verbindung zu IP ?.?.?.? Port 80" dazu nimmt der TCP-Stack einen beliebigen unbenutzten lokalen TCP-Port (er braucht ja eine vollständige Absenderadresse) und eröffnet damit eine Verbindung zum Server. Wenn jetzt ein anderer Computer im iNetz ein TCP-Paket an Deine IP-Adresse mit dem Port jener Verbindung schickt dann geht das ins Leere weil der Port ja eben nicht als Server-Socket sondern als Client-Socket (zu genau der einen angeforderten Gegenstelle) benutzt wird. Stell Dir mal vor was der Browser denken würde wenn er jetzt wegen diesem Port (er weiß üblicherweise noch nicht mal welche Portnummer der TCP-Stack für diese Verbindung ausgewürfelt hat) mit ankommenden Verbindungswünschen konfrontiert wird. Wenn die TCP-Spezifikation so ein unsinniges Verhalten vorschreiben würde wäre das ein massives Sicherheitsproblem. In einem OS ist das ein lustiges Einfallstor für DoS-Angriffe von einem Programm auf ein anderes Programm.

Ich wollte halt nur darauf hinaus das du synchron damit verbindest das du automatisch auch gleich eine Antwort bekommst und das hat damit nichts zu tun.
Ja, da hast Du recht. Sicher kann man das etwas unterschiedlich definieren aber da kommen wir dann bald zum Haarespalten. Ich weiß jetzt was du mit "synchron" meinst und ich gehe davon aus das Du weißt was ich mit "synchron" meine.

Denn du musst ja auch die Möglichkeit haben einfach eine Msg zu senden ohne eine Antwort zu wollen (Bsp. Server) und da macht sich dann asynchron einfach besser.
Deswegen will ich ja beides anbieten, ganz ohne asynchronen Messages geht ein Micro-Kernel-OS eben nicht.

Ansonsten ist natürlich aus Programmierersicht synchron viel sympatischer.
Schon weil das einfach der häufigere Anwendungsfall ist, das meiste läuft eben auf RPC hinaus.

Ich weiß jetzt nicht wie du das machen willst, aber wenn ein anderer Thread aufgerufen wird (was ja praktisch beim RPC passiert), dann kommt es auf die Scheduling-Strategie an, wann er läuft.
Ich möchte für synchrones IPC den Sheduler gar nicht bemühen. Der Client macht seinen Syscall für das IPC (damit landet die CPU im System-Mode), der passende Syscall-Handler markiert den aktuellen Thread als "blockiert wegen sync. IPC" und sichert seine wichtigsten Register in dessen Thread-Strukt, dann wird ein neuer Thread im Prozess des gewünschten Service erstellt, der mitgegebene Speicher wird in den Service-Prozess eingeblendet, der Stack und die Register für den neuen Thread vorbereitet und als letztes wird dieser PopUp-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Die Zeitscheibe des Callers läuft auch noch weiter (im System-Mode läuft die aber nicht weiter). Der Service-Thread erledigt seine Aufgabe und macht am Ende ein 'syscall_msg_sync_handler_end()', wenn das noch vor Ablauf der geerbten Zeitscheibe passiert wurde eben gar nichts geschedult. Im Syscall für 'syscall_msg_sync_handler_end()' (die CPU ist wieder im System-Mode) wird der geerbte Speicher wieder aus dem Service-Prozess entfernt, der PopUp-Thread gekillt, die gesicherten Register vom Client-Thread wieder hergestellt, die syscall_msg_sync_handler_end()-Parameter in die richtigen syscall_msg_sync_send()-Rückgabewert-Register geladen und als letztes der Client-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Es gab also genau 4 CPU-Mode-Wechsel und der Scheduler wurde nicht bemüht falls die Zeitscheibe des Clients noch gereicht hat. Ich denke das ist der schnellst mögliche Weg für synchrones IPC. Falls der Job beim Service doch länger dauert und dieser unterbrochen wird kommt es natürlich auch auf den Scheduller an wann der Job weiter bearbeitet wird.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #51 am: 27. August 2010, 14:44 »
Zitat von: erik
So weit ich das weiß bindet eine WIN32-Konsolen-Applikation eine passende DLL ein welche dann das Fenster generiert, dafür einen Message-Handler registriert (ich glaube unter Windows kann man Messages nur an Fenster schicken, würde ja auch Sinn ergeben) und der libc dann cin, cout und cerr zur Verfügung stellt.
So ungefähr habe ich mir das auch vorgestellt. Der Punkt ist nun aber, das dieses Fenster jede Art von Msg bekommen kann (das Fenster hat sich geändert, eine Taste wurde gedrückt, mit der Maus wurde geklickt, ...) und halt auch damit umgehen können muss.

Sicherlich werde ich bei mir davon gebrauch machen, das ich sagen kann auf diesen Port darf nur ein bestimmter Prozess senden, aber das ändert halt an unterschiedlich langen Msgs nichts.

Pass auf, du hast einen allgemeinen Port bei deinem VFS Service und der muss halt verschiedene unterschiedlich lange Nachrichten entgegen nehmen. Du weißt nicht wann welche Nachricht ankommt.

Du holst also eine Nachricht ab mit der Meinung das die angegebene Länge des Buffers für die Nachricht reicht, was ist aber wenn das nicht der Fall ist?

Zu deinem Bsp mit dem TCP Paket an einen Port. Um das jetzt mal auf das umzumünzen was ich meine, stell dir einfach vor das du ja unterschiedlich lange Pakete bekommen kannst, dies aber vor dem Empfang nicht wissen kannst. Einfachste Möglichkeit ist natürlich einfach einen Buffer zu nehmen, der das größte mögliche Paket repräsentiert, dann bist du immer auf der sicheren Seite.

Zitat von: erik
Ich möchte für synchrones IPC den Sheduler gar nicht bemühen. Der Client macht seinen Syscall für das IPC (damit landet die CPU im System-Mode), der passende Syscall-Handler markiert den aktuellen Thread als "blockiert wegen sync. IPC" und sichert seine wichtigsten Register in dessen Thread-Strukt, dann wird ein neuer Thread im Prozess des gewünschten Service erstellt, der mitgegebene Speicher wird in den Service-Prozess eingeblendet, der Stack und die Register für den neuen Thread vorbereitet und als letztes wird dieser PopUp-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Die Zeitscheibe des Callers läuft auch noch weiter (im System-Mode läuft die aber nicht weiter). Der Service-Thread erledigt seine Aufgabe und macht am Ende ein 'syscall_msg_sync_handler_end()', wenn das noch vor Ablauf der geerbten Zeitscheibe passiert wurde eben gar nichts geschedult. Im Syscall für 'syscall_msg_sync_handler_end()' (die CPU ist wieder im System-Mode) wird der geerbte Speicher wieder aus dem Service-Prozess entfernt, der PopUp-Thread gekillt, die gesicherten Register vom Client-Thread wieder hergestellt, die syscall_msg_sync_handler_end()-Parameter in die richtigen syscall_msg_sync_send()-Rückgabewert-Register geladen und als letztes der Client-Thread als running (auf der aktuellen CPU) markiert und hinein gesprungen (damit ist die CPU wieder im User-Mode). Es gab also genau 4 CPU-Mode-Wechsel und der Scheduler wurde nicht bemüht falls die Zeitscheibe des Clients noch gereicht hat. Ich denke das ist der schnellst mögliche Weg für synchrones IPC. Falls der Job beim Service doch länger dauert und dieser unterbrochen wird kommt es natürlich auch auf den Scheduller an wann der Job weiter bearbeitet wird.
Wow ... du willst aber ganz schön in die Prozesse eingreifen. Das klingt ja schon fast nach nem Signal Handler, der halt als Thread implementiert ist.

Das der Service-Thread die Zeit und Priorität vom Client-Thread bekommt gibt es glaub ich auch schon woanders. In irgendeinem Hobby-OS hatte das jemand schonmal probiert und wirklich was gebracht hatte es da glaub ich nicht.

Ich stelle mir das zwar kompliziert vor sowas zu implementieren, aber damit hättest du dann sowas wie eine Prioriäten Warteschlange für Msgs.

Was ich auch noch wollte. Ansich lassen sich unsere verschiedenen Ansichten eh nicht so einfach miteinander vergleichen, da du keinen "flat address space" hast, was ansich heutezutage die Normalität darstellt.
Ich würde auch sagen, das du dir da ziemliche Probleme mit aufhalst! Ich denke da an Fragmentierung und das es das Auslagern von Speicher auf einen Datenträger eher schwieriger macht bzw. solche Segmente dann gar nicht ausgelagert werden können (genaus wie ich nie SharedMem auslagern würde).

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #52 am: 27. August 2010, 15:55 »
Hallo,


Zitat von: erik
So weit ich das weiß bindet eine WIN32-Konsolen-Applikation eine passende DLL ein welche dann das Fenster generiert, dafür einen Message-Handler registriert (ich glaube unter Windows kann man Messages nur an Fenster schicken, würde ja auch Sinn ergeben) und der libc dann cin, cout und cerr zur Verfügung stellt.
So ungefähr habe ich mir das auch vorgestellt. Der Punkt ist nun aber, das dieses Fenster jede Art von Msg bekommen kann (das Fenster hat sich geändert, eine Taste wurde gedrückt, mit der Maus wurde geklickt, ...) und halt auch damit umgehen können muss.
Ich denke das ist das Problem dieser DLL, die muss eben einen vollständigen Fenster-Signal-Handler implementieren.

Pass auf, du hast einen allgemeinen Port bei deinem VFS Service und der muss halt verschiedene unterschiedlich lange Nachrichten entgegen nehmen. Du weißt nicht wann welche Nachricht ankommt.
Also in meinem Konzept gibt es dieses Problem nicht und auch das Konzept von tyndur hat dieses Problem nicht (denke ich zumindest da die Daten ja auf den Stack gepackt werden und außer das der Stack knapp werden könnte sind da ja erst mal keine bestimmten Größenbeschränkungen). Das dieses Problem durch das aktive Abholen der Messages entsteht ist Dir ja bewusst. Da musst Du jetzt die richtigen Design-Entscheidungen treffen um damit umgehen zu können.

Wow ... du willst aber ganz schön in die Prozesse eingreifen. Das klingt ja schon fast nach nem Signal Handler, der halt als Thread implementiert ist.
Ja, mein IPC-System greift ziemlich tief in die Strukturen rein die der Kernel verwaltet. Mein Konzept hat in der Tat mehr Ähnlichkeiten mit dem von tyndur (der wesentliche unterschied ist der das bei tyndur ein vorhandener Thread benutzt wird und ich dagegen einen neuen injizieren will) als mit Deinem Konzept. Aber Vielfalt ist ja schließlich was tolles und daher hoffe ich das Du Dein Konzept erfolgreich umsetzt.

Das der Service-Thread die Zeit und Priorität vom Client-Thread bekommt gibt es glaub ich auch schon woanders. In irgendeinem Hobby-OS hatte das jemand schonmal probiert und wirklich was gebracht hatte es da glaub ich nicht.
Wirklich viel bringen dürfte das auch nur auf gut ausgelasteten System und auch nur in der Hinsicht das die Applikationen mit höherer Priorität eben nicht so sehr von der Gesamt-Last beeinträchtigt werden wie die Applikationen mit niedrigerer Priorität. Man könnte damit z.B. einem Audio/Video-Player ermöglichen trotz hoher Systemlast flüssig zu arbeiten.

Ich stelle mir das zwar kompliziert vor sowas zu implementieren, aber damit hättest du dann sowas wie eine Prioriäten Warteschlange für Msgs.
Warum sollten auch die Wünsche einer Applikation mit niedriger Priorität jene Wünsche von Applikationen mit hoher Priorität behindern? Interessant wird es z.B. beim AHCI-Treiber, der könnte z.B. dafür sorgen das Jobs von Applikationen mit niedrigerer Priorität erst nach den Jobs von Applikationen mit höherer Priorität von der HDD abgearbeitet werden, das SATA-Protokoll bietet dafür einige Tricks.

Ansich lassen sich unsere verschiedenen Ansichten eh nicht so einfach miteinander vergleichen, da du keinen "flat address space" hast
Ich bin mir ziemlich sicher das sich mein IPC-Konzept auch auf Flat-Memory-Systemen implementieren lässt, ich sehe da jedenfalls keine Probleme. Wenn ich mit taljeth einig geworden wäre hätte ich das möglicherweise für tyndur umgesetzt.

Ich würde auch sagen, das du dir da ziemliche Probleme mit aufhalst! Ich denke da an Fragmentierung und das es das Auslagern von Speicher auf einen Datenträger eher schwieriger macht bzw. solche Segmente dann gar nicht ausgelagert werden können (genaus wie ich nie SharedMem auslagern würde).
Das ich mir da keine leichte Aufgabe gesucht hab ist mir bewusst, es soll ja auch ruhig eine richtige Herausforderung sein, um so größer ist der Genus wenn es läuft. In dem Thread http://forum.lowlevel.eu/index.php?topic=2470 (ab der 2. Seite) haben Svenska und ich recht intensiv über die Verwaltung von segmentiertem Speicher diskutiert.


Grüße
Erik
« Letzte Änderung: 27. August 2010, 16:00 von erik.vikinger »
Reality is that which, when you stop believing in it, doesn't go away.

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #53 am: 27. August 2010, 22:18 »
Zitat von: erik
Also in meinem Konzept gibt es dieses Problem nicht und auch das Konzept von tyndur hat dieses Problem nicht (denke ich zumindest da die Daten ja auf den Stack gepackt werden und außer das der Stack knapp werden könnte sind da ja erst mal keine bestimmten Größenbeschränkungen). Das dieses Problem durch das aktive Abholen der Messages entsteht ist Dir ja bewusst. Da musst Du jetzt die richtigen Design-Entscheidungen treffen um damit umgehen zu können.
Wieso gibt es das Problem bei deinem Konzept nicht? Ich meine du sagst ja bei dem Abholen der Nachricht wie groß diese sein darf, meine Frage ist nun was passiert wenn die Nachricht zu groß ist (aus welchem Grund auch immer)?
Was verstehst du unter aktivem und was unter passivem Warten?

Zitat von: erik
Warum sollten auch die Wünsche einer Applikation mit niedriger Priorität jene Wünsche von Applikationen mit hoher Priorität behindern? Interessant wird es z.B. beim AHCI-Treiber, der könnte z.B. dafür sorgen das Jobs von Applikationen mit niedrigerer Priorität erst nach den Jobs von Applikationen mit höherer Priorität von der HDD abgearbeitet werden, das SATA-Protokoll bietet dafür einige Tricks.
KISS? Ich meine wenn du nur "einfach" sagst, dass du die Nachrichten (in deinem Fall die sendenden Threads) nach der Priorität sortierst, dann finde ich das auch ungünstig. Denn dann kann es passieren das eine Nachricht mit niedriger Priorität verhungert, weil ständig Nachrichten mit höherer Priorität ankommen. Wenn du sowas machen willst, dann sollte die Zeit bei der Prioritätenberechnung auch eine Rolle spielen und dann finde ich wird das ganze wieder so komplex das es sich gar nicht mehr lohnt und du mit der einfachen Variante wieder besser dran bist ;)

Zitat von: erik
Ich bin mir ziemlich sicher das sich mein IPC-Konzept auch auf Flat-Memory-Systemen implementieren lässt, ich sehe da jedenfalls keine Probleme. Wenn ich mit taljeth einig geworden wäre hätte ich das möglicherweise für tyndur umgesetzt.
Ich habe mir mal die Beschreibung deines Hardwarekonzepts angeguckt. Daher kann ich sagen, das du bei deinem Konzept entweder ziemlich viel Speicher verschwendest (da deine Segmente ja an 4kb aligned und mindestens 256byte groß sein sollen (habe ich zumindest so gelesen/verstanden) oder du dir ne ziemliche Fragmentierung einhandelst (was übrigens auch mein größter Kritikpunkt an Nachrichten mit dynamischer Länge ist, da müsste dann schon ein verdammt guter MemoryManager dahinter stecken und der dürfte dann wieder nicht der schnellste sein).

Was das designen einer eigenen CPU angeht, das hätte ich wohl besser nicht lesen sollen. Jetzt habe ich richtig lust bekommen mich eventuell auch mal daran zu probieren ;) ("nur" mit Hilfe von Logisim)

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #54 am: 28. August 2010, 15:41 »
Hallo,


Wieso gibt es das Problem bei deinem Konzept nicht?
Ich dachte das hätte ich bereits deutlich genug erklärt. Hast Du meine letzten Beiträge wirklich gelesen? (Bitte nimm mir diese Frage nicht übel aber irgendwie bin ich verwundert)

Ich meine du sagst ja bei dem Abholen der Nachricht wie groß diese sein darf
Ich hole keine Nachrichten ab, ich bekomme diese Zugestellt! Der Service-Handler wird quasi vom Kernel aufgerufen, wie eine Art Call-Back, und bekommt die Message als Parameter mitgegeben. Die Funktion printf lässt sich doch auch mit beliebig langen Strings als Parameter aufrufen, es gibt keine bestimmten Grenzen, auf einer 64Bit-Plattform sollte ein sauber programmiertes printf sogar mit Strings in der Größe sehr vieler TBytes umgehen können.

Was verstehst du unter aktivem und was unter passivem Warten?
Ich schrieb von "aktivem Abholen" und darunter verstehe ich das der Service irgendeine Aktion aktiv ausführt um eine empfangene Nachricht zu bekommen (Du gehst quasi zur Post und musst das hoffentlich passende Transportbehältnis mitbringen, während ich möchte das ein Postbote zu mir kommt und dieser garantiert ein passendes Transportbehältnis von der Paket-Zentrale bekommen hat), das diese Aktion eventuell blockiert (Du in der Post warten/schlafen musst) falls noch keine Nachricht da ist spielt in dieser Betrachtung keine Rolle.

KISS? Ich meine wenn du nur "einfach" sagst, dass du die Nachrichten (in deinem Fall die sendenden Threads) nach der Priorität sortierst, dann finde ich das auch ungünstig. Denn dann kann es passieren das eine Nachricht mit niedriger Priorität verhungert, weil ständig Nachrichten mit höherer Priorität ankommen. Wenn du sowas machen willst, dann sollte die Zeit bei der Prioritätenberechnung auch eine Rolle spielen und dann finde ich wird das ganze wieder so komplex das es sich gar nicht mehr lohnt und du mit der einfachen Variante wieder besser dran bist
Wenn die Applikationen mit hoher Priorität den kompletten Computer beanspruchen um ihre Arbeiten zu erledigen dann verhungern natürlich die Applikationen mit niederer Priorität aber das ist ja gewollt, genau deswegen gibt es ja extra die Prioritäten damit das OS weiß welche Dinge dem User wichtig sind (also mit Vorrang bearbeitet werden müssen) und welche nicht (also warten dürfen). Bei dem Scheduler könnte man natürlich eine Art Fairness-Regel einbauen damit auch Applikationen mit niedrigerer Priorität nicht komplett verhungern, Windows kann das angeblich, aber davon ob das wirklich sinnvoll ist bin ich nicht überzeugt. Prioritäten sind ein Werkzeug um dem Computer zu sagen welche Wichtigkeit eine Aufgabe hat, es liegt am User dieses Werkzeug richtig zu nutzen. Wenn Du möchtest das ein Audio-Player im Hintergrund Musik spielt und diese nicht ins stocken geraten darf dann gibst Du dem Audio-Player eine hohe Priorität, wenn dadurch die Applikation im Vordergrund spürbar beeinträchtigt wird brauchst Du entweder einen schnelleren Computer oder Du musst Deine Ansprüche ändern so das die Musik auch mal stocken darf (der Audio-Player also keine hohe Priorität bekommt). Prioritäten machen Deinen Computer nicht schneller sie sorgen nur dafür das die vorhandene Performance gemäß Deiner Präferenzen genutzt wird.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #55 am: 28. August 2010, 16:02 »
Zitat von: erik
Ich dachte das hätte ich bereits deutlich genug erklärt. Hast Du meine letzten Beiträge wirklich gelesen? (Bitte nimm mir diese Frage nicht übel aber irgendwie bin ich verwundert)
Nein, die Frage war berechtigt ;) Ich vergesse immer das du Segmente nutzt! Bzw. dein Bsp.code vom fread irritiert mich da ein wenig. Wenn du Segmente benutzt dann macht es natürlich Sinn, das du ein Segment zurück bekommst und die Nachricht nicht erst in einen Buffer kopieren musst, aber bei deinem Bsp. übergibst du einen Pointer auf einen Buffer und die Länge des Buffers, das hat mich total durcheinander gebracht.

Was passiert eigentlich mit dem Segment auf der Senderseite? Kann der noch schreibend auf das Segment zugreifen? Und wie bekommt/erstellt er das Segment? Weil du ja auf 4kb aligned Segmente haben willst, muss sich der Sender darum kümmern dass der Speicher das ist oder fragt er nach einem passendem Segment?

Ich sag mal Segmente sind für sowas natürlich ganz praktische, es geht schneller (da kein kopieren) und du hast wesentlich besseren Schutz (weil man nicht außerhalb des Segments lesen/schreiben kann), nur die Verwaltung dürfte ziemlich anstrengend werden.

Zitat von: erik
Wenn die Applikationen mit hoher Priorität den kompletten Computer beanspruchen um ihre Arbeiten zu erledigen dann verhungern natürlich die Applikationen mit niederer Priorität aber das ist ja gewollt, genau deswegen gibt es ja extra die Prioritäten damit das OS weiß welche Dinge dem User wichtig sind (also mit Vorrang bearbeitet werden müssen) und welche nicht (also warten dürfen). Bei dem Scheduler könnte man natürlich eine Art Fairness-Regel einbauen damit auch Applikationen mit niedrigerer Priorität nicht komplett verhungern, Windows kann das angeblich, aber davon ob das wirklich sinnvoll ist bin ich nicht überzeugt. Prioritäten sind ein Werkzeug um dem Computer zu sagen welche Wichtigkeit eine Aufgabe hat, es liegt am User dieses Werkzeug richtig zu nutzen. Wenn Du möchtest das ein Audio-Player im Hintergrund Musik spielt und diese nicht ins stocken geraten darf dann gibst Du dem Audio-Player eine hohe Priorität, wenn dadurch die Applikation im Vordergrund spürbar beeinträchtigt wird brauchst Du entweder einen schnelleren Computer oder Du musst Deine Ansprüche ändern so das die Musik auch mal stocken darf (der Audio-Player also keine hohe Priorität bekommt). Prioritäten machen Deinen Computer nicht schneller sie sorgen nur dafür das die vorhandene Performance gemäß Deiner Präferenzen genutzt wird.
Also bei deinem Bsp gibt es aber einen Fehler (wie ich finde, du kannst da natürlich anderer Meinung sein), das Programm im Vordergrund sollte immer bevorzeugt werden und nicht irgendein Programm im Hintergrund.

Ansich sieht man eigentlich schon zu das es sowas wie verhungern nicht gibt bzw. das es nicht passiert. Also bei meinem momentanen Scheduler passiert das auch nicht (aber was noch nicht ist kann noch werden ;) ).

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #56 am: 29. August 2010, 09:27 »
Hallo,


Ich vergesse immer das du Segmente nutzt! Bzw. dein Bsp.code vom fread irritiert mich da ein wenig.
Der würde in einem Flat-Memory-System ganz genau so aussehen. Ich bin der Meinung das jeder Beispiel-Code, den ich in diesem Thread gepostet habe, auch auf einem Flat-Memory-System ganz exakt genau so aussehen würde. Auch die Funktionalität wäre die selbe, der einzigste Unterschied ist das man bei segmentiertem Speicher eben Segmente fürs Memory-Sharing benutzt und in einem MMU-basiertem Flat-Memory-System eben Pages. Auch die detaillierte Beschreibung meines IPC-Vorgangs, am Ende meines Beitrags vom 27.08. um 12:49, lässt sich 1 zu 1 auf Flat-Memory übertragen (deswegen hatte ich ja angeboten das für tyndur umzusetzen). Wenn Du das anders siehst dann erkläre mir das Bitte mal genau.

das du ein Segment zurück bekommst und die Nachricht nicht erst in einen Buffer kopieren musst
Wo bitte bekomme ich etwas "zurück"? Der Client gibt seinen Speicher für den Service "frei" und der Service ließt daraus was er zu tun hat und legt auch die Daten dort hinein. Das ist so als würdest Du am Buffet der Bedienung Deinen Teller hin halten mit einem Zettel drauf auf dem steht was Du drauf getan haben möchtest, die Bedienung tut das und signalisiert Dir wenn Du Deinen Arm wieder einholen darfst. Es war die ganze Zeit über Dein Teller und die Bedienung hat direkt damit gearbeitet anstatt Deine Wünsche erstmal lokal auf einem eigenen (internen) Teller zusammen zu stellen und am Ende dann umzuladen.

Was passiert eigentlich mit dem Segment auf der Senderseite? Kann der noch schreibend auf das Segment zugreifen?
Ja, aber der Client sollte so lange diesen Speicher nicht anfassen. Um bei dem Beispiel mit dem Buffet zu bleiben, da Du Deinen Teller immer noch selber in der Hand hällst kannst Du natürlich auch was drauf tun aber weil Du dabei wohl mit der Bedienung kollidieren würdest solltest Du es besser lassen. Bei meinem IPC-Konzept ist es so das der Cleint-Thread für diese Zeit blockiert (also schläft) und dadurch eigentlich gar nichts tun kann, natürlich könnte noch ein anderer Thread, aus dem Client-Prozess, auf diesem Speicher rumarbeiten aber das müsste man extra Programmieren und wer das tut ist eben selber schuld. Am Buffet würde das bedeuten das Du für die Dauer der Bearbeitung durch die Bedienung in Stase versetzt wirst, Du könntest zwar vorher dafür sorgen das z.B. ein anderes Mitglied Deiner Familie auch Zugriff auf Deinen Teller hat, während dieser von der Bedienung bearbeitet wird, aber das ist eben extra Aufwand.

Und wie bekommt/erstellt er das Segment?
Der Client erstellt gar kein Segment für IPC, er nimmt den Speicher den er schon hat und interessiert sich nicht dafür in welchem seiner Segmente dieser Speicher liegt.

Weil du ja auf 4kb aligned Segmente haben willst, muss sich der Sender darum kümmern dass der Speicher das ist oder fragt er nach einem passendem Segment?
Es ist Aufgabe des Kernels sich um diese Details zu kümmern und Aufgabe des CPU-Entwicklers dem Kernel das passende Werkzeug zu geben.

Ich sag mal Segmente sind für sowas natürlich ganz praktische, es geht schneller (da kein kopieren) und du hast wesentlich besseren Schutz (weil man nicht außerhalb des Segments lesen/schreiben kann), nur die Verwaltung dürfte ziemlich anstrengend werden.
Ja, das stimmt und war auch einer der Gründe warum ich diese Idee tatsächlich umsetzen will. Ich möchte beweisen das man mit Segmentierung ein sehr sicheres und gleichzeitig schnelles System erstellen kann, Segmente also kein überholtes Relikt vergangener Zeiten sind.


Also bei deinem Bsp gibt es aber einen Fehler (wie ich finde, du kannst da natürlich anderer Meinung sein), das Programm im Vordergrund sollte immer bevorzeugt werden und nicht irgendein Programm im Hintergrund.
Also wenn im Hintergrund der VLC läuft dann möchte ich schon das er bevorzugt wird auch wenn ich im Vordergrund einen längeren Compiler-Job starte, schließlich soll die Musik ja flüssig weiter laufen. Mir ist bewusst das der Compiler-Job dadurch (minimal) Verlangsamt wird aber das gehe ich ein wenn ich dafür beim warten wenigstens Musik hören kann (wenn ich will das der Compiler so schnell wie möglich fertig wird dann darf ich eben keine Musik hören).


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #57 am: 29. August 2010, 09:48 »
Also gut folgender Code irritiert mich ein wenig:
  File_Read_Responce_t responce;
  ulong responce_length = ~0;

  //ein blockierender Syscall (synchrones IPC) :
  const long retval = syscall_msg_sync_send(vfs_msg_id,&request,sizeof(request),NULL,0,&responce,&responce_length,sizeof(responce),data,&fread_length,length);
Die erstellst auf deinem Stack den Speicher für "response", der hat ne bestimmte Größe (entspricht der Struktur die hinter "File_Read_Response_t" steckt) die du ja auch dem Syscall mitgibts.
Ich habe das jetzt so verstanden das er also die Nachricht dort in den Speicher von "responce" und nur der Länge "sizeof(responce)" reinkopiert.

Zitat von: erik
Der Client erstellt gar kein Segment für IPC, er nimmt den Speicher den er schon hat und interessiert sich nicht dafür in welchem seiner Segmente dieser Speicher liegt.
Das Problem was ich hier nicht ganz verstehe ist, er muss also sehr wohl irgendwann mal ein Segment mit der Größe der Nachricht erstellt haben oder er nimmt einfach einen Buffer der irgendwo in irgendeinem Segment liegt.
Ist letzteres der Fall hast du doch aber ein Problem, da du entweder das ganze Segment mit dem Buffer einblenden musst (was dann wieder nicht sicher ist, da da ja auch andere Daten drin stehen können) oder du musst aus diesem Buffer ein neues Segment machen, was halt nicht immer geht, da du ja deine Segmente an 4kb ausrichtest/ausrichten musst.
Also entweder fordert der Sender vorher ein Segment entsprechend seines benötigten Buffers vom Kernel an oder er muss sich darum kümmern das der Buffer auch an 4kb ausgerichtet wird.

Das selbe Problem hast du wenn du es auf nem Flat-Memory-System umsetzen willst. Der Server muss dann dafür sorgen das der Buffer für die Nachricht an 4kb (Pagegröße) ausgerichtet ist und das nichts anderes in dieser Page steht, dann kannst du das mit dem einblenden machen, aber ansonsten nicht.

Wenn ich mir jetzt mal vorstelle, das der Sender die Nachricht selbst noch für was benutzen will und der Empfänger auch ganz gerne mit der Nachricht arbeiten will, dann muss einer von beiden die Nachricht doch in einen Buffer kopieren, dieser Schritt entfällt bei mir natürlich.

Zitat von: erik
Es ist Aufgabe des Kernels sich um diese Details zu kümmern und Aufgabe des CPU-Entwicklers dem Kernel das passende Werkzeug zu geben.
Wie gesagt ist meine Frage dann, was macht der Kernel wenn die Nachricht nicht an 4kb ausgerichtet ist?

Zitat von: erik
Ja, das stimmt und war auch einer der Gründe warum ich diese Idee tatsächlich umsetzen will. Ich möchte beweisen das man mit Segmentierung ein sehr sicheres und gleichzeitig schnelles System erstellen kann, Segmente also kein überholtes Relikt vergangener Zeiten sind.
Ich weiß jetzt nicht was dafür alles in Hardware getan werden muss, könnte mir aber vorstellen, dass wenn du komfortabel mit Segmenten arbeiten willst, der Hardwareoverhead in keinem Verhältnis dazu steht. Bzw. müssten dann auch noch alle Compiler neu geschrieben werden und auch diese Arbeit ist nicht zu unterschätzen. Denn ansich sind alle Compiler auf Flat-Memory ausgelegt (inzwischen, denn früher mussten sie ja auch mit Segmenten klarkommen).

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #58 am: 29. August 2010, 12:41 »
Hallo,


Also gut folgender Code irritiert mich ein wenig:  File_Read_Responce_t responce;
  ulong responce_length = ~0;

  //ein blockierender Syscall (synchrones IPC) :
  const long retval = syscall_msg_sync_send(vfs_msg_id,&request,sizeof(request),NULL,0,&responce,&responce_length,sizeof(responce),data,&fread_length,length);
Die erstellst auf deinem Stack den Speicher für "response", der hat ne bestimmte Größe (entspricht der Struktur die hinter "File_Read_Response_t" steckt) die du ja auch dem Syscall mitgibts.
Ich habe das jetzt so verstanden das er also die Nachricht dort in den Speicher von "responce" und nur der Länge "sizeof(responce)" reinkopiert.
In Response kommt nur die Antwort für fread also der Error-Code und vielleicht noch was anderes kleines. Der Buffer für die eigentlichen Nutzdaten wird mit den letzten 3 Parametern (beim Syscall) übergeben. Deshalb möchte ich immer 2 Nachrichten pro Richtung benutzen, einmal Request/Response und einmal Nutzdaten schreiben/lesen. Dabei ist es durchaus zulässig wenn nicht benutzte Buffer mit NULL + Länge 0 übergeben werden (siehe Parameter 4 und 5 vom Syscall, da in dem Beispiel ja keine Nutzdaten zu schreiben sind).

Das Problem was ich hier nicht ganz verstehe ist, er muss also sehr wohl irgendwann mal ein Segment mit der Größe der Nachricht erstellt haben oder er nimmt einfach einen Buffer der irgendwo in irgendeinem Segment liegt.
Letzteres.

Also entweder fordert der Sender vorher ein Segment entsprechend seines benötigten Buffers vom Kernel an oder er muss sich darum kümmern das der Buffer auch an 4kb ausgerichtet wird.
Weder noch. Beim Service ist einfach ein bischen mehr Speicher vom Client zu sehen als tatsächlich benötigt wird. Ich muss zwar meine Segmente auf 4kBytes (oder auf der 64Bit-Plattform auf 64kBytes), also kleinste Page-Größe, ausrichten (weil in einer Page niemals mehrere Segmente drin sein dürfen, wegen Defragmentieren und Swappen) aber wo die zugreifbaren Daten Anfangen und Enden kann ich jeweils auf 256 Bytes genau festlegen (theoretisch sogar noch genauer). Das bedeutet das im Worst-Case der Service vom Client pro Buffer bis zu 510 Bytes (2 * 255) Speicher sieht in dem er eigentlich nichts zu suchen hat, das ist zwar ein gewisses Risiko aber ich denke das ist verschmerzbar. Bei einem Monolithischen Kernel sieht der Kernel alles von der Applikation da ist mein Konzept schon mal ein echter Fortschritt. Wenn die Applikation das nicht will kann sie immer noch einen ausgerichteten Buffer benutzen, Vorne und Hinten 255 Bytes Platz lassen oder explizit das Kopieren beim Kernel anfordern. Sehr kleine Datenmengen (< ca. 4 kBytes) werden wohl immer kopiert, davon wären im obigen Beispiel Request und Responce betroffen so das der Service zumindest nicht im Stack-Frame der fread-Funktion rumschnüffeln kann.

Das selbe Problem hast du wenn du es auf nem Flat-Memory-System umsetzen willst. Der Server muss dann dafür sorgen das der Buffer für die Nachricht an 4kb (Pagegröße) ausgerichtet ist und das nichts anderes in dieser Page steht, dann kannst du das mit dem einblenden machen, aber ansonsten nicht.
Beim Paging existiert natürlich das selbe Problem nur etwas größer, hier sind es im Worst-Case 8190 Bytes, oder man kopiert zumindest die erste und letzte Page (was ich bei meinen Segmenten nicht so einfach machen kann).

Wenn ich mir jetzt mal vorstelle, das der Sender die Nachricht selbst noch für was benutzen will und der Empfänger auch ganz gerne mit der Nachricht arbeiten will, dann muss einer von beiden die Nachricht doch in einen Buffer kopieren, dieser Schritt entfällt bei mir natürlich.
Das wäre dann aber asynchrones I/O, also z.B. ein Schreibzugriff wo Du die Daten nur ans VFS übergen willst um so schnell wie möglich weitermachen zu können, hierfür müsste man dann eben wieder einen neuen VFS-Job definieren der die Nutzdaten nur in einen Cache kopiert und dann möglichst schnell zum Client zurückkehrt. Zumindest für das Kopieren wird der Client aber blockieren müssen (egal ob das Kopieren vom Kernel, im Syscall, oder vom Service, im IPC-Handler, gemacht werden muss, der zeitliche Unterschied beider Varianten dürfte sehr minimal sein).


Ich weiß jetzt nicht was dafür alles in Hardware getan werden muss, könnte mir aber vorstellen, dass wenn du komfortabel mit Segmenten arbeiten willst, der Hardwareoverhead in keinem Verhältnis dazu steht. Bzw. müssten dann auch noch alle Compiler neu geschrieben werden und auch diese Arbeit ist nicht zu unterschätzen. Denn ansich sind alle Compiler auf Flat-Memory ausgelegt (inzwischen, denn früher mussten sie ja auch mit Segmenten klarkommen).
Der Hardware-Aufwand sind Segmentregister, so wie bei x86 auch nur das ich davon 16 haben will und nicht blos 6. Segmentregister sind kein Hexenwerk, es läuft im wesentlichen auf jeweils ein Satz Schattenregister für den dekodiertem Segment-Descriptor hinaus. Beim eigentlichen Speicherzugriff kommt eine Addition (für die Segment-Basis) dazu und noch 2 Vergleiche ob das Offset zwischen Minimum und Maximum liegt, alle 3 Operationen können parallel ablaufen und sind auch recht schnell.
Das mit den Compilern ist allerdings ein Problem. Ich habe mich deswegen mal genauer mit dem LLVM beschäftigt und denke das dieser der richtige Kandidat ist weil er Pointer nicht auf Integer abbildet sondern als eigenständigen Basis-Daten-Typ betrachtet (neben Integer und Float). Beim Register-Allocator (wenn dann aus der internen Datenflussdarstellung Assemblercode generiert wird) muss ich dann eben berücksichtigen das ein Pointer immer 2 Register belegt, ein Segment-Register (für den Selector) und ein normales Register (für das Offset). Ein neues Back-End für den LLVM zu schreiben soll angeblich gar nicht mal schwer sein, für einfache RISC-CPUs (was auf meine CPU ja zutrifft) wurde da mal von unter 6 Monaten geschrieben. Für viele übliche Problem sind im LLVM-Framework bereits recht brauchbare generische Lösungen enthalten, die dürften für meine CPU zwar nicht immer gleich das optimale Ergebnis bringen aber wenigstens ein funktionierendes Ergebnis.


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

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #59 am: 29. August 2010, 20:22 »
Zitat von: erik
Bei einem Monolithischen Kernel sieht der Kernel alles von der Applikation da ist mein Konzept schon mal ein echter Fortschritt.
Ähm, der Kernel sieht immer alles von der Applikation. Was genau meinst du in dem Fall?

Zitat von: erik
Beim Service ist einfach ein bischen mehr Speicher vom Client zu sehen als tatsächlich benötigt wird. Ich muss zwar meine Segmente auf 4kBytes (oder auf der 64Bit-Plattform auf 64kBytes), also kleinste Page-Größe, ausrichten (weil in einer Page niemals mehrere Segmente drin sein dürfen, wegen Defragmentieren und Swappen) aber wo die zugreifbaren Daten Anfangen und Enden kann ich jeweils auf 256 Bytes genau festlegen (theoretisch sogar noch genauer). Das bedeutet das im Worst-Case der Service vom Client pro Buffer bis zu 510 Bytes (2 * 255) Speicher sieht in dem er eigentlich nichts zu suchen hat, das ist zwar ein gewisses Risiko aber ich denke das ist verschmerzbar.
Was ist wenn der Empfänger jetzt in den Buffer schreiben will? Muss er dann kopieren oder darf er in das Segment schreiben?
Damit (wenn er in das Segment schreiben darf) gibt es dann 2 Probleme, 1. kann er dann im ungünstigsten Fall Daten beim Empfänger ändern und der Sender kann mit den Daten in dem Segment praktisch nichts mehr anfangen, da sie ja nicht mehr dem entsprechen was er mal gesendet hat.
Das letzte Problem ist besonders fürs VFS entscheidend. Denn du willst ja das Kopieren vermeiden, wenn der Empfänger aber die Daten ändern darf, dann kannst du sie nicht mehr einfach an jeden Clienten senden (der die Datei lesen möchte), da sie nicht unbedingt dem entsprechen muss was eigentlich in der Datei steht.
Darf der Empfänger nicht in das Segment schreiben, musst du ja doch wieder kopieren, damit er das machen kann.

Also sparst du im Endeffekt 1 Kopiervorgang (aber auch das wären im "schlimmsten" Fall 50% Ersparnis).

 

Einloggen