-
Technisches
Gebiet
-
Die
vorliegende Erfindung betrifft allgemein Computerbetriebssysteme
und insbesondere ein Echtzeitbetriebssystem, das Anwendungsprogramme
oder -prozesse mit mehreren Threads einteilt.
-
HINTERGRUND
DER ERFINDUNG Problemstellung
-
Echtzeit-Computerbetriebssysteme überwachen
in der Regel die Ausführung
mehrerer gleichzeitig ausgeführter
Anwendungen (genauer gesagt, Anwendungen, die wiederholend eingeteilt
werden und deren Ausführung
verschachtelt ist). Diese Echtzeitbetriebssysteme müssen ein
Mittel zur Einteilung der Anwendungen bereitstellen. In einer „harten" Echtzeit-Betriebsumgebung,
wie zum Beispiel im Cockpit eines Flugzeugs, werden Anwendungen,
die kritische Funktionen, wie zum Beispiel Funkkommunikationsdaten
und Navigationsinformationen, überwachen,
in der Regel zusammen mit Anwendungen, die andere, weniger kritische
Funktionen überwachen, ausgeführt. Ein
Betriebssystem, das diese mehreren Anwendungen beaufsichtigt, muß sicherstellen,
daß die
Anwendungen, die die weniger kritischen Funktionen überwachen,
die Ausführung
der Anwendungen mit höherer
Kritizität
auf rechtzeitige Weise nicht verhindern. In einer solchen Umgebung
muß der
Anwendungs-Scheduler des Computerbetriebssystems dies durch Bereitstellen
einer bestimmten Form von Zeitpartitionierung zwischen den Anwendungen
erzielen.
-
Zur
Zeit verfügbare
Betriebssysteme berücksichtigen
Betriebssystemoverheads, wie zum Beispiel Interrupt-Abwicklung, nicht
genau und sind deshalb nicht in der Lage, Zeitsteuerungsgrenzen
zwischen den Anwendungen durchzusetzen. Diese Unfähigkeit
entsteht, weil die existierende Theorie und Praxis der ratenmonotonischen
Analyse (RMA – Rate
Monotonic Analysis) und der ratenmonotonischen Einteilung (RMS – Rate Monotonic
Scheduling) keine Computerbetriebssystemoverheads und Prozessor-Interrupt
bei der Bestimmung der Durchführbarkeit
eines vorgeschlagenen Ablaufplans berücksichtigen.
-
Aus
US-A-5386561 ist ein Zeitverteilungsbetriebssystem bekannt, das
die Prozessoren zur Ausführung
ordnet, wobei zur Ausführung
eines Prozesses eine vorbestimmte Zeitscheibe zugeteilt wird.
-
Gemäß der vorliegenden
Erfindung wird ein Verfahren zum Durchsetzen eines Ablaufplans für mehrere
gleichzeitig ausgeführte
Anwendungen, von denen jede mindestens einen Thread umfaßt, der durch
Aufrufen eines Computerbetriebssystemdienstes Verarbeitungszeitoverhead
verbraucht, bereitgestellt, mit den folgenden Schritten: jedem Thread
wird eine wiederkehrende entsprechende Zeitspanne zugeordnet; für jeden
Thread wird ein entsprechendes Zeitbudget eingerichtet; von jedem
entsprechenden Zeitbudget wird das von jedem Thread während der entsprechenden
Zeitspanne verbrauchte Verarbeitungszeitoverhead abgezogen; und
die Ausführung jedes
Threads wird beendet, wenn das entsprechende Budget eines Threads
für die
entsprechende Zeitspanne aufgebraucht ist.
-
Das
Anwendungseinteilungsverfahren der vorliegenden Erfindung ermöglicht eine
Integration kritischer Echtzeitfunktionen, wie zum Beispiel flugtechnische
Anzeige und Steuerung, während
gleichzeitig sichergestellt wird, daß die diese Funktionen bereitstellenden
Anwendungen auf rechtzeitige Weise ausgeführt werden. Das vorliegende
Einteilungsverfahren multiplext Prozessorzeit („CPU-Zeit") zwischen mehreren Anwendungs-Threads.
Die Zeit, die von dem Betriebs system bei der Ausführung im
Namen eines Threads benutzt wird, wird als Anwendungsverarbeitungszeit
behandelt, statt die resultierende verbrauchte Prozessorzeit als
ein Betriebssystemoverhead anzusehen. Jedem Thread wird ein Zeitbudget
zugewiesen. Die Prozessorzeit, die durch das Betriebssytem bei der
Ausführung
im Namen eines Threads verbraucht wird, wird als Teil des Zeitbudgets
der Anwendung der entsprechenden Anwendung zurückgerechnet. Die CPU-Zeit,
die einem Thread während
einer gegebenen Zeitspanne seiner Ausführung verfügbar ist, wird durch das vorliegende Verfahren
garantiert, wodurch durch Interrupt-Maskierung und Thread-Budgets
verfügbare
CPU-Zeit effizient
aufgeteilt wird. Betriebssystemoverheads werden somit genau genug
berücksichtigt,
um einen Ablaufplan für
die Anwendungen zu bestimmen, der durch das Betriebssystem durchsetzbar
ist. Außerdem
ermöglicht
die Art und Weise der Berücksichtigung
dieser Overheads eine Laufzeiterzeugung und -löschung von Threads über traditionelle
RMA-Verfahren.
-
Die
existierende RMA-Theorie berücksichtigt Betriebssystemoverheads
durch Spezifizieren einer Schranke für die prozentuale Benutzung,
die durch das Betriebssystem im Namen der auf dem Betriebssystem
ablaufenden Anwendungen erwartungsgemäß benutzt wird. In einem realen
System gibt es jedoch drei Kategorien von Overheads, die tatsächlich berücksichtigt
werden sollten.
- 1. Erstens muß das Interrupt-Ansprechen
und andere durch das Betriebssystem durchgeführte Aktivitäten, die
sich aus dem Vergehen der Zeit ergeben, berücksichtigt werden. Ein Beispiel
ist ein Ansprechen auf den periodischen System-„Tick", der eine Benachrichtigung über das
Ablaufen von Zeit für
das Betriebssystem bereitstellt.
- 2. Zweitens sollten durch Anwendungen verursachte Overheads,
die sich aus dem Aufrufen von Betriebssystemdiensten ergeben, berücksichtigt werden.
- 3. Als letztes muß die
Zeit berücksichtigt
werden, die für
das Ansprechen auf asynchrone Interrupts, z.B. nichtperiodische
E/A, wie zum Beispiel Netzwerkschnittstellensteuerungsinterrupts,
in Anspruch genommen wird.
-
Diese
Betriebssystemoverheads erfordern alle verschiedene Mechanismen,
um ihre Benutzung von Verarbeitungszeit in einem Schema auf RMA-Basis
genau zu berücksichtigen.
Overheads der Kategorie 1 (z.B. periodische Systemtaktinterrupt-Abwicklung)
sind definitionsgemäß streng
eine Funktion der Zeit und die Standard-RMA-Technik des Spezifizierens
dieser als Benutzungsoverhead ist angemessen.
-
Overheads
der Kategorie 2 (Aufrufe von Betriebssystemdiensten) sind eine Funktion
des Anwendungsverhaltens und können
im allgemeinen nicht so wie für
Overheads der Kategorie 1 berücksichtigt
werden. Der Grund dafür
besteht darin, daß durch
Anwendungen verursachte Overheads nicht nur von dem Ablaufen der
Zeit abhängen;
statt dessen hängen
sie auch von den bestimmten durch die Anwendung eingeleiteten Aktivitäten ab.
Overheads der Kategorie 2 können
in zwei Typen unterteilt werden:
- 2A. CPU-Zeit,
die durch Betriebssystemdienste, die im Namen einer Anwendung bereitgestellt werden,
verbraucht wird; und
- 2B. Blockierungszeit, die durch die effektive Prioritätserhöhung eingeführt wird,
die dem Sperren und Freigeben von einer gegebenen Anwendung zugeordneten
Interrupts zugeordnet ist.
-
Overheads,
die Aktivitäten
der Kategorie 2A zugeordnet sind, werden durch das Verfahren der vorliegenden
Erfindung dadurch berücksichtigt,
daß die
benutzte Zeit als Anwendungsverarbeitungszeit betrachtet wird, statt
die resultierende verbrauchte CPU-Zeit als ein Betriebssystemoverhead
zu betrachten. Das vorliegende Verfahren rechnet diese CPU-Zeit
der Anwendung als Teil des Zeitbudgets der Anwendung zurück.
-
Overheads,
die Aktivitäten
der Kategorie 2B zugeordnet sind, können als eine Mutex-Blockierungszeit
modelliert werden. Mutex-Dienste (MUTual EXclusion, gegenseitige
Ausschließung)
sind Systemfunktionen, die mehreren Threads einen synchronisierten
Zugriff auf Anwendungsbetriebsmittel ermöglichen. Wenn eine Anwendung
zum Beispiel mehrere Threads benutzt und sich diese Threads ein gemeinsames
Betriebsmittel, wie zum Beispiel eine Datenstruktur, teilen, können Mutexe
verwendet werden, um sicherzustellen, daß nur ein Thread auf einmal
auf die Datenstruktur zugreifen kann.
-
Das
Verfahren der vorliegenden Erfindung modelliert Blockierungszeit
der Kategorie 2B durch Betrachten eines kritischen Abschnitts als
ein Mutex mit den folgenden Eigenschaften:
- a)
Ein Prioritätshöchstwert,
der um eins größer als die
höchste
tatsächliche
Anwendungspriorität
ist;
- b) eine Zeitspanne, die der kürzesten Zeitspanne in dem analysierten
System äquivalent
ist; und
- c) eine Dauer gleich der Dauer des zeitkritischen Abschnitts,
der dem längsten
kritischen Abschnitt zugeordnet ist, der nicht Teil des Overheads
der Kategorie 1 ist (die Zeitspanne, in der Interrupts gesperrt
sind).
-
Overheads,
die Aktivitäten
der Kategorie 3 zugeordnet sind, können im allgemeinen nicht als strikte
Benutzung von Betriebssystembetriebsmitteln durch eine Anwendung
modelliert werden. Die existierende RMA- und RMS-Theorie approximiert die Kosten von
Overheads der Kategorie 3 durch Spezifizieren einer Verarbeitungszeit
für jedes
Auftreten des Interrupts und einer kleinsten Zwischenankunftsrate
für den
Interrupt, z.B. N Interrupts pro Sekunde, wobei jeder Interrupt
M Sekunden verbraucht. Das Ergebnis ist eine dem Interrupt zugeordnete
Zeitbenutzung (M*N). Einen Ablaufplan direkt als Ergebnis einer
solchen Analyse durchzusetzen, erfordert eine Verfolgung der Zwischenankunftsrate
des Interrupts sowie der von jedem Interrupt-Ansprechen verbrauchten
Zeit.
-
Durch
eine Ausführungsform
der vorliegenden Erfindung wird es unnötig, die Interrupt-Zwischenankunftsrate
zu verfolgen, indem (1) als Reaktion auf einen Interrupt ein Thread
aktiviert wird; (2) dem Interrupt eine Zeitspanne zugeordnet wird;
und (3) sichergestellt wird, daß während dieser
Periode die sich aus der Abwicklung so vieler Interrupts wie notwendig
ergebende akkumulierte CPU-Benutzung einen zulässigen Wert nicht übersteigt.
Unter diesen Aspekt der Erfindung fallen, wenn er implementiert wird,
die obenbeschriebenen Overheads der Kategorie 2B. Eine alternative
Ausführungsform
stellt einen Interrupt mit allen für einen Thread in der vorherigen Ausführungsform
beschriebenen Eigenschaften bereit. In diesem Fall werden die Ankunft
eines Interrupts und der Abschluß der Interrupt-Abwicklung
als eine Pseudothreadaktivierung behandelt.
-
Jede
dieser Lösungen
(Interrupt-Thread oder Pseudo-Thread)
ermöglicht
das Ankommen einer beliebigen Anzahl von einem spezifischen Interrupt
zugewiesenen Interrupt während
einer zugeordneten Periode und außerdem eine Variabilität der Verarbeitungszeit
für jeden
einzelnen Interrupt, solange die akkumulierte benutzte CPU-Benutzung für die Periode
den zulässigen
Wert für
diesen bestimmten Interrupt nicht übersteigt. Dieses Verfahren
vereinfacht die Durchsetzung des Budgets der Anwendung stark, weil,
nachdem die akkumulierte CPU-Zeit für die Periode
erschöpft
wurde, der Interrupt bis zum Ende der dem Interrupt zugeordneten
Zeitspanne maskiert werden kann. Die Maßnahme des Maskiertlassen des
Interrupts, wenn die CPU-Benutzung erschöpft wurde, reicht aus, um sicherzustellen,
daß keine
zusätzliche
CPU-Zeit durch den Interrupt-Handler verwendet wird und somit wird
eine ordnungsgemäße Zeitpartitionierung
durchgesetzt.
-
KURZE BESCHREIBUNG
DER ZEICHNUNG
-
Die
Erfindung wird bei Durchsicht der folgenden Beschreibung in Verbindung
mit der Zeichnung besser verständlich.
Es zeigen:
-
1 ein Diagramm der Einteilung
von drei Anwendungsthreads;
-
2 ein Flußdiagramm
des Threadeinteilungsprozesses;
-
3 ein Diagramm der Ausführung eines Interrupt-Handlers „I" und zweier Anwendungs-Threads;
und
-
4 ein Diagramm von Zeitsteuerungsereignissen,
die für
eine Einzelthreadaktivierung von Interesse sind.
-
AUSFÜHRLICHE
BESCHREIBUNG
-
Die
vorliegende Erfindung liefert einen Mechanismus zum Berücksichtigen
von Betriebssystemoverheads und Prozessorinterrupts, um Anwendungsprogramme
oder -prozesse mit mehreren Threads auf eine Weise einzuteilen,
die durch das Betriebssystem durchsetzbar ist. Im Kontext des vorliegenden
Verfahrens kann eine Anwendung (für die Zwecke der vorliegenden
Schrift gleichbedeutend mit dem Begriff „Prozeß") eine beliebige Anzahl von Threads
aufweisen. Jeder Thread ist einer Anwendung zugeordnet, die als
Eigner des Threads betrachtet wird. Die genaue Anzahl von Threads,
die zu einem beliebigen gegebenen Zeitpunkt aktiv sein können, ist
eine Funktion des der Anwendung bereitgestellten CPU-Budgets und
des Grads der den Threads der Anwendung zugeteilten Benutzung der CPU
(des Prozessors).
-
Der
Thread-Scheduler der vorliegenden Erfindung erfordert, daß jeder
Thread eine dem Thread zugeordnete Periode aufweist. Der Thread-Scheduler
ist ratenmonoton: d.h. er weist Prioritäten auf der Basis der Rate
des Threads zu (Perioden kürzerer Dauer
weisen höhere
Priorität
auf). Zusätzlich
ist es erforderlich, daß jede
Periode harmonisch ist. Zum Beispiel sind Perioden von 25 ms, 50
ms und 75 ms Vielfache einer Basisperiode, die 75 ms sind aber nicht
harmonisch, da sie kein Vielfaches von 50 ms sind. Perioden von
25 ms, 50 ms und 100 ms sind jedoch harmonisch und entsprechen somit
den Anforderungen des vorliegenden Verfahrens.
-
Ein
typischer periodischer Thread arbeitet in einer unendlichen Schleife
und führt
dabei in jeder Periode erneut und erneut dieselbe Logik durch. Das vorliegende
Verfahren ermöglicht
es einem Thread, einen Threadaufgabedienst aufzurufen, der innerhalb
dieser Schleife aufgerufen werden soll. Der Dienst suspendiert den
Thread bis zur nächsten
Periode dieses Threads. Dieses freiwillige Aufgeben der CPU ermöglicht anderen
Threads in dem System, ausgeführt
zu werden.
-
Jeder
periodische Thread besitzt ein CPU-Budget, das die maximale Zeit
repräsentiert,
für die
der Thread die Betriebsmittel der CPU während seiner Periode steuern
kann. Bei bestimmten Anwendungen wird der Threadaufgabedienst aufgerufen, bevor
das Budget dieser Anwendung aufgebraucht ist. Bei anderen Anwendungen
läuft der
Thread in einer kontinuierlichen Schleife ab, bis das Budget erschöpft ist,
und an diesem Punkt wird durch einen externen Timer ein Interrupt
erzeugt. Das Betriebssystem suspendiert dann den Thread bis zum
Beginn seiner nächsten
Periode, wodurch andere Threads in dem System rechtzeitig ausgeführt werden
können.
-
Jeder
Thread enthält
eine Struktur, die seinen Thread-Ausführungsstatus
enthält.
In dieser Struktur wird das anfängliche
CPU-Budget und das übrige
Budget nach der letzten Suspendierung des Threads gehalten, sowie
das PeriodCtr der Periode, in der das Budget des Threads zuletzt
wieder aufgefüllt
wurde. Jeder Rate wird ein Zähler
zugewiesen, der bei jeder Wiederholung in dieser Rate erhöht wird.
(Dieser Zähler
wird als PeriodCtr bezeichnet.) Die Speicherstelle der Threadausführungsstatusstruktur
eines gegebenen Threads kann durch Verwendung einer entsprechenden
Systemfunktion erhalten werden. Die in der Threadausführungsstatusstruktur
gespeicherten Werte können
durch einen Statusüberwachungsprozeß gemeldet
werden und können
dabei helfen, einen geeigneten Wert für das Budget des Threads zu
bestimmen. Das Verfahren der vorliegenden Erfindung implementiert
die ratenmonotonische Einteilung („RMS") mit einem Prioritätsvererbungsprotokoll. Das
RMS-Verfahren gibt
der periodischen Threadausführung
gemäß der Periode des
Threads Prioritäten.
Threads mit höherer
Rate besitzen höhere
Priorität.
Das Prioritätsvererbungsprotokoll
stellt sicher, daß Threads,
die ein Mutex verriegeln wollen, nicht durch Threads mit einer niedrigeren
Priorität
davon abgehalten werden.
-
Betriebssystemdienstanforderungsoverheads
-
1 ist ein Diagramm der Einteilung
von drei Anwendungs-Threads A, B und C; 2 ist ein Flußdiagramm des Threadeinteilungsprozesses.
Das vorliegende Verfahren wird am besten durch Bezugnahme auf 1 in Verbindung mit 2 verständlich. Die in dem vorliegenden
Beispiel verwendeten Perioden sind 5 Einheiten und 10 Einheiten.
Thread A besitzt eine Periode von 5 Einheiten und läuft in jeder
Periode 3 Einheiten lang. Thread B besitzt eine Periode von 10 Einheiten
und läuft
in jeder Periode 3 Einheiten lang. Thread C besitzt eine Periode
von 10 Einheiten und läuft
in jeder Periode 1 Einheit lang.
-
Mit
Bezug auf 2 werden im
Schritt 200 folgendermaßen Systemparameter initialisiert.
Ein Periodentimer wird auf die Basisperiode (kleinster Länge) für die eingeteilten
Threads gesetzt, d.h. in diesem Beispiel 5 Einheiten. Der Periodentimer
erzeugt am Ende jeder Periode kleinster Länge ein Interrupt. Als letztes
wird die Aktiv-Thread-Nummer n auf einen Wert von 1 initialisiert,
der in dem vorliegenden Beispiel Thread A, den Thread mit der höchsten Priorität, repräsentiert.
Jeder Thread-Zähler ThreadCtr(n)
1 bis N wird auf 0 gesetzt (wobei N die Anzahl von Threads ist;
d.h. in diesem Fall 3, und n die Thread-Nummer repräsentiert);
und jeder Periodenzähler
PeriodCtr (p), 1 bis P, wird auf 1 gesetzt (wobei P die Anzahl von
Perioden ist; d.h. in diesem Beispiel 2, und p die spezifische Periode
repräsentiert).
In diesem Beispiel hat p einen Wert von 1 für die Basisperiode von 5 Einheiten
und einen Wert von 2 für
die Periode von 10 Einheiten.
-
Jedem
Thread wird nicht nur eine Periode zugewiesen, sondern auch ein
anfängliches
Budget. Im Schritt 205 wird das Budget für Thread
n eingerichtet durch Setzen des Budgets auf einen im voraus festgelegten
Wert, der wie unten mit Bezug auf 4 beschrieben
bestimmt wird. Wenn das übrige
Budget eines Threads bei jeder Periodengrenze von dem anfänglichen
Budget aus „wiederaufgefüllt" werden würde, hätte die
Wiederauffülloperation
ein Laufzeitoverhead, das proportional zu der maximalen Anzahl von
Threads mit dieser Periode ist. In Systemen, bei denen keine Periodengrenzenversetzung
besteht, könnten
alle Threads an einer Periodengrenze wiederaufgefüllt werden,
was zu einem Overhead führt,
das proportional zu der Gesamtzahl von Threads in dem System wäre. Die
Wiederauffüllung muß jedoch
eine atomische Operation sein, was zu einem kritischen Abschnitt
führen
würde,
der effektiv unbegrenzt ist (d.h. nur durch die Anzahl von Threads
in dem System begrenzt), wodurch eine beliebig große Interrupt-Ansprechlatenz
verursacht wird.
-
Das
vorliegende Verfahren beseitigt diese unbegrenzte Interrupt-Ansprechlatenz
durch Verteilen des Auftretens der Zeiten, in denen das Budget-Wiederauffüllen auftritt.
Dies geschieht, indem jeder Periode p ein Periodenzähler (PeriodCtr)
zugeordnet und jedem Thread n ein „Zuletzt-Ausgeführt-Zählerwert" (ThreadCtr) zugeordnet
wird. Wie in 2 gezeigt
wird im Schritt 210 der Threadzähler für Thread B ThreadCtr (1) auf
einen Wert von 1 erhöht.
ThreadCtr (n) wird somit auf denselben Wert wie PeriodCtr (p(n))
gesetzt [wobei (p (n)) die Periode 1, die Periode des Thread n ist]
, um anzuzeigen, daß das
Budget für
Thread (n) für
seine Periode wiederaufgefüllt
wurde.
-
Jedes
Mal, wenn die CPU mit der Durchführung
von Anweisungen im Namen des Thread beginnt, wird ThreadTimer (n)
auf das verbleibende Budget für
diesen Thread, minus einem reservierten Overhead, gesetzt.
-
Somit
wird im Schritt 215 der Wert von RemBudget (n) (das verbleibende
Budget für
diesen Thread)) in ThreadTimer (n) geschrieben (wobei für Thread
A n = 1 ist), und der Thread-Timer wird gestartet. In dem Szenario
von 1 beginnt Thread
A im Schritt 220 mit der Ausführung zum Zeitpunkt t = 0.
-
Nachdem
Thread A mit der Ausführung
begonnen hat, setzt er die Ausführung
fort, bis eins von drei Ereignissen auftritt: (1) der Thread ruft
den Threadaufgabedienst auf (Schritt 240); (2) das Thread-Budget
ist erschöpft,
wobei an diesem Punkt ein Interrupt durch einen Thread-Timer-Interrupt erzeugt
wird; oder (3) ein Perioden-Timer-Interrupt
(das signalisiert, daß eine
Periodengrenze erreicht wurde) oder ein anderer Interrupt tritt.
auf. Wenn entweder Ereignis 1 oder Ereignis 2 auftritt, wird der
Thread dann bis zum Anfang seiner nächsten Periode suspendiert.
Wenn Ereignis 3 auftritt, wird der Thread suspendiert, bis die Threads
höherer
Priorität,
die durch den Interrupt bereitgemacht wurden, abgeschlossen sind,
woraufhin die Verarbeitung des Threads fortgesetzt wird. Wie in 1 gezeigt, wird zum Zeitpunkt
t = 3 die Ausführung
von Thread A durch einen Thread-Timer-Interrupt im Schritt 225 suspendiert,
da Thread A in einer Periode von 5 Einheiten 3 Einheiten lang abläuft. Im
Schritt 230 wird n (die Thread-Nummer) auf einen Wert von
2 für Thread
B gesetzt (an diesem Punkt führt
das Betriebssystem einen „Kontextwechsel" zu Thread n aus).
Als nächstes
wird im Schritt 235 bestimmt, ob das Budget für thread(n)
für seine
vorliegende Periode aufgefüllt
worden ist. Dies geschieht durch Vergleichen von ThreadCtr (n) mit
PeriodCtr (p(n)). Wenn die beiden Zähler gleich sind, wurde das
Budget des Threads bereits für
seine Periode wiederaufgefüllt.
Andernfalls (wie im vorliegenden Fall für Thread B, da PeriodCtr (p(B))=1
und ThreadCtr(B)=0 ist, mit B=2) , ist dies das erste Mal, daß Thread
B in seiner Periode ausgeführt
wurde. Deshalb wird im Schritt 205 das Budget für Thread
B wiederaufgefüllt. Im
Schritt 210 wird ThreadCtr (2) auf einen Wert von 1 (d.h.
einen Wert gleich dem Periodenzähler
des Threads) gesetzt, um anzuzeigen, daß das Budget für Thread
B nun für
seine Periode wiederaufgefüllt worden
ist. Die unerwünschte
einzelne „unbegrenzte" Operation des Wiederauffüllens von
N Threadbudgets wird also in N+1 kurze Operationen konstanter Zeit
umgesetzt: das Inkrementieren des Zählers der Periode und N Wiederauffüllungsoperationen
(eine für
jeden Thread).
-
Im
Schritt 215 wird der Thread-Timer für Thread B gestartet. Wie in 1 gezeigt, beginnt Thread
B mit der Ausführung
zum Zeitpunkt t ≈ 3
(im Schritt 220). Der genaue Zeitpunkt, an dem die Einleitung
der Ausführung
von Thread B tatsächlich
auftritt, ist etwas später
als t = 3, wegen der durch die Thread-Timer-Schreib-/Leseoperationen verbrauchten
Zeit und der verursachten Kontextwechselzeit, wie später mit
Bezug auf 4 erläutert werden
wird. Wie in 1 gezeigt,
wird zum Zeitpunkt t = 5 eine Periodengrenze erreicht und ein Perioden-Timer-Interrupt
erzeugt (Schritt 245). Bei der Einleitung jeder Basisperiode
werden im Schritt 250 die Periodenzähler für jede Periode mit einer Grenze
an diesem Wiederauftreten der Basisperiode erhöht. Im vorliegenden Fall wird
PeriodCtr (1), der Periodenzähler
für die Basisperiode,
erhöht,
aber die Periodenzähler
für die Periode
von 10 Einheiten [PeriodCtr (2)] nicht. Im Schritt 260 wird
der Thread-Timer für
Thread B [ThreadTimer (2)] gelesen und die darin gespeicherte übrige Zeit
wird als RemBudget (2), das übrige
Budget für
Thread B für
Periode 2, gesichert. Im Schritt 265 wird der Thread-Zähler n auf einen Wert von 1 (zurück-)gesetzt,
wodurch angezeigt wird, daß der Thread
mit der höchsten
Priorität
(Thread A) am Anfang der Basisperiode, die zum Zeitpunkt t = 5 beginnt,
ausgeführt
werden soll.
-
Im
Schritt 205 wird das Budget von Thread A wiederaufgefüllt und
Thread A wird wie oben beschrieben, für die Schritte 210, 215 und 220,
ausgeführt.
Wie in 1 gezeigt, wird
zum Zeitpunkt t = 8 die Ausführung
von Thread A durch einen Timer-Interrupt,
der durch den Timer [ThreadTimer (1)] von Thread A erzeugt wird
(Schritt 225), suspendiert, da Thread A in einer Periode
von 5 Einheiten 3 Einheiten lang läuft. Im Schritt 230 wird
n (die Thread-Nummer) auf einen Wert von 2 für Thread B erhöht. Als
nächstes
wird im Schritt 235 bestimmt, ob das Budget für Thread(n)
für seine
vorliegende Periode wiederaufgefüllt
worden ist. Da PeriodCtr (p(B)) und ThreadCtr(B) beide gleich 1
sind, wurde Thread B bereits für seine
Periode wiederaufgefüllt
und die Ausführung von
Thread B wird zum Zeitpunkt t ≈ 8
wiederaufgenommen. Deshalb wird der Thread-Timer für Thread B
im Schritt 215 durch Schreiben der im Budget von Thread
B verbleibenden Zeit RemBudget(B) in ThreadTimer(B) wiederaufgenommen.
-
Zum
Zeitpunkt t = 9 wird die Ausführung
von Thread B durch einen Timer-Interrupt, der durch den Timer von
Thread B [ThreadTimer (2)] erzeugt wird (Schritt 225),
suspendiert, da das Gesamtbudget (3 Zeiteinheiten) von Thread B
für seine
Zeitspanne von 10 Einheiten (Periode 2) erschöpft worden ist.
-
Als
letztes wird die Ausführung
von Thread C zum Zeitpunkt t ≈ 9
eingeleitet und zum Zeitpunkt t = 10 läuft das Budget (1 Zeiteinheit)
von Thread C ab. Das Diagramm von 1 läuft dann
wieder zurück zum
Zeitpunkt t = 0 und der obenbeschriebene Prozeß wird wiederholt.
-
Interruptabwicklungsoverheads
-
Bei
einem Ausführungsbeispiel
der vorliegenden Erfindung ist einem Interrupt eine Zeitspanne zugeordnet,
und ein Thread wird als Reaktion auf den Interrupt aktiviert. Overheads
der Kategorie 2B werden durchgesetzt, indem sichergestellt wird,
daß während dieser
Periode die akkumulierte CPU-(Prozessor-)-Benutzung, die sich aus der Abwicklung
so vieler Interrupts wie möglich
ergibt, einen vorbestimmten zulässigen
Wert nicht übersteigt.
Dieses Verfahren ermöglicht
es, daß eine
beliebige Anzahl von einem spezifischen Interrupt zugeordneten Interrupts
während
der zugeordneten Periode ankommt, und ermöglicht außerdem eine Variabilität der Verarbeitungszeit
für jeden
einzelnen Interrupt, solange die akkumulierte benutzte CPU-Benutzung
für die Periode
den zulässigen
Wert für
diesen bestimmten Interrupt nicht übersteigt. Nachdem die akkumulierte CPU-Zeit
des Interrupts für
die Periode erschöpft wurde,
wird der Interrupt bis zum Ende der dem Interrupt zugeordneten Periode
maskiert. Die Maßnahme des
Maskiertlassens des Interrupts, wenn die CPU-Benutzung erschöpft wurde,
reicht aus, um sicherzustellen, daß der Interrupt-Handler keine
zusätzliche
CPU-Zeit benutzt und somit eine ordnungsgemäße Zeitpartitionierung durchgesetzt
wird.
-
3 ist ein Diagramm der Ausführung eines
Interrupt-Handlers „I" und zweier Anwendungsthreads „A" und „B", die alle eine zugeordnete
Periode von 20 Einheiten aufweisen. Wie in 3 gezeigt, erfordert für jede Periode
von 20 Einheiten Thread A 6 Zeiteinheiten zur Ausführung und
Thread B 8 Zeiteinheiten. Deshalb werden die Threads A und B für insgesamt
14 Einheiten in jeder Periode von 20 Einheiten eingeteilt, so daß 6 Einheiten
in jeder Periode für
die Abwicklung von Interrupts verbleiben. Der Interrupt-Handler „I" kann (1) als ein
Thread, der als Reaktion auf eine Interrupt-Anforderung aktiviert
und genau wie die mit Bezug auf 1 beschriebenen Threads
behandelt wird; oder (2) als ein „Pseudo-Thread" mit allen Eigenschaften „formaler Threads", einschließlich einer
zugeordneten Zeitspanne und eines Zeitbudgets für die zugeordnete Periode,
betrachtet werden. In jedem Fall kann der Interrupt-Handler „I" der Ausführung anderer
Threads oft in einer gegebenen Periode zuvorkommen, solange die
Summe der Zeitdauer jeder Ausführung
des Interrupt-Handlers die für
den Thread bzw. Pseudo-Thread für
eine gegebene Periode reservierte Zeitbenutzung (Budget) nicht übersteigt.
-
Bei
einem Ausführungsbeispiel
des vorliegenden Systems wird der Interrupt-Handler „I" zu allen Zeiten
maskiert, außer
wenn sich der Interrupt-Handler-Thread mit einem Wartezustand befindet,
in dem er auf das nächste
Auftreten eines Interrupts wartet. Wenn der Interrupt-Thread suspendiert wird,
indem zum Beispiel sein Zeitbudget überstiegen wird (oder wenn
sich der Thread „freiwillig" selbst suspendiert),
dann bleibt der Interrupt lediglich maskiert. Diese Situation, wobei
ein Interrupt maskiert wird, außer
wenn er spezifisch auf das Auftreten eines Interrupts wartet, stellt
sicher, daß sich
die dem Interrupt zugeordnete Einrichtung immer in dem ordnungsgemäßen Zustand
befindet.
-
Wie
in 3 gezeigt, wird Thread
A zum Zeitpunkt t = 0 bis zum Zeitpunkt t = 3 ausgeführt, und zu
diesem Zeitpunkt wird ihm durch den Interrupt-Handler „I" zuvorgekommen, der
zwei Zeiteinheiten lang bis zum Zeitpunkt t = 5 am Punkt „a" ausgeführt wird.
Die Ausführung
des Threads A wird bis t = 7 wiederaufgenommen, und zu diesem Zeitpunkt wird
ihr wieder durch den Interrupt-Handler „I" zuvorgekommen, der zwei Zeiteinheiten
lang bis zum Zeitpunkt t = 9 am Punkt „b" ausgeführt wird. An diesem Punkt hat
der Interrupt-Handler „I" (d.h. der Thread bzw.
Pseudo-Thread) insgesamt
4 Zeiteinheiten aufgebraucht. Thread A nimmt die Ausführung von
t = 9 bis t = 10 wieder auf, und dort wird die Ausführung von
Thread 8 eingeleitet. Zum Zeitpunkt t = 12 wird Thread B durch den
Interrupt-Handler „I" zuvorgekommen, der
zwei Zeiteinheiten lang bis zum Zeitpunkt t = 14 am Punkt „c" ausgeführt wird.
Am Punkt „c" hat der Interrupt-Handler „I" insgesamt 6 Zeiteinheiten
verbraucht, wodurch sein Budget für die Periode erschöpft wird,
so daß der
dem Interrupt-Handler „I" zugeordnete Interrupt
bis zum Ende der vorliegenden Periode maskiert bleibt. Deshalb wird
die Ausführung
für den
Rest der Periode zu Thread B (oder zu einem bestimmten anderen Thread,
wenn er eingeteilt ist) transferiert.
-
Thread-Budgetzeitaufteilung
-
Ein
Problem, das bei vorbekannten Einteilungsverfahren auftritt, besteht
darin, daß Berechnungen
von Mutexen zugeordneter Blockierungszeit annehmen, daß alle von
einem Thread benutzte potentielle Zeit berücksichtigt wird. Wenn jedoch
ein Thread eine Betriebssystemfunktion mit t Einheiten vor dem Zeitpunkt,
zu dem der Thread-Timer eingeteilt ist, abzulaufen, aufrufen würde und
die aufgerufene Betriebssystemfunktion t oder mehr Zeiteinheiten
zur Ausführung
erfordert, dann kann der Thread-Timer tatsächlich negativ werden, so daß die Durchsetzung
einer ordnungsgemäßen Thread-Einteilung
verhindert wird. Der Grund für
das Negativwerden des Timers besteht darin, daß verschiedene kritische Betriebssystemoperationen
atomisch durchgeführt
werden müssen
und daher der dem Thread-Timer zugeordnete Interrupt während dieser „kritischen
Abschnitte" maskiert
(ignoriert) wird. Wenn unmittelbar vor dem Zeitpunkt des Ablaufens (Überschreiten
einer Zeitgrenze) eines Timers in einen kritischen Abschnitt eingetreten
wird, wird der Timer für
die Dauer des kritischen Abschnitts nicht bestätigt. Außerdem muß die Zeit, die es dauert,
um zwischen Threads umzuwechseln, berücksichtigt werden. Um diese
Kontextwechselzeit zu berücksichtigen,
setzt das vorliegende Verfahren einen Thread-Timer auf einen Wert,
der kleiner als das verbleibende Zeitbudget des Threads ist, wie
im folgenden Abschnitt erläutert
wird.
-
4 ist ein Diagramm der Zeitbeziehungen zwischen
den Entitäten,
aus denen eine typische Thread-Aktivierung
besteht. Ausdrücke,
die unten in Großbuchstaben
erscheinen, entsprechen den verschiedenen Thread-Budgetsegmenten
in dem Diagramm. Wie in 4 gezeigt,
wird, wenn ein Thread-Timer zu Anfang für einen gegebenen Thread gesetzt
wird, der Timer-Wert, auf den der Thread-Timer gesetzt wird, durch
Subtrahieren eines Werts eines Reservierten Overheads von dem Gesamtzeitbudget
des Threads bestimmt. Dieses Reservierte Overhead entspricht der
Zeit, die von dem längsten
kritischen Abschnitt von durch das Betriebssystem ausgeführten Anweisungen
verbraucht wird, während
der Interrupts gesperrt sind (mit der Bezeichnung CRIT), plus die
Zeit, die es dauert, um einen Thread-Kontextwechsel aus dem zur
Zeit ausgeführten
Thread heraus durchzuführen
(Bezeichnung CTXout). Es ist zu beachten, daß der Begriff „längster kritischer
Abschnitt" hier
den längsten
kritischen Abschnitt von Anweisungen bedeutet, die keinen Kontextwechsel
zu einem anderen Thread durchführen.
-
Es
ist ersichtlich, daß der
Thread zu einer „Endzeit", die gleich CTXout
+ CRIT Zeiteinheiten ist, unterbrochen wird, bevor sein Budget erschöpft ist und
der Timer abläuft.
Da ein Thread immer den Kontext zu einem anderen Thread wechseln
muß, wird der
Wert für
CTXout korrekt berücksichtigt.
Der Thread ruft im allgemeinen jedoch keinen kritischen Abschnitt
von Betriebssystemanweisungen auf, wenn der Timer-Interrupt ankommt,
und somit wird die CRIT-Zeit potentiell vergeudet. Wenn ein Thread jedoch
mehrmals in einer einzigen Periode aktiviert wird, wird das „Vergeuden" der CRIT-Zeit auf
die letzte Aktivierung des Threads begrenzt, da die tatsächliche
Threadausführungszeit
durch Lesen des Thread-Timers während
des Kontextwechsels bestimmt wird.
-
Die
ordnungsgemäße Berücksichtigung
der Zeit zwischen dem Zeitpunkt (Tr) des Lesens des Timers (d.h. „setzen" oder „rücksetzen") und der Zeit (Tw)
des Beschreibens des Timers (d.h. „gestartet") ist als T(w-r) gezeigt und wird beim
Bestimmen des Timer-Werts von dem Thread-Budget abgezogen. Die Zeit T(w-r) ist
in CTXout enthalten und ist somit als Teil des reservierten Overheads
enthalten. Obwohl sie in der tatsächlichen Thread-Ausführungszeit nicht
enthalten ist, definiert die Zeit, in der der Timer beschrieben
wird, den CTXin-Teil
des Kontextwechsels, der auftritt, während der Timer im Namen des Threads
läuft,
und wird daher nicht als Overhead abgezogen. Diese CTXin-Zeitspanne
enthält
die Zeit, die das Betriebssystem braucht, um den Thread für die Ausführung vorzubereiten,
einschließlich
der Zeit, die verbracht wird, um das Budget des Threads wiederaufzufüllen, wenn
eine solche Wiederauffüllungsoperation
auftritt. Der Timer-Wert ist somit die Summe von CTXin und der Thread-Ausführungszeit.