Welche Task ist die wichtigste in einem Multitasking Betriebssystem?

  • Welche Task ist die wichtigste in einem Multitasking Betriebssystem?

    Wie bitte? Was soll denn dieser Quatsch? Schließlich kommt es doch immer darauf an, was das Betriebssystem und dessen Tasks machen sollen! Insofern eine eher dümmliche Frage…

    Dennoch hat es mich einige Zeit gekostet, die Antwort auf diese dümmliche Frage zu finden (sie lautet übrigens nicht 42).

    Der Antwort kommt man näher, wenn man sich vorstellt, was der PC (hiermit ist der Program Counter / Instruction Pointer gemeint) macht, wenn auf einmal alle Tasks aufgrund von Sleeps(), Suspend() oder anderen blockierenden Aktionen (z.B. Auflaufen auf Mutexe) auf einmal in irgendeinen der wartenden oder ausgeplanten Zustände fallen.

    Stellen wir uns den Fall vor, bei dem die letzte laufende Task ein Sleep(…) ausführt. Sie wird irgendwo in ihrem Taskkontext hinterlegen, wann sie aufgeweckt werden will und dann den Scheduler kooperativ aufrufen, um den Rest ihrer Zeitscheibe an die CPU abzutreten. Der Scheduler durchläuft nun seine Tasks und findet alle blockiert. Das ist jetzt echt ungeschickt, weil er nun nicht weis, was er tun soll. Niemand ist mehr da, der etwas CPU-Zeit möchte! Doppelt blöd ist der Umstand, daß andererseits der Scheduler den ihn taktenden Timerinterrupt (ich vergaß – wir reden von einem im Grunde preemptiven Scheduler) blockiert halten muß, um seinerseits nicht reentrant aufgerufen zu werden. Ist der Timer aber blockiert, dann fehlt (bei „ohne RTC“-Systemen) die Uhr, die dem Scheduler verrät, daß endlich eine Task aufgeweckt werden kann. Das Zeitbewußtsein kommt in diesem Fall nämlich vom den Scheduler taktenden Timer.

    In so einer Situation ist der Scheduler froh, noch die Idletask zu finden, die ihrerseits nicht blockiert, weil sie niemand „von außen“ beeinflussen kann. Puh, Problem gelöst. Jedoch nicht wirklich schön!
    Auf resourcenarmen Mikrokontrollern tut jede überflüssige Task weh, da sie zumindest einen Stack einrichten muß, der es ihr erlaubt, einen kompletten Satz Register zu sichern plus alles obendrauf, was Interruptroutinen im Fall der Unterbrechung der Task noch zusätzlich verlangen. Im Falle von Atmel-Controllern (kleines Arduino Zeugs) mit 32 Registern und gaaaanz weeenig RAM, tut das schon mal weh. Man könnte sich jetzt Konstrukte vorstellen, die den Prozessor in den HALT-Mode (oder Sleep-Mode) versetzen und den Timerinterrupt öffnen, um dem Problem des „ich weis nicht was ich tun soll“ per CPU-Halt auszuweichen. Aber auch das werden Krücken, die ich hier nicht weiter vertiefen will…

    Bleiben wir also bei unserer guten alten Idle Task. Tatsächlich gibt es neben den Tasks ja auch noch Interrupts, die auch irgendwie verarztet werden wollen. Nun kann man sie als Interrupt ausführen, will aber doch immer irgendeine Information anschließend in Tasks weiterverarbeiten. Klar, Interrupts sperren, Hilfsvariable setzen, Interrupts freigeben. Oder das Betriebssystem unterstützt einen und überführt die Interrupts in „höchstpriore“ Tasks. Also noch eine Task – für das „Interrupt-forking“? Oder läßt sich hier vielleicht doch die niedrigpriore Idletask mißbrauchen? Damit wäre das Stackproblem wenigstens „halbiert“. Übernimmt die Idletask neben der „Retterfunktion in der Not“, also wenn sich alle anderen verdrückt haben, noch die Funktion der Entkopplung von Interrupts auf OS-Ebene, dann könnte man die Bytes für deren Stack als „gut aufgehoben“ ansehen. Das der Interrupt sicherstellen muß, daß die Idletask im Fall des „Interrupt-forkings“ temporär die höchste Priorität erhalten muß versteht sich von selbst.

    So, nun kommen meine Fragen. Hat jemand eine Idee, wie man sich bei einem Multitasking-Kernel den Stackspace für eine Idle-Task sparen kann? Kann man ihn sich überhaupt sparen, wenn man auf der anderen Seite sowieso „Interrupt-forking“ betreiben will? Ich sehe keine andere Lösung, als die oben skizzierte.

    And the winner of the day (until further notice) is – the idle task.

    Schöne Grüße

    schnasseldag

  • Welche Task ist die wichtigste in einem Multitasking Betriebssystem?? Schau mal ob du hier fündig wirst!

  • Hallo Schnasseldag,

    da scheint sich seit Rosenmontag ja so einiges bei Dir getan zu haben!

    Deine Frage möchte ich mal philosophisch beantworten. Ein Idle-Task sollte in möglichst allem so behandelt werden wie alle anderen Tasks auch - also inkl. Register auf den Stack speichern, wenn der Scheduler einen anderen Task aktiviert.
    Andererseits ... :s wozu bedarf es, dass der Idle-Task Register irgend wohin sichert, wenn er nichts weiter tut als "im Takt zu klatschen"?

    Daraus folgt die Antwort: Braucht's (wohl) nicht...

    Auf die weitere Diskussion bin ich jetzt schon gespannt!


    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    Einmal editiert, zuletzt von Andreas (19. Mai 2016 um 23:15)


  • da scheint sich seit Rosenmontag ja so einiges bei Dir getan zu haben!


    Ja, aber dennoch langsam. Der Teufel steckt halt im Detail.


    Andererseits ... :s wozu bedarf es, dass der Idle-Task Register irgend wohin sichert, wenn er nichts weiter tut als "im Takt zu klatschen"?


    Die Register sichert nicht die Task, sondern der Schedulercode (ob der kooperativ oder preemptiv aufgerufen wird, ist zunächst mal egal). Insofern kannst Du eine OS-Task ohne Prolog und Epilog definieren, die kein einziges Register benötigt, und dennoch wird der Scheduler alle Register sichern, weil er ja nicht weis, welche Register "seine" Tasks gesichert haben wollen.

    Code
    static void __attribute__ ((OS_task)) taskIdle()
    {
      while(1);
    }
  • Hallo Schnasseldag,


    Ja, aber dennoch langsam. Der Teufel steckt halt im Detail.

    Oh ja ...



    Die Register sichert nicht die Task, sondern der Schedulercode (ob der kooperativ oder preemptiv aufgerufen wird, ist zunächst mal egal).


    Das weiß ich - habe mich nur ein wenig flapsig ausgedrückt.

    Aber wer hindert Dich denn daran, beide Extreme auszuprobieren? Was wäre aus den Anfängen eines Betriebssystems geworden, wenn Linus Torvalds sich an damals gültigen Vorstellungen orientiert hätte - und neuen Ideen keinen Raum geschenkt hätte?

    Was auf den Stack kommt, muss dem Task, der es auf den Stack hat bringen lassen, zurückgegeben werden, wenn der Scheduler dem Task wieder Aufmerksamkeit schenkt.

    Wer nichts auf den Stack gebracht hat, braucht vom Scheduler auch nichts mehr bekommen, wenn er wieder an der Reihe ist.

    Beides sind in meinen Augen brauchbare Ansätze - unabhängig wie es sonst gehandhabt wird.


    Beste Grüße

    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    2 Mal editiert, zuletzt von Andreas (15. Oktober 2017 um 15:36)

  • Ob man ihn sich sparen kann - kA. Aber dein "interrupt forking" ist denke ich das, was als lower-interrupt-half bezeichnet wird. Und deine Idee, dort den IDLE-Task zu recyclen ist glaube ich eher so mau. Denn je nach Treiber kann Linux zumindest wohl auf zwei Mechanismen zugreifen - Tasklets und Workqueues. Erstere sind atomar, letztere koennen preemptiert werden. Womit du schon wieder mindestens zwei Stacks hast.

    Die Frage ist, warum du ein paar Bytes sparen willst? Einfach so? Warum genau da? Und wird deine IDLE-recycle-Logik in der Summe weniger Speicher belegen?

  • Andreas: Und woher soll der (üblicherweise im Interrupt laufende) Scheduler wissen, wieviele Register die Task (welche er gerade unterbricht) aktuell verwendet? Das kann er nicht. Insofern kann er nur davon ausgehen, daß er einfach alle sichern muß (inkl. der Flags). Es geht ja nicht nur darum, den Stack der unterbrochenen Task zu retten, sondern auch die aktuellen Registerwerte zum Zeitpunkt der Unterbrechung. Und ob die nun auf den Stack der unterbrochenen Task gepusht werden oder anderswo abgelegt werden, ist RAM-mäßig egal. Programmatisch bietet sich natürlich der Stack der aufgerufenen Task an.
    Automatisch zusammengefügt:


    Die Frage ist, warum du ein paar Bytes sparen willst? Einfach so? Warum genau da? Und wird deine IDLE-recycle-Logik in der Summe weniger Speicher belegen?

    @__deets__: Ich denke hier nicht an Linux und gigabyteweise RAM für einen Core i7. Ich denke hier an einen Atmega328 o.ä. Da hast Du 2k RAM und 32-Byteregister + SREG + 2 Byte Returnadresse sind schon mal 35 Byte. Dazu kommen noch die Bytes sämtlicher möglicher Interrupts. Unter 60-100 Byte Stack / Task wirst Du da nicht auskommen. Das sind dann schon mal an die 5% RAM für einen Stack allein. Da überlegt man sich schon ein Recyclen von Ressourcen...

  • Und auf das System bekommst du einen kompletten Scheduler mit Priority Queues und deinen oben beschriebenen Semaphoren und anderen Strukturen, in denen dann alle Tasks feststecken?

    So ein System wuerde doch einfach einen round-robin machen, und dann steht da halt ein Task in einer pollenden Schleife. Was ja im uebrigen eh immer eine Wahl ist. Zur Not verbrennt das System seine Zeit damit, jeden Task zu aktivieren, der dann sagt "check mal, ob meine Semaphore frei ist", womit dann wieder Kernel-Zeit anbricht.


  • Und auf das System bekommst du einen kompletten Scheduler mit Priority Queues und deinen oben beschriebenen Semaphoren und anderen Strukturen, in denen dann alle Tasks feststecken?


    In der Tat, das bekomme ich. Sogar eine Prioritätsvererbung ist drin. Anbei mal ein Auszug aus dem Mutex-Code.




    So ein System wuerde doch einfach einen round-robin machen, und dann steht da halt ein Task in einer pollenden Schleife. Was ja im uebrigen eh immer eine Wahl ist. Zur Not verbrennt das System seine Zeit damit, jeden Task zu aktivieren, der dann sagt "check mal, ob meine Semaphore frei ist", womit dann wieder Kernel-Zeit anbricht.


    Ich weis nicht 100%, ob ich das richtig verstehe. Aber Im Falle eines Mutex-locks Zeit verbraten und einfach zu warten, bis der Scheduler den Status wieder auf Waiting gesetzt hat, wäre tatsächlich ein Ausweg. So sah auch meine erste Implementierung aus, bis ich mich dann entschloß, den Scheduler kooperativ aufzurufen und so den Schedulevorgang zu erzwingen (also die Zeitscheibe freizugeben).

    Siehe dazu das Makro, welches den unten stehenden Funktionspointer zur Interruptroutine aufruft:

    Code
    SCHEDULE;
      // ( (T_pFn)(TIMER0_COMPA_vect_num<<1) ) ();
  • Was auch immer du da treibst - Spass macht es bestimmt. Ob es jenseits davon sinnvoll ist, kann ich schwer beurteilen. Bei so wenig Speicher ist Speicherfragmentierung ein riesen Problem. Dynamische Tasks fragmentieren den Speicher fast schon zwangslaeufig. Ich wuerde eine konkrete Anwendung dann eher mit fest definierten Task-Listen und zur Compilezeit schon bekannten Prioritaeten definieren - zumindest erst mal so aus dem Bauch raus. Interrupts bedienen Ringbuffer, und die Tasks muessen halt hoch genug priorisiert sein, um die dann auch abraeumen zu koennen.

    Dein Trick mit dem verschenken der Zeitscheibe ist erstmal nett, macht das System aber auch komplexer und nicht-deterministischer. Ein simples "ich spin-locke vor mich hin, bis was zu tun ist" in dem Tasks dann ggf. landen ist letztlich ja nichts anderes als dein IDLE. Womit ich nicht gesagt haben will, dass das besser ist. Kommt bestimmt auf das Szenario an.


  • @__deets__: ....Ich denke hier an einen Atmega328 o.ä. Da hast Du 2k RAM

    und deswegen mag ich den 328p bald nicht mehr

    ich nehme lieber wo möglich den 1284p (mighty mini) mit 16KB SRAM, sogar doppelt so viel wie ein m2560

    lasst die PIs & ESPs am Leben !
    Energiesparen:
    Das Gehirn kann in Standby gehen. Abschalten spart aber noch mehr Energie, was immer mehr nutzen. Dieter Nuhr
    (ich kann leider nicht schneller fahren, vor mir fährt ein GTi)


  • Was auch immer du da treibst - Spass macht es bestimmt.

    Hallo __deets__,

    es geht mir tatsächlich eher um das „man muß das einfach mal gemacht haben“. Klar, wer mit einem µC arbeitet, der hat wenige Resourcen und kann sich bei dem kleinen Footprint kaum ein Byte (RAM/Flash) neben dem eigentlichen Applikationscode leisten. Das ist mir schon klar. Tatsächlich gibt es ein CreateProcess, welches den Taskkontext inklusive des Stacks dynamisch vom Heap nimmt. Insofern kann ich Tasks dynamisch erzeugen (zerstören noch nicht, das ist aufgrund der Resourcenfreigabe erheblich komplizierter), jedoch obliegt es ja dem Anwender, seine Tasks während des Startups seiner Applikation zu erzeugen. Dann fragmentiert der Speicher auch nicht.

    Übrigens saß ich gestern Abend einen Denkfehler auf. Selbst wenn ich die Zeitscheibe z.B. bei einem blockierenden Mutex-lock() nicht freigebe und die Zeit verheize, löst das nicht mein Problem des „was macht der PC, wenn alle Tasks blockiert sind“. Es verschiebt den Zeitpunkt des Problemeintritts nur bis zum nächsten Schedulingtakt. Ein Spinlock würde zwar das Problem lösen, liegt aber außerhalb der Zuständigkeit des Betriebssystems, also beim Anwender. Insofern löst es zwar das Problem, hilft mir aber nichts im Zuge einer allgemeingültigen abgeschlossenen Lösung innerhalb des Betriebssystemkerns.

    Was den Nicht-Determinismus anbelangt, da hast Du Recht. Die Abgabe der Zeitscheibe führt zu Nichtdeterminismus und zwar in dem Fall, daß Tasks bedingt durch kooperative Calls in den Scheduler schneller scheduled werden, als es der Timer allein tun würde. Zudem erhöht meine derzeitige Implementierung den Scheduler-TickCount bei jedem Aufruf des Schedulers (egal ob kooperativ oder preemptiv aufgerufen). D.h. die Zeit „rennt“ vorweg.

    Ich hab‘ mir lange Gedanken darüber gemacht, ob das nun gut ist oder nicht und habe dann beschlossen, daß deterministische Aufgaben durch Timerinterrupts gelöst werden sollten und der Betriebssystemkern vom strengen Determinismus befreit wird und eher Ressourcen schonen sollte. Daher auch der Gedanke der Freigabe der CPU-Zeit im Falle von blockierenden Tasks. Ähnliches gilt für weitere Dinge, wie die Prioritätsvererbung. Die hierfür notwendig werdende Buchführung von blockierendem Mutex und dazugehöriger dynamischer Prozeßpriorität ist auch nicht für Nullkosten an RAM zu erhalten. Daher auch die bedingte Kompilierung…

    Schöne Grüße

    schnasseldag
    Automatisch zusammengefügt:


    ...und deswegen mag ich den 328p bald nicht mehr
    ich nehme lieber wo möglich den 1284p (mighty mini) mit 16KB SRAM, sogar doppelt so viel wie ein m2560

    Das kann ich gut nachvollziehen. Allerdings hatten mich mehrere Gründe dazu bewogen, zunächst den Atmega328 herzunehmen.
    a.) Ich bin ein armer Mann und kann mir so teure Hardware nicht leisten :)
    b.) Wenn's auf dem Atmega328 läuft, dann läuft's auch auf einem größeren Bruder.
    c.) Es erzieht einen während der Programmierung zu ein wenig Disziplin hinsichtlich gewählter Datenstrukturen und Typisierung.
    d.) Mit dem Xplained Mini gibt es ein günstiges Board mittels dessen ich per DebugWire aus AtmelStudio heraus ein wenig Debuggen kann.

    Ich hatte zunächst auch erst mit der Arduino IDE begonnen. Allerdings war mir die ewige Neukompiliererei dann doch ein Dorn im Auge und ich stieg auf AtmelStudio um.

    Ein kleines Beispiel zu c.) und der Festlegung von Datentypen zur internen Verweltung der Tasks. Vielleicht verdeutlicht das ja, was ich meine.

    Die typedef's sind derzeit für einen Atmega328 gewählt. Für einen ATTiny könnte man T_StackSize auch noch auf einen char herabsetzen. Die Tiny's haben ja eh kein RAM ;)


  • Das kann ich gut nachvollziehen. Allerdings hatten mich mehrere Gründe dazu bewogen, zunächst den Atmega328 herzunehmen.

    halt, ich bin auch "arm" aber für die Entwicklung nehme ich den m1284p mit reichlich Platz und SRAM und wenn ich keine DebugAusgaben mehr brauche kann ich auch optimiert auf den 328p gehen.

    Wie gesagt meine jetztige provisorische Rolladensteuerung läuft auf dem m328p mit sa Sonnenaufgangs- und su-Berechnung mit LCD von Nokia und 433MHz Sender LIB

    der ist knacke voll, keine Tasten zum Eingreifen, keine Serial.in Kommandos mehr, keine Menüführung.

    Der m328p reichte für die kleine wordclock mit 119 RGB LEDs mit DCF77 ohne wlan und NTP, die große wc24h mit 295 LEDs und wlan für NTP und Menüführung muss den m1284p bekommen.

    lasst die PIs & ESPs am Leben !
    Energiesparen:
    Das Gehirn kann in Standby gehen. Abschalten spart aber noch mehr Energie, was immer mehr nutzen. Dieter Nuhr
    (ich kann leider nicht schneller fahren, vor mir fährt ein GTi)

  • Hi jar,


    der ist knacke voll, keine Tasten zum Eingreifen, keine Serial.in Kommandos mehr, keine Menüführung.


    ... es hätte mich auch gewundert, wenn Du einen µC nicht bis in's letzte Nibble ausreizen würdest ;)

    Aber wo liegt eigentlich die Grenze zwischen µC und so einem Raspi? Vergessen wir mal den Preis. Niemand konsumiert hier zu Tausenden und muß deswegen zwischen Arduino und Raspi entscheiden. Ich sehe das eher so. Will ich eine Kleinigkeit "autark" steuern, dann würde ich mir etwas suchen, was schnell bootet, keine Verschleißteile besitzt und "schußsicher" ist - also so einen kleinen Arduinoableger. Liegt der Schwerpunkt im Web, dann freue ich mich über einen "gratis" IP Stack und ein Filesystem. Threads und ein Webserver sind auch ganz toll und wenn dann Posix die Code Migration vom Aldi-NAS Server auf den Raspi mit Apache zum Kinderspiel macht, nun dann fragt man sich wieso das Linux Zeug MS nicht schon lange abgelöst hat. Aber das liegt vermutlich an der Faulheit der "freien" Programmierer, immer noch kein volles Gegengewicht zu .Net gesetzt haben zu können. Mono, Boost und Co kommen da wohl immer noch nicht ran... Schade eigentlich.

    Naja, zurück zu Raspi und "Arduino". 5 Shields auf einem Arduino um einen IP-Stack, Webserver und IO zu bekommen wird am Ende auch zum Luxus. Der Raspi mit diesen HAT-Dingern versucht's von der anderen Seite - ich im Prinzip mit dem Hubo ja auch. Will man der "Menschheit etwas Gutes" tun, so stellt sich somit die Frage , wie eine Abstimmung wohl ausgehen würde, wenn die Frage hieße. "Welche Probleme wollt Ihr lösen und wie tief wollt Ihr in die Materie einsteigen?". Ich glaube, Bytezähler wie wir würden wohl in der Minderheit wiederzufinden sein, wenngleich ich mich zuweilen auch schon mal erdreiste 1000e von GDI Objekten zu konsumieren (manchmal muß man eben auch eine Byteorgie feiern können :) ). Aber zum einen ist das eine andere Geschichte, zum anderen wäre es wohl auch nicht representativ für das gros der Gemeinde?!

    Schöne Grüße

    schnasseldag


  • Naja, zurück zu Raspi und "Arduino". 5 Shields auf einem Arduino um einen IP-Stack, Webserver und IO zu bekommen wird am Ende auch zum Luxus. Der Raspi

    mein getunter POLLIN netio mit m1284p ist ja teurer als der pi aber dafür läuft er schon über 5 Jahre und ich blicke in der soft mehr durch als in Debian

    lasst die PIs & ESPs am Leben !
    Energiesparen:
    Das Gehirn kann in Standby gehen. Abschalten spart aber noch mehr Energie, was immer mehr nutzen. Dieter Nuhr
    (ich kann leider nicht schneller fahren, vor mir fährt ein GTi)

    Einmal editiert, zuletzt von jar (21. Mai 2016 um 00:24)


  • Hui, echt schnuckelig. :thumbs1: Wie sieht die Software für den IP Zugriff aus?

    groß

    nicht jetzt nicht hier morgen tel?

    lasst die PIs & ESPs am Leben !
    Energiesparen:
    Das Gehirn kann in Standby gehen. Abschalten spart aber noch mehr Energie, was immer mehr nutzen. Dieter Nuhr
    (ich kann leider nicht schneller fahren, vor mir fährt ein GTi)

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!