Autor Thema: [erledigt] IPC - Callback Funktion registrieren  (Gelesen 25005 mal)

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #20 am: 02. July 2010, 13:34 »
Hallo,


man hätte man ja auch so machen können, dass eine variable wie bool has_message auf true gesetzt wird.
Du meinst der Message-Handler setzt nur eine einzelne Variable und das Programm muss pollen? Wo ist da der Vorteil gegenüber einem Syscall "HabIchNeMessageDannGibMirDieGleichAnsonstenWarteNicht()"? Okay der Syscall benötigt etwas mehr CPU-Takte aber das Prinzip ist exakt das selbe.

hab ich dich richtig verstanden, dass mein beispiel oben besser ist?
Du meinst das von heute Früh 8:44? Das ist ungefähr die einfachste Lösung. Die kann man am schnellsten implementieren, bietet nur sehr wenig Fehlerpotential und ist für einen Monolithen erstmal recht brauchbar. Das Problem ist wie wartest Du auf mehrere verschiedene Events? Ein HTTP-Server mit sehr vielen gleichzeitig zu bearbeitenden Anfragen wird wohl kaum für jede TCP-Connection einen eigenen Thread starten (das geht zwar macht man aber nur bei Servern die nur wenig belastet sind). In der Win-API gibts dafür WaitForMultipleObject(), das stellt eine recht brauchbare Lösung für das Problem dar bis Du auf einer Multi-Core-Maschine auf die Idee kommst für jede CPU einen eigenen Thread starten zu wollen wo dann alle gleichzeitig WaitForMultipleObject() aufrufen wollen, auch das ist lösbar aber Du verstehst bestimmt was ich meine.

Für eine ordentliche Lösung muss man schon ziemlich tief in die Trickkiste greifen. Du solltest also erst mal überlegen was eigentlich Dein Ziel ist und dann prüfen welchen Aufwand Du bereit bist dafür zu investieren.


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

LittleFox

  • Beiträge: 306
    • Profil anzeigen
    • LF-Net.org
Gespeichert
« Antwort #21 am: 02. July 2010, 14:12 »
momentan werd ich  wohl für alles die einfachste Lösung nehmen.
Was ich dann später mache, weiß ich noch nicht.

mit dem Handler hätte es einen winzigsten Geschwindigkeitsvorteil gebracht, weil das Programm nur Nachrichten abruft, wenn welche da sind.

Ich werde jetzt
int main()
{
// initialisierung des programs
bool is_running = true;
MESSAGE message;
while(is_running)
{
message = get_message();
do_something_with_the_message(message);
}
diese lösung nehmen.

Danke an alle für die Hilfe.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #22 am: 02. July 2010, 14:31 »
Hallo,

Naja, die Arbeit geht vom Programm aus, nie vom Kernel.
Das trifft nicht immer zu, es gibt auch Dinge die immer vom Kernel ausgehen, könnten bei einem Monolithen eventuell sogar mehr sein als bei einem Micro-Kernel.
Hier ging es aber um die Kommunikation zwischen Kernel und Userland. Sachen, die vom Kernel ausgehen, bleiben meist auch da drin. Bei einem Mikrokernel - wenn z.B. die Tastatur etwas raushaut und die Speicherverwaltung reagieren muss - ist das etwas anderes, aber dort bleibt es trotzdem "im" Kernel.

Ich sehe bei Callback-Funktionen immer das Problem des Programmabsturzes, wenn der Handler nicht mehr existiert aber noch registriert ist. Dann hast du nämlich ein Problem. (In einem Mikrokernel ist das relativ egal, wenn die Speicherverwaltung abstürzt, ist das System eh im Popöchen.)

Alternativ gibt es nicht-blockierende Syscalls, die dann später die Antworten zurückliefern.
Das bedeutet dass das Programm mehrere Syscalls benötigt, einen um den Job beim Kernel zu starten und einen anderen um nach dem Ergebnis zu fragen. Der zweite Syscall könnte oft sogar mehrmals benötigt werden falls der Job eben noch nicht fertig ist.
Ich sehe kein Problem darin.

read() muss ja nicht blocken und liefert dann -EAGAIN zurück. Blockierendes Verhalten wird immer dann schwierig, wenn man quasiparalleles, asynchrones Verhalten in nur einem Thread erzeugen möchte, aus welchen Gründen auch immer. Aber als Haupt-API würde ich nie ein Callback-Verhalten verwenden.

Persönlich finde ich Callback-Funktionen ungünstig, da sie die Anwendungsentwicklung erschweren (asynchroner Programmfluss). Ein synchroner Ablauf sollte immer möglich sein.
Das stimmt zwar aber die meisten Ereignisse entstehen asynchron (Tastatur-Events, empfangene Netzwerk-Pakete usw.), wenn man maximale Performance (bei vertretbarer CPU-Last) möchte kommt man um eine asynchrone Ereignisbehandlung kaum herum.
Und? Die kann man doch ganz einfach simulieren, man nehme einen Thread der nur poll() oder WaitForSomething() aufruft - ohne Ereignisse muss der ja nichtmal gestartet werden - und bei einem Ereignis eine Funktion in einem anderen Thread aufruft. Das ist die gleiche Last wie mit Callbackhandler, aber so wird die Asynchronität nicht erzwungen.

Grüßle

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #23 am: 02. July 2010, 19:03 »
Hallo,


momentan werd ich wohl für alles die einfachste Lösung nehmen.
Was ich dann später mache, weiß ich noch nicht.
Falls Du Dir bis zu dem "später" nicht schon mit der existierenden Code-Basis andere Wege verbaut hast. Erst mal ein Provisorium zu entwickeln um dann später auf was besseres umzusteigen ist nicht nur doppelte Arbeit sondern oft auch nur sehr schwer umsetzbar. Du kennst http://www.scheissprojekt.de/hausbau.html? Ich hab schon mehrmals an Projekten in dieser Art mitmachen dürfen, da ist nie was gescheites bei raus gekommen (ich hab mich auch immer nach Kräften bemüht sowas schnellst möglich zu verlassen). Ich fange erst dann an mein OS zu coden wenn ich wenigstens einen guten Grobplan habe, klar laufe ich in die Gefahr das ich später bei den Details feststelle das mein Grobplan Mist ist und fange dann unter Umständen noch man ganz von vorne an, aber "No Risk - No Fun" und einen richtig tiefen Griff ins Klo hab ich persönlich noch nicht verursacht (bis jetzt zumindest).


Hier ging es aber um die Kommunikation zwischen Kernel und Userland.
Wenn der TCP-Socket (im monolithischen Kernel) einer wartenden Applikation die frisch eingetroffenen Daten überreichen will dann geht das IMHO schon vom Kernel ins Userland, der Tastatur-Treiber und viele andere mehr müssen einen ähnlichen Weg nutzen. Bei einem Micro-Kernel gehen nur die IRQs (und ein paar seltene Spezial-Sachen) vom Kernel ins Userland, alles andere ist User-2-User-Kommunikation.

Sachen, die vom Kernel ausgehen, bleiben meist auch da drin.
Dann würde der Computer "meist" nichts tun wenn irgend ein Ereignis anliegt. ;)

Ich sehe bei Callback-Funktionen immer das Problem des Programmabsturzes,
Das sehe ich ähnlich, da muss man schon recht gründlich vorgehen um da keine Stolperfallen zu bauen. Sowas meinte ich mit dem "deutlich" tieferen Griff in die Trickkiste.

.... Der zweite Syscall könnte oft sogar mehrmals benötigt werden falls der Job eben noch nicht fertig ist.
Ich sehe kein Problem darin.
Ich sehe da auch kein Problem, ich wollte diesen Umstand nur ansprechen, er kostet ein ganz klein wenig der kostbaren CPU-Zeit.

... wenn man quasiparalleles, asynchrones Verhalten in nur einem Thread erzeugen möchte
Das ist ja auch eine Kunst für sich, sowas sollte man immer vermeiden wenn es irgend möglich ist.

Aber als Haupt-API würde ich nie ein Callback-Verhalten verwenden.
Warum? Genau das habe ich in meinem OS vor.

.... wenn man maximale Performance (bei vertretbarer CPU-Last) möchte kommt man um eine asynchrone Ereignisbehandlung kaum herum.
Und? Die kann man doch ganz einfach simulieren ....
Mir ist klar das man mit asynchronen Dinge synchrone simulieren kann und auch umgedreht, aber damit kommt man nicht auf "maximale Performance (bei vertretbarer CPU-Last)". Wenn man ein annähernd optimales Ergebnis will dann muss man auch das passende Werkzeug benutzen, ansonsten kommt man zwar auch oft ins Ziel aber eben nicht mit dem Spitzenfeld sondern in der Nachzüglergruppe. Bitte nicht falsch verstehen, ich will hier keinen unnützen Effizienz-Wettbewerb starten. Aber man muss sich eben vorher überlegen was man eigentlich erwartet, ob man überhaupt ins Ziel will oder ob man auch einen der vorderen Plätze anstrebt.

und bei einem Ereignis eine Funktion in einem anderen Thread aufruft.
Wie ruft man denn Bitte eine Funktion in einem anderen Thread auf?


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

LittleFox

  • Beiträge: 306
    • Profil anzeigen
    • LF-Net.org
Gespeichert
« Antwort #24 am: 02. July 2010, 19:57 »
 :| :| :|
inzwischen habe ich mich entschieden, nochmal komplett von vorne anzufangen, da mein Sourcecode etwas (sehr ;)) unübersichtlich ist. Bis jetzt hab ich alles programmiert, wie dieses Haus gebaut wurde. :oops:

Es ist nur Schade, weil ich schon Grafiktreiber, Maustreiber, GDT, IDT, Tastaturtreiber usw. fertig hatte.

Was empfehlt ihr mir den jetzt für die Nachrichten? Bis jetzt kamen ja immer nur Nachteile von beiden ;)
Kann man auch beides gleichzeitig verwenden?

Naja, ich werd mich dann jetzt erstmal wieder an die GDT setzen, trotzdem Danke für die Hilfe.

EDIT: was mir gerade aufgefallen ist, wird bei euch die IP-Adresse 127.0.0.1 gespeichert?!?

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #25 am: 02. July 2010, 20:26 »
Hallo,


inzwischen habe ich mich entschieden, nochmal komplett von vorne anzufangen
Ich hoffe das ist nicht meine Schuld, ich möchte wirklich nicht das andere Leute ihre wertvolle Arbeit wegwerfen nur weil ich mal wieder etwas zu überspitzt geschrieben habe.

Bis jetzt hab ich alles programmiert, wie dieses Haus gebaut wurde.
Dann versuche wenigstens aus deinen Fehlern zu lernen. Ich habe am Anfang meiner Programmiererei auch etliche Fehler gemacht (auch heute bin ich längst nicht perfekt und werde das mit Sicherheit auch nie sein), so wie sicher jeder hier. ;)

Was empfehlt ihr mir den jetzt für die Nachrichten? Bis jetzt kamen ja immer nur Nachteile von beiden ;)
Kann man auch beides gleichzeitig verwenden?
Du kannst natürlich beide Services parallel anbieten. Die Frage ist ob Du sowas bei einem Monolithen wirklich dringend benötigst. Fürs erste sollte es reichen wenn Deine Syscalls überwiegend blockierend arbeiten (so wie DOS), damit kommen die meisten Programme ganz gut zurecht. Wenn Du dann später was besseres dazu haben möchtest kannst Du dann (wenn es so weit ist) immer noch die konkreten Anforderungen analysieren.

Mit ein bisschen Weitsicht und Bedacht sollte es gut möglich sein auch später noch grundsätzlich neue Features zu integrieren. Leider braucht man dafür auch einiges an Erfahrung und die bekommt man nur in der Praxis also musst Du erst mal irgendwie anfangen um wenigstens vom Fleck zu kommen. Ich programmiere jetzt seit fast 20 Jahren, da fällt es leicht über "Weitsicht und Bedacht" zu lamentieren, aber Du darfst Dir sicher sein mein Anfang war auch ziemlich hart.

Das entscheidende bei den ersten Schritten ist nicht ob Du auf der Nase landest oder wie weit Du kommst, sondern ob Du was lernst!

was mir gerade aufgefallen ist, wird bei euch die IP-Adresse 127.0.0.1 gespeichert?!?
Was genau meinst Du damit?


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

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #26 am: 02. July 2010, 20:33 »
Wenn der TCP-Stack einer wartenden Applikation etwas mitteilen möchte, handelt es sich um eine Antwort auf eine von der Anwendung ausgehende Anfrage - sonst würde sie nicht warten.

Sachen, die vom Kernel ausgehen, bleiben da auch meistens drin - die Anwendung holt sich ihre Keystrokes etc. ja von dort ab. Im Extremfall (Callback) hat sie vorher halt Interesse angemeldet. Der Kernel schickt den Anwendungen niemals ungefragt etwas - davon stürzen die in der Regel ab.

Wenn ich eine Funktion in einem anderen Thread aufrufe, dann ist das quasi ein Callback. Zwischen Threads geht das mMn auch, weil die Anwendung ja zwangsweise darauf ausgelegt sein muss; als Haupt-API würde ich es wegen der komplzierten Verwendung nicht benutzen.

Oder, um es mit Fefe auszudrücken: Wenn eine API falsch zu bedienen leichter ist, als sie richtig zu bedienen, dann taugt sie nichts. Und Callback-Funktionen in jeder Lebenslage korrekt zu bedienen halte ich für schwierig.

Ein Thread, welcher in einer Endlosschleife Ereignisse abfragt und dann ein Callback aufruft ist nicht wesentlich langsamer, als ein Kernel, der diesen Callbackaufruf selbst tut. Zumal ein Threadwechsel keinen vollständigen Speicherkontext voraussetzt. Ein asynchrones Verhalten im Gegenzug synchronisieren halte ich für aufwändig, zumindest fällt mir kein schneller Weg dafür ein.

Gruß,
Svenska,
der jetzt wieder rausgeht und weitergrillt. :-P

LittleFox

  • Beiträge: 306
    • Profil anzeigen
    • LF-Net.org
Gespeichert
« Antwort #27 am: 02. July 2010, 20:45 »
Ich hoffe das ist nicht meine Schuld
Ne, ist es nicht, kannst dir meine main.h ja mal angucken: http://lf-os.googlecode.com/svn/trunk/main.h
Das ist wirklich das blanke Chaos (wer braucht schon mehrere Header Dateien  :-P)

Also das Endergebnis ist, dass der Kernel nie etwas an die programme schicken sollte, außer in Ausnahmefällen (Runterfahren ...)??

was mir gerade aufgefallen ist, wird bei euch die IP-Adresse 127.0.0.1 gespeichert?!?
Was genau meinst Du damit?
Hier im Forum wird doch die IP Adresse gespeichert, wenn man einen Beitrag erstellt (unter dem Beitrag rechts, man sieht nur die eigenen).
Bei mir wird da 127.0.0.1 gespeichert, was ja localhost wäre :D

Achja, GDT und IDT hab ich schon gemeistert ;)

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #28 am: 03. July 2010, 02:34 »
Bei mir wird auch nur 127.0.0.1 angezeigt - was gespeichert wird, mag vielleicht noch was anderes sein (bezweifel ich aber).

Ich würde das Endergebnis so umformulieren: Der Kernel schickt nie etwas an unvorbereitete(!) Programme. Also quasi ohne Anforderung von diesen. Signale sind da eine Ausnahme und werden zumindest unter Unix auch so behandelt. Mit der WinAPI kenne ich mich allerdings nicht aus, nur, dass sie stellenweise ähnlich (Sockets) und stellenweise völlig anders (ereignisbasierte Callback-GUI) funktioniert. :-)

Gruß,
Svenska

Programm Noob

  • Gast
Gespeichert
« Antwort #29 am: 03. July 2010, 02:56 »
littlefox kannst du mir deinen jetztigen code bitte als zip bei rapidshare hochladen oder mir eine andere downloadquelle nennen. Ich würde mir den nämlich gerne mal ansehen. Vor allem, wie d das mit den treibern gemacht hast.

Programm Noob

LittleFox

  • Beiträge: 306
    • Profil anzeigen
    • LF-Net.org
Gespeichert
« Antwort #30 am: 03. July 2010, 09:21 »
reicht dir als Downloadquelle SVN?
http://lf-os.googlecode.com/svn/trunk

Die Treiber sind bei mir aber in den Kernel mit rein kompiliert.

EDIT:
Bei mir wird auch nur 127.0.0.1 angezeigt - was gespeichert wird, mag vielleicht noch was anderes sein (bezweifel ich aber).
Vor dem Update gings ;)

Also sollen sich die Programme alles was Sie brauchen selber beim Kernel erfragen. Sehe ich das richtig?
Wie werden die Signale bei den UNIXoiden den umgesetzt?
« Letzte Änderung: 03. July 2010, 09:24 von littlefox »

Programm Noob

  • Gast
Gespeichert
« Antwort #31 am: 03. July 2010, 12:08 »
Klar reicht SVN.
Meine Treiber sind momentan auch im Kernel. aber mir geht es drum wie du die aufrufst und wie du deinen VGA Treiber programmierst. Vielen Dank

Programm Noob

LittleFox

  • Beiträge: 306
    • Profil anzeigen
    • LF-Net.org
Gespeichert
« Antwort #32 am: 03. July 2010, 12:14 »
wobei ich zugeben muss, dass ich den VGA Treiber auch gefunden habe :oops:
die primitives.c ist aber selbst geschrieben ;)

gefunden hab ich den VGA treiber hier:
http://files.osdev.org/mirrors/geezer/osd/graphics/modes.c

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #33 am: 03. July 2010, 13:57 »
Hallo,


Ich würde das Endergebnis so umformulieren: Der Kernel schickt nie etwas an unvorbereitete(!) Programme. Also quasi ohne Anforderung von diesen.
Dem kann ich ohne Einschränkungen zustimmen!
Das könnte man sogar auf ein Micro-Kernel-OS erweitern "Prozesse senden sich gegenseitig nur das was der Empfänger auch explizit erwartet". Ein Service ist für alle offen aber nur für bestimmte Anfragen und ein Client erwartet natürlich nur die zugehörigen Antworten auf seine Anfragen und ein Treiber bekommt vom Kernel auch nur die IRQ-Messages für die er sich registriert hat.

Signale sind da eine Ausnahme ...
Da der Prozess ja erst mal einen Signal-Handler registrieren muss bevor er was empfangen kann würde ich sagen das auch (UNIX-)Signale keine Ausnahme sind. Es könnte gut sein das die libc bei einem typischen *NIX sowas schon per Default immer tut und dann bloß das weitergibt was der User-Code tatsächlich will (über eine libc-API).


So wie ich das sehe gibt es genau 3 Möglichkeiten Daten vom monolithischen Kernel in eine User-Space-Applikation zu bekommen:
  • Das Programm benutzt einen blockierenden Syscall (open/read/write/close/GetMyNextMessage/WaitForEvent/...) und wartet immer bis es etwas bekommt. Die allermeisten (unparallelisierten) Programme dürften damit gut zurecht kommen und es ist leicht im Kernel zu implementieren. Die Latenz ist erstmal recht gering (das Programm wartet ja bereits wenn was kommt), trotzdem wird man damit keine Performance-Wunder erleben. Wenn ein Programm mehrere Dinge parallel machen will dann muss es eine entsprechende Anzahl an Threads benutzen (z.B. ein HTTP-Server müsste für jede ankommende TCP-Connection einen neuen Thread starten), was natürlich nur für eine überschaubare Menge an Threads eine intelligente Lösung darstellt.
  • Das Programm benutzt asynchrone nichtblockierende Syscalls, es startet also eine Aktion und kann dann erstmal weiterarbeiten und weitere Jobs starten. Später fragt es nach dem Status der Jobs und entscheidet dann was es als nächstes tut (ein HTTP-Server könnte z.B. den TCP-Socket als nächstes mit neuen Daten versorgen welcher am wenigsten Daten im Sende-Puffer hat). Die Latenz ist höher weil das Programm ja nicht ständig fragt was fertig ist aber dafür kann man (mit vertretbaren Aufwand) mit einem Thread ne ganze Menge Jobs kontinuierlich parallel am laufen halten so das man trotzdem eine ziemlich gute Gesamt-Performance erreicht. Solche Mechanismen bedeuten im Kernel und auch im Programm eine erhebliche Menge an (Programmier-)Mehraufwand und eine deutlich bessere Trickkiste.
  • Das Programm könnte sich über erledigte Jobs per Call-Back benachrichtigen lassen, die Jobs werden wie zuvor über nichtblockierende Syscalls gestartet. Hierbei ist erstmal wieder die Latenz recht kurz da der Call-Back-Handler ja immer (fast) sofort losläuft. Die CPU-Last scheint auch kleiner als zuvor zu sein weil ja das Programm nicht wiederholt nachfragen muss ob schon irgendwas fertig ist (es fallen also weniger Syscalls an) und der Call-Back-Handler sich nur um die Jobs kümmert die tatsächlich Aufmerksamkeit benötigen (es müssen auch keine Listen mit anhängigen Jobs durchgearbeitet werden um einen zu finden für den was zu tun ist). Die benötigten Mechanismen sind nochmal deutlich komplexer und es gibt einige zusätzliche Stolperfallen (wenn z.B. ein Programm in dem Moment gekillt wird wo der Kernel gerade einen Call-Back starten will, bei Multi-Core ja problemlos möglich) die wiederum eine nochmals bessere Trickkiste und eine sehr saubere und gründliche Spezifikation (in der auch wirklich alle Sonderfälle geregelt sind) benötigen. Ein Nachteil ist das der Call-Back-Handler oft nicht alles machen kann (so wie ein IRQ-Handler auch). Zusätzlich benötigt der Call-Back-Handler einen Kontext im Programm in dem er läuft, dazu wird normalerweise einer der Programm-Threads unterbrochen wie bei den klassischen UNIX-Signalen.
    Ein Ausweg wäre es wenn der Kernel für jeden Call-Back einen neuen (leeren) Thread im Programm erstellt, so ein Thread hätte dann alle Möglichkeiten im Programm und unterbricht auch nicht einen anderen Thread bei einer wichtigen Tätigkeit. So etwas wurde aber wohl noch nie gemacht, ich habe für dieses PopUp-Threads-Konzept zwar ein paar wenige theoretische Überlegungen aber keine praktischen Beispiele gefunden. Ich möchte diesen Weg in meinem Micro-Kernel-OS (für die Services) gehen, einer muss ja den Anfang machen, bei einem Micro-Kernel hat das auch eher Vorteile weil dort (fast) alle Services nicht direkt über Syscalls sondern über RPC/IPC abgewickelt werden.


Wenn ich eine Funktion in einem anderen Thread aufrufe, dann ist das quasi ein Callback.
Das habe ich immer noch nicht verstanden. Wie ruft man eine Funktion in einem anderen Thread auf? Der Kernel kann den Thread unterbrechen und dort einen Signal-Handler aufrufen (eben wie die CPU bei einem IRQ) so als würde der unterbrochene Thread einen CALL machen, aber zwischen zwei Threads in einem User-Mode-Prozess geht das IMHO nicht.

als Haupt-API würde ich es wegen der komplzierten Verwendung nicht benutzen.
Das kann man ja hinter der libc verbergen und für ein einfaches (open/read/write/close) wird man sicher keine Call-Backs benutzen, da sind blockierende Syscalls (beim Monolithen) oder synchrones RPC (beim Micro-Kernel) deutlich besser, Call-Backs sind nur dann interessant wenn man mit wirklich vielen parallel Jobs arbeitet oder für die Bereitstellung von Services in der Personality von einem Micro-Kernel-OS.


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

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #34 am: 03. July 2010, 14:24 »
Okay, mit den Signalen stimmt auch wieder.

Du hast irgendwo in deiner Auflistung das normale asynchrone I/O vergessen, wo also read()/write()/... zwar blockieren, aber es einen speziellen Syscall gibt, der vorher nachfragt, ob diese Syscalls blocken würden. Das heißt, der Programmfluss ist trotzdem synchron, aber es wird nicht gewartet. Das programmiert sich leichter. Beispiele dafür sind select(), poll(), epoll (Linux) und kqueue (*BSD). Als Grund-API hast du dann also nur blockierende Syscalls, kannst damit aber trotzdem recht einfach asynchrones I/O starten.

Dann brauchst du nicht für jede Anfrage einen Thread, was nicht skaliert, hast aber die Freiheit, beliebig viele Worker-Threads zu verwenden. Ein Thread, der im Prinzip nur auf Ereignisse wartet und dann beliebig verteilt.

Was die Threads angeht, da diese ja in einem gemeinsamen Speicherkontext laufen, müsste man da eigentlich auch einfach hinspringen können - ich dachte da eher an ein Syscall in den Kernel (bzw. die libc), welche den anderen Thread beeinflusst.

Gruß,
Svenska

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #35 am: 03. July 2010, 14:58 »
select() usw. dürften Eriks Option 2 entsprechen.

Und asynchrones I/O ist das strenggenommen auch nicht: Du blockierst zwar nicht, wenn es nichts zu lesen gibt, aber wenn du dann mal das read() startest, läuft das eigentliche Lesen synchron ab. Dürfte bei Sockets natürlich in der Praxis nicht wirklich einen Unterschied machen, weil es wenig genug Daten sind, die außerdem nur im Speicher ein bisschen hin- und hergeschoben werden; und beim Zugriff auf normale Dateien ergibt select() nicht besonders viel Sinn.
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 #36 am: 03. July 2010, 18:36 »
Hallo,


... wo also read()/write()/... zwar blockieren, aber es einen speziellen Syscall gibt, der vorher nachfragt, ob diese Syscalls blocken würden.
Das ist asynchrones I/O, man initiiert einen Job und prüft dann ob schon Daten da sind damit man beim abholen eben nicht blockiert. Das ist ganz klar Option 2.

select() usw. dürften Eriks Option 2 entsprechen.
Sicher? Gibt es zu dem select() irgendwo eine verständliche Dokumentation? Ich hab leider nichts brauchbares gefunden.


Was die Threads angeht, da diese ja in einem gemeinsamen Speicherkontext laufen, müsste man da eigentlich auch einfach hinspringen können
Nur weil sich zwei Threads einen gemeinsamen Speicherkontext teilen ist der Gesamt-Kontext, dazu gehören die Register genauso wie der Stack, nicht gleich identisch. Man kann nicht mit einem CALL oder vergleichbares eine Funktion im Kontext eines anderen Threads aufrufen.

ich dachte da eher an ein Syscall in den Kernel (bzw. die libc), welche den anderen Thread beeinflusst.
So wie bei den (UNIX-)Signalen? Das würde ich meinen Worker-Threads nicht antun wollen, die sind mit ihrem letzten Job eventuell noch nicht fertig.


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

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #37 am: 03. July 2010, 19:11 »
select() ist genau diese Funktion, die vorher prüft, ob read blockieren würde. select funktioniert so, dass du drei verschiedene fd_sets (also Mengen von Filedeskriptoren) angibst: Einmal für lesen, einmal für schreiben und einmal für Fehler. select() wirft dann diejenigen Deskriptoren raus, die nicht bereit sind zum lesen/schreiben bzw. die keinen Fehler haben. Wenn keiner der Filedeskriptoren bereit ist, blockiert select. Für das Blockieren gibt es nochmal einen Parameter, das einen Timeout angibt.

Und nein, es ist nicht asynchrones I/O, weil die Übertragung der Daten (also das eigentliche read/write) nicht im Hintergrund abläuft.
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 #38 am: 03. July 2010, 23:21 »
Hallo,


select() ist genau diese Funktion, die vorher prüft, ...
Ahja, jetzt bin ich wieder etwas schlauer, Danke. Warum können andere Dokus nicht auch erst mal mit so einem kompakten Dreizeiler anfangen anstatt gleich mit Details um sich zu werfen so das man keine Chance hat das grobe Ganze zu erkennen? (darüber wie ich das in einer libc umsetzen kann will ich jetzt lieber noch nicht nachdenken)

Und nein, es ist nicht asynchrones I/O, weil die Übertragung der Daten (also das eigentliche read/write) nicht im Hintergrund abläuft.
Ich würde schon sagen das es sich dabei um asynchrones I/O handelt, auch wenn es nicht ganz der Definition entspricht. Für TCP betrachtet übertragen read und write doch nur zwischen einem User-Puffer und einem Kernel-Puffer (simples memcpy bei einem Monolithen) die eigentliche Datenübertragung von/nach draußen passiert dann doch echt asynchron. Bei Dateien muss man natürlich trotzdem warten bis die HDD die Daten gelesen/geschrieben hat (wenn wir mal SW-Caches außer acht lassen) aber da macht select() auch nur extrem wenig Sinn. Der einzigst verbleibende Unterschied ist das bei richtigem asynchronem I/O auch das memcpy asynchron im Hintergrund abläuft, das kann natürlich ein paar Auswirkungen für das Programm haben.
Vielleicht können wir uns auf "fast asynchrones I/O" einigen? ;)
Das select() ist auf jeden Fall unendlich viel dichter an Option 2 als an Option 1. (da fällt mir auf es gibt hier keine nummerierten Aufzählungen, dafür ne Menge unnützer Firlefanz)


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

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #39 am: 04. July 2010, 03:56 »
Wenn du Dateizugriffe multiplexen willst, kann es schon sinnvoll sein; beispielsweise liest du von einer modernen SSD mit ordentlich Datenrate und einer antiken Festplatte je ein GB. Für Schreiben gilt entsprechendes, für Sockets ebenfalls.

Während die alte Platte nicht hinterherkommt, blockt dein Programm - der Programmfluss ist synchron. Also lässt du besser immer nur ein paar Blöcke lesen (bzw. rufst ein readahead() auf, welches nicht blockiert) und wartest, bis die Daten gelesen sind, ehe du dann dein richtiges read() aufrufst. In dem Moment stört das nicht weiter, da dein readahead() garantiert, dass die Daten im Plattencache liegen und das read() nur zu einem memcpy() verkommt. Im Allgemeinen ist das nämlich nicht der Fall. Bei Sockets ist das noch schlimmer, weil die Geschwindigkeiten viel geringer sind.

Die Erweiterungen (select() und poll() sind umständlich zu handhaben und können allein von der API her nicht besser als linear skalieren) bezeichnet man als High-Performance AIO (asynchrones I/O), daher würde ich select()/poll() ebenfalls dem AIO zuordnen, obwohl der Programmfluss eines solchen Programms synchron ist. Übrigens skaliert unter Linux ein fork()-basierter Webserver besser als ein auf poll() basierender... vgl. Fefe :-)

Gruß,
Sebastian

 

Einloggen