
- •Введение
- •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. Принципы функционирования кластеров
- •Список использованной литературы
- •Компилятор
Механизмы ipc
Многозадачная ОС реального времени должна предоставить механизмы, позволяющие нескольким процессам, выполняющимся одновременно в изолированных адресных пространствах, общаться друг с другом. Связь между процессами (InterProcess Communication, сокращенно IPC) является средством к разработке приложений как совокупности процессов, в которых каждый процесс выполняет отведенную ему часть общей задачи.
Процессы операционной системы QNX Neutrino, в каких бы «родственных» отношениях они ни находились, выполняются каждый в своем изолированном адресном пространстве и не могут обмениваться информацией без использования IPC [15]. В QNX Neutrino реализован ряд таких механизмов, как стандартных, так и уникальных. К стандартным механизмам IPC относятся:
именованные и неименованные программные каналы;
разделяемая память;
очереди сообщений POSIX, реализованные с помощью администратора ресурсов mqueue, который обеспечивает функции: открытия и создания очереди, отправки и приема сообщения.
В микроядре Neutrino реализована поддержка нескольких специальных механизмов IPC:
Синхронные сообщения QNX. Они наряду с архитектурой микроядра являются фундаментальным принципом QNX. Это самый быстрый способ обмена данными произвольного размера в QNX;
Pulses (импульсами) - это сообщения фиксированной длинны, не блокирующие отправителя и имеющие размер 40 бит (8-битный код импульса и 32 бита данных);
Сигналы POSIX (простые и реального времени);
Асинхронные сообщения, как и механизм импульсов, использует буфер для хранения сообщений. Функции асинхронных сообщений имеют такие же имена, как и функции обычных сообщений, но с префиксом asyncmsg.
Программные каналы достаточно широко используются для обмена данными между процессами. В нашем распоряжении два типа программных каналов — неименованные и именованные. Каждый из этих типов обладает особенностями, которые следует принимать во внимание при организации межпроцессного взаимодействия.
Неименованный программный канал — это механизм, который командный интерпретатор использует для создания конвейеров. Этот механизм реализован в администраторе ресурсов pipe и обеспечивает передачу данных через промежуточный буфер в оперативной памяти (в адресном пространстве администратора pipe). Неименованный канал создается функцией pipe(), которой в качестве аргумента передается массив из двух целых чисел. В этот массив записывается два файловых дескриптора, один из которых в последующем используется для записи информации, другой — для чтения. К достоинствам неименованных программных каналов можно отнести то, что они являются стандартным POSIX-механизмом IPC, а также то, что это — достаточно быстрый механизм. К недостаткам — то, что обмен данными возможен только между процессами-"родственниками", т. к. процесс может получить файловые дескрипторы для работы с неименованным каналом, только наследуя их от родительского процесса.
Именованные каналы — это POSIX-механизм, поддержка которого реализована в файловой системе Neutrino. В основе механизма лежит особый тип файла — FIFO, выполняющего функцию буфера для данных. Файл типа FIFO можно создать двумя способами: из командной строки — утилитой mkfifo; из программы — функцией mkfifo(). Поскольку FIFO — это файл, имеющий имя, то, во-первых, работа с ним выполняется практически так же, как с обычным файлом (open(), read(),write(), close() и т. п.), во-вторых, именованный канал медленнее неименованного, но данные, записанные в именованный канал, сохраняются в случае сбоя (например, отключения питания).
Разделяемая память — чрезвычайно важный и широко используемый POSIX-механизм обмена данными большого объема между процессами. Для его использования необходимо на стороне каждого из взаимодействующих процессов выполнить следующие действия:
1. С помощью функции shm_open() создается или открывается существующий регион памяти, который будет разделяться.
2. С помощью функции shm_ctl() задаются нужные атрибуты разделяемого региона.
3. С помощью функции mmap() на разделяемый регион памяти отображается фрагмент адресного пространства процесса, после чего с этим фрагментом выполняются необходимые операции (например, чтения или записи данных).
4. Для отмены отображения адресного пространства процесса на разделяемый регион используется функция unmap(), для закрытия разделяемого региона — функция shm_close(), для уничтожения — функция shm_unlink().
Рассмотрим пример из двух программ — shm_creator и shm_user.
Пример работает так:
1. Запускается программа shm_creator, которая создает регион разделяемой памяти, задает его параметры и отображает на него некий буфер, содержащий текстовую строку.
2. Запускается программа shm_user, которая отображает регион разделяемой памяти, созданный программой shm_creator, на свой буфер и печатает содержимое этого буфера.
Приведем исходные тексты обеих программ [15]. Текст файла shm_creator.c выглядит так:
#include<stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <inttypes.h>
int main()
{
int fd, status;
void* buffer;
fd = shm_open("/swd_es", O_RDWR | O_CREAT, 0777);
if( fd == -1 ) { perror("shm_creator"); return EXIT_FAILURE; }
status = ftruncate(fd, 100);
if (status!=0) { perror("shm_creator"); return EXIT_FAILURE; }
buffer=mmap(0,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if (buffer == MAP_FAILED) { perror("shm_creator"); return EXIT_FAILURE; }
sprintf(buffer, "It's a nice day today, isn't it?");
printf("shm_creator: %s\n", buffer);
return EXIT_SUCCESS;
}
Итак, сначала вызываем функцию shm_open():
fd = shm_open("/swd_es", O_RDWR|O_CREAT, 0777);
Первый аргумент "/swd_es" — имя региона разделяемой памяти (или, как еще говорят, разделяемого объекта памяти);
Когда имя разделяемого объекта начинается с символа /, объект будет помещен в служебный "каталог" /dev/shmem. То есть реальное имя создаваемого нами региона — /dev/shmem/swd_es.
Второй аргумент представляет собой битовую маску из нескольких флагов, к этим флагам относятся:
O_RDONLY — открыть объект только для чтения;
O_RDWR — открыть объект для чтения и записи;
O_CREAT — создать разделяемый объект с режимом доступа, заданным третьим аргументом функции shm_open(). Если объект уже существует, то флаг O_CREAT игнорируется, за исключением случаев, когда указан еще и флаг O_EXCL;
O_EXCL — этот флаг используют совместно с флагом O_CREAT. В результате, если разделяемый объект памяти уже существует, то функция shm_open() завершится с ошибкой;
O_TRUNC — этот флаг работает, когда объект уже существует и успешно открыт для чтения/записи. При этом размер объекта становится равным нулю (режим доступа и идентификатор владельца сохраняются).
Третий аргумент задает атрибуты доступа к разделяемому объекту. Функция вернет файловый дескриптор fd, который в последующем и будет использоваться для доступа к данному разделяемому объекту. Теперь нужно сделать, чтобы разделяемый объект имел нужный размер и параметры. Для этого используем функцию shm_ctl(): ftruncate(fd, 100);
В качестве первого аргумента используется тот самый идентификатор объекта разделяемой памяти fd, который вернула функция shm_open();
Иногда вместо функции ftrancate() используют функцию shm_ctl(). Теперь созданный объект, имеющий нужный размер, необходимо отобразить на виртуальное адресное пространство нашего процесса:
buffer = mmap(0, 100, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
Первый и последний аргументы в нашем случае не нужны — они требуются при работе с физической памятью. Второй аргумент (100) указывает, какой величины фрагмент разделяемого объекта стоит отобразить на адресное пространство процесса (мы отобразили весь объект). Третий аргумент представляет собой битовую маску, которая может содержать несколько флагов:
PROT_EXEC — разделяемый объект доступен для исполнения;
PROT_NOCACHE — не кэшировать разделяемый объект;
PROT_NONE — разделяемый объект недоступен;
PROT_READ — разделяемый объект доступен для чтения;
PROT_WRITE — разделяемый объект доступен для записи.
Четвертый аргумент определяет режим отображения региона. В нашем случае лучше задать MAP_SHARED (остальные режимы используются для работы с физической памятью). Пятый аргумент — идентификатор разделяемого объекта.
Функция mmap() возвращает указатель на область виртуальной памяти процесса, на который отображен разделяемый объект (buffer). С полученым указателем мы можем поступать, как нам вздумается. Все, что мы запишем по этому адресу, будет отображено на разделяемый объект (конечно же, столько байт, сколько мы задали функции mmap()).
Запишем в буфер текстовую строку:
sprintf(buffer, "It's a nice day today, isn't it?");
Теперь посмотрим на исходный текст программы shm_user.c:
#include<stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
int main()
{
int fd;
char *buffer;
fd = shm_open("/swd_es", O_RDONLY, 0777);
if( fd == -1 ) {
perror("shm_user");
return EXIT_FAILURE;
}
buffer = mmap(0, 100, PROT_READ, MAP_SHARED, fd, 0 );
if (buffer == MAP_FAILED) { perror("shm_user"); return EXIT_FAILURE;}
printf("shm_user: %s\n", buffer);
unmap(buffer, sizeof (buffer));
return EXIT_SUCCESS;
}
Как видно из текста программы, для получения доступа к разделяемому объекту снова используется функция shm_open(). Для того чтобы отобразить разделяемый регион на адресное пространство процесса, используется функция mmap(). В результате получаем указатель buffer на область виртуальной памяти процесса, который можно использовать.
Распечатаем содержимое разделяемого объекта: printf("shm_user: %s\n", buffer).
По аналогии можно передавать между процессами любые структуры данных. Но за правильность интерпретации данных, содержащихся в разделяемой памяти, и определения момента подготовки данных к считыванию отвечает разработчик. Поэтому воизбежании нарушений целостности данных, нужно использовать механизмы синхронизации. Основным POSIX-механизмом синхронизации процессов являются семафоры.
Сигналы в QNX инициируются некоторыми событиями в системе и посылаются процессу для его уведомления о том, что произошло нечто неординарное, требующее определенной реакции [17]. Порождающее сигнал событие может быть действием пользователя или может быть вызвано другим процессом или ядром операционной системы. Действия, вызываемые для обработки сигнала, являются принципиально асинхронными.
Сигналы могут быть использованы как простейшее, но мощное средство межпроцессного взаимодействия. Все относящиеся к сигналам определения находятся в заголовочном файле signal.h. Поток может отреагировать на полученный сигнал следующими способами: Стандартное действие - выполнение действия, предписанного для обработки этого сигнала по умолчанию. Игнорирование сигнала - сигнал не оказывает никакого воздействия на ход выполнения потока получателя. Вызов обработчика - по поступлению сигнала вызывается функция реакции, определенная пользователем. Если для сигнала устанавливается функция-обработчик, то говорят, что сигнал перехватывается (относительно стандартного действия). В QNX определены 64 сигнала в трех диапазонах: 1...40 - 40 POSIX-сигналов общего назначения; 41...56 - 16 POSIX-сигналов реального времени, введенных в стандарт позже; 57...64 - 8 сигналов, используемых в QNX Neutrino для специальных целей. Специальные сигналы не могут быть проигнорированы или перехвачены.
Сигналы являются не блокирующим отправителя способом IPC. Сигналы имеют структуру, подобную импульсу (1 байт кода + 4 байта данных, и тоже могут ставиться в очередь. Для отправки процессу сигнала администратор может использовать две утилиты- стандартную UNIX- утилиту kill и более гибкую QNX – утилиту slay. По умолчанию обе этих утилиты посылают сигнал SIGINTR, при получении которого процесс обычно уничтожается. Процесс может определить три варианта поведения при получении сигнала:
Игнорировать сигнал – для этого следует задать соответствующее значение такому атрибуту потока, как сигнальная маска (говорят не «игнорировать», а «маскировать» сигнал). Не могут быть проигнорированы три сигнала: SIGSTOP, SIGCONT, SIGTERM;
Использовать обработчик по умолчанию - т.е. ничего не предпринимать. Обычно обработчиком по умолчанию для сигналов является уничтожение процесса.
Зарегистрировать собственный обработчик - т.е. собственную функцию, которая будет вызвана при получении сигнала.
Поток может вызвать функцию ожидания того или иного сигнала (сигналов). При этом поток станет SIGNAL-блокированным.
Спецификация POSIX определяет порядок обработки сигналов только для процессов. Поэтому при работе с многопоточными процессами в QNX необходимо учитывать следующие правила.
Действие сигнала распространяется на весь процесс.
Сигнальная маска распространяется только на поток.
Неигнорируемый сигнал, предназначенный для какого-либо потока, доставляет только этому потоку.
Неигнорируемый сигнал, предназначенный для процесса, передается первому не SIGNAL-блокированному потоку.