Autor Thema: aktivieren des Kernel bei SMP (beim booten)  (Gelesen 9877 mal)

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« am: 23. January 2011, 01:39 »
Hallo,


ich bin gerade auf ein kleines Design-Problem in meinem Boot-Prozess gestoßen: Wie aktiviert man am besten die einzelnen CPUs bei einem SMP-System wenn damit das aktivieren des Kernels verbunden ist?

Meine Situation ist folgende:
Der Kernel liegt im RAM und ist komplett vorbereitet (also alle internen Datenstrukturen sind einsatzbereit und 3 User-Mode-Prozesse sind auch eingerichtet, die Lebendgeburt könnte also jetzt stattfinden) aber die eine aktive CPU (BSP, ist noch im Code vom Boot-Loader) muss nun erst noch die anderen (APs) aktivieren weil der Kernel selber das nicht kann (warum sollte der Code für diese Funktionalität auch Platz im Kernel belegen wenn das doch nur ein einziges mal beim booten gemacht werden muss). Ich hatte erst die Idee alle CPUs der Reihe nach aufzuwecken und alle warten zu lassen bis wirklich alle da sind so das alle CPUs gleichzeitig per IRET in den User-Mode springen (auf jeder CPU läuft dann ein individueller Thread vom idle-Prozess) aber das erscheint mir irgendwie unpraktisch. Ich würde lieber eine CPU nach der anderen Aufwecken und die springen sofort in den User-Mode und von dort dann nach der kurzen initial-Zeitscheibe der idle-Threads zurück in den Scheduler welcher dann den init-Prozess ans laufen bringt. Es kann also passieren das einige CPUs schon voll normalen User-Mode-Code und normalen Kernel-Code (IRQ-Handler usw.) abarbeiten während andere CPUs noch deaktiviert sind oder sich noch im INIT-Mode befinden (und Boot-Loader-Code ausführen). Bei der ersten Möglichkeit würde das nicht passieren weil alle CPUs gleichzeitig mit der richtigen Arbeit anfangen so das mein OS ein einem einzigen Moment zum Leben erwacht und nicht in mehreren kleinen Häppchen. Ich möchte trotzdem lieber die zweite Möglichkeit benutzen, schon weil da schneller angefangen wird das eigentliche System hochzufahren (die APs warten eben nicht bis der BSP alle APs aktiviert hat sondern es gibt sofort Action).
Seht Ihr da irgendein Problem?

Ich hoffe ich habe mich einigermaßen verständlich ausgedrückt (trotz der Uhrzeit). Wenn nicht dann Bitte einfach nachfragen.

Wie gestalten andere eigentlich das Aufwecken der restlichen CPUs? Wimre haben doch hier ein paar Leute SMP ans laufen bekommen.


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 #1 am: 23. January 2011, 02:07 »
Hallo,

ist zu diesem Zeitpunkt der Scheduler bereits aktiv? Für den BSP allein, der noch den Bootcode ausführt, ist er nicht notwendig. (Sofern der Bootstrap-Code rein linear ist.)

Ansonsten könntest du nämlich einfach alle CPUs außer des BSP aktivieren und in die Idle-Schleife schicken; die letzte Aktion des Bootloaders wäre dann, den Scheduler zu starten, der dann die Verteilung der Usermode-Prozesse auf die CPUs vornimmt. Das System beginnt also mit dem Tod des Bootloaders an zu leben.

Die CPUs einzeln zu aktivieren und direkt mit Usermode-Prozessen zu befüllen halte ich aus verschiedenen Gründen nicht für sinnvoll:

(a) Sollten Prozesse eine gewisse CPU-Affinität haben, um den CPU-eigenen Cache besser ausnutzen zu können. Startest du dein Vierprozessorsystem jetzt CPU-weise, dann landen erstmal alle Prozesse auf CPU2 (CPU1 ist noch mit dem Bootstrap beschäftigt) und der Scheduler muss sie dann später auf andere CPUs verteilen und zerstört somit unnötigerweise deren Cache.

(b) Weißt du nicht unbedingt, ob eine CPU defekt ist. Wenn die Idle-Schleife allerdings abstürzt, kannst du sie direkt wieder aus dem System entfernen, ohne schon Usercode ausgeführt zu haben. Die Wahrscheinlichkeit, dass du eine defekte CPU findest, ist beim Systemstart wesentlich höher als zu einem späteren Zeitpunkt, an dem sie schon Code ausgeführt hat...

(c) Der Boot-Prozess ist in der Regel nicht CPU-limitiert. Das Hauptproblem ist die I/O-Last. Die paar tausend Taktzyklen, die du gewinnst, wenn du die CPUs während des Bootstraps schon direkt ins aktive Scheduling übernimmst, bringen nichts, weil die Daten erst noch umständlich geladen werden müssen. (Das gilt natürlich nur, wenn dein Userspace von einem sekundären Medium geladen wird. Das sollte aber der Normalfall sein.)

Normalerweise machen Kernel keine Lebendgeburt. Bei Linux hast du den Fall, dass der Kernel als Uniprozessor läuft und dann der Reihe nach CPUs aktiviert. Sie sind dann auch direkt im Scheduling eingebunden. Da aber das System zu dem Zeitpunkt nicht nur lebensfähig ist, sondern sogar lebendig, ist das etwas anderes.

Hoffe, das hilft dir ungefähr weiter.
Gruß,
Svenska

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 23. January 2011, 10:02 »
@Svenska

zu a) Eine gewisse, aber nicht das man sagt das ein Prozess nur noch auf dieser einen CPU läuft. Denn das läuft dann darauf hinaus, dass viele CPUs idlen und ein paar, auf denen die Threads gestartet wurden, machen die ganze arbeit. Von daher würde ich das als nicht so schlimm erachten.

zu b) Defekte CPUs (die nicht vom BIOS deaktiviert wurden, bei x86) würde ich komplett ignorieren. Weil wie stellst du fest ob sie defekt sind? Ich würde wenn mir der Idle-Prozess abstürzt das ganze System in einen Panic schicken, weil normal ist das nicht. Soll der Nutzer erstmal die CPU tauschen und dann wieder neu versuchen.

zu c) Also gut ich starte für jede vorhandene Hardware einen Thread der die Hardware initialisiert. Viele Threads heißen für mich, es wäre gut wenn man die auch auf viele CPUs verteilen kann. Würde also schon was bringen.

Ich lasse die vorhandenen APs vom Bootloader starten und die warten dann an einer Spinlock. Wenn der Kernel dann läuft und sich soweit initialisert hat (ein wenig arbeit wird noch vom Kernel verrichtet), lässt er jede CPU, nacheinander, loslaufen und die wiederrum müssen auch noch ein paar Initialisierungen machen, aber die laufen zum Großteil schon parallel ab. Ab einer gewissen CPU Anzahl macht es sich durchaus bemerkbar ob der Code parallel oder hintereinander abläuft.

Im hinblick auf die Zukunft, ich denke da an so Many-Core Projekte von Intel, wo man zwar 100 und mehr Cores hat, der einzelne Core aber wesentlich langsamer ist als heutige Cores, macht es wieder Sinn so schnell wie möglich alles parallel laufen zu lassen.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 23. January 2011, 12:56 »
Hallo,


ist zu diesem Zeitpunkt der Scheduler bereits aktiv?
Ja. Also sobald die erste CPU in den User-Mode wechselt kann diese CPU auch eine Kontext-Switch-Exception ausführen und damit den Scheduler auf den Plan rufen. Da ich für den erste Aktivierung eine kurze Rest-Zeitscheibe von 1µs vorgeben möchte dürfte es also problemlos möglich sein das der erste AP bereits einen kompletten Kontext-Switch über den Scheduler erledigt hat (also vom ursprünglichen idle-Thread zum init-Thread gewechselt hat) während der BSP noch damit beschäftigt ist die restlichen APs aufzuwecken.

Für den BSP allein, der noch den Bootcode ausführt, ist er nicht notwendig. (Sofern der Bootstrap-Code rein linear ist.)
Für den Boot-Code selber ist kein Scheduler erforderlich. Der eigentliche Kernel ist da auch noch nicht am Leben. Worum es mir hier geht ist quasi der letzte Schritt im Boot-Code, nämlich die eigentliche Lebendgeburt (alle notwendigen Vorbereitungen sind schon erledigt bevor der Boot-Code hiermit anfängt).

Ansonsten könntest du nämlich einfach alle CPUs außer des BSP aktivieren und in die Idle-Schleife schicken; die letzte Aktion des Bootloaders wäre dann, den Scheduler zu starten, der dann die Verteilung der Usermode-Prozesse auf die CPUs vornimmt. Das System beginnt also mit dem Tod des Bootloaders an zu leben.
Das war ja meine erste Idee die aber bedeutet das es erst dann los geht wenn der Boot-Code alle APs aufgeweckt hat und das kostet IMHO unnötig Zeit. Nebst dessen das ich meinen Scheduler nicht aktivieren kann, er ist eine Art Exception-Handler der von der CPU automatisch aufgerufen wird wenn die Zeitscheibe abgelaufen ist. Also so bald ich eine CPU das erste mal in den User-Mode schicke muss der Scheduler bereits voll funktionsfähig sein und das ist er auch weil er vom Boot-Code passend vorbereitet wurde (noch vor der Lebendgeburt).

(a) Sollten Prozesse eine gewisse CPU-Affinität haben, um den CPU-eigenen Cache besser ausnutzen zu können....
Ich denke das ist für den Boot-Vorgang erst mal absolut unerheblich. In diesem Moment existieren nur 3 Prozesse. init und exec haben jeweils nur einen Thread (exec ist ein Service der eh nur auf Zuruf arbeitet) und idle hat so viele Threads wie CPUs vorhanden sind (wird vom Boot-Code so vorbereitet). Erst init erstellt dann weitere Prozesse (mit Hilfe des exec-Service), init ist bei mir ne Art initrd mit etwas integriertem Code als eigenständiger User-Mode-Prozess realisiert.

(b) Weißt du nicht unbedingt, ob eine CPU defekt ist....
Und wie soll ich feststellen ob eine CPU defekt ist? Wenn die CPU aufwacht ist sie erst mal im INIT-Mode (auch eine Art System-Mode) und führt noch etwas Code vom Boot-Code aus (um die Register vorzubereiten usw.) und macht dann ein IRET, in diesem winzigen Boot-Code könnte man ja noch einen kurzen Selbsttest unterbringen aber mehr kann ein OS IMHO nicht tun. Wenn ein System wegen einer defekten CPU abstürzt dann muss eben der User die CPUs tauschen, außerdem gibt es ja noch die Maschine-Check-Architekture die es vielleicht ermöglicht wenigstens eine einigermaßen brauchbare Fehlermeldung auszugeben damit der User zumindest eine wage Idee hat was wohl defekt sein könnte.

(c) Der Boot-Prozess ist in der Regel nicht CPU-limitiert. Das Hauptproblem ist die I/O-Last. Die paar tausend Taktzyklen, die du gewinnst, wenn du die CPUs während des Bootstraps schon direkt ins aktive Scheduling übernimmst, bringen nichts, weil die Daten erst noch umständlich geladen werden müssen. (Das gilt natürlich nur, wenn dein Userspace von einem sekundären Medium geladen wird. Das sollte aber der Normalfall sein.)
In dem Moment wo die Lebendgeburt meines Kernel passieren soll gibt es im System nichts weiter außer ein paar CPUs und den Haupt-Speicher, es gibt noch keine PCI-Devices (die sind alle noch im Reset) und der physische Adressraum ist ansonsten noch komplett leer. Alles was das OS benötigt um an eine HDD (oder auch an eine LAN-Freigabe) zu kommen muss also in init enthalten sein und von diesem entpackt und gestartet werden bis dann endlich der richtige Boot-Vorgang anfangen kann. Wenn wir von einem schlanken Linux-Kernel mit fetter initrd (die alle möglichen Treiber als Module enthält) ausgehen dann muss der Linux-Kernel ja auch erst mal das Plug-&-Play über den PCI laufen lassen und alle nötigen Treiber aus der initrd holen bis er dann endlich das root-File-System mounten kann um dann von dort /sbin/init zu starten, dieser Abschnitt im Boot-Prozess dürfte wohl schon im wesentlichen CPU-limitiert sein. Bei einem Micro-Kernel ist nur der Unterschied das dieser bereits voll einsatzfähig ist weil ja Treiber usw. gar nicht zu seinen Aufgaben gehören. Mein Kernel weiß gar nicht das es überhaupt PCI gibt, der kennt nur CPUs und Speicher und so bald das beides da ist kann er glücklich leben und den Rest dem User-Mode überlassen.

Normalerweise machen Kernel keine Lebendgeburt.
Ich will ja auch keine normale Plattform entwickeln. ;) Das mit der Lebendgeburt empfinde ich persönlich als ganz gute Variante weil ich so auch vermeiden kann das Code im Kernel ist der nur genau ein einziges mal benötigt wird, außerdem will ich ja einen schlanken Microkernel bauen bei dem gar nicht viel vorzubereiten ist (eigentlich nur ein paar Datenstrukturen und die Einsprungspunkte der Exception-Handler müssen den CPUs bekannt gemacht werden). Mein Kernel ist eigentlich nur eine Ansammlung von Interrupt/Exception/Syscall-Handlern und enthält keine eigenständigen Aktivitäten.


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 #4 am: 23. January 2011, 13:20 »
aber die eine aktive CPU (BSP, ist noch im Code vom Boot-Loader) muss nun erst noch die anderen (APs) aktivieren weil der Kernel selber das nicht kann (warum sollte der Code für diese Funktionalität auch Platz im Kernel belegen wenn das doch nur ein einziges mal beim booten gemacht werden muss).
Hotplugging willst du schon per Design verbieten?
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 #5 am: 23. January 2011, 14:25 »
Hallo,


Hotplugging willst du schon per Design verbieten?
Du meinst Hotplugging von CPUs? Zum jetzigen Zeitpunkt definitiv ja, ich will ja das mein System überhaupt erst mal funktioniert. Was ich später vielleicht mal alles unterstützen möchte steht noch in den Sternen. Und das bisschen Code dann doch noch in den Kernel zu holen sehe ich nun auch nicht als so grundsätzliche Designänderung das es quasi unmachbar wäre.


Edit: bevor ich es vergesse: wie habt ihr denn bei tyndur das CPU-Hotplugging designtechnisch umgesetzt? ;) Ich glaube das können überhaupt nur ganz wenige OSe auf diesem Planeten.


Grüße
Erik
« Letzte Änderung: 23. January 2011, 21:37 von erik.vikinger »
Reality is that which, when you stop believing in it, doesn't go away.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 23. January 2011, 18:00 »
[CPU-Affinität] Eine gewisse, aber nicht das man sagt das ein Prozess nur noch auf dieser einen CPU läuft. Denn das läuft dann darauf hinaus, dass viele CPUs idlen und ein paar, auf denen die Threads gestartet wurden, machen die ganze arbeit. Von daher würde ich das als nicht so schlimm erachten.
Das ist mir schon klar. Einfacher ist es aber, wenn der Scheduler alle beim Start vorhandenen CPUs sieht und die Anwendungen direkt gleichmäßig verteilt, anstatt erstmal alle Anwendungen auf CPU2 zu legen und später von dort nochmal wegzunehmen.

Defekte CPUs (die nicht vom BIOS deaktiviert wurden, bei x86) würde ich komplett ignorieren. Weil wie stellst du fest ob sie defekt sind? Ich würde wenn mir der Idle-Prozess abstürzt das ganze System in einen Panic schicken, weil normal ist das nicht. Soll der Nutzer erstmal die CPU tauschen und dann wieder neu versuchen.
Ist ja auch in Ordnung. Zu der Zeit sollte aber noch kein Usercode gelaufen sein. Wenn du CPU-Hotplugging irgendwann unterstützt, dann kannst du die CPU auch einfach nicht in den aktiven Scheduler übernehmen.

Also gut ich starte für jede vorhandene Hardware einen Thread der die Hardware initialisiert. Viele Threads heißen für mich, es wäre gut wenn man die auch auf viele CPUs verteilen kann. Würde also schon was bringen.
Bringt nichts, weil dein Kernel erstmal für jeden Thread den Hardwaretreiber von sekundärem Speicher laden muss (sofern du nichts initrd-artiges benutzt). Das Problem sind nicht die paar Zyklen Ineffizienz, weil du noch während des CPU-Bootstraps schon sämtliche Hardware initialisieren möchtest, sondern die Wartezyklen, die durch I/O geschehen. Es geht hier ausschließlich um den Zeitraum, in dem du der Reihe nach alle vorhandenen CPUs erstmalig einschaltest.

Im hinblick auf die Zukunft, ich denke da an so Many-Core Projekte von Intel, wo man zwar 100 und mehr Cores hat, der einzelne Core aber wesentlich langsamer ist als heutige Cores, macht es wieder Sinn so schnell wie möglich alles parallel laufen zu lassen.
Ja, die sind aber weit von dem entfernt, was dein OS unterstützt und auch das, was eriks Plattform bietet. Bei euch geht es um 100..101 CPUs.

Das war ja meine erste Idee die aber bedeutet das es erst dann los geht wenn der Boot-Code alle APs aufgeweckt hat und das kostet IMHO unnötig Zeit. Nebst dessen das ich meinen Scheduler nicht aktivieren kann, er ist eine Art Exception-Handler der von der CPU automatisch aufgerufen wird wenn die Zeitscheibe abgelaufen ist. Also so bald ich eine CPU das erste mal in den User-Mode schicke muss der Scheduler bereits voll funktionsfähig sein und das ist er auch weil er vom Boot-Code passend vorbereitet wurde (noch vor der Lebendgeburt).
Okay. Das mit dem "unnötig Zeit" sehe ich unkritisch. Wieviel Zeit ließe sich denn einsparen? Wieviele Millisekunden dauert es denn, eine CPU hochzufahren? Solange du keine großen CPU-Mengen im System hast, lohnt es sich u.U. nicht.

Dein Aufbau klingt allerdings stark danach, dass es an sich total egal ist, ob du alle CPUs gleichzeitig aufweckst oder nacheinander. Von daher ist die Antwort ebenfalls egal (und eine Geschmackssache).

Erst init erstellt dann weitere Prozesse (mit Hilfe des exec-Service), init ist bei mir ne Art initrd mit etwas integriertem Code als eigenständiger User-Mode-Prozess realisiert.
Zu dem Zeitpunkt, wo du anfänst, Usercode auszuführen, sollte meiner Meinung nach das System schon einigermaßen stabil sein. Das heißt, RAM und CPU sind voll einsatzfähig.

Wenn ein System wegen einer defekten CPU abstürzt dann muss eben der User die CPUs tauschen, außerdem gibt es ja noch die Maschine-Check-Architekture die es vielleicht ermöglicht wenigstens eine einigermaßen brauchbare Fehlermeldung auszugeben damit der User zumindest eine wage Idee hat was wohl defekt sein könnte.
Das wäre übrigens ein Grund dafür, den BSP als allererstes ins aktive Scheduling zu übernehmen. Denn der hat den Boot-Code bereits ausgeführt und funktioniert garantiert; also kann er auch die Fehlermeldung ausgeben. Deine Herangehensweise ist ja, dass du den BSP als allerletztes aktivierst, da er bis zuletzt noch das System hochfährt.

Alles was das OS benötigt um an eine HDD (oder auch an eine LAN-Freigabe) zu kommen muss also in init enthalten sein und von diesem entpackt und gestartet werden bis dann endlich der richtige Boot-Vorgang anfangen kann.
Ja, und die Frage ist, ob zu diesem Zeitpunkt noch CPUs aktiviert werden sollen oder nicht. Ich bin der Meinung "nein", allerdings aus Prinzip. Wenn man sehr viele CPUs hat, mag es sich lohnen, ich sehe allerdings keinen Vorteil. Das System bootet nur äußerst selten (verglichen mit der Laufzeit). Wenn dein Kernel rein passiv ist, spielt es im Übrigen keine Rolle.

Wenn wir von einem schlanken Linux-Kernel mit fetter initrd (die alle möglichen Treiber als Module enthält) ausgehen dann muss der Linux-Kernel ja auch erst mal das Plug-&-Play über den PCI laufen lassen und alle nötigen Treiber aus der initrd holen bis er dann endlich das root-File-System mounten kann um dann von dort /sbin/init zu starten, dieser Abschnitt im Boot-Prozess dürfte wohl schon im wesentlichen CPU-limitiert sein.
Sicher ist das Zerpflücken der initrd im wesentlichen CPU-limitiert, aber es macht nur einen äußerst geringen Bruchteil der Bootzeit aus. Das Mounten des root-Dateisystems dauert wesentlich länger, weil du dazu auf die Festplatte warten musst usw. Aber das ist egal, weil du diesem Zeitpunkt definitiv alle CPUs aktiv sein sollten.

Bei einem Micro-Kernel ist nur der Unterschied das dieser bereits voll einsatzfähig ist weil ja Treiber usw. gar nicht zu seinen Aufgaben gehören. Mein Kernel weiß gar nicht das es überhaupt PCI gibt, der kennt nur CPUs und Speicher und so bald das beides da ist kann er glücklich leben und den Rest dem User-Mode überlassen.
Ja. Also ist er dafür zuständig, dass die CPUs auch funktionieren und aktiv sind. :-)

Ich persönlich mag es, Initialisierung vom Rest danach zu trennen.
Das macht übrigens auch das Debugging einfacher. Wenn das System noch während der CPU-Initialisierung abstürzt, den Grund zu finden (ist es der BSP? irgendein AP? der AP, der gerade Init ausführt? Init selbst?), stell ich mir schwierig vor.

Machen musst du es am Ende eh selbst. ;-)

Gruß,
Svenska

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 23. January 2011, 20:50 »
Zitat von: svenska
Wenn du CPU-Hotplugging irgendwann unterstützt, dann kannst du die CPU auch einfach nicht in den aktiven Scheduler übernehmen.
Äh doch, das ist theoretisch jetzt schon möglich. Wenn ich denn wollte könnte ich CPUs einfach anhalten und wieder loslaufen lassen, ohne dass das Probleme gibt (jedenfalls nicht mit dem Scheduler).

Du stellst dir den Scheduler wahrscheinlich wie eine Art Verteiler vor oder? Bei mir gibt es eine Queue wo alle Threads drin sind die bereit sind und was machen wollen (vereinfacht gesagt) und jede CPU ruft bei einem Timer-Int den Scheduler-Code auf. Dieser holt sich dann den Thread mit der höchsten Priorität aus dieser Queue und lässt ihn laufen. Ist die Zeitscheibe von dem Thread abgelaufen, kommt er wieder zurück in die Queue.

Zitat von: svenska
Ja, die sind aber weit von dem entfernt, was dein OS unterstützt und auch das, was eriks Plattform bietet. Bei euch geht es um 1..100 CPUs.
Naja, theoretisch (nur durch Qemu getestet) läuft mein OS auch mit 255 CPUs (ich habe es gerade testen wollen und irgendwo in meinem Init-Code, der schon im Kernel läuft, gibt es noch einen Fehler).

Die Anzahl ist im Moment auch nur durch die APIC-IDs beschränkt.

Zitat von: svenska
Wenn das System noch während der CPU-Initialisierung abstürzt, den Grund zu finden (ist es der BSP? irgendein AP? der AP, der gerade Init ausführt? Init selbst?), stell ich mir schwierig vor.
Ich weiß nicht was du unter abstürzen meinst, aber wenn es eine Exception gibt, gebe ich mit an auf welcher CPU die aufgetreten ist und damit weiß ich dann schon wo die passiert ist.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 23. January 2011, 22:43 »
Hallo,


Einfacher ist es aber, wenn der Scheduler alle beim Start vorhandenen CPUs sieht
Was ist daran einfacher? Mein Scheduler sieht die CPUs gar nicht, er wird einfach aufgerufen (von irgendeiner CPU wenn dort der Zeitscheiben-Counter abgelaufen ist), legt den aktuellen Thread wieder nach hinten in die "runnable"-Queue und holt sich den nächsten Thread vom vorderen Ende der "runnable"-Queue. Dazu muss der Scheduler noch nicht mal wissen wie viele CPUs es eigentlich gibt oder auf welcher CPU er gerade aktiv ist. Solange das reinlegen und rausholen von Threads in/aus der Queue atomar passiert können auch mehrere CPUs gleichzeitig den Scheduler aufrufen.

und die Anwendungen direkt gleichmäßig verteilt anstatt erstmal alle Anwendungen auf CPU2 zu legen und später von dort nochmal wegzunehmen.
Zu diesem Zeitpunkt gibt es genau einen Thread der tatsächlich vernünftige Arbeit leistet und das ist der eine Thread von init. Auf welcher CPU der läuft ist erst einmal ziemlich egal solange er überhaupt möglichst schnell an die Reihe kommt (was auch ziemlich schnell passieren dürfte da ja die vielen idle-Threads alle eine niedrigere Priorität haben).

Wobei ich auch ehrlich sagen muss das ich mir um (dynamische) CPU-Affinität noch keine Gedanken gemacht habe, das muss ich gleich mal auf meine Todo-Liste setzen (aber mit niedriger Priorität, ist ja schließlich nur ein Performance-Aspekt und keine richtige Funktionalität).

Das mit dem "unnötig Zeit" sehe ich unkritisch. Wieviel Zeit ließe sich denn einsparen? Wieviele Millisekunden dauert es denn, eine CPU hochzufahren? Solange du keine großen CPU-Mengen im System hast, lohnt es sich u.U. nicht.
Selbst bei 100 CPUs dürfte es hier wohl höchstens um eine zweistellige Millisekunden-Zeit gehen (es sei denn ich baue in den Boot-Code noch einen aufwendigen CPU-Selbsttest ein). Es geht mir hier eher um das Design und eine elegante Umsetzung, aber auch ein klein wenig um die benötigte Zeit für das Hochfahren. Erst alle CPUs vor einer Barriere zu sammeln damit diese dann von allen CPUs (möglichst, exakt geht ja eh nicht) gleichzeitig überschritten wird ist zwar interessant aber auch aufwendig (also Tipp/Debug-Aufwand für den Code) und IMHO völlig unnötig.

Dein Aufbau klingt allerdings stark danach, dass es an sich total egal ist, ob du alle CPUs gleichzeitig aufweckst oder nacheinander. Von daher ist die Antwort ebenfalls egal (und eine Geschmackssache).
Nun, wenn es egal ist dann kann ich ja beruhigt den einfacheren Weg benutzen (das dieser auch noch eine Winzigkeit schneller ist nehme ich dankend mit). ;)

Zu dem Zeitpunkt, wo du anfänst, Usercode auszuführen, sollte meiner Meinung nach das System schon einigermaßen stabil sein. Das heißt, RAM und CPU sind voll einsatzfähig.
Da ist auf jeden Fall gewährleistet, auch wenn noch nicht alle CPUs dabei sind. Bis dann überhaupt der PCI-Scan fertig ist und die ersten Treiber instantiiert werden (also tatsächlich mehrere nützliche Threads gerne eine CPU bekommen wollen) sollten dann auch tatsächlich alle CPUs mitten im Spiel sein.

Das wäre übrigens ein Grund dafür, den BSP als allererstes ins aktive Scheduling zu übernehmen. Denn der hat den Boot-Code bereits ausgeführt und funktioniert garantiert; also kann er auch die Fehlermeldung ausgeben.
Und das restliche Bootstrapping einem ungetesteten AP überlassen? Hm, das halte ich für keine gute Idee. Dann soll lieber der BSP, bevor er selber in den User-Mode wechselt, prüfen ob auch wirklich alle APs in Action sind. Eine Maschine-Check-Exception kann ansich jede andere CPU auch ausgeben, auf meiner Plattform ist das ausführen von externen Interrupts (Maschine-Check-Exceptions werden bei mir über den Chipsatz abgewickelt weil die "fehlerhafte" CPU einfach in den Shutdown zurück geht) nicht an bestimmte CPUs gebunden sondern es nimmt immer die CPU den Interrupt an welche gerade den User-Mode-Code mit der niedrigsten Priorität ausführt.

Deine Herangehensweise ist ja, dass du den BSP als allerletztes aktivierst, da er bis zuletzt noch das System hochfährt.
Ja, so habe ich mir das gedacht.

Ja, und die Frage ist, ob zu diesem Zeitpunkt noch CPUs aktiviert werden sollen oder nicht.
Ich gehe stark davon aus das bis da hin wirklich alle CPUs aktiv sind. Es müssten schon tausende CPUs im System vorhanden sein wenn das nicht mehr zuverlässig erreicht werden soll. Und selbst wenn wirklich noch nicht alle CPUs da sind, solange die bedürftigen Threads alle eine CPU abbekommen ist es doch auch egal.

Also ist er dafür zuständig, dass die CPUs auch funktionieren und aktiv sind. :-)
Ganz klar jein. Der Kernel soll die CPUs verwalten, das eigentliche Aktivieren ist aber erst mal noch nicht seine Aufgabe. Die CPUs, die aktiviert werden, kommen früher oder später eh beim Kernel vorbei, spätestens wenn das erste mal der Zeitscheiben-Counter abgelaufen ist muss die CPU in den Kernel-Mode.

Das macht übrigens auch das Debugging einfacher. Wenn das System noch während der CPU-Initialisierung abstürzt, den Grund zu finden (ist es der BSP? irgendein AP? der AP, der gerade Init ausführt? Init selbst?), stell ich mir schwierig vor.
Ich sehe das Problem nicht. Abstürzen bedeutet ja nicht zwangsläufig dass das System plötzlich gar nichts mehr tut (gerade auf einem SMP-System) sondern das irgendetwas nicht so läuft wie es soll und eben diese Abweichung muss man finden und dann sieht man meistens auch recht schnell die Ursache dafür.

Machen musst du es am Ende eh selbst. ;-)
Das ist ja gerade der Grund warum ich überhaupt damit angefangen hab. ;)


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 #9 am: 24. January 2011, 02:40 »
Tja, da ihr mir jetzt endgültig alle Argumente zerstört habe, ist es mir jetz auch egal. :-P

Erst alle CPUs vor einer Barriere zu sammeln damit diese dann von allen CPUs (möglichst, exakt geht ja eh nicht) gleichzeitig überschritten wird ist zwar interessant aber auch aufwendig (also Tipp/Debug-Aufwand für den Code) und IMHO völlig unnötig.
Kann ich nicht einschätzen.

Ich trenne in Gedanken den Bootvorgang in Phasen auf, wobei jede Phase vollständig erledigt sein muss, ehe es in die nächste Phase übergeht. Das Ineinanderfließen finde ich halt nicht schön. Beides ist möglich.

Das wäre übrigens ein Grund dafür, den BSP als allererstes ins aktive Scheduling zu übernehmen. Denn der hat den Boot-Code bereits ausgeführt und funktioniert garantiert; also kann er auch die Fehlermeldung ausgeben.
Und das restliche Bootstrapping einem ungetesteten AP überlassen? Hm, das halte ich für keine gute Idee.
Wenn der BSP ins aktive Scheduling überführt wird, ist das Bootstrapping komplett beendet. Da gibt es insbesondere keinen Rest. ;-) Init wird also auf dem "ehemaligen" BSP ausgeführt.

Der AP führte während des Bootstraps eine Idle-Schleife (oder Testprogramm oder Minibenchmark oder...) aus, ist also nicht ganz ungetestet.

Gruß,
Svenska

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 24. January 2011, 11:56 »
Hallo,


Tja, da ihr mir jetzt endgültig alle Argumente zerstört habe, ist es mir jetz auch egal. :-P
Sorry, so wollte ich das nun auch wieder nicht.
Trotzdem bleibt wohl das Ergebnis das es scheinbar keinen triftigen Grund gibt der gegen ein gestaffeltes aktivieren der CPUs spricht.

Ich trenne in Gedanken den Bootvorgang in Phasen auf, wobei jede Phase vollständig erledigt sein muss, ehe es in die nächste Phase übergeht. Das Ineinanderfließen finde ich halt nicht schön. Beides ist möglich.
Das sehe ich eigentlich genau so wie Du auch aber in diesem konkreten Fall erschien mir das beharren darauf irgendwie unbequem und deswegen hab ich dann diesen Thread gestartet um mal andere Meinungen dazu zu bekommen. Danke!

Wenn der BSP ins aktive Scheduling überführt wird, ist das Bootstrapping komplett beendet. Da gibt es insbesondere keinen Rest.
Wenn der BSP vorher alle APs aktiviert hat (egal ob alle gleichzeitig oder gestaffelt) dann hast Du natürlich recht aber wenn der BSP noch davor (so wie von Dir vorgeschlagen) in den aktiven Dienst übergeht (also in den User-Mode wechselt und damit dem laufenden Kernel und nicht mehr dem Boot-Code gehört) dann bleibt ja eben noch die Arbeit übrig die restlichen CPUs zu aktivieren. Da das nicht der Kernel selber machen soll (das müsste man dann ja irgendwie in einen der Handler mit integrieren da der Kernel ja keine eigenständigen Aktivitäten besitzt) bleibt dafür nur der Boot-Code übrig. Das ganze einem User-Mode-Prozess (z.B. init) zu überlassen finde ich doof weil dazu ne ganze Menge an privilegierten Befehlen nötig sind für die dann extra Syscalls (mit zugehöriger Rechteprüfung damit da nur init ran kann) implementiert werden müssten. Ich bin schon der Meinung dass das aktivieren aller CPUs Teil des Boot-Code sein sollte (solange ich nicht CPU-Hotplugging unterstützen möchte).

Init wird also auf dem "ehemaligen" BSP ausgeführt.
Wenn alle CPUs wirklich gleichzeitig aktiviert werden (in den User-Mode wechseln) dann ist es ziemlich zufällig welche CPU dann als erstes im Scheduler landet (es sein denn ich benutze gestaffelte Zeitscheiben so das damit eine Reihenfolge entsteht) und damit init bekommt, wenn die CPUs gestaffelt los laufen dann ist es eben die erste CPU die vom Boot-Code aktiviert wurde und wenn das der BSP sein soll dann muss einer der APs das Aufwecken der restlichen CPUs übernehmen.

Der AP führte während des Bootstraps eine Idle-Schleife (oder Testprogramm oder Minibenchmark oder...) aus, ist also nicht ganz ungetestet.
Während der BSP den Boot-Code abarbeitet sind alle anderen CPUs noch im Shutdown-Modus, haben also noch keinen einzigen Befehl ausgeführt. Erst durch das antriggern vom letzten Abschnitt des Boot-Code wachen die CPUs auf und sind dann an einer beim antriggern übergebenen Position im Boot-Code (meine CPUs sollen erst mal in einem System-Modus aufwachen) und dort kann eventuell noch ein kurzer Selbsttest geschehen bevor dann das Springen in den User-Mode (per IRET) vorbereitet (also Register laden usw.) und durchgeführt wird. In der gestaffelten Version wird dieser allerletzte Code-Abschnitt dann auch vom BSP ausgeführt, nachdem er alle APs aufgeweckt hat und eventuell geprüft hat ob auch wirklich alle APs laufen.


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 #11 am: 24. January 2011, 13:27 »
Mal noch ein paar Gedanken für den parallelen Start von CPUs.

Wenn man eine SSD im System hat dürfte wahrscheinlich eher ein CPU-Limit als ein I/O-Limit vorhanden sein. Vorallem da erik ja auf seiner eigenen Platform nicht die Performance aktueller CPUs erreichen möchte.

Das nächste ist, das jede CPU bestimmt irgendwelche Initialisierungs Arbeiten machen muss und da wäre es schon schön, wenn das alles so gleichzeitig wie möglich passieren würde.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 24. January 2011, 16:39 »
Hallo,


Wenn man eine SSD im System hat dürfte wahrscheinlich eher ein CPU-Limit als ein I/O-Limit vorhanden sein.
Eine SSD ist zwar schnell aber so schnell nun auch wieder nicht. Die Daten müssen immer noch über das SATA-Kabel, AHCI-Controller und den PCIe-BUS bis in den RAM gelangen und das dauert auch seine Zeit. Interessant ist die SSD vor allem wenn viele parallele Anforderungen kommen oder wenn große Mengen an Daten zu übertragen sind, beides dürfte auf einen üblichen Boot-Vorgang nur begrenzt zutreffen.

Vorallem da erik ja auf seiner eigenen Platform nicht die Performance aktueller CPUs erreichen möchte.
Was heißt hier möchte? Das ist vor allem eine finanzielle Frage. Selbst mit nem FPGA aus der Ober-Klasse (der dann problemlos mehrere Tausend Dollar kostet) dürfte ich kaum über 300MHz CPU-Takt hinaus kommen. Und nen eigenen Chip anfertigen zu lassen ist erst recht unbezahlbar, zumindest für mich als Privatperson.

Das nächste ist, das jede CPU bestimmt irgendwelche Initialisierungs Arbeiten machen muss und da wäre es schon schön, wenn das alles so gleichzeitig wie möglich passieren würde.
Das wäre in beiden von mir bedachten Szenarien der Fall. Es geht mir nur darum ob ich die CPUs vor dem Übertritt in den User-Mode (nach der CPU-Initialisierung) an einer Barriere sammeln soll oder ob ich darauf verzichten kann. Das Sammeln an einer Barriere würde nur den Übertritt in den User-Mode aller CPUs verzögern bzw. an die zuletzt aktivierte CPU anpassen wogegen ohne die Barriere die ersten CPUs schon etwas früher mit der eigentlichen Arbeit beginnen. Die Initialisierungsarbeit pro CPU findet immer parallel statt, nur leicht gestaffelt weil der BSP ja einen AP nach dem anderen aktiviert.


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 #13 am: 24. January 2011, 16:49 »
Zitat von: erik
Interessant ist die SSD vor allem wenn viele parallele Anforderungen kommen oder wenn große Mengen an Daten zu übertragen sind, beides dürfte auf einen üblichen Boot-Vorgang nur begrenzt zutreffen.
Warum nicht? Warum nicht die Treiber parallel laden lassen, jeder in einem extra Thread und dann auch so initialisieren lassen?

Vorallem auf deiner Architektur sollte die SSD ein CPU Limit bewirken können. Ich habe letztens irgendwo gelesen das es schon einer verdammt schnellen CPU (und da war schon von den schnellsten SandyBridges die rede) verlangt damit man überhaupt so viele Anforderungen an die SSD stellen kann damit sie nicht mehr hinterher kommt. Denn die Daten müssen ja auch verarbeitet werden.

Zitat von: erik
Es geht mir nur darum ob ich die CPUs vor dem Übertritt in den User-Mode (nach der CPU-Initialisierung) an einer Barriere sammeln soll oder ob ich darauf verzichten kann.
Warum solltest du sie aber sammeln, welchen Vorteil hätte das oder welchen Nachteil wenn du es nicht machen würdest?
Ich würde sie loslaufen lassen sobald es möglich ist.

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #14 am: 24. January 2011, 17:47 »
Hallo,


Warum nicht? Warum nicht die Treiber parallel laden lassen, jeder in einem extra Thread und dann auch so initialisieren lassen?
Treiber werden IMHO von einem Plug&Play-Manager gestartet, wenn er eine passende Hardware findet, und den müsste man dazu parallelisieren bzw. das Treiberstarten in zusätzliche Worker-Threads auslagern, das kann man machen aber für den Einstieg ist das IMHO noch nicht unbedingt nötig.

Vorallem auf deiner Architektur sollte die SSD ein CPU Limit bewirken können.
Auf meiner Plattform werden noch viel kleinere Dinge ein CPU-Limit bewirken, dessen bin ich mir voll bewusst. :cry:

Warum solltest du sie aber sammeln, welchen Vorteil hätte das oder welchen Nachteil wenn du es nicht machen würdest?
Um genau diese Frage zu diskutieren habe ich ja diesen Thread gestartet.

Ich würde sie loslaufen lassen sobald es möglich ist.
Das möchte ich ja auch tun, aber es verwischt eben die Grenze zwischen Bootstrap-Vorgang und dem Laufen des eigentlichen OS. Das ist so als würde auf einem x86-System auf ein paar CPUs noch das BIOS arbeiten während auf anderen CPUs schon munter das OS läuft.


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 #15 am: 24. January 2011, 19:04 »
Zitat von: erik
Das möchte ich ja auch tun, aber es verwischt eben die Grenze zwischen Bootstrap-Vorgang und dem Laufen des eigentlichen OS. Das ist so als würde auf einem x86-System auf ein paar CPUs noch das BIOS arbeiten während auf anderen CPUs schon munter das OS läuft.
Wo wäre das Problem? Bzw. was dauert denn solange das es dazu kommen könnte?

Ich muss fairerweise sagen, das ich solch eine Barriere habe, weil ich ja den TSC aller CPUs halbwegs gleich haben möchte.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #16 am: 24. January 2011, 21:31 »
Wenn der BSP vorher alle APs aktiviert hat (egal ob alle gleichzeitig oder gestaffelt) dann hast Du natürlich recht aber wenn der BSP noch davor (so wie von Dir vorgeschlagen) in den aktiven Dienst übergeht (also in den User-Mode wechselt und damit dem laufenden Kernel und nicht mehr dem Boot-Code gehört) dann bleibt ja eben noch die Arbeit übrig die restlichen CPUs zu aktivieren.
Nein. Ich war von einem aktiven Scheduler ausgegangen, also einem Stückchen Code, welches aktiv Prozesse auf CPUs verteilt.

Der Bootstrap-Code endet und legt genau dieses Stückchen Code auf den ehemaligen BSP. Die anderen APs sind zu dem Zeitpunkt schon voll aktiv, haben nur keine Aufgaben zu erledigen (und führen darum die Idle-Schleife aus). Die müssen nicht mehr aktiviert werden, nur mit Aufgaben zugebombt. ;-)

Wenn der Scheduler so aufgebaut ist, dass er neue Aufgaben bevorzugt auf die CPU legt, auf der er gerade läuft (sich also auf diese Weise beendet), dann landet init auch definitiv auf dem ehemaligen BSP. ;-)

Allerdings ist das bei einem rein passiven Scheduler etwas hässlich zu implementieren, daher gebe ich dir recht.

Gruß

erik.vikinger

  • Beiträge: 1 277
    • Profil anzeigen
Gespeichert
« Antwort #17 am: 24. January 2011, 22:17 »
Hallo,


Wo wäre das Problem?
Nun, dieser Thread hat ja nun ergeben das es da wohl kein echtes Problem zu geben scheint.

Bzw. was dauert denn solange das es dazu kommen könnte?
Es ist einfach nicht möglich alle CPUs gleichzeitig zu aktivieren, auch wenn das eigentliche antriggern ein recht kurzer Vorgang ist (wohl im Bereich weniger µs) so ist er doch nicht unendlich kurz und daraus ergibt sich damit immer eine gewisse Staffelung.


Ich war von einem aktiven Scheduler ausgegangen, also einem Stückchen Code, welches aktiv Prozesse auf CPUs verteilt.
Hä, also da verstehe ich jetzt absolut nicht was Du meinst. Der Scheduler kann doch nur der CPU einen neuen Thread zuweisen auf der er gerade läuft. Er kann doch nicht auf CPU 3 laufen (weil dort gerade die Zeitscheibe des dort laufenden Threads abgelaufen ist) und der CPU 5 einen neuen Thread zuweisen. Wie soll das gehen? Was passiert mit dem Thread der gerade auf CPU 5 läuft?

Die anderen APs sind zu dem Zeitpunkt schon voll aktiv, haben nur keine Aufgaben zu erledigen
Das ist doch ein Widerspruch in sich. Eine aktive CPU führt auch etwas aus (und sei es nur ein while(true){HLT;} auch wenn das aus User-Sicht nichts sinnvolles ist). Entweder eine CPU ist aktiv und führt Code aus oder sie ist inaktiv (Shutdown, Schlafzustand o.ä.) und macht nichts.

(und führen darum die Idle-Schleife aus)
Also bei meiner CPU soll es so sein das es unmöglich ist eine CPU unbegrenzt lange im User-Mode zu halten, der Zeitscheiben-Counter zählt gnadenlos bis 0 runter und dann gibt es eine Yield-Exception (egal ob der User-Mode-Code das will oder nicht) und die CPU kommt damit wieder in den Kernel-Code (der Scheduler ist ja Teil des Kernels). Auch auf anderen Plattformen kommt doch früher oder später der Timer-IRQ (falls da nicht irgendwo etwas maskiert wurde) und holt die CPU in den Kernel zurück. Es ist in meiner Vorstellung eigentlich unmöglich die idle-Schleife unbegrenzt lange auszuführen.

Wenn der Scheduler so aufgebaut ist, dass er neue Aufgaben bevorzugt auf die CPU legt, auf der er gerade läuft (sich also auf diese Weise beendet), dann landet init auch definitiv auf dem ehemaligen BSP. ;-)
Ich weiß zwar nicht wie Du das mit dem Scheduler meinst aber auf dem BSP landet init mit hoher Wahrscheinlichkeit nicht. Wenn alle CPUs gleichzeitig losgelassen werden (die Variante mit der Barriere) dann ist es mehr oder weniger Zufall wo init landet und wenn die CPUs gestaffelt loslaufen (also ohne Barriere) dann landet init wohl auf der ersten CPU die den Scheduler aufruft und das ist wohl die CPU die als erstes vom BSP aktiviert wurde (und damit nicht der BSP selber).


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 #18 am: 25. January 2011, 22:11 »
Hallo,

okay, gut, ich sehe wir verstehen unter Scheduler verschiedene Dinge.

Ich dachte, dass eine CPU, nachdem ihre Zeitscheibe abgelaufen ist, einfach aus einer CPU-eigenen Liste die nächste Aufgabe holt und diese abarbeitet. (Eventuell sind es auch mehrere Listen, je nach Priorität.)

Aufgabe des Schedulers ist es, diese Listen mit Inhalt (und Aufgaben zu füllen). Das heißt, wenn eine CPU keine Aufgaben in ihrer Liste findet, dann ruft sie den Scheduler auf, sofern er noch nicht läuft. Läuft er bereits auf einer anderen CPU, macht die CPU die Idle-Schleife.

Der Scheduler markiert global, dass er aktiv ist, und verteilt dann die noch nicht (wieder) verteilten Prozesse des Systems auf die einzelnen CPUs, die dann mit Beginn der nächsten Zeitscheibe ihre Listen abarbeiten.

Die APs werden vom Bootstrap-Code mit leeren Listen versehen, außerdem wird markiert, dass der Scheduler bereits läuft. Also führen alle APs die Idle-Schleife aus. Wenn der Boot-Code endet, startet er den Scheduler auf seiner eigenen CPU und wenn der das möchte, führt er "Init" auch auf dieser CPU aus.

Die Barriere ist also nur, dass der Scheduler Aufgaben verteilen muss (am Anfang stehen außer "init" keine an).

Gruß,
Svenska

FlashBurn

  • Beiträge: 844
    • Profil anzeigen
Gespeichert
« Antwort #19 am: 25. January 2011, 22:18 »
Auf welcher CPU soll denn der Scheduler deiner Meinung nach laufen? Und wie willst du bei dieser Art Verteilung sicher stellen, das auch ein neuer Thread der eine höhere Priorität hat als alle anderen gleich an die Reihe kommt? Wie willst du überhaupt die Threads vernünftig verteilen?

 

Einloggen