
- •Введение
- •1. Основные понятия в операционных системах
- •1.1. Классификация и функции операционных систем
- •1.2. Ос общего назначения и реального времени
- •1.3. Выполнение команд в вычислительной системе
- •1.4. Прерывания
- •1.5 Архитектуры операционных систем
- •1.6. Управление оперативной памятью вычислительной системы
- •1.7. Общие сведения о процессах и потоках
- •2. Операционная система windows
- •2.1. Версии операционной системы Windows
- •2.2. Архитектура операционной системы windows
- •2.3. Процессы и потоки в Windows
- •2.4. Взаимодействие процессов
- •2.5. Управление потоками в Windows
- •2.6. Файловые системы Windows
- •2.7. Установка и последовательность загрузки Windows
- •Последовательность загрузки Windows xp
- •2.8. Интерпретатор команд и пакетные файлы
- •2.9. Конфигурирование Windows
- •3. Операционная система qnx neutrino
- •3.1. Версии операционной системы qnx Neutrino
- •3.2. Архитектура операционной системы qnx Neutrino
- •3.3. Процессы в qnx6
- •Завершение процесса
- •3.4. Потоки в qnx6
- •Завершение потока
- •3.5. Управление потоками и процессами в qnx6
- •Механизмы ipc
- •Средства синхронизации в qnx
- •3.6. Файловые системы qnx
- •Типы файлов
- •3.7. Инсталляция и последовательность загрузки qnx
- •3.8. Интерпретаторы команд и пакетные файлы в qnx
- •3.9. Конфигурирование qnx
- •4. Виртуальные машины
- •4.1. Общие сведения о виртуальных машинах
- •4.2. Работа с виртуальной машиной VmWare
- •5. Защита от сбоев и несанкционированного доступа
- •5.1. Принципы построения систем безопасности
- •5.2. Безопасность операционной системы windows
- •6. Сетевые возможности операционных систем
- •6.1. Аппратаное обеспечение локальных сетей
- •6.2. Сети Windows
- •6.3. Локальная сеть на основе qnet
- •6.4. Глобальные сети
- •7. Многопроцессорные системы
- •7.1. Архитектуры многопроцессорных операционных систем
- •7.2. Принципы функционирования smp
- •7.3. Принципы функционирования кластеров
- •Список использованной литературы
- •Компилятор
Средства синхронизации в qnx
В QNX Neutrino реализовано несколько способов синхронизации потоков. Причем только два из них (mutex и condvar) реализованы непосредственно в микроядре, остальные надстройки над этими двумя [17]:
Взаимоисключающая блокировка (mutual exclusion lock- mutex, «мютекс (мутекс)»)- это механизм обеспечивает исключительный доступ потоков к разделенным данным. Только один поток в один момент времени может владеть мютексом. Если другие потоки попытаются захватить мютекс, они становятся мютекс-заблокированными.
Если в состоянии мютекс-блокировки находится поток с приоритетом выше, чем у потока-владельца блокировки, то значение эффективного приоритета этого потока автоматически повышается до уровня высокоприоритетного заблокированного потока;
Условная переменная (condition variable, или condvar ) -предназначена для блокировки потока до тех пор, пока соблюдается некоторое условие. Это условие может быть сложным. Условные переменные всегда используются с мутекс-блокировкой для определения момента снятия мутекс-блокировки;
Барьер ‑ устанавливает точку для нескольких взаимодействующих потоков, на которой они должны остановиться и дождаться «отставших» потоков. Как только все потоки из контролируемой группы достигли барьера, они одновременно разблокируются и могут продолжить исполнение;
Ждущая блокировка – упрощенная форма совместного использования условной переменной с мутексом. В отличие от прямого применения mutex+ condvar имеет некоторые ограничения;
Блокировка чтения /записи (rwlock)-простая и удобная в использовании ‘надстройка’ над условными переменными и мутексами;
Семафор – это, можно (условно) сказать, мутекс со счетчиком. Вернее, мутекс является семафором со счетчиком, равным единице.
Большинство этих механизмов работает в пределах одного процесса, но это ограничение преодолевается путем использования механизма разделяемой памяти.
Кроме перечисленных способов синхронизацию можно осуществлять с помощью FIFO-диспетчеризации, ”родных” QNX-сообщений и атомарных операций.
Рассмотрим синхронизацию потоков на барьерах и одновременно выясним эффективности однопоточное и многопоточное выполнение приложений на примере программы t1.cc [17]:
//Однопотосчное и много поточное приложение T1.cc
#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#include <unistd.h>
#include <limits.h>
#include <pthread.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
#include <errno.h>
#include <math.h>
// преобразование процессорных циклов в милисекунды:
static double cycle2milisec ( uint64_t ccl ) {
const static double s2m = 1.E+3;
const static uint64_t cps = SYSPAGE_ENTRY( qtime )->cycles_per_sec; // частота процессора:
return (double)ccl * s2m / (double)cps;
};
static int nsingl = 1;
// рабочая функция
void workproc( int how ) {
const int msingl = 30000;
for( int j = 0; j < how; j++ ) // ... якобы вычисления
for( uint64_t i = 0; i < msingl * nsingl; i++ ) i = ( i + 1 ) - 1;
};
static pthread_barrier_t bstart, bfinish;
struct interv { uint64_t s, f; };
interv *trtime;
void* threadfunc ( void* data ) {
// заблокироваться на барьере, чтоб соскочить с него одновременно
pthread_barrier_wait( &bstart );
int id = pthread_self() - 2;
trtime[ id ].s = ClockCycles();
workproc( (int)data );
trtime[ id ].f = ClockCycles();
pthread_barrier_wait( &bfinish );
return NULL;
};
int main( int argc, char *argv[] ) {
int opt, val, nthr = 1, nall = SHRT_MAX;
while ( ( opt = getopt( argc, argv, "t:n:p:a:" ) ) != -1 ) {
switch( opt ) {
case 't' :
if( sscanf( optarg, "%i", &val ) != 1 ) perror( "parse command line failed" ), exit( EXIT_FAILURE );
if( val > 0 && val <= SHRT_MAX ) nthr = val;
break;
case 'p' :
if( sscanf( optarg, "%i", &val ) != 1 ) perror( "parse command line failed" ), exit( EXIT_FAILURE );
if( val != getprio( 0 ) )
if( setprio( 0, val ) == -1 ) perror( "priority isn't a valid" ), exit( EXIT_FAILURE );
break;
case 'n' :
if( sscanf( optarg, "%i", &val ) != 1 ) perror( "parse command line failed" ), exit( EXIT_FAILURE );
if( val > 0 ) nsingl *= val;
break;
case 'a' :
if( sscanf( optarg, "%i", &val ) != 1 ) perror( "parse command line failed" ), exit( EXIT_FAILURE );
if( val > 0 ) nall = val;
break;
default :
perror( "parse command line failed" ), exit( EXIT_FAILURE );
break;
}
};
if( nthr > 1 ) cout << "Multi-thread evaluation, thread number = " << nthr;
else cout << "Single-thread evaluation";
cout << " , priority level: " << getprio( 0 ) << endl;
_clockperiod clcout;
ClockPeriod( CLOCK_REALTIME, NULL, &clcout, 0 );
cout << "rescheduling = \t" << clcout.nsec * 4. / 1000000. << endl;
// калибровка единичного выполнения
const int NCALIBR = 512;
uint64_t tmin = 0, tmax = 0;
tmin = ClockCycles();
workproc( NCALIBR );
tmax = ClockCycles();
cout << "calculating = \t" << cycle2milisec ( tmax - tmin ) / NCALIBR << endl;
// многопотоковое выполнение
if( pthread_barrier_init( &bstart, NULL, nthr ) != EOK ) perror( "barrier init" ), exit( EXIT_FAILURE );
if( pthread_barrier_init( &bfinish, NULL, nthr + 1 ) != EOK ) perror( "barrier init" ), exit( EXIT_FAILURE );
trtime = new interv [ nthr ];
int cur = 0, prev = 0;
for( int i = 0; i < nthr; i++ ) {
cur = (int)floor( (double)nall / (double)nthr * ( i + 1 ) + .5 );
prev = (int)floor( (double)nall / (double)nthr * i + .5 );
if( pthread_create( NULL, NULL, threadfunc, (void*)( cur - prev ) ) != EOK )
perror( "thread create" ), exit( EXIT_FAILURE );
};
pthread_barrier_wait( &bfinish );
for( int i = 0; i < nthr; i++ ) {
tmin = ( i == 0 ) ? trtime[ 0 ].s : __min( tmin, trtime[ i ].s );
tmax = ( i == 0 ) ? trtime[ 0 ].f : __max( tmax, trtime[ i ].f );
};
cout << "evaluation = \t" << cycle2milisec ( tmax - tmin ) / nall << endl;
pthread_barrier_destroy( &bstart );
pthread_barrier_destroy( &bfinish );
delete trtime;
exit( EXIT_SUCCESS );
};
Логика этого приложения крайне проста: есть некоторая продолжительная по времени рабочая функция (workproc), выполняющая многогратно. Число выполнений определяется ключом запуска а. А время ее единичного выполнения задается ключом n. В примере установлена диспетчеризация по умолчанию - круговая, или карусельная). Весь объем этой работы делится поровну (или почти поровну) между несколькими (ключ t) потоками. На рис.53 показано выполнение программы t1.cc в перспективе System Profiler.
Рис.53. Выполнение программы t1.cc
Сравниваем усредненное время единичного выполнения рабочей функции для разного числа выполняющих потоков (в выводе "calculating" - это время эталонного вычисления в одном главном потоке, a "evaluation" - время того же вычисления, но во многих потоках). Для того чтобы иметь еще большую гибкость, предоставляется возможность переопределять приоритет, под которым в системе все это происходит (ключ р).
Краткий итог этого теста может звучать так: при достаточно высоком уровне приоритета (выше 12-13, когда на его выполнение не влияют процессы обслуживания клавиатуры, мыши и др.) время выполнения в «классическом» последовательном коде и в многопоточном коде (где несколько тысяч потоков!) практически не различаются. Различия не более 8%, причем в обе стороны.
В отличие от создаваемых параллельных процессов, рассмотренных ранее, все потоки, создаваемые в рамках одного процесса, разделяют единое адресное пространство процесса, и поэтому все переменные процесса, находящиеся в области видимости любого потока, доступны этому потоку.