KURT-Linux

Piotr Książek

Wprowadzenie

Nazwa KURT-Linux rozwija się w Kansas University Real-Time Linux.

Rozszerza on możliwości systemu Linux o rozdzielczość czasu rzędu 10 mikrosekund oraz scheduler czasu rzeczywistego.

KURT jest miękkim systemem czasu rzeczywistego. Oznacza to, że program szeregujący stara się zapewnić żądane czasy wykonania, ale niepowodzenie nie ma żadnych następstw.

Instalacja

Łatanie jądra

KURT-Linux jest łatą na jądro systemu Linux. Najnowsza wersja dotyczy jądra 2.4.18. Oprócz właściwej łaty KURT-Linux potrzebne są jeszcze dwie inne (preemption, lock breaking) dostępne na stronie KURT. Nakładamy je na czyste źródła jądra, w odpowiedniej kolejności (wszystkie pliki są w /usr/src:
cd /usr/src
tar xjf linux-2.4.18.tar.bz2
cd linux
cat ../preempt-kernel-rml-2.4.18-5.patch | patch -p1
cat ../lock-break-rml-2.4.18-1.patch | patch -p1
cat ../kurt-2.4.18-2.patch | patch -p1

Konfiguracja

Korzystamy ze standardowych narzędzi do konfiguracji jądra (do wyboru):
make config
make menuconfig
make xconfig
Opcje dotyczące KURT-Linux to:
  1. KU Datastreams Kernel Interface - narzędzia do monitorowania systemu. Pozwalają na uruchamianie testów. Należy zaznaczyć KU Datastreams Kernel Interface oraz wszystkie opcje z podmenu DSKI Family Configuration.
  2. KU High-Resolution Timing Layer - obsługa zwiększonej precyzji zegara. Niezbędne do pracy z KURT-Linux. Należy zaznaczyć dwie pierwsze opcje (KU High-Resolution Timer Support i High-Resolution Timer Calibration Module).
  3. KU Real-Time Kernel - obsługa szeregowania w czasie rzeczywistym. Należy zaznaczyć pierwsze trzy opcje. (Uwaga: Jądro nie uruchamia się przy zaznaczonej opcji Execution Interval Enforcement).

Kompilacja i instalacja jądra

Jądro kompilujemy w standardowy sposób:
make clean
make dep
make bzImage
Po zainstalowaniu jądra i ponownym uruchomieniu komputera KURT-Linux jest gotowy do pracy.

Kompilacja i instalacja KURT API

Najpierw kompilujemy API:
tar xjf kurt-2.4.18-2.tar.bz2
cd kurt-2.4.18
./configure
make
make install
Uwaga: Należy zmienić wartości zmiennych: KURT_LINUX, INSTALL_LIB, INSTALL_INCLUDE w pliku Makefile.include.

Następnie tworzymy odpowiednie urządzenia:

mknod /dev/kurt c 240 0
mknod /dev/utime c 241 0
mknod /dev/dstream c 242 0
KURT-Linux jest już gotowy do pracy.

UTIME

Jest modyfikacja standardowego jądra Linuksa umożliwiająca bardziej precyzyjne liczenie czasu. Efekt ten uzyskano programując układ zegarowy (8254) tak, by generował przerwanie na żądanie, a nie okresowo. Pozwoliło to uzyskać rozdzielczość rzędu mikrosekund.

Dostęp do tego mechanizmu uzyskujemy za pomocą jednej z poniższych metod:

nanosleep()

Parametry wywołania funkcji są takie same jak w oryginalnym jądrze. Przykładowe wykorzystanie tej metody demonstruje funkcja nanosleep_test():
void nanosleep_test(int delay) {
   struct timespec sleepy;
   sleepy.tv_sec = 0;
   sleepy.tv_nsec = delay * 1000;
   nanosleep(&sleepy, 0);
}

Przerwania zegarowe

Standardowo Linux udostępnia dla każdego procesu trzy zegary. UTIME pozwala na zaprogramowanie jednego z nich (ITIMER_REAL) z mikrosekundową precyzją. Korzysta się przy tym z funkcji setitimer(). Jej użycie demonstruje poniższy program:
void timer_handler(int signal) {
}

void setitimer_test(int delay, int interval, int tries) {
   struct itimerval timer;
   sigset_t mask;
   struct sigaction action;
   int i;

   timer.it_interval.tv_sec = 0;
   timer.it_interval.tv_usec = interval;
   timer.it_value.tv_sec = 0;
   timer.it_value.tv_usec = delay;
   sigemptyset(&mask);
   action.sa_handler = timer_handler;
   action.sa_flags = 0;
   sigaction(SIGALRM, &action, 0);
   setitimer(ITIMER_REAL, &timer, 0);

   for (i = 0 ; i < tries ; i++) {
      sigsuspend(&mask);
   }

   timer.it_interval.tv_sec = 0;
   timer.it_interval.tv_usec = 0;
   timer.it_value.tv_sec = 0;
   timer.it_value.tv_usec = 0;
   setitimer(ITIMER_REAL, &timer, 0);
}

select()

funkcja czekająca na zmianę statusu pewnej liczby deskryptorów. Pozwala ona zadeklarować czas po jakim ma się zakończyć. Tutaj także dzięki modyfikacjom UTIME została zwiększona rozdzielczość czasowa.
void select_test(int delay) {
   struct timeval timeout;
   timeout.tv_sec = 0;
   timeout.tv_usec = delay;
   select(0, 0, 0, 0, &timeout);
}

poll()

funkcja czekająca na zdarzenie na pewnym deskryptorze. W tej funkcji Linux wymusza rozdzielczość co najwyżej 1ms (co i tak jest lepszym wynikiem niż 10ms).
void poll_test(int delay) {
   poll(0, 0, delay);
}

Tryb jądra

Podczas pisania własnych modułów użytkownik może korzystać bezpośrednio z zegarów jądra, które zapewniają poprawioną rozdzielczość.

KURT

KURT zapewnia szeregowanie zadań w czasie rzeczywistym z dokładnością 10 mikrosekund zapewnianą przez UTIME.

Użytkownik wysyła do jądra informację kiedy dany proces czasu rzeczywistego powinien się rozpocząć i kiedy zakończyć. Cała komunikacja z podsystemem KURT odbywa się przez pseudo-urządzenie ``/dev/kurt''.

Proces szeregujący

Standardowo Linux zapewnia następujące klasy procesów: KURT zapewnia trzy typy szeregowania podczas pracy w trybie czasu rzeczywistego. Domyślnie uruchomione jest zwykłe szeregowanie Linuksa. Dopiero ręczne przełączenie powoduje pracę w czasie rzeczywistym. Służy do tego funkcja switch_to_rt. Wymaga ona podania trybu w jakim ma pracować scheduler. Dostępne są trzy możliwości:

Procesy

Procesy czasu rzeczywistego mogą pracować w jednym z trzech trybów: Dodatkowo jeśli został wybrany tryb KURT_EXPLICIT, to musi zostać wybrany jeden z podtrybów: Poniższa tabela przedstawia możliwe kombinacje trybów:

Kernel modes Process modes
  EPISODIC CONTINUOUS
  EXPLICIT ANYTIME PERIODIC EXPLICIT ANYTIME PERIODIC
FOCUSSED OK - - OK OK -
PREFFERED OK - - OK OK -
MIXED OK - OK OK Linux -

Przykład

Process

Przykładowy program wykorzystujący system czasu rzeczywistego. Wypisuje aktualny czas przy każdorazowym obudzeniu.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <asm/timex.h>
#include <linux/rt.h>

void do_stuff(void) {
   struct timeval time;
   cycles_t tsc;
   static int i = 0; /* Just get the time and print it */
   tsc = get_cycles();
   gettimeofday(&time, NULL);
   printf("(%d)Time is %d.%06d (tsc = %Lu).\n", i++,
   (int)time.tv_sec, (int)time.tv_usec, tsc);
}

int main(int argc, char **argv) {
   int kurtdev;
   struct rtparams rt_param;
   if (argc != 2) {
      fprintf(stderr, "Usage : %s <rtid>\n", argv[0]);
      exit(1);
   }

   /* Open /dev/kurt */
   kurtdev = kurt_open();
   rt_param.rt_id = atoi(argv[1]);
   rt_param.period = 0;
   rt_param.exec_time = 300;
   rt_param.rt_mode = KURT_EXPLICIT | KURT_EPISODIC;
   rt_param.rt_name[0] =  \0 ;
   set_rtparams(kurtdev, 0, RT_REGISTER, &rt_param);
   printf("Real-time process %u entering work loop... "
          "(terminate with CTRL-C)\n", rt_param.rt_id);

   /* Real-time work loop, exit by hitting CTRL-C */
   while (1) {
      /* This will suspend the process before the first
       * wakeup, and between each following wakeup */
      rt_suspend(kurtdev, SUSPEND_IF_NRT | START_SCHED);
      do_stuff();
   }
   return 0;
}

Schedule

Program wymagany aby informować jądro, kiedy należy wybudzić proces.
#include <stdio.h>
#include <linux/timer.h>
#include <linux/rt.h>
int main(void) {
   FILE *sched_file;
   struct timer_list timer;
   int i, fd;

   /* Open binary schedule file */
   sched_file = fopen("/tmp/simple_schedule.bin", "w");
   fd = fileno(sched_file);

   timer.list.next = NULL;
   timer.list.prev = NULL;

   /* Send event to KURT s process module */
   timer.data = KURT_PROCESS_MODULE;

   /* All events are wakeup timers */
   timer.flags = KURT_WAKEUP;

   /* The schedule will not be longer than a jiffy, so expires
    * will remain at 0 for every timer. */
   timer.expires = 0;

   for (i = 0; i < 10; i++) {
      /* Events are 500 microseconds apart */
      timer.usec = i * 500;

      /* Send to real-time process 0 */
      timer.function = 0;

      /* Write the event to file */
      write(fd, &timer, sizeof(struct timer_list));

      i++;
      timer.usec = i * 500;

      /* Send to real-time process 1 */
      (unsigned long)timer.function = 1;

      /* Write the event to file */
      write(fd, &timer, sizeof(struct timer_list));
   }

   /* Write the repeat event (5ms schedule) */
   timer.usec = 5000;
   timer.data = KURT_REPEAT;
   write(fd, &timer, sizeof(struct timer_list));

   fclose(sched_file);
   exit(0);
}

Potrzebne pliki