суббота, 7 ноября 2009 г.

Великі відступи навколо формул у OpenOffice.org Writer

Як не крути, користуватися весь час TeX'ом немає ніякої можливості через те, що документообіг у нашій країні побудовано на офісному продукті усім відомої компанії. Задоволення керівництво отримує тільки від документів у форматі MS Word, відповідно для підготовки такого роду документів бішьш підходить OpenOffice.org Writer.

Одна його властивість, що дуже мене дратувала - робити величезні відступи навколо об'єктів-формул. І чесно кажучи, все ніяк не міг зрозуміти, де саме ці відступи встановлено. С цим і жив :). Аж ось вчора натрапив на статтю тут на Блогері, у якій все зрозуміло написано, більш того питання виявилося з ЧАП, що удвічі соромніше.

Щоб прибрати відступи, достатньо змінити параметри обтікання у стилі об'єкту-формули. За картинками та докладними роз'ясненями звертйтеся по вказаним посиланням.

четверг, 7 мая 2009 г.

Linux крізь осцилоскоп

Оригінал: http://linuxgazette.net/issue95/pramode.html

Вступ
Нещодавно я зробив декілька схем і перевіряв їх на своєму старому 20мГц осцилоскопі. І думав, що було б цікаво перевірити як складна, динамічна природа багатозадачної операційної системи впливає на роботу чутливого до часу виконання коду дивлячись на сигнал, що генерується такими програмами на осцилоскопі. Ця стаття описує декілька експериментів, що я зробив, спершу на “звичайному” ядрі 2.4.18, а потім на ядрі залатаному “розширенням реального часу”, що провадиться проектом RTAI. Я передбачаю, що читач має деякі базові навички у програмуванні ядра.
Інструментарій експерименту

Я перетворив старий мотлох у вигляді комп'ютеру з ЦП Cyrix у свою експериментальну платформу з “вбудованим linux”. Материнської плату було витягнуто з корпусу, НЖМД (HDD), монітор, клавіатуру та інше було видалено, залишив тільки Ethernet-карту з завантажувальною ПЗП (boot ROM) та ISA-протоплату (protoboard). Ця машина грузить повноцінну Linux-систему розташовану на відстані декількох футів. Таким чином я можу експериментувати з залізом не дуже хвилюючись, що пошкоджу щось коштовне. Ну і звичайно передбачив можливість завантажувати на вибір: чисте або залатане RTAI ядро 2.4.18.

Простий генератор сигналу
Ось невеличка програма, що робить у просторі користувача, при запуску з правами супер-користувача вона генерує сигнал на пінах паралельного порту, котрі можна бачити на осцилоскопі.
#include <asm/io.h> 

#define ON 100000
#define OFF ON*10

delay(unsigned int i)
{
while(i--);
}

main()
{
iopl(3);
while(1) {
outb(0xff, 0x378);
delay(ON); //2
outb(0x0, 0x378);
delay(OFF);
}
}

Програма робить радикально просто. Піни 2-9 паралельного порту роблять як вихідні — вони доступні через порт вводу-виводу, чия адреса 0x378. Якщо ви записуєте 0xff у 0x378, то включаєте (тобто, подаєте високий рівень, 5 В) на всі ці піни, записуючи 0x0 вимикаєте їх. Цю програму компілюємо з -O2 та запускаємо від суперкористувача (щоб заробив виклик outb, спочатку викликаємо iopl, що використовується для призначення рівню привілеїв, а щоб він виконався потрібно бути суперкористувачем).


На моїй системі, я спостерігаю сигнал з високим рівнем близько 2.5 — 2.7 мс з осцилоскопом налаштованим на 1 мс поділку. Результат може суттєво варіюватися в залежності від швидкості вашого процесору.

Чому прості речі не такі вже і прості
Кожен хто проходив базовий курс по мікропроцесорам, мабуть має знати як генерувти затримки за допомогою написання циклів. Це те саме, що ми будемо робити у даному випадку — зовсім дитяча справа.

З цікавості, я зареєструвався у іншій консолі і запустив команду “yes”, що генерує постійний потік символу “y” на екрані. Потім я подивився на осцилоскоп і побачив, що мій симпатичний сигнал перетворився у казна що. Періоди ВКЛ та ВИКЛ стали настільки подовженими, що я побачив майже гладку лінію, що змінювалася у межах від 0 В до 5 В.

Я зробив ще один експеримент. Запустив “ping -f” на систему зі швидшого комп'ютеру, і знову на осцилоскопі було видно, що сигнал суттєво збуджується.

Причину такої ситуації нескладно зрозуміти. Моя програма змагається з іншими за цикли(час) ЦП. Між виконанням циклу затримки, керування може перейти до іншої програми, збільшуючи затримку задану у моїй програмі. Повеневий(flood) пінг призводить до збільшення активності ядра системи, що також спричиняє шкідливий ефект на часові властивості моєї програми.

Вирішення проблеми просте — не турбувати програму, що генерує сигнал. Нехай вона повністю контролює ЦП. Тоді виникає питання, а навіщо взагалі складна багатозадачна система? Подивимось.

Назвемо програму, що генерує сигнал програмою “реального часу”. Візуалізуємо програму як “задачу” чия робота змінювати стан пінів паралельного порту у визначені інтервали часу. Якщо генерований сигнал використовується для керування фізичним пристроєм, як-то сервомотор (оберт сервомотору контролюється довжиною періоду високого рівня імпульсу чия загальна довжина близько 20 мс. Коли період високого рівеня змінюється від 1 мс до 2 мс, ротор сервомотору повертається на 180 о), зміни у довжині імпульса можуть мати драматичні наслідки. Мій серводвигун Futaba S2003 дико дригається коли керується програмою схожою на ту, що вище, якщо на неї впливає інший процес. Програма реального часу має граничні часові строки (timing deadlines), які МАЄ витримувати, для коректного виконання. Класичним рішенням є проектування пристроїв керування з використанням спеціалізованих мікроконтролерів та цифрових сигнальних процесорів. Та разом із здешевленням заліза ПК, значно розширився діапазон проблем коли нам необхідна можливість виконувати програми чутливі до часових строків і у той же час, робити інші справи, як-то взаємодіяти через мережу, візуалізувати дані з графічними інтерфейсами, реєструвати дані у допоміжні сховища і такі інші справи, що не потребують точного виконання за графіком, так звані “non-realtime” завдання.

Якщо можливо модифікувати ядро Linux таким чином, що часові обмеження накладаємі на деякі завдання (що створені і запущені якимось спеціальним чином) завжди виконуватимуться, навіть при присутності інших “non-realtime” завдань, то ми матимемо ідеальну ситуацію. Пізніше у статті буде показано, що таких рішень є декілька.


Засинання проти зациклення
Не дивлячись на факт, що синхронність програми дуже залежить від іншої діяльності, що відбувається у системі, ми використовуємо цикли ЦП виконуючи компактні цикли (так само, на складних мікропроцесорах як Pentium, важко обчислювати затримки підраховуючи інструкції). Чому не дозволити програмі заснути?

Використовуючі функції як “nanosleep”, ми інструктуємо Операційну Систему перевести на процес у режим сну, щоб прокинутись у визначений час. Але знову, є можливість, що наш процес не прокинеться для виконання у визначений час, тому що ОС буде зайнята виконуючі деяку дію у режимі ядра (наприклад, обробку пакетів TCP/IP, чи виконуючи операцію дискового вводу-виводу) чи інший процес буде заплановано, як раз перед тим як ядро прокине наш.

Робимо у просторі ядра
Що як ми створимо наш код генерації сигналу у вигляді модулю ядра?
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/param.h>
#include <asm/uaccess.h>
#include <asm/io.h>

static char *name = "foo";
static int major;

#define ON 100000
#define OFF ON*10

void delay(unsigned int i)
{
while(i--);
}

static int
foo_read(struct file* filp, char *buf, size_t count, loff_t *f_pos)
{
while(1) {
outb(0xff, 0x378);
delay(ON);
outb(0x0, 0x378);
delay(OFF);
}
return 0;
}

static struct file_operations fops = {
read: foo_read,
};

int init_module(void)
{
major = register_chrdev(0, name, &fops);
printk("Registered, got major = %d\n", major);
return 0;
}

void cleanup_module(void)
{
printk("Cleaning up...\n");
unregister_chrdev(major, name);
}

Виконання нескінченного циклу у ядрі має катастрофічні наслідки — поки розглядається наявність процесів рівня користувача. Жоден такий процес не зможе виконуватись, доки керування здійснюється на рівні ядра (так спроектовано ОС). А нам хочеться мати ситуацію коли завдання реального часу співіснують зі звичайними завданнями.

Хоча завдання простору користувача не можуть впливати на нашу програму, все ще можливо генерувати переривання на мережевій карті за допомогою повеневого пінгу. Оскільки переривання обслуговуються навіть при виконанні коду ядра, сигнал на осцилоскопі дещо змінюється від очикуваного.

Можливо засипати у режимі ядра — це запобігає замиканню системи та не вирішує проблеми мирного співіснування коду реального часу зі звичайним.
Введення у Linux реального часу

Що як встромити нано-ядро між Linux і нашим залізом? Це ядро буде керувати Linux і набором завдань реального часу. З Linux воно буде поводитись як з завданням дуже низького пріоритету, що буде виконуватись тільки коли жодне з високопріоритетних завдань реального часу не потребує процесору. Керування перериваннями буде у руках цього спеціалізованого ядра — запити Linux на заборону переривань будуть сприйматись таким чином, що переривання насправді не будуть заборонені, просто Linux не зможе їх побачити, при цьому завдання реального часу зможуть далі виконувати свої обробники переривань, без великої затримки.

Ця нова концепція, запропонована доктором Віктором Йодаікеном (Dr.Victor Yodaiken), призвела до появи RTLinux. Багато інших університетів і дослідницьких організацій спробували започаткувати свої власні реалізації — однією з найбільш успішних (і повністю вільною) є RTAI, що розробляється дослідниками у Департаменті Аерокосмічної Інженерії Міланської Політехніки (Dipartimento di Ingegneria Aerospaziale - Politecnico di Milano (DIAPM)).
Отримання та установка RTAI

RTAI може бути отримано звідси. У ньому є два головних компоненти:
  • HAL (шар абстракції заліза) заплата до ядра Linux,
  • набір модулів для виконання планування, міжпроцесної взаємодії, синхронізації і таке інше.
Перед латанням та інсталяцією нового ядра мають бути уважно прочитані інструкціїподані у файлі README.INSTALL (особливо ті, що стосуються деяких опцій налаштування ядра. "Встановити інформацію про версію на завантажувальні модулі" має бути відключена. Ймовірно ви використовуєте однопроцесорну систему — відповідно не забудьте відключити підтримку SMP (можливо, також відключити керування живленням)). Як тільки ви перезавантажитеся з новим ядром, можна буде компілювати головні модулі RTAI і приклади. Перед запуском будь-яких програм, вам потрібно завантажити три модулі: rtai.o, rtai_fifos.o та rtai_sched.o.


Генерація сигналу за допомогою RTAI

Давайте подивимось на програму RTAI, що генерує сигнал на вихідних пінах паралельного порту:

#include <linux/module.h>
#include <rtai.h>
#include <rtai_sched.h>

#define LPT1_BASE 0x378
#define STACK_SIZE 4096
#define TIMERTICKS 1000000 /* 1 milli second */

static RT_TASK my_task;

static void fun(int t)
{
unsigned char c = 0x0;
while(1) {
outb(c, LPT1_BASE);
c = ~c;
rt_task_wait_period();
}
}

int init_module(void)
{
RTIME tick_period, now;

rt_set_periodic_mode();
rt_task_init(&my_task, fun, 0, STACK_SIZE, 0, 0, 0);
tick_period = start_rt_timer(nano2count(TIMERTICKS));
now = rt_get_time();
rt_task_make_periodic(&my_task, now + tick_period, 2*tick_period);
return 0;
}

void cleanup_module(void)
{
stop_rt_timer();
rt_busy_sleep(10000000);
rt_task_delete(&my_task);
}


Оглянемо загальну ідею, перед тим як розбирати специфічні деталі. По-перш, нам потрібно, щоб завдання робило щось корисне. task це просто фкункція на C. Структура більшості наших завдань буде подібною до — зробити щось, заснути на деякий час, знову щось зробити, повторити. Один з способів заснути — викликати rt_task_wait_period, питання скільки ми будемо спати? Ми засинаємо на деякий фіксований період часу, що може бути добутком базового tick. Системний 8254 годинник може бути запрограмований на генерацію переривань на частоті 1 кГц (тобто, 1000 разів у секунду). Планувальник RTAI виконує планування на кожному тіку, якщо ми оберемо інтервал нашого завдання у 2 тіки і інтервал між кожним у 1 мс, то планувальник буде будити наше завдання після 2 мс.

Ми починаємо з init_module. Спочатку конфігуруємо годинник як “періодичний” (оскільки існує інший режим). Функція rt_task_init приймає адресу об'єкту типу RT_TASK, адресу нашої функції і розмір стеку, окрім деяких інших значень. Проводиться деяка “ініціалізація” і інформація зберігається у об'єкті типу RT_TASK, що може бути пізніше використаний для ідентифікації цього специфічного завдання.

Наш період тіків TICK_PERIOD дрівнює 1000000 нс (1 мс). Функція nano2count конвертує цей час у внутрішні “одиниці лічення”. Годинник запускається з періодом тіків еквівалентним 1 мс (це робить функція start_rt_timer).

Залишається тільки запустити наше завдання і виставити його період (пам'ятайте, період, що використовується rt_task_wait_period для визначення часу, через який завдання буде прокинуте). Ми встановлюємо період у 2 тіки і інструктуємо планувальник самостійно запустити його на наступному тіку.

Тіло нашого завдання дуже просте — воно просто записує значення на вихідні піни паралельного порту, значення змінної, що записується у порт інвертується і очикується наступний період (що буде 2 мс). Після прокидання, повторюється та ж сама послідовність, і знову, і знову... У кінцевому результаті ми побачимо на осцилоскопі сигнал з високим рівнем 2 мс і низьким 2 мс.

Я подивився на форму сигналу спочатку на ненавантаженій системі. Потім повторив для завантаженої повеневим пінгом. Форма сигналу залишилася незмінною. RTAI дає нам гарантію, що Linux буде завжди виконуватись як низькопріоритетне завдання, тобто лише у разі коли немає завдань реального часу, що необхідно обслужити. Просинання завдання реального часу призведе до миттєвої передачі йому керування (звичайно, тут є затримки, що виникають з причини витіснення чогось, що у цей час виконується, активацію планувальника реального часу і передачі керування назад завданню, що тільки-но виконувалось — ці затримки теж мають бути константою). Ось чому ми можемо бачити досить стабільний сигнал, навіть при навантаженні.

Ось сегмент коду, що демонструє використання функції rt_sleep:

#define LPT1_BASE 0x378
#define STACK_SIZE 4096
#define TIMERTICKS 1000000 /* 1 milli second */

#define ON_TIME 3000000 /* 3 milli seconds */
#define OFF_TIME 1000000 /* 1 milli second */

static RT_TASK my_task;
RTIME on_time, off_time;

static void fun(int t)
{
while(1) {
outb(0xff, LPT1_BASE);
rt_sleep(on_time);
outb(0x0, LPT1_BASE);
rt_sleep(off_time);
}
}

int init_module(void)
{
RTIME tick_period, now;

rt_set_periodic_mode();
rt_task_init(&my_task, fun, 0, STACK_SIZE, 0, 0, 0);
tick_period = start_rt_timer(nano2count(TIMERTICKS));
on_time = nano2count(ON_TIME);
off_time = nano2count(OFF_TIME);
now = rt_get_time();
rt_task_make_periodic(&my_task, now + tick_period, 2*tick_period);
return 0;
}


Базовий період тіку 1 мс. Наші періоди високого та низького рівнів сигналу є інтегральними добутками цього періоду (3 мс та 1 мс кожен). Виклик rt_sleep(on_time) переведе завдання у режим сну — воно прокинеться через період у 3 тіки. Зробить щось і знову перейде у сон на період у 1 тік.
Використання каналів FIFO для взаємодії між завданнями реального часу і звичними завданнями

Це може бути необхідним для передачі даних між звичною програмою простору користувача та завданням RTAI (і навпаки). Таке легко зробити з використанням каналів типу FIFO. Для прикладу, завдання RTAI може генерувати ШІМ-сигнал і ви можете контролювати його параметри (ширину) з простору користувача. При інсталяціі RTAI створюється декілька файлів пристроїв у каталозі /dev/, що називаються rtf0, rtf1 і так далі. Програма користувача позначає кожен FIFO за ім'ям, тоді як завдання RTAI номерами 0, 1, 2...

#include <linux/module.h>
#include <linux/errno.h>
#include <rtai.h>
#include <rtai_sched.h>
#include <rtai_fifos.h>


#define STACK_SIZE 4096
#define COMMAND_FIFO 0
#define FIFO_SIZE 1024


int fifo_handler(unsigned int fifo)
{
char buf[100];
int r;

r = rtf_get(COMMAND_FIFO, buf, sizeof(buf)-1);
if (r <= 0) return r;
rt_printk("handler called for fifo %d, get = %d\n", fifo, r);
buf[r] = 0;
rt_printk("data = %s\n", buf);
return 0;
}

int init_module(void)
{
/* Create fifo, set handler */
rtf_create(COMMAND_FIFO, FIFO_SIZE);
rtf_create_handler(COMMAND_FIFO, fifo_handler);

return 0;
}

void cleanup_module(void)
{
printk("cleaning up...\n");
}


У init_module, ми створюємо канал fifo і встановлюємо fifo_handler як функцію, що викликається коли хтось записує у fifo. Функція rtf_get читає дані з каналу. Після компіляції і завантаження модулю, якщо ми зробимо щось на кшталт:
echo hello > /dev/rtf0

то побачимо, що обробник викликано і він читає дані з fifo-каналу.


Що читати далі

Якщо ви зацікавились у програмуванні у реальному часі, то почати потрібно з чудового “Real Time and Embedded Guide” написаного Германом Брюнінксом (Herman Bruyninckx). Програмування за допомогою RTAI детально пояснюється у мануалі “RTAI manual” та “RTAI programming guide” доступних для завантаження з домашньої сторінки проекту.


Висновки

ОС що провадять підтримку детерміністичного виконання завдань з суворими часовими вимогами це лише частина тематики проектування систем реального часу. Після кількох днів гри з RTAI, я зрозумів, що проектування систем реального часу, це щось, що не може бути зроблене новаком як я — потрібно вкласти багато часу, зусиль і терпіння для повного розуміння своєї системи (апаратної настільки ж як і програмної частини) і використовувати всі інструменти правильно. А взагалі, це не повинно зупиняти вас від експерименту та веселощів!

воскресенье, 22 февраля 2009 г.

Широтно імпульсна модуляція: фокус з Linux/RTAI

Переклад статті від Pramode C.E.

(http://linuxgazette.net/issue97/pramode.html)

У минулій статті, я розповів про основи програмування у реальному часі з Linux/RTAI. Ця стаття демонструє приємний фокус, що ви можете зробити на вашому домашньому “тазику” , якщо зробите просту електричну схему для вашого паралельного порту(LPT) (і звичайно запустите пропатчене RTAI ядро). Також я продемонструю прості приклади використання:

  • міжпроцесних повідомлень

  • поштових скриньок (Mail boxes)

Будьте обачні коли граєтесь з залізом вашого ПК – і не шукайте мене у разі якщо щось спалите!

Фокус

Ви хочете щоб світлодіод (LED) повільно займався, поступово стаючи все яскравішим. А досягнувши максимуму, почав поступово згасати, теж потроху. І така поведінка повторювалась циклічно. Контролюючи силу струму, що тече через нього, ми можемо контролювати його яскравість. Однак паралельний порт ПК може давати лише два рівня напруги: низький – 0 В та високий – близько 5 В. І цього неможливо змінити, ми можемо лише включати та виключати світлодіод миттєво, але це не те чого нам хочеться.

Широтно імпульсна модуляція

Рис. 1.ШІМ сигнал

Уявіть, що ви ідете на автомобілі особливим чином. Ви швидко їдете на протязі 3 секунд, потім стоїте на місці на протязі 7 секунд, потім знову таким чином. Якщо ви будете продовжувати так робити, ви через деякий час здолаєте відстань між двома точками. Поділивши відстань на цей час, можна отримати “середню” швидкість. Що станеться з нею, якщо ви збільшите кількість часу швидкої їзди? Звичайно вона зросте, а якщо зменшите – то зменшиться. Таким самим чином, замість прикладення постійної DC-напруги до світлодіода (LED), ми прикладаємо сигнал фіксованої частоти (скажімо 1 кГц, період 1 мс). Світлодіод буде яскраво горіти на протязі 1 мс періоду, тобто можна сказати сигнал буде високим 0,8 мс і низьким на протязі 0,2 мс. Варіюючи циклічність зміни рівня сигналу (залишаючи частоту фіксованою), ми зможемо доставляти різні кількості потужності до схеми, роблячи світло яскравішим чи тьмянішим. Тепер ми зможемо контролювати аналоговий пристрій у суто цифровий спосіб!

Залізо

Вам не знадобиться нічого окрім світлодіоду (бажано яскравого) та 1 кОм резистору. Резистор та світлодіод мають бути з'єднані послідновно між піном №2 (вихідним) та піном №25 (землею) паралельного порту. Вісім вихідних пінів (з 2 по 9) паралельного порту можуть бути керовані через порт вводу-виводу(IO) 0x378. Якщо записати 1 до 0x378 то у результаті високий рівень з'явиться лише на піні №2; Якщо записати 0xff то високий рівень з'явиться на усіх пінах, при запису іншого шаблону-маски можна керувати логічні рівні на довільних пінах.

Рис. 2.Принципова схема

Резистор обмежує силу струму, що тече скрізь світлодіод до декількох мілліампер. Проблема полягає у тому, що світлодіод буде горіти дуже кволо, якщо обмежити силу струму, що через нього тече. Рішенням є використання транзистору як перемикача.


Програмне забезпечення

Головна ідея проста – ми пишемо задачу реального часу, що буде вмикати світлодіод, засинати на деякий час, вимикати його, знову засинати на деякий час. Сума часу ввімкненого та вимкненого стану 1 мс. З початку, час ввімкненого стану буде 0 мс, а вимкненого 1 мс. На наступній ітерації, ввімкнений стан буде 1 * 1 мкс, а вимкнений (1 мс – ввімкнений час). Н а наступній ітерації, ввімкнений стан буде 2 * 1 мкс, і так далі. На 1000-ній ітерації ввімкнений стан буде 1000 * 1 мкс, тобто 1 мс і вимкнений буде (1 мс – ввімкнений час), тобто 0 мс. Як тільки ми дістанемо такого моменту, ми починаємо зменшувати час ввімкненого стану так, що він поступово зменшується до 0 мс, а час вимкненого стану зросте до 1 мс. Цей процес повторюється. Таким чином через 1 с (кожна ітерація займає 1 мс, і ми маємо 1000 ітерацій), світлодіод пройде від вимкненого стану до найяскравішого можливого рівня, а у наступну 1 с повільно повернеться до вимкненого стану.

Ось код, що реалізує цю ідею:

#include <linux/kernel.h>
#include <linux/module.h>
#include <rtai.h>
#include <rtai_sched.h>

#define STACK_SIZE 4096

#define MIN_ON_PERIOD 0
#define TOTAL_PERIOD 1000000 /* 1ms */
#define NSTEPS 1000
#define STEP_PERIOD 1000

#define LPT1 0x378


static RTIME on_time, off_time, total_period;
static RT_TASK my_task;

enum direction {DOWN, UP};

static void pwm_task(int n)
{
int step = 0;
static int dir = UP;

while(1) {
outb(0xff, LPT1);
rt_sleep(on_time);
outb(0x0, LPT1);
rt_sleep(off_time);
if(step == NSTEPS) {
dir = !dir;
step = 0;
}
step++;
if(dir == UP) on_time = nano2count(step*STEP_PERIOD);
else if(dir == DOWN) on_time = total_period - nano2count(step*STEP_PERIOD);
off_time = total_period - on_time;
}
}

int init_module(void)
{
RTIME now;

rt_set_oneshot_mode();
rt_task_init(&my_task, pwm_task, 0, STACK_SIZE, 0, 0, 0);
start_rt_timer(0);

on_time = nano2count(MIN_ON_PERIOD);
off_time = nano2count(TOTAL_PERIOD);
total_period = nano2count(TOTAL_PERIOD);

now = rt_get_time() + total_period;
rt_task_make_periodic(&my_task, now, total_period);

return 0;
}


void cleanup_module(void)
{
stop_rt_timer();
rt_busy_sleep(10000000);
rt_task_delete(&my_task);
}

Код ШІМ pwm_task() має бути легко зрозумілим.

Оскільки RTAI гарантує, що завдання реального часу завжди виконаються у завдані строки (deadlines), караючи лише звичні (non-realtime) завдання, ми можемо бачити, що процес генерації ШІМ сигналу гладенько продовжується навіть при високому навантаженні системи. Часовий крок у 1 мкс, що використаний у нашому коді для RTAI буде важко досягти, та однак ми не побачимо жодних візуальних ознак вказуючих на це (поки не використаємо осцилоскоп для перегляду форми сигналу). Але, це всього лиш кумедна програмка!

Посилання повідомлень

Завдання можуть посилати повідомлення одне одному. Повідомлення — це просто целочислене значення. Ось проста програма, що демонструє посилання повідомлень:


#include <linux/module.h>
#include <rtai.h>
#include <rtai_sched.h>

#define LPT1_BASE 0x378
#define STACK_SIZE 4096
#define TIMERTICKS 1000000000

static RT_TASK tasks[2];

static void task_sender(int t)
{
int msg = 0xab;
RT_TASK *r;
r = rt_send(&tasks[1], msg);
rt_printk("sender: r = %x\n", r);
}

static void task_receiver(int t)
{
int msg;
RT_TASK *r;

r = rt_receive(&tasks[0], &msg);
rt_printk("receiver: msg = %x\n", msg);
rt_printk("receiver: r = %x\n", r);
}


int init_module(void)
{
RTIME tick_period, now;

rt_set_periodic_mode();
rt_task_init(&tasks[0], task_sender, 0, STACK_SIZE, 0, 0, 0);
rt_task_init(&tasks[1], task_receiver, 0, STACK_SIZE, 0, 0, 0);

rt_printk("sender = %x\n", &tasks[0]);
rt_printk("recevier = %x\n", &tasks[1]);
tick_period = start_rt_timer(nano2count(TIMERTICKS));
now = rt_get_time();
rt_task_make_periodic(&tasks[1], now + tick_period, tick_period);
rt_task_make_periodic(&tasks[0], now + 2*tick_period, tick_period);
return 0;
}

void cleanup_module(void)
{
stop_rt_timer();
rt_busy_sleep(10000000);
rt_task_delete(&tasks[0]);
rt_task_delete(&tasks[1]);
}

Завдання отримувач запускається на наступному тіку(відмітці) таймеру(годинника). І негайно запускає функцію rt_receive. Перший аргумент це адреса об'єкту RT_TASK відповідаючого завданню відправнику. Оскільки відправник ще не активний, task_receive блокується. На наступному тіку годинника, task_sender стає активним і посилає повідомлення “message0xab до task_receiver. Завдання отримувач виходить з заблокованого стану та друкує отримане повідомлення і адресу об'єкту RT_TASK відповідаючого завданню відправнику.

Використання поштових скриньок

Поштова скринька (mailbox) це зручний механізм, використовуючи який багато завдань можуть зв'язуватися один з одним. Погляньте на цю невеличку програму:

#include <linux/module.h>
#include <rtai.h>
#include <rtai_sched.h>

#define LPT1_BASE 0x378
#define STACK_SIZE 4096
#define TIMERTICKS 1000000000

static RT_TASK tasks[2];
static MBX my_mbx;

static void task_sender(int t)
{
int msg = 0x12cd, r;
r = rt_mbx_send(&my_mbx, &msg, sizeof(msg));
rt_printk("sender: r = %d\n", r);
}

static void task_receiver(int t)
{
int msg, r;
r = rt_mbx_receive(&my_mbx, &msg, sizeof(msg));
rt_printk("receiver: msg = %x\n", msg);
rt_printk("receiver: r = %d\n", r);
}


int init_module(void)
{
RTIME tick_period, now;

rt_set_periodic_mode();
rt_task_init(&tasks[0], task_sender, 0, STACK_SIZE, 0, 0, 0);
rt_task_init(&tasks[1], task_receiver, 0, STACK_SIZE, 0, 0, 0);

rt_mbx_init(&my_mbx, 4*sizeof(int));
tick_period = start_rt_timer(nano2count(TIMERTICKS));
now = rt_get_time();
rt_task_make_periodic(&tasks[1], now + tick_period, tick_period);
rt_task_make_periodic(&tasks[0], now + 2*tick_period, tick_period);
return 0;
}

void cleanup_module(void)
{
stop_rt_timer();
rt_busy_sleep(10000000);
rt_mbx_delete(&my_mbx);
rt_task_delete(&tasks[0]);
rt_task_delete(&tasks[1]);

}

Скринька представляє статичну змінну (static variable) типу MBX. Ми створюємо нову поштову скриньку викликаючи rt_mbx_init. Другий аргумент це розмір скриньки. Завдання відправник викликає rt_mbx_send і зберігає повідомлення 'msg' розміру 'sizeof(msg)' у поштову скриньку. Завдання отримувач отримує повідомлення викликаючи rt_mbx_receive. Отримувач блокується1 поки всі байти повідомлення не будуть отримані (чи поки не виникне якась помилка).

Висновки

Я почав вивчення Linux/RTAI через цікавість, та однак в мене виникла спокуса знати більше. Я сподіваюсь моя цікавість стане заразною і більше читачів почнуть лудити самостійно. Тільки не забудьте розповісти нам про ваші експерименти!


1Коли кажуть блокується, то мають на увазі, що завдання призупиняє своє виконання.