Архитектура микроядра системы QNX — КиберПедия 

Историки об Елизавете Петровне: Елизавета попала между двумя встречными культурными течениями, воспитывалась среди новых европейских веяний и преданий...

История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...

Архитектура микроядра системы QNX

2021-05-27 40
Архитектура микроядра системы QNX 0.00 из 5.00 0 оценок
Заказать работу

Им. Д. Ф. УСТИНОВА

Кафедра _И3_  

 


Комплекс лабораторных робот по основам применения операционной системы реального времени QNX

 

Разработали: Гаврилов В.В

                    Кононов О.А.

                   Маллер А.А

 

САНКТ-ПЕТЕРБУРГ

2003 г.

 

                                  РЕФЕРАТ

 Комплекс лабораторных работ содержит 33 с., 8 рис., 3 источника, ОСРВ, МИКРОЯДРО, ОБМЕН СООБЩЕНИЯМИ, СЕРВЕР, КЛИЕНТ, ПК, ПРОЦЕСС, ПОТОК, СИНХРОНИЗАЦИЯ.

Объектом рассмотрения является программное обеспечение, которое используется в системах жесткого реального времени.

Цель работы – ознакомление студентов с основными механизмами взаимодействия процессов на примере средств, предоставляемых операционной системой QNX.

 

 

         СОДЕРЖАНИЕ

 

ОПРЕДЕЛЕНИЯ, ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ.. 4

ВВЕДЕНИЕ.. 5

1 Стуктурная организациа ОСРВ QNX.. 6

1.1 Архитектура микроядра системы QNX.. 6

1.1.1 Настоящее ядро. 6

1.1.2 Системные процессы.. 7

1.2 Микроядро. 7

1.3 Системные и пользовательские процессы.. 8

1.4 Драйверы устройств. 9

1.5 Связь между процессами (IPC) 9

1.5.1 Передача сообщений. 9

1.6 QNX как сеть. 10

2 КОМПЛЕКС ЛАБОРАТОРНЫХ РАБОТ. 11

2.1 Лабораторная работа №1 «Простейший пример». 11

2.1.1 Теория. 11

2.1.2 Текст программы.. 11

2.1.3 Последовательность действий. 11

2.1.4 Результаты.. 11

2.2 Лабораторная работа №2 «Процессы и потоки». 12

2.2.1 Теория. 12

2.2.2 Текст программы.. 16

2.2.3 Последовательность действий. 17

2.2.4 Результаты.. 17

2.3 Лабораторная работа №3 «Обмен сообщениями». 17

2.3.1 Теория. 17

2.3.2 Текст программы.. 22

2.3.3 Последовательность действий. 23

2.3.4 Результаты.. 23

2.4 Лабораторная работа №4 «Тайм - ауты». 24

2.4.1 Теория. 24

2.4.2 Текст программы.. 26

2.4.3 Последовательность действий. 27

2.4.4 Результаты.. 27

2.5 Лабораторная работа №5 «Барьеры». 28

2.5.1 Теория. 28

2.5.2 Текст программы.. 29

2.5.3 Последовательность действий. 30

2.5.4 Результаты.. 30

2.6 Лабораторная работа №6 «Условные переменные». 30

2.6.1 Теория. 30

2.6.2 Текст программы.. 31

2.6.3 Последовательность действий. 32

2.6.4 Результаты.. 32

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ.. 33

 

ОПРЕДЕЛЕНИЯ, ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ

ПК  персональный компьютер

ОСРВ Операционная система реального времени

IPC Связь между процессами (Interprocess communication, сокращенно IPC)

ВВЕДЕНИЕ

Главная обязанность операционной системы состоит в управлении ресурсами компьютера. Все действия в системе – диспетчеризация прикладных программ, запись файлов на диск, пересылка данных по сети и т.п. - должны выполняться совместно настолько слитно и прозрачно, насколько это возможно.

Некоторые области применения предъявляют более жесткие требования к управлению ресурсами и диспетчеризации программ, чем другие. Приложения реального времени, например, полагаются на способность операционной системы обрабатывать многочисленные события в пределах ограниченного интервала времени. Чем быстрее реагирует операционная система, тем большее пространство для маневра имеет приложение реального времени в пределах жестких временных рамок.

Операционная система QNX идеальна для приложений реального времени. Она обеспечивает все неотъемлемые составляющие системы реального времени: многозадачность, диспетчеризацию программ на основе приоритетов и быстрое переключение контекста [1].

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

QNX достигает своего уникального уровня производительности, модульности и простоты благодаря двум фундаментальным принципам:

 

· архитектура на основе микроядра;

· связь между процессами на основе сообщений.

 

Рассматриваемый комплекс лабораторных работ по применению ОСРВ QNX ориентирован на использование в курсе «Программное обеспечение систем реального времени»

1 Стуктурная организациа ОСРВ QNX

Микроядро

 

Микроядро QNX отвечает за выполнение следующих функций:

 

· связь между процессами - Микроядро управляет маршрутизацией сообщений; оно также поддерживает и другие формы IPC сигналы, флаги;

· сетевой интерфейс низкого уровня - Микроядро осуществляет доставку всех сообщений, предназначенных для процессов на других узлах сети;

· диспетчеризация процессов - входящий в состав Ядра планировщик решает, какому из запущенных процессов должно быть передано управление;

· первичная обработка прерываний - все аппаратные прерывания и исключения сначала проходят через Микроядро, а затем передаются соответствующему драйверу или системному менеджеру.

 

 

 

Рисунок 2  Внутри микроядра QNX.

 

Драйверы устройств

 

Драйверы устройств - это процессы, которые являются посредниками между операционной системой и устройствами и избавляют операционную систему от необходимости иметь дело с особенностями конкретных устройств.

Так как драйверы запускаются как обычные процессы, добавление нового драйвера в QNX не влияет на другие части операционной системы. Таким образом, добавление нового драйвера в QNX не требует ничего, кроме непосредственно запуска этого драйвера.

После запуска и завершения процедуры инициализации, драйвер может выбрать один из двух вариантов поведения:

 

· стать расширением определенного системного процесса;

· продолжать выполнение как независимый процесс.

Связь между процессами (IPC)

 

В типичной для многозадачной системы реального времени ситуации, когда несколько процессов выполняются одновременно, операционная система должна предоставить механизмы, позволяющие им общаться друг с другом.

Связь между процессами (Interprocess communication, сокращенно IPC) является ключом к разработке приложений как совокупности процессов, в которых каждый процесс выполняет отведенную ему часть общей задачи.

QNX предоставляет простой, но мощный набор возможностей IPC, которые существенно облегчают разработку приложений, состоящих из взаимодействующих процессов.

1.5.1 Передача сообщений

QNX была первой коммерческой операционной системой своего класса, которая использовала передачу сообщений в качестве основного способа IPC. Именно последовательное воплощение метода передачи сообщения в масштабах всей операционной системы обусловливает мощность, простоту и элегантность QNX.

Сообщения в QNX - это последовательность байт, передаваемых от одного процесса другому. Операционная система не пытается анализировать содержание сообщения - передаваемые данные имеют смысл только для отправителя и получателя, и ни для кого более.

Передача сообщения позволяет не только обмениваться данными, но и является способом синхронизации выполнения нескольких процессов. Когда они посылают, получают или отвечают на сообщения, процессы претерпевают различные "изменения состояния", которые влияют на то, когда и как долго они могут выполняться. Зная состояния и приоритеты процессов, ядро организует их диспетчеризацию таким образом, чтобы максимально эффективно использовать ресурсы центрального процессора (ЦП).

Приложение реального времени и другие ответственные приложения по праву нуждаются в надежном механизме передачи сообщений, т.к. входящие в состав этих приложений процессы тесно взаимосвязаны. Реализованный в QNX механизм передачи сообщений способствует упорядочению и повышению надежности программ.

QNX как сеть

 

В простейшем случае локальная сеть обеспечивает разделяемый доступ к файлам и периферийным устройствам для нескольких соединенных между собой компьютеров. QNX идет гораздо дальше этого простейшего представления и объединяет всю сеть в единый, однородный набор ресурсов.

Любой процесс на любом компьютере в составе сети может непосредственно использовать любой ресурс на любом другом компьютере. С точки зрения приложений, не существует никакой разницы между местным или удаленным ресурсом, и использование удаленных ресурсов не требует каких-либо специальных средств. Более того, чтобы определить, находится ли такой ресурс как файл или устройство на локальном компьютере или на другом узле сети, в программу потребуется включить специальный дополнительный код!

Пользователи могут иметь доступ к файлам по всей сети, использовать любое периферийное устройство, запускать программы на любом компьютере сети (при условии, что они имеют надлежащие полномочия). Связь между процессами осуществляется единообразно, независимо от их местоположения в сети. В основе такой прозрачной поддержки сети в QNX лежит всеобъемлющая концепция IPC на основе передачи сообщений.

 

2 КОМПЛЕКС ЛАБОРАТОРНЫХ РАБОТ

2.1 Лабораторная работа №1 «Простейший пример»

2.1.1 Теория

 

Минимальный набор действий, необходимый для демонстрации примера программы для QNX:

1. Набрать текст программы.

2. Откомпилировать программу.

3. Запустить программу на исполнение.

Текст программы можно набрать во встроенном редакторе, или взять готовый текстовый файл.

Для дальнейших действий, желательно сделать текущим каталог, где находится текст программы. Для этого можно воспользоваться командами # cd <имя дериктории> (- сменить текущую директорию на указанную) или # cd.. (- подняться на уровень выше). Чтобы просмотреть содержимое директории, можно воспользоваться командой # ls.

Чтобы откомпилировать программу, можно воспользоваться встроенным компилятором - GCC. Для этого в командной строке необходимо написать # gcc <имя_файла>. Если в тексте программы есть ошибки, то они будут выведены на экран. Если ошибок нет, буден создан файл a.out – это и есть исполняемый файл программы. Чтобы его запустить на исполнение, в командной строке необходимо написать # `pwd`/a.out.

2.1.2 Текст программы

 

#include <stdio.h>

int main(void)

{printf("Hello World \n");

return(1);

}

2.1.3 Последовательность действий

 

Создаём текстовый файл программы.

Компилируем его и запускаем на исполнение.

2.1.4 Результаты

 

# cd..

# ls

..lastlogin.ph a.out  lab2   lab4

...profile  lab1 lab3    lab5

# cd lab1

# ls

.      ..     myfirst.c

# gcc myfirst.c

# ls

.      ..      a.out   myfirst.c

# `pwd`/a.out

Hello World

#

Процессы и потоки

 

На самом высоком уровне абстракции система состоит из множества процессов. Каждый процесс ответственен за обеспечение служебных функций определенного характера, независимо от того, является ли он элементом файловой системы, драйвером дисплея, модулем сбора данных, модулем управления или чем-либо еще.

В пределах каждого процесса может быть множество потоков. Число потоков варьируется. Один разработчик ПО, используя только единственный поток, может реализовать те же самые функциональные возможности, что и другой, использующий пять потоков. Некоторые задачи сами по себе приводят к многопоточности и дают относительно простые решения, другие в силу своей природы, являются однопоточными, и свести их к многопоточной реализации достаточно трудно.

Почему процессы?

 

Почему же не взять просто один процесс с множеством потоков? В то время как некоторые операционные системы вынуждают вас программировать только в таком варианте, возникает ряд преимуществ при разделении объектов на множество процессов:

· возможность декомпозиции задачи и модульной организации решения;

· удобство сопровождения;

· надежность.

Концепция разделения задачи на части, т. е., на несколько независимых задач, является очень мощной. И именно такая концепция лежит в основе QNX. Операционная система QNX состоит из множества независимых модулей, каждый из которых наделен некоторой зоной ответственности. Эти модули независимы и реализованы в отдельных процессах. Единственная возможная установить зависимость этих модулей друг от друга — наладить между ними информационную связь с помощью небольшого количества строго определенных интерфейсов.

Это естественно ведет к упрощению сопровождения программных продуктов, благодаря незначительному числу взаимосвязей. Поскольку каждый модуль четко определен, и устранять неисправности в одном таком модуле будет гораздо проще - тем более, что он не связан с другими.

Запуск процесса

 

Теперь обратим внимание на функции, предназначенные для работы с потоками и процессами. Любой поток может осуществить запуск процесса; единственные налагаемые здесь ограничения вытекают из основных принципов защиты (правила доступа к файлу, ограничения на привилегии и т. д.).

Запуск потока

 

Теперь, когда мы знаем, как запустить другой процесс, давайте рассмотрим, как осуществить запуск другого потока.

Любой поток может создать другой поток в том же самом процессе; на это не налагается никаких ограничений (за исключением объема памяти, конечно!) Наиболее общий путь реализации этого — использование вызова функций pthread_create():

 

#include <pthread.h>

int int

pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

 

Функция pthread_create() имеет четыре аргумента:

 

· thread - указатель на pthread_t, где хранится идентификатор потока;

· attr - атрибутная запись;

· start_routine - подпрограмма, с которой начинается поток;

· arg - параметр, который передается подпрограмме start_routine.

 

Отметим, что указатель thread и атрибутная запись (attr) — необязательные элементы, вы можете передавать вместо них NULL.

Параметр thread может использоваться для хранения идентификатора вновь создаваемого потока. Обратите внимание, что в примерах, приведенных ниже, мы передадим NULL, обозначив этим, что мы не заботимся о том, какой идентификатор будет иметь вновь создаваемый поток.

 

Если бы нам было до этого дело, мы бы сделали так:

 

pthread_t tid;

pthread_create (&tid,...

printf («Новый поток имеет идентификатор %d\n», tid);

 

Такое применение совершенно типично, потому что вам часто может потребоваться знать, какой поток выполняет какой участок кода.

 

Небольшой тонкий момент. Новый поток может начать работать еще до присвоения значения параметру tid. Это означает, что вы должны внимательно относиться к использованию tid в качестве глобальной переменной. В примере, приведенном выше, все будет корректно, потому что вызов pthread_create() отработал до использования tid, что означает, что на момент использования tid имел корректное значение.

Новый поток начинает выполнение с функции start_routine (), с параметром arg.

Атрибутная запись потока

 

Когда вы осуществляете запуск нового потока, он может следовать ряду четко определенных установок по умолчанию, или же вы можете явно задать его характеристики.

Прежде, чем мы перейдем к обсуждению задания атрибутов потока, рассмотрим тип данных

Синхронизация

 

Самый простой метод синхронизации — это «присоединение» (joining) потоков. Реально это действие означает ожидание завершения.

Присоединение выполняется одним потоком, ждущим завершения другого потока. Ждущий поток вызывает pthreadjoin():

#include <pthread.h>

int

pthread_join (pthread_t thread, void **value_ptr);

 

Функции pthreadjoin() передается идентификатор потока, к которому вы желаете присоединиться, а также необязательный аргумент value_ptr, который может быть использован для сохранения возвращаемого присоединяемым потоком значения. (Вы можете передать вместо этого параметра NULL).

Где нам брать идентификатор потока?

В функции pthread_create() в качестве первого аргумента указатель на pthread_t. Там и будет сохранен идентификатор вновь созданного потока.

 

2.2.2 Текст программы

 

#include <stdio.h>

#include <pthread.h>

#include <sys/neutrino.h>

 

pthread_t thread_id1;

pthread_t thread_id2;

 

void * long_thread1(void *notused)

{

 int n;

for(n=0;n<5;n++)

{

printf("Eto pervii potok, TID %d - N povtora %d \n", thread_id1, n);

sleep(2);

}

}

 

void * long_thread2(void *notused)

{

 int m;

for(m=0; m<5; m++)

{

printf("Eto vtoroi potok, TID %d - N povtora %d \n", thread_id2, m);

sleep(1);

}

}

 

int main(void)

{

 

printf("Prog threads PID %d \n",getpid());

 

pthread_create(&thread_id1, NULL, long_thread1, NULL);

pthread_create(&thread_id2, NULL, long_thread2, NULL);

 

sleep(40);

 

return(1);

}

2.2.3 Последовательность действий

 

В программе создаются и запускаются на исполнение два потока. Когда один поток приостанавливается, сразу начинает работу другой. Приостановка реализована функцией sleep(n), которая останавливает процесс на n секунд. На экране можно наблюдать, как по очереди работают два процесса.

2.2.4 Результаты

 

# gcc pthread.c

# `pwd`a.out

Prog threads PID 852000

Etot pervii potok, TID 0 - N povtora 0

Etot vtoroi potok, TID 0 - N povtora 0

Etot vtoroi potok, TID 3 - N povtora 1

Etot pervii potok, TID 2 - N povtora 1

Etot vtoroi potok, TID 3 - N povtora 2

Etot vtoroi potok, TID 3 - N povtora 3

Etot pervii potok, TID 2 - N povtora 2

Etot vtoroi potok, TID 3 - N povtora 4

Etot pervii potok, TID 2 - N povtora 3

Etot pervii potok, TID 2 - N povtora 4

#

 

Клиент

 

Клиент, который желает послать запрос серверу, блокируется до тех пор, пока сервер не завершит обработку запроса. Затем, после завершения сервером обработки, запроса клиент разблокируется, чтобы принять «ответ».

Это подразумевает обеспечение двух условий: клиент должен «уметь» сначала установить соединение с сервером, а потом обмениваться с ним данными с помощью сообщений — как в одну сторону (запрос — «send»), так и в другую (ответ — «reply»).

Установление соединения

 

Первое, что должны сделать — это установить соединение с помощью функции ConnectAttach(), описанной следующим образом:

 

#include <sys/neutrino.h>

int ConnectAttach

(int nd,pid_t pid, int chid, unsigned index, int flags);

 

Функции ConnectAttach() передаются три идентификатора;

· nd— дескриптор узла (Node Descriptor),

· pid— идентификатор процесса (process ID)

· chid — идентификатор канала (channel ID).

Вместе эти три идентификатора, которые обычно записываются в виде «ND/PID/CHID», однозначно идентифицируют сервер, с которым клиент желает соединиться. Аргументы index и flags мы здесь просто проигнорируем (установим их в ноль).

Итак, предположим, что мы хотим подсоединиться к процессу, находящемуся на нашем узле и имеющему идентификатор 77, по каналу с идентификатором 1. Ниже приведен пример программы для выполнения этого:

 

int coid;

coid = ConnectAttach (0, 77 „ 1, 0, 0);

 

Можно видеть, что присвоением идентификатору узла (nd) нулевого значения мы сообщаем ядру о том, что мы желаем установить соединение на локальном узле.

Соединиться надо с процессом 77 и по каналу 1.

С этого момента есть идентификатор соединения — небольшое целое число, которое однозначно идентифицирует соединение моего клиента с конкретным сервером по заданному каналу.

Теперь можно применять этот идентификатор для отправки запросов серверу сколько угодно раз. Выполнив все, для чего предназначалось соединение, его можно уничтожить с помощью функции:

ConnectDetach (coid);

Сервер. Создание канала

 

Сервер должен создать канал — то, к чему присоединялся клиент, когда вызывал функцию ConnectAttach(). Обычно сервер, однажды создав канал, приберегает его «впрок».

Канал создается с помощью функции ChannelCreate() и уничтожается с помощью функции ChannelDestroyQ:

 

#include <sys/neutrino.h>

int ChannelCreate (unsigned flags);

int ChannelDestroy (int chid);

 

Таким образом, для создания канала сервер должен сделать так:

int chid;

chid = ChannelCreate (0);

 

Теперь у нас есть канал. В этом пункте клиенты могут подсоединиться (с помощью функции ConnectAttachQ) к этому каналу и начать передачу сообщений:

Рисунок 3 Связь между каналом сервера и клиентским соединением.

 

Обработка сообщений

 

В терминах обмена сообщениями, сервер отрабатывает схему обмена
в два этапа:

 

· этап «приема» (receive);

· этап «ответа» (reply).

Рисунок 4 Взаимосвязь функций клиента и сервера при обмене сообщениями.

Обсудим два простейших варианта соответствующих функций, MsgReceiveQ и MsgReply().

 

#include <sys/neutrino.h>

int MsgReceive (int chid,void *rmsg, int rbytes, struct _msg_info *info);

int MsgReply (int rcvid, int status, const void *msg, int nbytes);

 

Четыре основных элемента:

 

1 Клиент вызывает функцию MsgSend() и указывает ей на буфер передачи (указателем smsg и длиной sbytes). Данные передаются в буфер функции MsgReceiveQ на стороне сервера, по адресу rmsg и длиной rbytes. Клиент блокируется.

2 Функция MsgReceiveQ сервера разблокируется и возвращает идентификатор отправителя rcvid, который будет впоследствии использован для ответа. Теперь сервер может использовать полученные от клиента данные.

3 Сервер завершил обработку сообщения и теперь использует идентификатор отправителя rcvid, полученный от функции MsgReceiveQ, передавая его функции MsgReply(). Заметьте, что местоположение данных для передачи функции MsgReply() задается как указатель на буфер (smsg) определенного размера (sbytes). Ядро передает данные клиенту.

4 Наконец, ядро передает параметр sts, который используется функцией MsgSend() клиента как возвращаемое значение. После этого клиент разблокируется.

 

Для каждой буферной передачи указываются два размера (в случае запроса от клиента клиента это sbytes на стороне клиента и rbytes на стороне сервера; в случае ответа сервера это sbytes на стороне сервера и rbytes на стороне клиента). Это сделано для того, чтобы разработчики каждого компонента смогли определить размеры своих буферов — из соображений дополнительной безопасности.

2.3.2 Текст программы

// server.c

#include <stdio.h>

#include <pthread.h>

#include <inttypes.h>

#include <errno.h>

#include <sys/neutrino.h>

 

void server(void)

{

int rcvid;    //Ykazivaet komy nado otvechat

int chid;     //Identifikator kanala

char message[512]; //

 

printf("Server start working \n");

 

 chid=ChannelCreate(0);    //Sozdanie Kanala

 printf("Chanel id: %d \n", chid);

 printf("Pid: %d \n", getpid());

 // vipolniaetsa vechno- dlia servera eto normalno

 while(1)

{

// Polychit i vivesti soobshenie

 

rcvid=MsgReceive(chid,message,sizeof(message), NULL);

printf("Polychili soobshenie, rcvid %X \n",rcvid);

printf("Soobshenie takoe: \"%s\". \n", message);

   

// Podgotovit otvet

   

strcpy(message,"Eto otvet");

   

MsgReply(rcvid, EOK, message, sizeof(message));

printf("\"%s\". \n", message);

}

 

int main(void)

{

printf("Prog server \n");

server();

sleep(5);

return(1);

}

 

//client.c

#include <stdio.h>

#include <pthread.h>

#include <inttypes.h>

#include <errno.h>

#include <sys/neutrino.h>

 

int main(void)

{

char smsg[20];

char rmsg[200];

int coid;

long serv_pid;

printf("Prog client, Vvedite PID servera \n");

scanf ("%ld",&serv_pid);

printf("Vveli %ld \n", serv_pid);

coid=ConnectAttach(0,serv_pid,1,0,0);

printf("Connect res %d \n, vvedite soobshenie ", coid);

scanf("%s",&smsg);

printf("Vveli %s \n", smsg);

if(MsgSend(coid,smsg,strlen(smsg)+1,rmsg, sizeof(rmsg))==-1)

 {  

 printf("Error MsgSend \n");

}

return(1);

}

2.3.3 Последовательность действий

 

После компиляции программ сервера и клиента у нас будет 2 исполняемых файла. Назовём их server и client соответственно.

Программу server необходимо запустить в фоновом режиме # server &. Она начнёт работать: создаст канал, выведет номер канала и идентификатор процесса сервера, будет ждать сообщения от клиента.

Потом необходимо запустить клиента. Он попросит ввести идентификатор процесса сервера, для установления соединения с ним, и само сообщение (20 символов). Далее можно наблюдать, как сервер получит сообщение, выведет его и пошлёт ответ.

На этом клиент закончит свою работу, но его можно запустить ещё раз и послать другое сообщение.

Остановить работу сервера можно функцией kill < идентификатор процесса сервера >.

2.3.4 Результаты

 

# `pwd`/server &

[3] 2019364

# Prog server

Server start working

Chanel id: 1

Pid: 2019364

# `pwd`/client

Prog client, Vvedite PID servera

2019364

Vveli 2019364

Connect res 3

, vvedite soobshenie Hello_server

Vveli Hello_server

Polychili soobshenie, rcvid 2

Soobshenie takoe: "Hello_server".

"Eto otvet".

# `pwd`/client

Prog client, Vvedite PID servera

2019364

Vveli 2019364

Connect res 3

, vvedite soobshenie I_snova_Privet

Vveli I_snova_Privet

Polychili soobshenie, rcvid 2

Soobshenie takoe: "I_snova_Privet".

"Eto otvet".

# kill 2019364

#

Тайм-ауты ядра

 

QNX/Neutrino позволяет вам получать тайм-ауты по всем блокированным состояниям. Наиболее часто у вас может возникнуть потребность в этом при обмене сообщениями: клиент, посылая сообщение серверу, не желает ждать ответа «вечно». В этом случае было бы удобно использовать тайм-аут ядра. Тайм-ауты ядра также полезны в сочетании с функцией pthreadjoin (): завершения потока тоже не всегда хочется долго ждать.

Ниже приводится декларация для функции TimerTimeout(), которая является системным вызовом, ответственным за формирование тайм-аутов ядра.

 

#include <sys/neutrino.h>

int

TimerTimeout (clockid_t id,

int flags,

const struct sigevent *notify,

const uint64_t *ntime,

uint64 t *otime);

 

Видно, что функция TimerTimeout() возвращает целое число (индикатор удачи/неудачи; 0 означает, что все в порядке, -1 — что произошла ошибка, и ее код записан в errno). Источник синхроимпульсов (CLOCK_REALTIME, и т.п.) указывается в id, параметр flags задает соответствующее состояние (или состояния). Параметр notify всегда должен быть событием уведомления типа SIGEV_UNBLOCK; параметр ntime указывает относительное время, спустя которое ядро должно сгенерировать тайм-аут. Параметр otime показывает предыдущее значение тайм-аута и в большинстве случаев не используется (вы можете передать вместо него NULL).

Важно отметить, что тайм-ауты «взводятся» функцией TimerTimeout(), а запускаются по входу в одно из состояний, указанных в параметре flags. Сбрасывается тайм-аут при возврате; из любого системного вызова. Это означает, что вы должны заново «взводить» тайм-аут перед каждым системным вызовом которому вы хотите его применить. Сбрасывать тайм-аут после системного вызова не надо — это выполняется автоматически.

Применение барьера

 

Два метода синхронизации: один метод с применением функции pthrea. dJoin(), который мы только что рассмотрели, и метод с применением барьера.

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

Однако, с применением функции pthreadJoin () мы ожидаен завершения потоков. Это означает, что на момент ее разблокирования потоков нет больше с нами; они закончили работу и завершились.

В случае с барьером, мы ждем «встречи» определенного числа потоков у барьера. Когда заданное число потоков достигнуто, мы их все разблокируем (заметьте, что потоки при

этом продолжат выполнять свою работу),

Сначала барьер следует создать при помощи функции barrier_ init0:

 

 

#include <sync.h>

int

barrier_init (barrier t        *barrier,

const barrier attr t *attr,

int count);

 

Эта функция создает объект типа «барьер» по переданному ей адресу (указатель на барьер хранится в параметре barrier) и назначает ему атрибуты, которые определены в attr (мы будем использовать NULL, чтобы установить значения по умолчанию). Число потоков, которые должны вызывать функцию barrier_ wait(), передается в параметре count.

После того как барьер создан, каждый из потоков должен будет вызвать функцию barrier_ wait(), чтобы сообщить, что он отработал:

 

#include <sync.h>

int barrier_wait (barrier t *barrier);

После того как поток вызвал barrier_ wait(), он будет блокирован до тех пор, пока число потоков, указанное первоначально в параметре count функции barrier_ mit(), не вызовет функцию barrier'_ waU() (они также будут блокированы). После того как нужное число потоков выполнит вызов функции barrier_ wait(), все эти потоки будут разблокированы «одновременно».

 

 

Текст программы

#include <stdio.h>

#include <time.h>

#include <sync.h>

#include <sys/neutrino.h>

 

barrier_t barrier;   

 

//int data_ready = 0;

//int inf = 0;

//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

//pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;

 

void *thread1 (void * not_used)

{

time_t now;

char buf[27];

time(&now);

printf("Potok 1, vremia starta %s \n", ctime_r(&now,buf));

sleep(3);

barrier_wait(&barrier);

time(&now);

printf("barier v potoke 1, vremia srabativania %s \n", ctime_r(&now,buf));

}

 

void *thread2 (void * not_used)

{

time_t now;

char buf[27];

time(&now);

printf("Potok 2, vremia starta %s \n", ctime_r(&now,buf));

sleep(6);

barrier_wait(&barrier);

time(&now);

printf("barier v potoke 2, vremia srabativania %s \n", ctime_r(&now,buf));

}

 

main()

{

time_t now;

char buf[27];

barrier_init(&barrier, NULL, 3);

printf("Start \n");

pthread_create(NULL,NULL, thread1,NULL);

pthread_create(NULL,NULL, thread2,NULL);

time(&now);

printf(" Main(): oshidanie y bariera, vremia %s \n", ctime_r(&now,buf));

barrier_wait(&barrier);

time(&now);

printf("barier v main(), vremia srabativania %s \n", ctime_r(&now,buf));

sleep(5);

}

2.5.3 Последовательность действий

Основной поток создал объект типа «барьер» и инициализировал его значением счетчика, равным числу потоков (включая себя!), которые должны «встретиться» у барьера, прежде чем он «прорвется». В нашем примере этот индекс был равен 3 — один для потока main(), один для потока thread1 () и один для потока thread2(). Затем, как и прежде, стартуют потоки вычисления графики (в нашем случае это потоки thread 1() и thread2()). Для примера вместо приведения реальных алгоритмов графических вычислений мы просто временно «усыпили» потоки, указав в них sleep (20) и sleep (40), чтобы имитировать вычисления. Для осуществления синхронизации основной поток (та in()) просто блокирует сам себя на барьере, зная, что барьер будет разблокирован только после того, как рабочие потоки аналогично присоединятся к нему.

Как упоминалось ранее, с функцией pthreadJoin() рабочие потоки для синхронизации главного потока с ними должны умереть. В случае же с барьером потоки живут и чувствуют себя вполне хорошо. Фактически, отработав, они просто разблокируются по функции barrier•_ wait(). Тонкость здесь в том, что вы обязаны предусмотреть, что эти потоки должны делать дальше! В нашем примере с графикой мы не дали им никакого задания для них — просто потому что мы так придумали алгоритм. В реальной жизни вы могли бы захотеть, например, продолжить вычисления.

 

2.5.4 Результаты

# root/a.out

Start

Potok 1, vremia starta Tue Oct 21 00:29:01 2003

 

Potok 2, vremia starta Tue Oct 21 00:29:01 2003

 

 Main(): oshidanie y bariera, vremia Tue Oct 21 00:29:01 2003

 

barier v potoke 2, vremia srabativania Tue Oct 21 00:29:07 2003

 

barier v main(), vremia srabativania Tue Oct 21 00:29:07 2003

 

barier v potoke 1, vremia srabativania Tue Oct 21 00:29:07 2003

 

#/**

 

Им. Д. Ф. УСТИНОВА

Кафедра _И3_  

 


Комплекс лабораторных робот по основам применения операционной системы реального времени QNX

 

Разработали: Гаврилов В.В

                    Кононов О.А.

                   Маллер А.А

 

САНКТ-ПЕТЕРБУРГ

2003 г.

 

                                  РЕФЕРАТ

 Комплекс лабораторных работ содержит 33 с., 8 рис., 3 источника, ОСРВ, МИКРОЯДРО, ОБМЕН СООБЩЕНИЯМИ, СЕРВЕР, КЛИЕНТ, ПК, ПРОЦЕСС, ПОТОК, СИНХРОНИЗАЦИЯ.

Объектом рассмотрения является программное обеспечение, которое используется в системах жесткого реального времени.

Цель работы – ознакомление студентов с основными механизмами взаимодействия процессов на примере средств, предоставляемых операционной системой QNX.

 

 

         СОДЕРЖАНИЕ

 

ОПРЕДЕЛЕНИЯ, ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ.. 4

ВВЕДЕНИЕ.. 5

1 Стуктурная организациа ОСРВ QNX.. 6

1.1 Архитектура микроядра системы QNX.. 6

1.1.1 Настоящее ядро. 6

1.1.2 Системные процессы.. 7

1.2 Микроядро. 7

1.3 Системные и пользовательские процессы.. 8

1.4 Драйверы устройств. 9

1.5 Связь между процессами (IPC) 9

1.5.1 Передача сообщений. 9

1.6 QNX как сеть. 10

2 КОМПЛЕКС ЛАБОРАТОРНЫХ РАБОТ. 11

2.1 Лабораторная работа №1 «Простейший пример». 11

2.1.1 Теория. 11

2.1.2 Текст программы.. 11

2.1.3 Последовательность действий. 11

2.1.4 Результаты.. 11

2.2 Лабораторная работа №2 «Процессы и потоки». 12

2.2.1 Теория. 12

2.2.2 Текст программы.. 16

2.2.3 Последовательность действий. 17

2.2.4 Результаты.. 17

2.3 Лабораторная работа №3 «Обмен сообщениями». 17

2.3.1 Теория. 17

2.3.2 Текст программы.. 22

2.3.3 Последовательность действий. 23

2.3.4 Результаты.. 23

2.4 Лабораторная работа №4 «Тайм - ауты». 24

2.4.1 Теория. 24

2.4.2 Текст программы.. 26

2.4.3 Последовательность действий. 27

2.4.4 Результаты.. 27

2.5 Лабораторная работа №5 «Барьеры». 28

2.5.1 Теория. 28

2.5.2 Текст программы.. 29

2.5.3 Последовательность действий. 30

2.5.4 Результаты.. 30

2.6 Лабораторная работа №6 «Условные переменные». 30

2.6.1 Теория. 30

2.6.2 Текст программы.. 31

2.6.3 Последовательность действий. 32

2.6.4 Результаты.. 32

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ.. 33

 

ОПРЕДЕЛЕНИЯ, ОБОЗНАЧЕНИЯ И СОКРАЩЕНИЯ

ПК  персональный компьютер

ОСРВ Операционная система реального времени

IPC Связь между процессами (Interprocess communication, сокращенно IPC)

ВВЕДЕНИЕ

Главная обязанность операционной системы состоит в управлении ресурсами компьютера. Все действия в системе – диспетчеризация прикладных программ, запись файлов на диск, пересылка данных по сети и т.п. - должны выполняться совместно настолько слитно и прозрачно, насколько это возможно.

Некоторые области применения предъявляют более жесткие требования к управлению ресурсами и диспетчеризации программ, чем другие. Приложения реального времени, например, полагаются на способность операционной системы обрабатывать многочисленные события в пределах ограниченного интервала времени. Чем быстрее реагирует операционная система, тем большее пространство для маневра имеет приложение реального времени в пределах жестких временных рамок.

Операционная система QNX идеальна для приложений реального времени. Она обеспечивает все неотъемлемые составляющие системы реального времени: многозадачность, диспетчеризацию программ на основе приоритетов и быстрое переключение контекста [1].

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

QNX достигает своего уникального уровня производительности, модульности и простоты благодаря двум фундаментальным принципам:

 

· архитектура на основе микроядра;

· связь между процессами на основе сообщений.

 

Рассматриваемый комплекс лабораторных работ по применению ОСРВ QNX ориентирован на использование в курсе «Программное обеспечение систем реального времени»

1 Стуктурная организациа ОСРВ QNX

Архитектура микроядра системы QNX

 

QNX состоит из небольшого ядра, координирующего работу взаимодействующих процессов. Как показано на рисунке, структура


Поделиться с друзьями:

Двойное оплодотворение у цветковых растений: Оплодотворение - это процесс слияния мужской и женской половых клеток с образованием зиготы...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

Типы сооружений для обработки осадков: Септиками называются сооружения, в которых одновременно происходят осветление сточной жидкости...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.424 с.