Материал: Вбудована система обробки текстової інформації

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Слід зауважити, що існують різні додаткові варіанти модуля procnto (не кажучи про версії для різних процесорів):

ñ  procnto-smp - варіант модуля procnto з підтримкою симетричної багатопроцесорності;

ñ  procnto-instr - варіант модуля procnto, обладнаний засобами трасування подій;

ñ  procnto-smp-instr.

Насправді функціональність ОС може розширюватися не тільки за допомогою процесів, але і за допомогою динамічно приєднуваних бібліотек (Dynamic Link Library, DLL). Правильніше буде сказати, що як функціональність ОС розширюється за допомогою процесів, так функціональність процесів розширюється за допомогою DLL. Більше того, адміністратор ресурсів може бути реалізований або як програма, або як DLL.

Таким чином, у загальному випадку QNX складається:

ñ  з мікроядра Neutrino;

ñ  адміністратора процесів;

ñ  адміністратора ресурсів;

ñ  прикладних програм.

         1.4 Процеси та потоки в QNX


Процес (process) - це програма, що виконується. Процес (або «задача») включає код і дані програми, а також різну додаткову інформацію - змінні системного оточення і таке інше.

Крім процесу важливим поняттям є «потік управління» (thread, або просто «потік», раніше його іноді називали «ниткою»). Потік управління - це фрагмент процесу, що містить безперервну послідовність команд, які можуть виконуватися паралельно з іншими потоками того ж і інших процесів.

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

В залежності від характеру розроблюваного додатку, потоки можуть виконуватися незалежно один від одного та без потреби зв'язуватися між собою, або вони можуть бути сильно зв'язані. Щоб забезпечити зв'язок і синхронізацію взаємодіючих потоків, Neutrino2 надає різноманітні сервіси IPC і синхронізації.

Для роботи з потоками існує бібліотека pthreads (потоки POSIX), яка не входить до складу мікроядра. Вони дуже корисні в ряді випадків, тому підтримка потоків - обов'язкова властивість POSIX-сумісних ОС.

В таблиці 1 подано POSIX-функції потоків, що входять до складу мікроядра.

Таблиця 1.

POSIX запит

Запит мікроядра

pthread_create ()

ThreadCreate ()

Створити новий потік для виконання

pthread_exit ()

ThreadDestroy ()

Знищити потік

pthread_detach ()

ThreadDetach ()

Відокремити потік

pthread_join ()

ThreadJoin ()

Приєднати до потоку, очікуючи його завершення

pthread_cancel ()

ThreadCancel ()

Скасувати потік у точці скасування

-

ThreadCtl ()

Змінити Neutrino-визначені характеристики потоку

pthread_mutex_init ()

SyncTypeCreate ()

Створити mutex

pthread_mutex_destroy ()

SyncDestroy ()

Знищити mutex

pthread_mutex_lock ()

SyncMutexLock ()

Блокувати mutex

pthread_mutex_trylock ()

SyncMutexLock ()

Умовно блокувати mutex

pthread_mutex_unlock ()

SyncMutexUnlock ()

Розблокувати mutex

pthread_cond_init ()

SyncTypeCreate ()

Створити змінну умови

pthread_cond_destroy ()

SyncDestroy ()

Знищити змінну умови

pthread_cond_wait ()

SyncCondvarWait ()

Очікувати на змінну умови

pthread_cond_signal ()

SyncCondvarSignal ()

Повідомити про змінну умови

pthread_cond_broadcast ()

SyncCondvarSignal ()

Передати за сигналом змінну умови

pthread_getschedparam ()

SchedGet ()

Одержати параметри диспетчеризації потоку

pthread_setschedparam ()

SchedSet ()

Встановити параметри диспетчеризації потоку

pthread_sigmask ()

SignalProcMask ()

Одержати або встановити маску сигналів потоку

pthread_kill ()

SignalKill ()

Послати сигнал завершення потоку.



Хоча потоки в межах процесу спільно використовують адресний простір процесу, кожен потік ще має й свої локальні дані. У деяких випадках ці дані захищені в межах ядра, у той час як інші локальні дані постійно знаходяться незахищеними в адресному просторі процесу. Нижче приведені деякі типи локальних ресурсів потоку:

ñ  tid - кожен потік ідентифікований цілочисленим значенням, що починається з 1 і є унікальним у межах процесу, де існує потік;

ñ  набір регістрів - кожен потік має свій власний лічильник програми, вказівник вершини стеку, і слово процесора;

ñ  стек - кожен потік виконується в своєму власному стеку, збереженому в межах адресного простору його процесу;

ñ  маска сигналу - кожен потік має власну маску сигналу.

Зазвичай потоки використовують:

ñ  для розпаралелювання задачі на багатопроцесорних ЕОМ;

ñ  для більш ефективного використання процесора (наприклад, коли один потік чекає користувача введення, інший може виконувати розрахунки);

ñ  для полегшення спільного використання даних (всі потоки процесу мають вільний доступ до даних процесу).

         1.5 Засоби синхронізації потоків

забезпечує POSIX-засоби синхронізації потоків, деякі з них можуть бути застосовні між потоками в різних процесах. Служби синхронізації вміщують наступне:

·   умовні змінні,

·   бар'єри,

·   sleepon-блокування,

·   блокування читання / запису,

·   семафори,

·   FIFO-планування,

·   передача / прийом/відповідь,

·   атомарні операції.

Взаємно-виключні блокування, чи м'ютекси, є найбільш простими засобами синхронізації. М'ютекс використовується, щоб гарантувати монопольний доступ до загальнодоступних між потоками даних. Тільки один потік може блокувати м'ютекс у будь-який даний час. Потоки, що намагаються блокувати вже блокований м'ютекс, блокуються. Коли потік розблокує м'ютекс, потік з вищим пріоритетом, що очікує м'ютекс, розблокується і стане новим власником м'ютексу. Таким чином, потоки будуть послідовно захоплювати м'ютекс через критичний інтервал часу в порядку пріоритетної черги.

Умовна змінна, чи condvar, використовується, щоб блокувати потік у межах критичної секції програмного коду, доки певна умова не буде задоволена. Умова може бути довільною.

Бар'єр - це механізм синхронізації, що дозволяє призупинити декілька потоків, змушуючи їх чекати у визначеній точці (біля «бар'єру»), доки таких потоків не набереться певне число. Коли зазначене число таких потоків, що чекають у бар'єра, набралося, вони усі розблокуються і можуть продовжувати далі своє виконання.блокування схожі на condvar, з декількома розходженнями. Подібно condvar, sleepon блокування може використовуватися, щоб блокувати потік, доки певна умова не буде задовільнена. На відміну від condvar, sleepon-блокування мають «захований» м'ютекс, який програмно не доступний, і тому їх використання більш просте й надійне.

Блокування читання / запису використовуються для розв'язку проблеми «читач / письменник». Коли певний потік намагається записати дані до загальної області пам'яті, то за допомогою використання блокувань читання / запису його запит може бути відхилений, доки потік, що читає ці дані не розблокує її. Аналогічні дії можна виконати і при спробі читання під час запису даних іншим потоком.

Семафори аналогічні м'ютексам з тим розходженням, що доступ до ресурсу має не один потік, а кілька. Звільнення ресурсу збільшує семафор на одиницю, а захоплення - зменшує на одиницю. Якщо потік очікує на семафорі, що є позитивним, він не будете блокований. Чекання на не позитивному семафорі блокує, поки деякий інший потік не збільшує його. Семафори були визначені, щоб синхронізувати процеси. Для синхронізації між потоками в окремому процесі, м'ютекс буде більш ефективний, ніж семафори.алгоритм може гарантувати, що ніякі два потоки того ж самого пріоритету не виконають критичну секцію водночас. Усі FIFO-потоки в системі одного пріоритету будуть виконуватися по черзі, доки вони добровільно не звільнять процесор.

Передачі повідомлень Send/Receive/Reply здійснюють неявну синхронізацію за характером блокування. Це єдина синхронізація, що може використовуватися в мережі.

У деяких випадках необхідно виконати коротку операцію з гарантією, що операція не буде вивантажуватися іншим потоком чи програмою обробки переривання. Neutrino забезпечує атомарні операції для додавання значення, віднімання значення, очищення бітів, встановлення бітів.

 

         1.6 Організація зв'язку між процесами


Мікроядро Neutrino2 пропонує наступні форми IPC:

ñ  передача повідомлень,

ñ  сигнали,

ñ  черги повідомлень POSIX,

ñ  загальнодоступна пам'ять,

ñ  канали,

ñ  FIFOs.

Потік, що виконує MsgSend(), надсилаючи повідомлення до іншого потоку (який може бути в межах іншого процесу), буде блокований, доки той інший потік не виконає MsgReceive(), обробить повідомлення, і не виконає MsgReply(). Якщо потік виконує MsgReceive() без попередньо посланого повідомлення, то він блокується, доки інший потік не виконує MsgSend(), тобто не надішле повідомлення (рис. 1).

Рис. 1. Обмін повідомленнями між клієнтом та сервером

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

Служби передачі повідомлень копіюють повідомлення безпосереднє з адресного простору одного потоку до іншого без буферизації. Neutrino не аналізує зміст повідомлення - дані в повідомленні мають значення тільки для відправника й одержувача.

Для простих повідомлень Neutrino забезпечує функції, що беруть вказівник безпосередньо на буфер без потреби в IOV (Input-Output Vector - вектор введення / виведення).

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

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

Додатково до синхронних Send/Receive/Reply служб, Neutrino також підтримує передачу повідомлень фіксованого розміру без блокування його відправника. Вони визначені як імпульси і мають чотири байти даних плюс один байт коду. Імпульси часто використовуються як механізм повідомлення в межах програм обробки переривань. Вони також дозволяють серверам відповідати клієнтам без їхнього блокування.

Передача повідомлень API (Інтерфейс прикладної програми) складається з наступних функцій (табл. 2).

Таблиця 2.

Функція

Опис

MsgSend()

Надіслати повідомлення і заблокуватися до отримання відповіді

MsgReceive()

Очікувати на повідомлення

MsgReceivePulse()

Очікувати на импульс

MsgReply()

Відповісти на повідомлення

MsgError()

Відповісти тільки зі станом помилки.

MsgRead()

Читати додаткові дані від отриманого повідомлення

MsgWrite()

Записати додаткові дані до повідомлення відповіді

MsgInfo()

Одержати інформацію про отримане повідомлення

MsgSendPulse()

Надіслати імпульс

MsgDeliverEvent()

Надіслати клієнту подію

MsgKeyData()

Повідомлення перевірки ключа


Стандарт POSIX визначає набір неблокуючих засобів передачі повідомлень, відомих як черги повідомлень. На відміну від примітивів передачі повідомлень Neutrino2, черги повідомлень постійно знаходяться поза ядром. Черги повідомлень здійснюють проміжне накопичення повідомлень, вони існують незалежно від процесів, що їх використовують. В Neutrino2 створені черги повідомлення будуть з'являтися в просторі імен файлів у каталозі /dev/mqueue.

Загальнодоступна пам'ять пропонує процесам використовувати вказівники, щоб безпосередньо читати і записувати в неї. Загальнодоступна пам'ять часто використовується разом з одним із примітивів синхронізації, щоб зробити модифікації даних атомарними. Загальнодоступна пам'ять найбільш ефективна, коли використовується для модифікування великої кількості даних. Семафори і м'ютекси - придатні примітиви синхронізації для використання їх з загальнодоступною пам'яттю. Загальнодоступна пам'ять реалізована в Neutrino у менеджері процесів (procnto).

Канал - це безіменний файл, що служить як канал введення / виведення між двома чи більше процесами, коли один процес записує в канал, а інший читає з каналу. Менеджер каналу забезпечує буферизацію даних. Канал буде вилучений, як тільки обоє його кінці закрилися. Канали звичайно використовуються, коли два процеси хочуть працювати паралельно з даними, що переміщаються від одного процесу до іншого в одному напрямку.- це ті ж самі канали, за винятком того, що вони є постійними файлами, які збережені в filesystem каталогах.

2. Практична частина

        

         2.1 Алгоритм роботи системи


1. Головний потік створює два потока «читачі» та потік «письменник» який блокує MUTEX, зчитує та зберігає текст і запускає таймер.

2. Потоки «читачі» по черзі зчитують текст і виводять відповідну його частину.

3. Головний потік та інші потоки зупиняються за допомогою системного переривання від таймера.

         2.2 Результати тестування системи


Під час виконання курсового проекту було написане на мові високого рівня програмування С. Компіляція файлів server.c, client.c, зміст котрих представлений у розділі «додатки» даного проекта, була здійснена за допомогою компілятора gcc, що є частиною дистрибутива операційної системи QNX.

Тестування системи виконується наступним чином:

Запускаємо програму, вводимо текст та натискаємо Enter, після чого потоки «читачі» починають його зчитувати і виводити. Перший потік вибере з усього тексту лише цифри, а другий - лише літери. Роботу програми ілюстровано на рисунку 2.

Рис. 2. Представлення роботи програми

         2.3 Опис основних елементів програмного коду файлу code.c


volatile int data_ready = 0 - змінна для контролю read-write доступу. Ключове слово volatile вказує, що поле може бути змінене декількома потоками, що виконуються одночасно. Поля, оголошені як volatile, не проходять оптимізацію компілятором, яка передбачає доступ за допомогою окремого потоку. Це гарантує наявність найбільш актуального значення в поле в будь-який час. Як правило, модифікатор volatile використовується для поля, звернення до якого виконується через декілька потоків без використання оператора lock для серіалізациі доступу.

arg = malloc (sizeof(str)) - елемент функції - письменника, виділяє пам’ять розміром sizeof(str).

strcpy (arg, str) - переносить значення str в область пам‘яті arg.

timer_create (CLOCK_REALTIME, NULL, &timerid) - створює новий таймер._settime (timerid, 0, &value, NULL) - встановлює час тайм-ауту._sleepon_lock() - Pthread_sleepon_lock () викликає pthread_mutex_lock () для мьютекса, який знаходиться в класі pthread_sleepon*. Ця функція викликається перед перевіркою умови, які визначають, чи потрібно викликати pthread_sleepon_wait (), pthread_sleepon_signal (), або pthread_sleepon_broadcast (). Це мьютекс не дозволяє іншим потокам змінювати дані поки з ними працює поточний потік.

Pthread_sleepon_signal () - функція розблокує найвищий пріоритетом потоки, які очікують на адресу.

Pthread_sleepon_unlock () викликає pthread_mutex_unlock () длямьютекса пов'язаного з pthread_sleepon * () класом функцій

void * reader_num (void *arg) - функція-читач, зчитує текст з області пам’яті, виявляє наявність цифер в тексті, друкує їх на екран

if ((char) str[i] >= '0' && (char) str[i] <= '9') - умова перевірки знаку на належність до цифр, використовує ASCII - код для визначення символу

sched_yield() - Процес може звільнити процесор без блокування, викликавши sched_yield. Процес буде переміщений в кінець черги процесів з однаковим статичним пріоритетом, і управління будепередано іншому процесу. Зауваження: якщо поточний процес є єдиним в черзі процесів з тим же пріоритетом, то він продовжить роботу відразу після виклику функції sched_yield. УPOSIX-системах, в яких існує функція а саме в <unistd.h>, задається визначення _POSIX_PRIORITY_SCHEDULING

while (data_ready!= 1)  {pthread_sleepon_wait (&data_ready);} - реалізація очікування сигналу від потоку-письменника для доступу до тексту в область пам’яті.

void * reader_let (void *arg) функція-читач, зчитує текст з області пам’яті, виявляє наявність букв в тексті, друкує їх на екран if (((char) str[i] >= 'a' && (char) str[i] <= 'z') || ((char) str[i] >= 'A' && (char) str[i] <= 'Z')) - перевірка на належність символу до букв, використовується ASCII - код символу

int main() - головня функція програми

char str[65535] = «» - створення типу текстового типу даних для передачі потоку - письменнику, який занесе цей текст в память

gets(str); - зчитує введені знаки в змінну str

result = pthread_create (&thread1, NULL, writter, &str); - створення потоку-письменника

Pthread_create () функція використовується, щоб створитинову потік, з атрибутами зазначеними в Attr, в рамках процесу. Якщо Attr є NULL, за замовчуванням використовуються стандартні арибути. Якщо атрибути, зазначені в Attr були змінені пізніше, атрибути потоку не змінюються. Приуспішному завершенні pthread_create () зберігає ідентифікатор створеного потоку в місці зазаначеному потоком.

Потік створюється з виконанням start_routine аргументу в якості єдиного аргументу. Якщо start_routine повертається, ефект ніби був неявний виклик pthread_exit () за допомогою значення, що повертається start_routine як статус виходу. Потік, в якому main() спочатку викликається відрізняється від цього. Коли він повертається з main (), ефект аналогічний виклику exit() за допомогою значення, що повертається main() в якості виходу.

Сигнал стану нового потіку ініціалізується таким чином:

Сигнал маска успадковується від створеного потоку.

Набір сигналів в очікуванні для нового потоку порожній.

Якщо pthread_create () зазнає невдачі, новий потік не створюється, змінна потоку залишається невизначеною

result = pthread_create (&thread2, NULL, reader_num, &str); - створення потоку для читача 1= pthread_create (&thread3, NULL, reader_let, &str); - створення потоку для читача 2 = pthread_join (thread1, NULL); - Функція pthread_join блокує роботу потоку що викликала його виконання до завершення thread'а з ідентифікатором thread.Після розблокування в покажчик, розташований за адресою status_addr, заноситься адреса, який повернув зупинений thread або при виході з асоційованою з ним функції, або привиконанні функції pthread_exit (). Якщо нас не цікавить, що повернула нам нитку виконання, як цього параметра можна використовувати значення NULL. Функція повертає значення 0 при успішному завершенні. Увипадку помилки повертається позитивне значення (а ненегативне, як в більшості системних викликів і функцій!), яке визначає код помилки, описаний у файлі errno.h. Значеннясистемної змінної errno при цьому не встановлюється.

result = pthread_join (thread2, NULL); - приєднання до потоку - читача 2= pthread_join (thread3, NULL); - приєднання до потоку - читача 2(str); - вивільнення памяті, виділеної під текст.

        

        
Висновки


В результаті виконання курсової роботи в операційній системі QNX 6.5 була розроблена вбудована система.

Була розроблена програма (головній потік) яка створює та записує на виконання три потоки, де перший потік («письменник»), запам'ятовує цей текст в буферній області пам'яті, а другий та третій потоки («читачі») зчитують з цієї області пам'яті, причому другий потік зчитує тільки літери, а третій потік - тільки цифри.

        

        
Література

     

1. Роб Кертен «Введение в QNX/Neutrino

2. Руководство по программированию приложений реального времени в QNX Realtime Platform», - ООО «Издательство Петрополис», 2001. - 480 с.

3. Сергій Зиль «Операционная система реального времени QNX от теории к практике» друге выдання

        

        

        
Додаток


#include <sys/neutrino.h>

#include <sys/netmgr.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <fcntl.h>

#include <unistd.h>

#include <errno.h>

#include <pthread.h>

#include <string.h>

int data_ready = 0; //var for read-write lock

void * writter (void *arg) {* str;= arg;= malloc (sizeof(str)); //new memory for variable(arg, str); //copy string to memory_t timerid;itimerspec value;.it_value.tv_sec = 10;_create (CLOCK_REALTIME, NULL, &timerid); //new timer_settime (timerid, 0, &value, NULL); //set timer(1) {(data_ready == 0) {_sleepon_lock(); //lock thread_ready = 1;_sleepon_signal (&data_ready); //send signal_sleepon_unlock();} //unlock thread

}

}

void * reader_num (void *arg) {i;* str;

//while(1) {_sleepon_lock(); //thread lock(data_ready!= 1)

{pthread_sleepon_wait (&data_ready); //wait for signal

}= arg;(«Reader for number:»);(i = 0; i < strlen(str); i++) //for all string((char) str[i] >= '0' && (char) str[i] <= '9') //check numbers(«%c», (char) str[i]);(«\n»);_ready = 0; //switch flag_sleepon_unlock(); //unlock thread_yield(); //yield for second thread

}

// }

void * reader_let (void *arg) {i;* str;

//while(1) {_sleepon_lock();(data_ready!= 1)

{pthread_sleepon_wait (&data_ready);

}= arg;(«Reader for leter:»);(i = 0; i < strlen(str); i++)(((char) str[i] >= 'a' && (char) str[i] <= 'z') || ((char) str[i] >= 'A' && (char) str[i] <= 'Z'))(«%c», (char) str[i]);(«\n»);_ready = 0;_sleepon_unlock();_yield();

}

// }main()

{(«Enter string:»);str[65535] = «»;(str);

id, result;_t thread1, thread2, thread3;= pthread_create (&thread1, NULL, writter, &str); //new thread= pthread_create (&thread2, NULL, reader_num, &str);= pthread_create (&thread3, NULL, reader_let, &str);= pthread_join (thread1, NULL); //join thread until thread death= pthread_join (thread2, NULL);= pthread_join (thread3, NULL);

free(str);

}