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