Home Map Index Search News Archives Links About LF
[Top Bar]
[Bottom Bar]
[Photo of the Author]
Ismael Ripoll
Об авторе: Доктор Философии из Полетихнического Университета Валенсии в 1996. Профессор в области операционных систем в подразделении DISCA. Научные интересы включают планировщики реального времени (schedulrers) и операционные системы. Работает с Линуксом с 1994. Хобби: Пересечение Пиринеев, лыжи, радиолюбительство.

Написать автору

Содержание:
KU Линукс реального времени (KURT)
Для чего хорошо реальное время?
Загружаемые модули
Программы реального времени
Коммуникация между задачами
Заключение
Ссылки

Real Time Linux II

[Ilustration]

В этой статье, посвященной RT-Linux я попытаюсь предложить более практичное рассмотрение RT-Linux. Перед тем, как углубиться в детали, я дам краткий обзор недавно появившейся операционной системы реального времени называемой Linux KURT.




Линукс реального времени KU (KURT)

В началие этого (1998) года, был выпущен рилиз новой операционной системы реального времени, базирующейся на Линуксе. KURT - это операционная система мягкого реального времени (мягкого или жесткого), т.е. планировщик старается выделять запрашиваемые времена исполнения, но если какая-либо задача завершиться позже, чем ожидалось, это не является трагедией и ничего драматического не происходит. Задачи реального времени KURT-а могут пользоваться всеми возможностями всех утилит Линукса, в противоположность к задачам RT-Linux. В ядро внесены следующие улучшения (модификации):

  • Улучшено разрешение системных часов. В Linux-386, период прерываний часов 10 мс (100 раз в секунду), и это используется кернелем для принятия решений по управлению и измерения времени. KURT использует для управления временем тот же механизм, что и RT-Linux. Он программирует чип таймера (8254) так, чтобы тот генерировал прерывания по запросу, вместо генерации через промежуток времени. Таким образом может быть достигнуто разрешение часов с порядка микросекунд.
  • Планировщик был модифицирован для того, чтобы включить новую политику планировки, SCHED_KURT, кроме тех, которые уже включены в кернель Линукса и которые определены в POSIX: SCHED_FIFO, SCHED_RR и SCHED_OTHER.
  • Были добавлены новые системные вызовы для доступа к возможностям реального времени.

Задачи реального времени выполнены в виде динамически загружаемых модулей.

Одной из наиболее характерных черт KURT-а является политика планировки. Было принято решение воплотить циклический планировщик. Этот тип планировщика использует таблицу называемую plan (план), которая содержит все планируемые действия: момент активирования, задачу для исполнения, длительность задачи и т.п.. Таблица составляется в фазе конструирования системы. Позднее, во время работы системы, работа планировщика в основном состоит в простом чтении таблицы и следовании инструкциям. Когда достигнут конец таблицы, планировщик переходит к началу и продолжают выполнение задач -- поэтому планировщик и называется циклическим. этот тип планировщика имеет мног преимуществ:

  • Этот планировщик весьма легко воплощается

  • Он эффективен

  • После построения плана, возможность воплощения системы может быть немедленно проверена (некоторые исследователи утверждают, что только этот медтод гарантирует 100-процентно правлиьную работу ситемы)

Главная трудность состоит в генерации самого плана. Более того, каждый раз, когда модифицируется любой из параметров задачи, необходимо перестраивать план, кроме этого нередко, для его хранения требуется огромное количество памяти.

Для чего хорошо реальное время?

Возможно многие люди думают, что техника реального времени используется только в NASA, или в ракетах и прочих экзотических вещах. Хотя это и было правдой в прошлом, сейчас ситуация решительно изменилась -- и собирается измениться еще больше -- в связи с увеличивающейся интеграцией информационных систем и электроники в повседневную жизнь людей. Среди повседневных ситуаций мы найдем реальное время в области телекоммуникационных и мультимедийных приложений. Например, если мы хотим, чтобы наш компьютер повторял звуковой файл, сохраненный на жестком диске, программа должна непрерывно (или лучше периодически) читать, разжимать, и посылать звуковую информацию на звуковую плату. Если в одно и то же время, когда мы слушаем музыку, мы работаем с другим приложением, скажем с текстовым редактором, или просто компилируем кернель Линукса, конечно периодически будут моменты тишины, когда процессор выполняет другие задачи. Если вместо звука, мы воспроизводим на нашей системе видео, результатом могло бы быть воспроизведение с периодически замирающим изображением. Эти типы систем известны, как системы мягкого реального времени (нарушение периодов исполнения не ведет к разрушительным последствиям, но ухудшает качество сервисов, предоставляемых системой).

Приложения RT-Linux идут дальше обычных приложений реального времени. При помощи RT-Linux мы можем полностью контролировать PC (я говорю PC, а не компьютер, потому что к настоящему моменту не имеется воплощений RT-Linux для других архитектур) как в случае MSDOS. Во время работы задчи реального времени можно обращаться к портам PC, устанавливать обработчики прерываний, на время запрещать прерывания, ... другими словами мы можем "повесить" систему как если бы мы работали под Windows. Однако эта возможность весьма привлекательна для тех из нас, кто любит подсоединять электронные "примочки" к компьютеру.

Загружаемые модули

Чтобы понять и иметь возможность использовать RT-Linux, необходимо разобраться с динамически загружаемыми модулями для Линукса. Matt Welsh написал целую статью, где он объясняет в деталях все, что относится к модулям.

Что это такое?

В большинстве воплощений UNIX, имеется только один путь для доступа к аппаратной части компьютера (портам, памяти, прерываниям и т.д.) - при помощи специальных файлов и предварительно установленных драйверов устройств. Хотя имеется много хороших книг, объясняющих, как написать драйвер устройства, часто это долгая и скучная работа, т.к. необходимо написать солидное количество функций, чтобы связать драйвер с системой.

Модули, это "фрагменты операционной системы" которые могут быть подключены и отключены от нее во время исполнения. Когда программа, составляемая из нескольких исходных файлов скомпилирована, сначала каждый файл компилируется отдельно, чтобы сгенерировать объкетный файл ".o", затем все объекты должны быть слинкованы вместе, чтобы связать все ссылки и получить отдельный исполняемый файл. Предположим, что объектный файл содержащий функцию main может быть запущен, и что операционная система смогла загрузить его в память и слинковать его с остальными объектными файлами, только тогда когда это необходимо. Ну вот, кернель может проделывать это сам с собой. Когда Линукс стартует, в память загружается только исполняемый файл vmlinuz. Он содержит абсолютно необходимые элементы кернеля. Позднее, во время выполнения, он может загружать и выгружать модули избирательно, когда они нужны.

Модульность это подключаемая черта Линуксового кернеля, она может быть подключена во время компиляции кернеля Линукса. Кернели всех дистрибутивов, которые мне известны, скомпилированы с возможностью загрузки модулей.

Имеется даже возможность компиляции новых модулей и загрузка их без необходимости не только перекомпиляции, но и перезагрузки системы.

С момента загрузки модуля, он переходит в центральную часть операционной системы, следовательно:

  • Он может использовать все функции и ему доступны все переменные и структуры кернеля.

  • Код модуля исполняется на максимальном уровне привилегий процессора. На архитектуре i386 он исполняется в кольце уровня 0, следовательно для него возможен любой тип доступа к вводу/выводу и выполнение привелигированных команд.

  • Память как для программ, так и для данных отображается непосредственно в физическую память, над которой невозможно проводить "paging" или как неправильно говорят "swapping". Следовательно невозможно генерировать "page fault" во время выполнения модуля.

Как мы могли видеть, динамически загруженный модуль уже имеет часть характеристик, присущих программе реального времени: он избегает задержек из-за "page faults" и он имеет доступ ко всем аппаратным ресурсам компьютера.

Как их создать и использовать?

Модули строятся из иcходного текста "C". Вот пример миниатюрного модуля (для выполнения большинства из следующих команд необходимо быть привелигированным пользователем, root-ом):

example1.c

  #define MODULE
  #include <linux/module.h>
  #include <linux/cons.h>
  static int output=1;

  int init_module(void) {
    printk("Output= %d\n",output);
    return 0;
  } 
  void cleanup_module(void){   
    printk("AdiСs, Bye, Chao, Ovuar, \n");
  }

Для его компиляции мы использовали следующие параметры:

# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -c example1.c

Ключ -c говорит gcc, что он должен остановиться после генерации объектного файла и пропустить этап линковки. Результат - файл example1.o.

У кернеля нет стандартного выхода, следовательно мы не можем использовать функцию printf()... вместо этого кернель предлагает свою собственную версию этой функции printk(), она работает почти также как printf(), исключая то, что она посылает выход в кольцевой буфер кернеля. Оно находится там, где кончают свой путь все сообщения системы, фактически это сообщения6 которые мы видим при запуске систеиы. В любое время мы можем проверить эти сообщения, используя команду dmseg или непосредственно проверяя файл /proc/kmsg.

Заметьте, что функция main() отсутствует и вместо нее мы находим функцию init_module(), которая не принимает никаких параметров. cleanup_module() - последняя функция, которая должна быть вызвана перед выгрузкой модуля. Загрузка модуля производится командой insmod

  # insmod example1.o

Сейчас мы должны установить модуль example1 и выполнить его функцию init_module(). Чтобы увидеть результат напечатайте:

  # dmesg | tail -1
  Output= 1   

Команда lsmod распечатывает список модулей, загруженных в кернель:

# lsmod
Module    Pages   Used by:
example1      1          0
sb            6          1
uart401       2  [sb]    1
sound        16  [sb uart401]  0 (autoclean) 

И наконец, мы используем rmmod чтобы выгрузить модуль:

  # rmmod example1
  # dmesg | tail -2
  Output= 1
  AdiСs, Bye, Chao, Orvua,     

Выход dmesg показывает нам, что функция cleanup_module() была выполнена.

Нам необходимо только знать как передать параметры в модуль. Это удивительно просто. мы можем присвоить значения глобальным переменным, передавая параметры в insmod. Например:

  # insmod ejemplo1.o output=4
  # dmesg | tail -3
  Output= 1
  AdМos, Bye, Chao, Orvua,
  Output= 4                

Сейчас мы знаем все, что нам надо о моюулях, давайте вернемся к RT-Linux.

Наша первая программа реального времени

Во-первых помните, что для использования RT-Linux мы должны подготовить кернель Линукса для поддержки модулей реального времени -- мы объясняли эту операцию в предыдущей статье.

Есть два пути использовать RT-Linux:

  1. Как классическую систему реального времени с планировщиком, основанным на фиксированных приоритетах.

  2. Как на примитивном PC, что-то похожее на то, что может быть сделано под DOS: перехватить прерывания и получить полный контроль над компьютером.

В этот раз я буду обсуждать как использовать RT-Linux как систему с фиксированными приритетами. Пример, который мы сейчас увидим, не делает ничего "полезного", он просто запускает задачу реального времени (простой цикл)

example2.c
  #define MODULE
  #include <linux/module.h>
  #include <linux/kernel.h>
  #include <linux/version.h>
  #include <linux/rt_sched.h>
  RT_TASK task;
	  
  void fun(int computo) {
    int loop,x,limit;
    limit = 10;
    while(1){
      for (loop=0; loop<computo; loop++)
        for (x=1; x<limit; x++);
      	
      rt_task_wait();
    }
  }
  
  int init_module(void) {

    RTIME now = rt_get_time(); 

    rt_task_init(&task,fun, 50 , 3000, 1);
    rt_task_make_periodic(&task,
          now+(RTIME)(RT_TICKS_PER_SEC*4000)/1000000,
	 (RTIME)(RT_TICKS_PER_SEC * 100)/1000000);
    return 0;
  }

  void cleanup_module(void){
    rt_task_delete(&task);
  }

И опять мы компилируем пример командой:

# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -D__RT__ -c example2.c

Поскольку программа - модуль, входная точка - функция init_module(). Первое, что необходимо сделать, это прочитать текущее время и сохранить его в локальной переменной; функция rt_get_time() возвращает число RT_TICKS_PER_SEC прошедших со времени загрузки (в текущей версии RT_TICKS_PER_SEC - 1.193.180, что дает разрешение 0.838 микросекунд). Функцией rt_task_init() структура "task" инициализируется, но еще не запускается. Главная программа только что созданной задачи - fun(), которая является вторым параметром. Следующий параметр - это значение данных, которое передается новой задаче, когда та начинает выполняться, заметьте, что fun() ожидает параметр типа int . Следующий параметр - размер стека задачи; так как каждая задача имеет свой собственный тред, каждая нуждается в собственном стеке. Последний параметр - приоритет; в нашем случае, когда в системе присутствует только одна задача, мы можем установить любое значение.

rt_task_make_periodic() преобразует задачу в периодическую. Ей передаются два значения, первое это момент в абсолютном времени, когда задача будет активизирована в первый раз и второе это период между успешными активациями, которые происходят периодически после после первой активации.

Задача реального времени (функция fun()), это бесконечная петля в которой выполняются только два действия: цикл, который только вхолостую тратит время и затем вызывается функция rt_task_wait(). rt_task_wait(), это функция, которая приостанавливает выполнение задачи, которая ее вызывает, до следущего момента активации, момента, когда выполнение продолжится сразу выполнением инструкции, следующей непосредственно после rt_task_wait(). Читатель должен понимать, что периодическая задача не выполняется с самого начала после каждой активации, вместо этого, задача сама приостанавливает выполнение (после окончания ее работы) и ждет следующей активации. Это позволяет писать задачи, которые выполняют инициализацию только во время первого вызова.

Для того, чтобы выполнить example2, сначала мы должны установить модуль rt_prio_sched, так как наша задача нуждается в функциях rt_task_make_periodic(), rt_task_delete() и rt_task_init(). Функции rt_get_time() нет внутри модуля, она находиться в кернеле Линукса, следовательно, для того чтобы ее использовать ее не надо устанавливать.

  # modprobe rt_prio_sched
  # insmod ./example2.o

Так как rt_prio_sched - модуль системы, он был создан во время компиляции кернеля Линукса и следовательно, был скопирован в директорию /var/modules/2.0.33/. Мы используем команду modprobe так как это самый легкий путь загрузить модули (она исчет модули в директориях модулей) (См. modprobe(1)).

Если все идет хорошо, подав команду lsmod мы увидим, что оба модуля были правильно загружены.

Итак, в этом месте, читатель уже имеет работающую программу реального времени. Вы заметели что нибудь особенное? Если процессор медленный, читатель возможно заметит, что Линукс работает медленнее, чем обычно. Вы можете попробовать увеличить количество итераций внутри петли в функции fun(), изменяя третий параметр в rt_task_init(). Я рекомендую запустить программу ico, чтобы оценить насколько меньше процессорного времени осталось, так как время мспользовано программой реального времени, эффект похож, как будто процессор работает на меньшей частоте, Линукс будет верить, что всем его процессам нужно больше времени для выполнения тех же самых задач. Если время вычисления (время, требуемое для выполнения всех итераций петли) больше, чем 100 микросекунд, Линукс "повиснет", так как здесь Линукс - фоновая задача и задача реального времени потребляет 100% времени. В действительности Линукс не висит, ему просто не достается процессорного времени.

Коммуникация между задачами

В RT-Linux есть только один способ коммуникации: FIFO(первый пришел первый ушел) реального времени. Они работают очень похоже на Юниксовые трубы (pipes), коммуникация через потоки данных без структуры. FIFO это буфер на фиксированное количество байт, который позволяет выполнять операции чтения и записи.

Используя FIFO возможно установить связь как между задачами реального времени, так же как и с обычными задачами Линукса.

С точки зрения обычных процессов FIFO является файлом со специальными свойствами. Обычно они называются: /dev/rtf0, /dev/rtf1, и т.д. Эти файлы не существуют в Линуксе и должны быть созданы следующим путем:

# for i in 0 1 2 3; do mknod /dev/rtf$i c 63 $i; done

Если нужно иметь больше FIFO, они могут быть созданы использованием той же процедуры: rtf4, rtf5, и т.д.. Эти специальные файлы выступают как интерфейсы к хандлерам операционной системы, но если хандлер не существует, тогда спецальные файлы не соответствуют ничему, фактически открытие любого из специальных файлов не выполняется, если операционная система не имеет ассоциированных хандлеров.

DrawObject

FIFO используются, как если бы они были бы обычными файлами (open, read/write, close). Для того, чтобы обычные процессы Линукса могли использовать их, сначала программа реального времени должна создать соответствующий FIFO.

С точки зрения задачи реального времени,, FIFOs используются через специфические функции:

  • rt_create(unsigned int fifo, int sise): создает FIFO с буфером размера size. С этого момента и до уничтожения, устройство, доступное через /dev/rtf[fifo] существует и может использоваться.

  • rt_destroy(unsigned int fifo): соответствующий FIFO уничтожается и его память перераспределяется.

  • rt_fifo_put(fifo, char *buf, int count): попытка записать count байтов в буфер buf. Если места в буфере недостаточно, возвращается -1.

  • rt_fifo_get(fifo, char *buf, count): попытка прочитать count байтов из FIFO, если нет соответсвующих данных, возвращает -1.

Давайте посмотрим на пример функции, которая использует эти функции. Это немного модифицированный пример из дистрибутива (sound):

example3.c
  #define MODULE
  #include <linux/module.h>
  #include <linux/rt_sched.h> 
	 
  #include <linux/rtf.h>
  #include <asm/io.h>

  RT_TASK task;  

  static int filter(int x){
    static int oldx;
    int ret;
    if (x & 0x80) {
      x = 382 - x;
    }
    ret = x > oldx;
    oldx = x;
    return ret;
  }

  void fun(int dummy) {
    char data;
    char temp;
    while (1) {
      if (rtf_get(0, &data, 1) >  0) {
        data = filter(data);
        temp = inb(0x61);            
        temp &= 0xfd;
        temp |= (data & 1) <<  1;
        outb(temp,0x61);
      }
      rt_task_wait();
    }
  }

  int init_module(void){
    rtf_create(0, 4000);
    
    /* enable counter 2 */
    outb_p(inb_p(0x61)|3, 0x61);
    
    /* to ensure  that the output of the counter is 1 */
    outb_p(0xb0, 0x43);
    outb_p(3, 0x42);
    outb_p(00, 0x42);
    
    rt_task_init(&task, fun,  0 , 3000, 1);   
    rt_task_make_periodic(&task, 
                   (RTIME)rt_get_time()+(RTIME)1000LL, 
                   (RTIME)(RT_TICKS_PER_SEC / 8192LL));

    return 0;
  } 

  void cleanup_module(void){
    rt_task_delete(&task);  
    rtf_destroy(0);
  }

Как и во втором примере, нам нужны сервисы из модуляrt_prio_sched, но для того, чтобы испоьзовать FIFO, мы должны также загрузить модуль rt_fifo_new.

Создана периодическая задача реального времени с частотой 8192Гц. Эта задача читает байты из FIFO 0 и, если найдет что-нибудь, посылает на порт громкоговорителя ПК. Если мы копируем звуковой файл в формате ".au" в /dev/rtf0 мы сможем слышать его. Нет неоходимости говорить, что качество звука ужасное, т.к. схема громкоговорителя ПК позволяет использовать только один бит для модуляции сигнала. В директории testing/sound дистрибутива содержится файл linux.au, который может использоваться для тестов.

Скомпилируйте и выполните его:

   # gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -D__RT__ -c example3.c
   # modprobe rt_fifo_new
   # modprobe rt_prio_sched
   # insmod example3.o
   # cat linux.au > /dev/rtf0

Заметьте, как команда cat может использоваться для записи в любой файл, включая специальные файлы. Мы можем также использовать команду cp.

Чтобы сравнить, как характеристики программного времени влияют на качество воспроизведения, нам нужно только написать программу, которая делает ту же самую операцию, но как обычный процесс Линукса:

example4.c
  #include <unistd.h>
  #include <asm/io.h>
  #include <time.h>

  static int filter(int x){
    static int oldx;
    int ret;
    if (x & 0x80)
      x = 382 - x;
    ret = x > oldx;
    oldx = x;
    return ret;
  }
  espera(int x){
    int v;
    for (v=0; v<x; v++);
  }
  void fun() {
    char data;
    char temp;

    while (1) {
      if (read(0, &data, 1) >  0) {
        data = filter(data);
        temp = inb(0x61);
        temp &= 0xfd;
        temp |= (data & 1) << 1;
        outb(temp,0x61);
      }
      espera(3000);
    }
  }

  int main(void){
    unsigned char dummy,x;
    ioperm(0x42, 0x3,1); ioperm(0x61, 0x1,1);

    dummy= inb(0x61);espera(10);
    outb(dummy|3, 0x61);

    outb(0xb0, 0x43);espera(10);

    outb(3, 0x42);espera(10);
    outb(00, 0x42);

    fun();
  }

Эта программа может быть скомпилирована как и любая обычная программа:

  # gcc -O2 example4.c -o  example4

И чтобы выполнить ее:

  # cat linux.au | example4

Чтобы получить доступ к портам компьютера из обычного процесса Линукса, мы должны запросить разрешения у операционной системы. Это базовая и необходимая мера защиты, чтобы избежать прямого доступа программы к жесткому диску, например. Вызов ioperm() говорит операционной системе, что мы хотим получить доступ к данному диапазону адресов ввода/вывода. Такое разрешение может получить только программа, выполняющаяся с привелегиями root. Интересна и другая деталь, как получена частота 8192Гц, которая используется для модуляции генерируемого звука. Имеется системный вызов называемый nanodelay(), но у него разрешение всего лишь миллисекунды, следовательно мы должны использовать временные часы, при помощи цикла задержки. Цикл настроен так, что он более или менее работает на Пентиуме 100 МГц.

Теперь я предлагаю читателю протестировать example4 вместе с программой ico, Как она звучит теперь? Похоже на версию реального времени? Итак, годится реальное время для чего-нибудь?

Заключение

Вторая статья сконцентрирована на деталях программирования задач реального времени. Приведенные примеры очень просты и не годятся для практического использования, в следующей статье я дам более полезные применения. Мы сможем управлять из Линукса телевизором или, что более удивительно, управлять Вашим Линуксом при помощи дистанционного управления!!!.

Литература:

Перевод на русский: Евгений Казанов
http://www.dkd.ot.lt/hompag/linux


Web page maintained by Miguel A Sepulveda
© Ismael Ripoll 1998
LinuxFocus 1998