Механизмы синхронизации
12 Потокам так же необходимо разделять ресурсы, например переменные в памяти. Для защиты от искажения, вызванного одновременным редактированием одних и тех же данных разными потоками, используются переменные, которые называются объектами синхронизации. Это мютексы, семафоры, события. В задачах реального времени к этим объектам предъявляется специфические требования, так как на них возможны задержки выполнения потоков, поскольку их назначение — блокирование доступа к некоторому разделяемому ресурсу. Проблема, возникающая при блокировании ресурса, - инверсия приоритетов. Это когда поток высокоприоритетный и низкоприоритетный разделяют общий ресурс, и есть еще один поток, имеющий средний приоритет среди них. Когда высокоприоритетный поток в состоянии готовности, а поток с низким приоритетом активен, и он заблокировал ресурс, то поток с более высоким приоритетом вытеснит с более низким, а ресурс останется заблокирован. И когда высокоприритетному потоку понадобится ресурс, то он сам перейдет в заблокированное состояние. Если в состоянии готовности находится только низкоприоритетный поток, то ничего страшного не произойдет, низкоприориттеный поток освободит заблокированный ресурс и будет вытеснен потоком с более высоким приоритетом. А если в момент блокирования потока с высоким приоритетом в состоянии готовности находится поток со средним приоритетом, то активным станет он, а с низким приоритетом опять будет вытеснен. И получит он управления после выполнения потока со средним приоритетом. И критическое время обслуживания потока с высоким приоритетом будет пропущено. Если это поток жесткого реального времени, то это недопустимо. Защита от нее заключается в наследовании приоритетов . Суть его в наследование низкоприоритетным потоком, захватившим ресурс, приоритета от высокоприоритетного потока, которому ресурс нужен. Есть еще один метод — Протокол Предельного Приоритета. Он заключается в добавлении к стандартным свойствам объектов синхронизации параметра, определяемого максимальным приоритетом потока, которые к объекту обращается. Если параметр установлен, то приоритет потока, который обращается к данном объекту синхронизации, будет увеличен до указанного уровня, и не сможет быть вытеснен никаким потоком, который сможет нуждаться в заблокированном ресурсе. После разблокирования ресурса, приоритет потока понижается до начального уровня, и так получается что-то вроде наследования приоритетов. Микроядро Микроядро — визитная карточка QNX. Защита памяти процессов и связь между ними на основе синхронного обмена сообщениями. Базовые функции ОС вынесены в отдельный модуль, включающий микроядро и менеджер процессов. Микроядро — коммутирующий элемент по сути, шина, обеспечивающая интеграцию других изолированных программных компонентов в единую систему. Практическая часть Установка QNX QNX стоит сразу устанавливать на виртуальную машину. Версия для некоммерческого использования доступна для скачивания на веб-сайте разработчика www.qnx.com. При установке на VirtualBox у меня возникли проблемы, QNX не устанавливался скорее всего по причине отсутствия поддержки процессором виртуализации. Поэтому следующая попытка была поставить QNX на VMware Workstation. Там почти все заработало, но были проблемы с сетью. Установить QNX очень просто, установка заключается практически лишь только в выборе раздела для установки. Устанавливать QNX на жесткий диск в качестве полноценной ОС для работы не имеет смысла. Для разработки программного обеспечения, которое нуждается в реальном времени ( будет работать под управлением QNX) можно использовать модификацию IDE Eclipse — QNX Momentics, или же работать с QNX посредством, используя виртуализацию( Parallels, VMware, VirtualBox XEN). Следует с осторожностью подходить к выбору аппаратного обеспечения для работы проектов под управлением QNX, так для многого оборудования отсутствуют драйвера. Тест на инверсию приоритетов Код программы, написанном на С/С++: #include <stdlib.h> #include <stdio.h> #include <iostream.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h>
const int THRNUM = 3, NCKL = 10, LINOUT = 5, BUFLEN = THRNUM * NCKL + 1;
inline void workfun( int n ) { for( long i = 0; i < n; i++ ) double f = sqrt( 1. + sqrt( (double) rand() ) ); };
char buf[ BUFLEN ]; volatile unsigned ind = 0, nfin = 0; long nrep = 1000; bool bmutex = false, bsemaphore = false; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static sem_t semaphore, semfinal;
void *thrfunc( void *p ) { int t = (int)p; if( t != 1 ) { if( bmutex ) pthread_mutex_lock( &mutex ); if( bsemaphore ) sem_wait( &semaphore ); }; struct timespec tv; tv.tv_sec = 0; tv.tv_nsec =(long)( 1e+6 ); sem_trywait( &semfinal ); nanosleep( &tv, &tv ); for( int i = 0; i < NCKL; i++ ) { buf[ ind++ ] = t + '0'; workfun( nrep ); }; if( t != 1 ) { if( bmutex ) pthread_mutex_unlock( &mutex ); if( bsemaphore ) sem_post( &semaphore ); }; if( ++nfin == THRNUM ) sem_post( &semfinal ); };
int main( int argc, char *argv[] ) { cout << "inverse test, QNX API, vers.1.05L" << endl; int c = 0; while( ( c = getopt( argc, argv, "hmst:" ) ) != -1 ) switch( c ) { case 'h': cout << "\t" << argv[ 0 ] << " [ h | { m | s } | t = value ]" << endl; exit( EXIT_SUCCESS ); case 'm': bmutex = true; break; case 's': bsemaphore = true; break; case 't': nrep = 1; for( int i = 0; i < atoi( optarg ); i++ ) nrep *= 10; break; default: exit( EXIT_FAILURE ); }; sem_init( &semaphore, 0, 1 ); cout << "repeating number = " << nrep; // << ", debug level = " << ndebug; if( bmutex ) cout << ", lock on mutex"; if( bsemaphore ) cout << ", lock on semaphore"; cout << endl; struct sched_param param; int polisy; pthread_getschedparam( pthread_self(), &polisy, ¶m ); pthread_attr_t attr; for( int i = 0; i < THRNUM; i++ ) { pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); pthread_attr_setinheritsched( &attr, PTHREAD_EXPLICIT_SCHED ); pthread_attr_setschedpolicy( &attr, SCHED_RR ); attr.param.sched_priority = param.sched_priority + i + 1; pthread_create( NULL, &attr, &thrfunc, (void*)i ); }; sem_init( &semfinal, 0, 0 ); sem_wait( &semfinal ); buf[ ind ] = '\0'; cout << buf << endl; exit( EXIT_SUCCESS );
}; Скомпилируем и запустим его на QNX. Вот вывод программы: repeating number = 100000, debug level = 1 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11]
repeating number = 100000, debug level = 1, lock on mutex 0[11:13] 0[11:13] 0[11:13] 0[11:13] 0[11:13] 0[11:13] 0[11:13] 0[11:13] 0[11:13] 0[11:13] 1[12:12] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12]
repeating number = 100000, debug level = 1, lock on semaphore 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 1[12:12] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 0[11:11] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13] 2[13:13]
Тестовые потоки запускаются с приоритетами 11, 12, 13, а запускающий поток имеет приоритет 10. В первом случае нет взаимного блокирования потоков. Все потоки выполняются в такой последовательности 2-1-0 в соответствии со своими статическими приоритетами, указанными перед выполнением. А динамиеский приоритет равен статическому. Во втором случае — взаимное блокирование на мютексе. Поток 0 начинает выполняться, «почувствовав» блокирование со стороны 2, под приоритетом ожидающего потока 2 ([11:13]), вытесняет поток 1. Затем, освободив мютекс, передает управлению потоку 2, после его завершения потоку 1. это наследование приоритетов, препятствующее возникновению инверсии приоритетов. В третьем случае - блокирование на семафоре. Здесь потоки выполняются в таком порядке: 1-0-3 — это инверсия приоритетов. А вот краткий вывод того же самого: repeating number = 100000, debug level = 1 222222222211111111110000000000
repeating number = 100000, debug level = 1, lock on mutex 000000000012222222222111111111
repeating number = 100000, debug level = 1, lock on semaphore 111111111100000000002222222222 Теперь если запустить немного измененный код этой программы для Windows, то получим следующее:
repeating number = 500000, no priority 210021021021012211020021220101
repeating number = 500000 011111111110002222222222000000
repeating number = 500000, lock on mutex 000000000022222222221111111111 Как ни странно в Windows, не должно быть принято никаких мр по защите от инверсии приоритетов, так как она не ориентирована на realtime поведение, но в третьем случае мы видим отчетливое наследование приоритетов при блокирование на мютексе. А в первом случае наблюдаются перебои ( там потоки равного приоритета) в отличие от QNX.
Особенности написания кода под QNX Драйверы В случае ОС реального времени для встраиваемого оборудования необходимость написания драйверов — обычное дело. Поэтому разработчики QNX (фирма QSSL) создали беспрецедентную по своей простоте — технологию создания драйверов. Это если сравнивать с техникой драйверов в системах: OS-360 — IBM/360; MS-DOS; Windows 95/98; Linux x86. Почему такую технологию удалось создать в QNX? Ответ — все дело в микроядре. ОС QNX построена на редко реализуемой в ОС концепции — коммутации сообщений. Ядро ОС при этом выступает в качестве компактного коммутатора сообщений между взаимодействующими программными компонентами. Все запросы, обслуживаемые драйвером, предусматриваемые POSIX ( open(), read()...), реально же посылаются драйверу (менеджеру ресурса) в виде сообщений уровня микроядра. Код сообщения определяет тип операции, а последующее тело сообщения — конкретные параметры запроса, зависящие от типа операции. ( в этой технике могут обрабатываться и сообщения произвольных, не специфицированных системой типов, несущих в себе любой пользовательский каприз). Вот порядок взаимодействия: В этой ОС все запросы API пакуются в тело сообщений, а микроядро системы обеспечивает их синхронизацию и доставку от отправителя к получателю. Для любых взаимодействий в ОС имеется единый механизм передачи, приема и обработки запросов, однако в других семействах ОС такого нет. Из этого понятно, что: · любой драйвер (менеджер ресурса) в этой логической модели рассматривается, как сервер некоторой услуги (ресурса), предоставляющий сервис клиентам — пользовательским приложениям; · драйвер теперь выглядит как и заурядное приложение в системе, а значит может загружаться и выгружаться динамически; · ОС становится защищенной от ошибок функционирования драйверов, как и от задачи пользовательского уровня — они работают в разных кольцах аппаратной защиты памяти; · возможность динамически выгружать и загружать драйвера — путь к построению систем высокой живучести; · драйвер может быть написан на С/С++; · обеспечивается полная переносимость исходного кода; · единообразно и автоматически обеспечиваются механизмы синхронизации в ОС на низшем уровне синхронизации сообщений микроядром; · огромный плюс: драйвер-сервер совершенно одинаково обслуживает запросы клиентов как со своего компьютера, так и с любого другого по сети QNET (сетевая система QNX)
12
Популярное: Как вы ведете себя при стрессе?: Вы можете самостоятельно управлять стрессом! Каждый из нас имеет право и возможность уменьшить его воздействие на нас... Модели организации как закрытой, открытой, частично открытой системы: Закрытая система имеет жесткие фиксированные границы, ее действия относительно независимы... Как распознать напряжение: Говоря о мышечном напряжении, мы в первую очередь имеем в виду мускулы, прикрепленные к костям ... ©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (236)
|
Почему 1285321 студент выбрали МегаОбучалку... Система поиска информации Мобильная версия сайта Удобная навигация Нет шокирующей рекламы |