KURT als eine mögliche Lösung


... [ Linux als Echtzeitbetriebssystem ] ... [ Thema Echtzeit für Linux ] ... [ Referenzen ] ...

Übersicht: KURT als eine mögliche Lösung


UTIME

Eines der Probleme, die bei Linux den harten Echtzeiterfordernissen entgegenstehen, ist die Ereignisbehandlung, die eine maximale zeitliche Granularität von 10 Millisekunden erlaubt. Um die zeitliche Granularität zu erhöhen könnte man einfach den Timer- Chip so programmieren, dass er die CPU mit einer höheren Frequenz unterbricht. Dies ist in Linux ohne großen Aufwand machbar: Man muss nur den Wert des HZ- Macros erhöhen. Es gibt an, wie oft die CPU pro Sekunde unterbrochen wird ([Hz]=[1/sec]). Standard ist auf den meisten Platformen 100, das entspricht einer Periode von 10 Millisekunden. Natürlich könnte man den Wert einfach auf 100.000 setzen (eine Periode von 10 Mikrosekunden), was allerdings den Overhead, der für die Prüfung auf Ereignisse entsteht, dramatisch erhöhen.
Beispiel:
Erhöht die zeitliche Auflösung auf 10 Mikrosekunden, muss der Timer die CPU 100.000 mal pro Sekunde unterbrechen und die ISR durchführen. Unter Standard- Linux dauert die Abarbeitung eines Timer- Interrupts ungefähr eine Mikrosekunde. Das bedeutet, dass sich alleine dadurch ein Overhead von 10 Prozent ergibt. Und dies nur durch die Prüfung auf Ereignisse. Der eigentliche Ereignis- Code kommt noch dazu.

Dies ist also nicht praktikabel.
Wichtig für eine sinnvolle Lösung ist, dass Timer- Interrupts zwar jede Mikrosekunde auftreten können, aber nicht auftreten müssen. Deshalb weicht UTIME, die Ereignissbehandlung von KURT, von der verbreiteten Methode ab, den Timer- Chip so zu programmieren, dass die CPU mit einer festen Frequenz unterbrochen wird. Stattdessen wird der Time angewiesen, die CPU gerade rechtzeitig für das nächste Ereignis zu unterbrechen. Man sagt, der Timer läuft im One- Shot Modus.
Um eine mirkosekundengenaue Auflösung zu erhalten, wurde jiffies_u in den timer Struct eingefügt. Außerdem gibt es zwei weitere globale Variablen: jiffies_u gibt die Mikrosekunden innerhalb des aktuellen jiffies an und jiffies_intr gibt an, wenn ein neuer jiffy angefangen hat.
Da die Timer- Interrupts nicht mehr für das Führen der Systemuhr genutzt werden können (sie finden ja nicht mehr periodisch statt), wird die Funktion update_jiffies_u benutzt um die Werte von jiffies und jiffies_u mittels des Time Stamp Counters (TSC) aktuell zu halten. Die Korrelation zwischen dem TSC und den jiffies wird während des Bootens ermittelt:
Zunächst werden die aktuellen Werte des TSC und der Jiffies in base_cpuctr und base_jiffies gespeichert um sie später als feste Baseline verwenden zu können. Zusätzlich muss die Anzahl der Zyklen, die equivalent zu einer Sekunde sind, in der globalen Variablen cycles_per_sec gespeichert werden. (Der Einfachheit halber werden auch cycles_per_usec und cycles_per_jiffies entsprechend ermittelt.) Jetzt muss der Timer- Chip noch auf den One- Shot- Modus umgestellt werden. Das alles passiert in utime_init, das in vereinfachter Form folgendermaßen aussieht:

1 void utime_init(void)
2 {
3     unsigned long long first_time, next_time;
4     unsigned long first_jiffies;
5     int i=0;

6     first_jiffies = jiffies;
7     while (first_jiffies == jiffies);

8     first_time = get_cpuctr();
9     while (i++ < 5) {
10        first_jiffies = jiffies;
11        while (jiffies - first_jiffies < HZ);
12    }
13    next_time = get_cpuctr();

14    cycles_per_sec = (next_time - first_time) / 5;
15    cycles_per_usec = cycles_per_sec / 1000000;
16    cycles_per_jiffies = cycles_per_sec / HZ;

17    base_cpuctr = last_update = get_cpuctr();
18    base_jiffies = jiffies;

19    change_timer_mode(timer_chip_oneshot, 0);
20}

Die neue ISR ruft update_jiffies_u auf um die Systemuhr zu aktualisieren und ermittelt dann die Zeit bis zum nächsten Ereignis. Der Timer- Chip wird dann so programmiert, dass er die CPU zu diesem Zeitpunkt unterbricht. Dadurch wird der Umfang der Berechnungen der ISR wesentlich erhöht. Insbesondere durch die time_to_next_event Funktion, denn während die ISR abgearbeitet wird sind die aktuellen Ereignisse noch am Anfang der Liste. Sie also alle timer übergehen, die bei diesem Interrupt bearbeitet werden und das nächste Ereignis finden, das in der Zukunft liegt.

1 void timer_interrupt()
2 {
3     update_jiffies_u();
4     t = time_to_next_event();
5     program_timer_chip(t);
6     run_timer_list = 1;
7     if (jiffies_intr) {
8         maintain_heart_beat();
9         jiffies_intr--;
10    }
11}

Mit update_jiffies_u wird die Systemzeit aktualisiert:

1 void update_jiffies_u(void)
2 {
3     static unsigned long long last_update = 0;
4     unsigned long long this_update;

5     this_update = get_cpuctr();
6     arch_update_jiffies(this_update - last_update);
7     last_update = this_update;
8 }

Die get_cpuctr() Funktion liest den TSC aus und ist deshalb CPU-abhängig. arch_update_jiffies macht die Berechnungen, die nötig sind um die jiffies und jiffies_u Variablen zu aktualisieren. Die Version für den Pentium:

1 void arch_update_jiffies(unsigned long difference)
2 {
3     unsigned long long current_time;
4     unsigned long new_jiffies, new_jiffies_u;
5     unsigned long seconds, remainder;
6     unsigned long excess_jiffies, excess_cycles, jiffies_jumped;

7     time = get_cpuctr() - base_time;
8     seconds = time / cycles_per_second;
9     remainder = time % cycles_per_second;

10    excess_jiffies = remainder / cycles_per_jiffies;
11    new_jiffies = base_jiffies + (seconds * HZ) + excess_jiffies;
12    excess_cycles = remainder - (excess_jiffies * cycles_per_jiffies);
13    new_jiffies_u = excess_cycles / cycles_per_usec;

14    jiffies_jumped = new_jiffies -jiffies;
15    if (jiffies_jumped) {
16        lost_ticks += jiffies_jumped;
17        jiffies_intr += jiffies_jumped;
18        jiffies = new_jiffies;
19    }
20    jiffies_u = new_jiffies_u;
21}

Wie auch in Standard- Linux werden die Ereignisroutinen aufgerufen, wenn der Kernel die Kontrolle an die Benutzerprozesse übergibt. Natürlich muss auch die run_timer_list Funktion angepasst werden:

1 void run_timer_list()
2 {
3     while (timers in queue) {
4         if ((timer->expires < jiffies) || ((timer->expires == jiffies) && (timer->usec <= jiffies_u))) {
5             (*timer->function)(timer->data);
6             timer = timer->next;
7             detach timer from queue;
8         } else {
9             break;
10        }
11    }
12}


Scheduling- Mechanismus von KURT

Das andere Problem von Standard- Linux in Bezug auf die Echtzeitfunktionalität ist der Scheduling- Mechanismus.
Zusätzlich zu den drei in Linux vorhandenen Scheduling- Modi stellt KURT drei weitere, übergeordnete Modi zu Verfügung, die eine Beeinflussung durch andere Systembestandteile reduzieren sollen:
  1. normal mode: Linux funktioniert als normales, time- sharing Betriebssystem
  2. focused mode: Es dürfen nur Echtzeit- Prozesse laufen
  3. mixed mode: Alle Prozesse werden ausgeführt, wobei Nichtechtzeit- Prozesse nur laufen, wenn keine Echtzeit- Prozesse Rechenzeit benötigen
In beiden Echtzeitmodi können Echtzeitprozesse auf alle Systemdienste zurückgreifen, die auch Nichtechtzeitprozesse benutzen können.
Die Linux Prozesse können, wie schon erwähnt, in verschiedenen Schedulern laufen. Dies sind normalerweise SCHED_OTHER, SCHED_RR und SCHED_FIFO. Zusätzlich gibt es jetzt noch SCHED_KURT für harte Echtzeitprozesse. Dieser Modus ist allerdings nicht wie die anderen jederzeit verfügbar, denn es besteht die Möglichkeit zwischen Echtzeit- und Normalbetrieb global umzuschalten. Ist Echtzeit abgeschaltet, so werden die Prozesse mit dem normalen SCHED_OTHER- Scheduler verwaltet. Befindet sich das System hingegen im Echtzeitbetrieb, werden sie von KURT verwaltet, was wiederum auf zwei Arten passieren kann: Im focusierten Modus, d.h. nur Prozesse die den SCHED_KURT- Scheduler benutzen werden ausgeführt, oder im offenen Modus, d.h. alle Prozesse können laufen, aber Echtzeitprozesse werden bevorzugt.
Das globale Einschalten des Echtzeitbetriebs erfolgt über einen Aufruf von switch_to_rt:

int switch_to_rt(int timer_chip_mode, unsigned long period, int rt_mode, char *cmdline, int length);


Umschalten in den Normalbetrieb läßt sich mittels eines Aufrufs von switch_to_normal:

int switch_to_normal(int force);


Durchgeführt wird das Scheduling durch einen Kern (Core), der die Echtzeitereignisse verwaltet, und einer Menge von Echtzeitmodulen (RTMods) und -prozessen, die die Funktionalität eines zugeordneten Echtzeitereignisses implementieren.

KURT Core:

Der Kern ist dafür verantwortlich, dass beim Auftreten von Echtzeitereignissen die entsprechenden RTMods zur richtigen Zeit aufgerufen werden. Weil der von KURT verwendete Scheduler ein "explicit plan scheduler" ist, müssen alle Anwendungen die Zeiten festlegen, zu denen Echtzeitereignisse auftreten (d.h. RTMods werden aufgerufen). Informationen über die Ereignisse (u.a. derren Zeitpunkt) werden in einer Schedule- Datei gespeichert.


Echtzeitmodule

Echtzeitmodule sind eine Möglichkeit, die eigenen Echtzeitaufgaben zu implementieren. Sie sind Standard- Kernelmodule können wie diese zur Laufzeit des Systems geladen und entladen werden, womit man leicht die Funktionalität des Systems ändern kann. Da sie im Kernelmodus laufen gibt es keinen Overhead durch Kontextwechsel, wie es bei Benutzerprozessen der Fall wäre. Außerdem haben sie die Möglichkeit des Zugriffs auf Geräte und andere Teile des Kernels, wie er Benutzerprozessen nicht erlaubt ist. Dies ist einer der Gründe, weshalb Echtzeitmodule, wie andere Kernelmodule auch, besonders sorgfältig geschrieben sein sollten. Nicht nur die Systemstabilität und -performace auch die Systemsicherheit hängt ganz entscheidend davon ab, wie die Module geschrieben sind.
Um eine RTMod zu registrieren, muss man die Funktion register_rtmod aufrufen:

int register_rtmod(struct rtmod_info *info);

Der Rückgabewert ist die module_id, die dem Echtzeitmodul zugewiesen wurde.

1 struct rtmod_info {
2     char *name;
3     unsigned long (*modify_timer_data)(struct rt_event*);
4     int (*init_rtmod)(const char*, const int*);
5     int (*cleanup_rtmod)(void);
6     void (*event_handler)(unsigned long);
7     int (*rtmod_cmd)(int, void *, unsigned long);
8 }

Alle Echtzeitmodule sollten mindestens den Namen und die event_handler Funktion zur Verfügung stellen. (Alle anderen Elemente von rtmod_info können NULL sein.)
Man kann die ID eines Echtzeitmoduls durch get_rtmod_num ermitteln:

int get_rtmod_num(char *rtmod_name);

Ist kein Modul mit dem angegebenen Namen registriert wird -1 geliefert.

Mit unregister_rtmod kann das Echtzeitmodul die Registrierung wieder aufheben:

int unregister_rtmod(char *name);

KURT sort dafür, dass keine weiteren Ereignisse für dieses Modul erstellt werden.

Mit der rtmod_cmd Funktion ein Benutzerprozess direkt mit einem Echtzeitmodul kommunizieren. Das kann wichtig sein, wenn es nötig ist mit einem speziellem RTMod Daten auszutauschen.

int rtmod_cmd(int rtmod_id, int command, void *buffer, int size);


Durch rt_schedule_events können Benutzerprozesse Echtzeitereignisse erzeugen.

int rt_schedule_events(struct timeval *time, int type, int num_times, char *filename);

Die Schedule- Datei ist die binäre Representation einer Menge von Ereignissen, die durch KURT ausgeführt werden sollen. Ein Ereignis hat folgende Struktur:

1 struct rt_event {
2     unsigned long data_1;
3     unsigned long data_2;
4     unsigned long data_3;
5     unsigned long module_id;
6     unsigned long tenms;
7     unsigned long usec;
8 }

Die module_id gibt das Modul an, das aufgerufen werden soll, wenn das Ereignis auslöst. Dann werden dem Modul 12 Byte Daten (data_1 bis _3) übergeben. Der Zeitpunkt, an dem das Ereignis auftritt, wird als Offset in zehn Millisekunden (tenms) und der Mircosekunde innerhalb dieser zehn Millisekunden (usec; Wertebereich: 0-9999) angegeben.


Programmierung eines Echtzeitmodulen

Echtzeitmodule sind zunächst einmal Kernelmodule und müssen deshalb die beiden Funktionen init_module und cleanup_module enthalten, die aufgerufen werden wenn das Modul in den Kernel geladen (mit insmod) bzw. wenn es wieder entladen (mit rmmod) wird.
Das Mindeste, was init_module machen muss, ist das Echtzeitmodul bei KURT mittels register_rtmod registrieren.

1 int init_module(void) {
2     int status;

3     if ((status = register_rtmod(&parbit_rtmod_info)) < 0){
4         return status;
5     }
6     return 0;
7 }

Der rtmod_info Struct gibt KURT die nötigen Informationen, damit das Modul von Ereignissen informiert werden kann.

1 static struct rtmod_info parbit_rtmod_info =
2 {
3     "parbit",      /* Module name */
4     NULL,          /* Modify timer data */
5     init_parabit,  /* Initialization function */
6     NULL,          /* Cleanup function */
7     output_val,    /* Event handler */
8     NULL,          /* rtmod_cmd handler */
9 };

Der Modulname wird auf "parabit" festgelegt (Zeile 3). Außerdem wird noch eine Initialisierungsfunktion definiert (Zeile 5), die aufgerufen wird wenn der Kernel in einen der Echtzeitmodi geschaltet wird, und eine Ereignisbehandlungsroutine festgelegt (Zeile 7), die durchgeführt wird wenn ein Ereignis für dieses Modul auslöst.
Die Parameter, die an switch_to_rt übergeben werden, werden von KURT an die Initialisierungsfunktion weitergegeben. In diesem Beispiel werden sie einfach ausgegeben:

1 int init_parbit(const char *str, const int *ints){
2     if (str)
3         printk(KERN_INFO "Parbit was passed %s as string\n", str);
4     if (ints){
5         printk(KERN_INFO "Parbit was passed %d int args\n", ints[0]);
6         for(int i=1; i<=ints[0]; i++){
7             printk(KERN_INFO "Parbit was passed %d as init arg\n",ints[i]);
8         }
9     }
10    return 0;
11}

Der Hauptteil eines Echtzeitmoduls ist meistens die Ereignisbehandlungsfunktion. In diesem Beispiel wird beim ersten Aufruf der Paralellport deaktiviert, bei nächsten Aufruf wieder eingeschaltet, usw. . Jede Ereignisbehandlungsroutine muss einen Long- Parameter entgegennehmen, mit dem das Modul ermitteln kann, von welchen Ereignissen es aufgerufen wurde. Dieser wird hier nicht benutzt.

1 void output_val(unsigned long useless_data){
2     static int i = 0;

3     if (i==0){
4         outb (0, 0x378);    /* turn off parallel port */
5     } else {
6         outb (1, 0x378);    /* turn on parallel port */
7     }
8     i = 1 - i;
9     return 0;
10}

Zuletzt muss noch cleanup_module implementiert werden, das dem Modul erlaubt "aufzuräumen" bevor es entladen wird. Im Beispiel wird einfach die Registrierung als RTMod aufgehoben, indem unregister_rtmod mit dem eigenen Modulnamen aufgerufen wird.

1 void cleanup_module(void){
2     unregister_rtmod(parbit_rtmod_info.name);
3 }


Echtzeitprozesse

KURT stellt ein spezielles eingebautes Echtzeitmodul zu Verfügung, das Process RTMod. Wenn dieses Modul aufgerufen wird, wird der Kontext zu einen Benutzerprozess gewechselt. Hierüber ist es möglich Echtzeitbenutzerprozesse zu schreiben. Es muss unterschieden werden zwischen Echtzeitmodulen, die im Kernelmodus laufen, und Echtzeitprozessen, die im Benutzermodus laufen. Daraus folgt, dass die Benutzung von Echtzeitprozessen den Overhead durch Kontextwechsel und zusätzliche Systemaufrufe und dadurch auch die Verzögerung zunimmt. Andererseits wird die Entwicklungsarbeit in einiger Hinsicht erleichtert und es ist auch weniger anfällig gegen Programmierfehler, da ein Fehler in einem Echtzeitprozess nicht das restliche System in Mitleidenschaft ziehen kann.
Jeder SCHED_KURT Prozess muss seine Eigenschaften in einem rtparams Struct speichern und diese mit set_rtparams KURT mitteilen:

1 struct rtparams{
2     int rt_id;
3     int priority;
4     unsigned long proc_req;
5     unsigned long period;
6 }

Die nachfolgende API ist Teil des Process RTMod und erlaubt das Arbeiten mit Echtzeit- Prozessen.

Mit set_rtparams kann ein Prozess sich oder einen anderen Prozess als SCHED_KURT- Prozess registrieren bzw. wieder abmelden.

int set_rtparams(int pid, int policy, struct rtparams *param);


get_rtparams liefert die SCHED_KURT- Charakteristika eines bestimmten Prozesses.

int get_rtparams(int pid, struct rtparams *params);

Über get_rtstats kann man an statistische Daten eines Echtzeitprozesses gelangen:

int get_rtstats(int pid, struct rtstats *info);

Diese Informationen sind folgendermaßen strukturiert:

1 struct rtstats{
2     int rt_abort;
3     int suspended;
4     unsigned long rt_num_woken_up;
5     unsigned long rt_num_suspended;
6     unsigned long rt_num_missed;
7 }



int get_num_rtprocs(void);

Liefert die Anzahl der registrierten SCHED_KURT- Prozesse.

int rt_suspend(int susp_mode);

Verdrängt den aufrufenden Prozess bis das nächste Mal ein Ereignis für ihn ausgelöst wird.
Der Parameter susp_mode kann nur bestimmte Werte annehmen:


Programmierung von Echtzeitprozessen

Je nach Aufgabenstellung gibt es zwei Arten einen Echtzeitprozess zu implementieren:
  1. Periodischer Prozess, dessen Ereignisse von KURT ausgelöst werden
  2. Nicht periodischer Prozess, dessen Ereignisse von einem zweiten Prozess ausgelöst werden

Ein periodischer SCHED_KURT- Prozess wird immer folgende Grundstruktur haben:

1 void main(){
2     set_rtparams();   /* inform KURT about processing requirements etc. */
3     switch_to_rt();   /* switch kernel to real-time mode */
4     for (i=0;i<num_iters;i++){
5         rt_suspend();   /* suspend until next period */
6         do_stuff();   /* process information for this period */
7     }
8     switch_to_normal();
9 }

Wenn man dieses Gerüst mit Leben füllt, könnte das in etwa so aussehen:

1 int main(int argc, char **argv){
2     struct rtparams rt_params;
3     struct timeval tv[5000];
4     int i;

5     rt_params.rt_id = 1;
6     rt_params.proc_req = 500;
7     rt_params.period = 10000;
8     rt_params.priority = (MAX_KURT_PRIORITY + MIN_KURT_PRIORITY)/2;
9     if (set_rtparams(0, SCHED_KURT, &rt_params)){
10        perror("set_rtparams");
11        exit(1);
12    }
13    get_rtparams(0, &rt_params);

14    if (switch_to_rt(timer_chip_oneshot, 0, SCHED_ALL_PROCS, NULL, 0)){
15        perror("switch_to_rt");
16        exit(1);
17    }

18    for (i = 0; i < 5000; i++){
19        if (rt_suspend(SUSPEND_IF_NRT | START_SCHED)){
20            perror("rt_suspend");
21            exit(0);
22        }
23        gettimeofday (&tv[i], NULL);
24    }

25    rt_suspend(STOP_SCHED);
26    switch_to_normal(1);
27}

Das Beispielprogramm ist ein periodischer Prozess, der sich zunächst bei KURT als Echtzeitprozess registriert, indem er einen Rechenbedarf von 500 ms alle 10000 ms ankündigt (set_rtparams. Dann wird der Kernel in den Echtzeitmodus umgeschaltet (switch_to_rt) und bei 5000 Durchläufen jeweils die aktuelle Zeit in einem Array gespeichert.

Ein nicht periodischer Prozess hat einen ähnlichen Aufbau wie ein periodischer, ist aber auf einen anderen Prozess angewiesen, der seine Ereignisse auslöst, da dies in diesem Fall nicht von KURT übernommen werden soll (und auch nicht übernommen werden kann - wann sollen Ereignisse ausgelöst werden?).

1 int main(int argc, char **argv){
2     struct rtparams rt_params;
3     struct timeval tv[5000];
4     int i = 0;

5     rt_params.rt_id = 1;
6     rt_params.proc_req = 0;
7     rt_params.period = 0;
8     rt_params.priority = MAX_KURT_PRIORITY;

9     if (set_rtparams(0, SCHED_KURT, &rt_params)){
10        perror("set_rtparams");
11        exit(1);
12    }

13    while (1){
14        if (rt_suspend(SUSPEND_IF_NRT | START_SCHED)){
15            perror("rt_suspend");
16            exit(0);
17        }
18        if (i >= 5000){
19            i = 0;
20        }
21        gettimeofday (&tv[i++], NULL);
22    }
23}

Dieser nicht periodische Echtzeitprozess macht im Prinzip das selbe wie das vorherige Programm, wird aber ohne Weiteres nicht laufen, da die Ereignisse, die den Prozess nach dem Aufruf von rt_suspend wieder aufwecken könnten noch nicht ausgelöst werden. Zu beachten ist, dass Periode und benötigte Rechenzeit mit 0 angegeben werden. Dadurch erkennt KURT, dass es sich um einen nicht periodischen Prozess handelt. Der zweite Prozess muss nun Folgendes machen:

1 int main(){
2     set the scheduler of the current process to SCHED_KURT;
3     wait for all of the KURT processes to register themselfs;
4     switch to real-time mode;
5     schedule the real-time events;
6     switch to normal mode;
7     set the scheduler of the current process to SCHED_OTHER;
8 }

In unserem Beispiel:

1 int main(int argc, char **argv){
2     struct rtparams rt_params;

3     rt_params.rt_id = 0;
4     rt_params.rt_proc_req = 0;
5     rt_params.period = 0;
6     rt_params.priority = (MAX_KURT_PRIORITY + MIN_KURT_PRIORITY)/2;

7     if (set_rtparams(0, SCHED_KURT, &rt_params)){
8         perror("set_rtparams");
9         exit(1);
10    }
11    get_rtparams(0, &rt_params);

12    while (get_num_rtprocs() < 2){
13        sleep(1);
14    }

15    if (switch_to_rt(time_chip_oneshot, 0, SCHED_ALL_PROCS, NULL, 0)){
16        perror("switch_to_rt");
17        exit(1);
18    }

19    if (rt_schedule_events(NULL, RT_FROM_MEMORY, 3, "schedule_file")){
20        perror("rt_schedule_events");
21        exit(1);
22    }

23    sleep(1);
24    if (switch_to_normal(0)){
25        perror("switch_to_normal failed");
26    }

27    rt_params.rt_id=0;
28    if (set_rtparams(0, SCHED_OTHER, &rt_params)){
29        perror("set_rtparams failed");
30    }
31}


Was erreicht werden kann

Nachfolgende Tablellen machen deutlich, dass KURT in der Reaktion auf Ereignisse wesentlich zuverlässiger ist Standard- Linux.
Hier einige Konfidenzintervalle für das Prozess- Scheduling der verschiedenen Scheduler:
Number of Processes % of events found within 'x' Microseconds of Expected Time
10 µsec 50 µsec 100 µsec 500 µsec 1000 µsec
1 62.22% 99.59% 99.80% 100.00% 100.00%
10 3.36% 15.12% 30.25% 99.34% 99.72%
20 1.44% 7.45% 14.76% 66.48% 99.78%
30 1.21% 6.10% 11.03% 46.61% 73.11%
(a)SCHED_FIFO


Number of Processes % of events found within 'x' Microseconds of Expected Time
10 µsec 50 µsec 100 µsec 500 µsec 1000 µsec
1 99.80% 100.00% 100.00% 100.00% 100.00%
10 96.66% 99.47% 99.67% 100.00% 100.00%
20 99.34% 99.67% 99.91% 100.00% 100.00%
30 97.33% 99.54% 99.80% 99.99% 100.00%
(b)SCHED_KURT (SCHED_ALL_PROCS Mode)


Number of Processes % of events found within 'x' Microseconds of Expected Time
10 µsec 50 µsec 100 µsec 500 µsec 1000 µsec
1 99.80% 99.80% 100.00% 100.00% 100.00%
10 96.61% 99.72% 99.87% 100.00% 100.00%
20 99.23% 99.40% 99.61% 99.80% 100.00%
30 99.52% 99.75% 99.89% 100.00% 100.00%
(c)SCHED_KURT(SCHED_KURT_PROCS Mode)


Durch Diskettenzugriffe werden zeitweise Interrupts deaktiviert, was negative Auswirkungen auf die Performance hat. Bei nachfolgende Werten wurde im Hintergrund auf das Diskettenlaufwerk zugegriffen:

Number of Processes % of events found within 'x' Microseconds of Expected Time
10 µsec 50 µsec 100 µsec 500 µsec 1000 µsec
1 50.20% 81.33% 83.74% 98.99% 99.59%
10 5.50% 17.19% 30.70% 84.40% 92.25%
20 2.00% 7.07% 13.21% 55.50% 84.20%
30 1.09% 4.71% 8.65% 36.47% 64.32%
(a)SCHED_FIFO


Number of Processes % of events found within 'x' Microseconds of Expected Time
10 µsec 50 µsec 100 µsec 500 µsec 1000 µsec
1 40.16% 60.04% 73.89% 97.60% 99.60%
10 68.43% 81.68% 88.61% 97.17% 99.17%
20 87.94% 95.17% 97.37% 99.52% 99.66%
30 56.26% 60.74% 65.27% 82.01% 95.72%
(b)SCHED_KURT(SCHED_ALL_PROCS Mode)


... [ Linux als Echtzeitbetriebssystem ] ... [ Thema Echtzeit für Linux ] ... [ KURT als eine mögliche Lösung ] ... [ Referenzen ] ...