СИСТЕМЫ РЕАЛЬНОГО ВРЕМЕНИ Методические указания к лабораторным работам для студентов направления подготовки 230100.68 «Информатика и вычислительная техника» Составитель А. А. Будаева Владикавказ 2015 МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования «СЕВЕРО-КАВКАЗСКИЙ ГОРНО-МЕТАЛЛУРГИЧЕСКИЙ ИНСТИТУТ (ГОСУДАРСТВЕННЫЙ ТЕХНОЛОГИЧЕСКИЙ УНИВЕРСИТЕТ)» Кафедра Автоматизированной обработки информации СИСТЕМЫ РЕАЛЬНОГО ВРЕМЕНИ Методические указания к лабораторным работам для студентов направления подготовки 230100.68 «Информатика и вычислительная техника» Составитель А. А. Будаева Допущено редакционно-издательским советом Северо-Кавказского горно-металлургического института (государственного технологического университета). Протокол заседания РИСа № 2 от 18.02.2014 г. Владикавказ 2015 1 УДК 004.415.2 ББК 32.973.26-0.18.2 Б90 Рецензент: кандидат технических наук, доцент Северо-Кавказского горно-металлургического института (государственного технологического университета) Даурова А. А. С59 Системы реального времени: Методические указания к лабораторным работам для студентов направления подготовки 230100.68 – «Информатика и вычислительная техника» / Сост.: А. А. Будаева; Северо-Кавказский горно-металлургический институт (государственный технологический университет). – Владикавказ: Северо-Кавказский горно-металлургический институт (государственный технологический университет). Изд-во «Терек», 2015. – 68 с. Методические указания содержат описание 6 лабораторных работ, соответствующих рабочей программе по дисциплине «Системы реального времени» для студентов направления подготовки «Информатика и вычислительная техника» 230100.68. В методических указаниях на примерах рассматриваются основные механизмы программного обеспечения реального времени, предоставляемые ОСРВ, на основе QNX Momentics. Приведены индивидуальные задания по каждой лабораторной работе. Методические указания способствуют приобретению навыков и умений работы с операционными системами реального времени, удовлетворяющих стандарту POSIX и расширениям операционной системы QNX Neutrino. УДК 004.415.2 ББК 32.973.26-0.18.2 Редактор: Иванченко Н. К. Компьютерная верстка: Куликова М. П. Составление. ФГБОУ ВПО «Северо-Кавказский горно-металлургический институт (государственный технологический университет)», 2015 Будаева А. А., составление, 2015 Подписано в печать 30.01.2015. Формат 60х84 1/16. Бумага офсетная. Гарнитура «Таймс». Печать на ризографе. Усл. п.л. 3,95. Уч.-изд. л. 2,59. Тираж 20 экз. Заказ № . Северо-Кавказский горно-металлургический институт (государственный технологический университет). Издательство «Терек». Отпечатано в отделе оперативной полиграфии СКГМИ (ГТУ). 362021, г. Владикавказ, ул. Николаева, 44. 2 Содержание Лабораторная работа № 1. Таймеры в Windows .................................... Теоретический материал ........................................................................... Порядок выполнения работы .................................................................... Контрольные вопросы ............................................................................... Лабораторная работа № 2. Решение задачи «взаимоисключающий доступ» с помощью механизма семафоров ............................................... Теоретический материал ........................................................................... Порядок выполнения работы .................................................................... Контрольные вопросы ............................................................................... Лабораторная работа № 3. Установка системы QNX6. Работа в оболочке Photon ............................................................................................ Теоретический материал ........................................................................... Порядок выполнения работы .................................................................... Контрольные вопросы ............................................................................... Лабораторная работа № 4. Команды операционной системы QNX .... Теоретический материал ........................................................................... Порядок выполнения работы .................................................................... Контрольные вопросы ............................................................................... Лабораторная работа № 5. Командные сценарии операционной системы QNX ................................................................................................ Теоретический материал ........................................................................... Порядок выполнения работы .................................................................... Контрольные вопросы ............................................................................... Лабораторная работа № 6. Разработка приложений в среде PhAB ..... Теоретический материал ........................................................................... Порядок выполнения работы .................................................................... Контрольные вопросы ............................................................................... Список литературы ....................................................................................... Приложение. Описание типов данных _SYSTEMTIME, LongWord… 3 5 – 19 24 25 – 33 34 36 – 42 – 43 – 49 50 51 – 57 58 59 – 66 – 67 68 Общие методические указания к выполнению лабораторных работ При выполнении лабораторных работ необходимо: 1. В соответствии с целью работы сформулировать задачу, которая должна быть решена с помощью приложения. 2. Разработать алгоритм решения задачи. 3. Разработать приложение, включающее интерфейс, программные модули вычислительных процедур, формы представления результатов. 4. Выполнить компьютерное моделирование. 5. Провести тестирование алгоритма и приложения. 6. Сделать выводы и обобщения. 7. Составить электронный вариант отчета с результатами выполнения приложения. Образец оформления титульного листа приведен в приложении. При выполнении работ рекомендуется обратиться к литературе [1–12]. 4 Лабораторная работа № 1 ТАЙМЕРЫ В WINDOWS Цель работы: 1. Изучить службы таймеров, предоставляемые операционной системой Microsoft Windows. 2. Рассмотреть назначение основных процедур и функций этих служб, назначение и смысл их входных параметров и возвращаемых значений. 3. Научиться применять на практике полученные знания. Теоретический материал Службы таймеров для Microsoft Windows Таймер – внутренняя функция операционной системы, которая позволяет многократно измерять указанный интервал времени. После истечения промежутка времени, контролируемого таймером, должно выполняться то или иное действие, например, вызываться на выполнение определённая программа. Точность, с которой таймер контролирует заданный интервал времени, называется его разрешающей способностью. Точность таймера зависит от тактовой частоты системы. Имеется два типа событий от таймера: единственное, которое происходит однажды после указанного количества миллисекунд, и периодические. Периодические вызовы по таймеру поступают неоднократно, каждый раз, когда протекает указанное число миллисекунд. Операционная система Microsoft Windows поддерживает службы обычных и мультимедийных таймеров. Различие между мультимедийными и обычными таймерами состоит в том, что мультимедийные таймеры вызывают события обработки сразу, а обычные таймеры добавляют сообщение о необходимости обработки события по таймеру к очереди сообщений. Мультимедийные таймеры работают с наибольшей точностью среди других служб таймеров и позволяют приложению намечать события с наивысшей разрешающей способностью, возможной для используемых аппаратных средств. Мультимедийные таймеры полезны для приложений, которым требуется выбор времени с высоким разрешением. Приложения, не требующие высокоточного выбора времени, должны использовать службу обычных таймеров. 5 Мультимедийные таймеры С помощью мультимедийных таймеров приложения могут запрашивать и получать сообщения таймеров через заданные интервалы времени. Сервис мультимедийных таймеров позволяет намечать как однократные, так и периодические события. Прежде чем приложение начинает использовать услуги мультимедийного таймера, оно должно определить текущее системное время. Системное время – это время, измеряемое в миллисекундах от начала работы операционной системы Microsoft Windows. Для определения астрономического времени используется функция GetSystemTime, а для определения системного времени – функция GetTickCount. Функция GetSystemTime заполняет структуру _SYSTEMTIME, а GetTickCount возвращает значение LongWord. Описание используемых типов данных (_SYSTEMTIME, LongWord) приведено в приложении. Для установки мультимедийного таймера используется функция timeSetEvent. Чтобы запустить таймер, приложение должно вызвать функцию timeSetEvent, указывая в качестве параметров задержку времени, разрешающую способность, адрес функции вызова, данные пользователя (в дальнейшем они будут сообщаться функции вызова при её запуске таймером) и тип таймера (одиночный или периодический). Функция timeSetEvent возвращает идентификатор таймера, который может использоваться, чтобы остановить или идентифицировать события от таймера. Формат функции timeSetEvent: Function timeSetEvent(uDelay: UINT; uResolution: UINT; lpTimeProc: TFNTimeCallBack; dwUser: DWORD; fuEvent: UINT): MMRESULT. Здесь uDelay – задержка времени в миллисекундах; uResolution – разрешающая способность таймера, в миллисекундах; lpTimeProc – адрес функции, которая запускается при срабатывании таймера; dwUser – данные пользователя, сообщаемые вызываемой функции при срабатывании таймера (при отсутствии данных параметру присваивают нулевое значение); fuEvent – тип таймера. Функция timeSetEvent в случае успеха возвращает идентификатор таймера uID, в противоположном случае – ошибка. Типы используемых параметров (UINT, TFNTimeCallBack, MMRESULT) приведены в приложении. Интервал между вызовами таймера называется задержкой времени (uDelay). Разрешающая способность тем выше, чем меньше значение параметра uResolution. Разрешающая способность, равная нулю, 6 указывает на то, что события должны произойти с максимально возможной точностью. Чтобы уменьшить затраты ресурсов системы, нужно использовать как можно большее значение параметра uResolution. Отношение между разрешающей способностью таймера и длиной задержки времени играют важную роль в событиях от таймера. Например, если разрешающая способность равна 5 мс, а задержка времени равна 100 мс, то таймер запускает функцию вызова (lpTimeProc) в интервале от 95 до 105 миллисекунд. Определены следующие типы таймеров (fuEvent): TIME_ONESHOT – таймер срабатывает один раз после истечения uDelay миллисекунд; TIME_PERIODIC – таймер срабатывает периодически, после истечения каждых uDelay миллисекунд. После инициализации таймер будет запускать функцию, адрес которой указан в параметре lpTimeProc функции timeSetEvent. Функцию вызова обычно называют TimeProc. Формат функции TimeProc: Procedure TimeProc(uID: UINT; uMsg: UINT; dwUser: DWORD; dw1: DWORD; dw2: DWORD) stdcall. Здесь uID – идентификатор таймера; uMsg – зарезервировано (не использовать); dwUser – данные пользователя, указанные в соответствующем параметре функции timeSetEvent; dw1 и dw2 – зарезервировано (не использовать). Приставка stdcall ставится для функций и процедур, запуск которых осуществляет операционная система, а также это означает, что параметры будут считываться справа налево. Удаляет таймер функция timeKillEvent. Формат функции timeKillEvent: Function timeKillEvent(uID: UINT): MMRESULT. Здесь uID – идентификатор таймера, который следует удалить. Приложение не должно вызывать никакие системые функции внутри функции вызова TimeProc. К ним относятся: GetSystemTime, GetTickCount, timeSetEvent и timeKillEvent. В этом случае необходимо предусмотреть дополнительные функции установки и удаления таймера вне пределов процедуры TimeProc. Например, для создания таймера можно использовать функцию следующего вида: Function BuildTimer(uDelay : UINT; uResolution:UINT; fuEvent : UINT): MMRESULT; Begin Result := TimeSetEvent(uDelay,uResolution,@TimeProc,0,fuEvent); End; 7 для удаления таймера – процедуру: Procedure DestroyTimer(Var T_ID: UINT); Begin TimeKillEvent(T_ID); T_ID := 0; End; Перед освобождением памяти (например, в случае завершения работы приложения), содержащей функцию вызова (lpTimeProc), необходимо выключить все активные таймеры. Рассмотрим задачи на применение службы мультимедийных таймеров в среде Delphi. Задача 1. Необходимо создать периодический таймер TM1 с интервалом срабатывания 5 секунд, точностью 5 миллисекунд, который после запускает на выполнение функцию GetWorkResult и в зависимости от возвращённого этой функцией значения выполняет следующие действия: если функция GetWorkResult вернёт значение 0, то запускается одиночный таймер TM2 с интервалом срабатывания 1 секунда и точностью 1 миллисекунда, который вызывает на выполнение процедуру Work_TM2; если функция GetWorkResult вернёт значение 1, то таймер TM1 выключается. Разрабатываемая программа должна содержать описание переменных, используемых в качестве идентификаторов таймеров; описание процедур и функций и основную программу. Сначала рассмотрим функцию GetWorkResult и процедуру Work_TM2, которые вызываются при срабатывании соответствующих таймеров. Работу функции GetWorkResult реализуем с помощью обращения к датчику равномерно распределённых случайных чисел 0 и 1: Function GetWorkResult: Integer; Begin Result := Random(2); End; В результате выполнения функции GetWorkResult возвращаемое значение (Result) будет равно 0 или 1. Пусть процедура Work_TM2 выполняет следующие действия: 8 Procedure Work_TM2; Var I : Integer; Begin I := 30000; While I <> 0 do I := I – 1; End; Сначала должен быть запущен таймер TM1. Для его установки будем использовать функцию TimeSetEvent следующего вида: TM1 := timeSetEvent(5000, 5, @TimeProc, 0, TIME_PERIODIC). Опишем процедуру вызова TimeProc, адрес которой был указан в качестве одного из аргументов функции TimeSetEvent: Procedure TimeProc(uTimerID,uMessage:UINT;dwUser,dw1,dw2:DWORD) stdcall; Begin If uTimerID = TM1 Then//сработал таймер с идентификатором TM1 Case GetWorkResult of 0:TM2 := BuildTimer(1000,1,TIME_ONESHOT); 1:DestroyTimer(TM1); End Else Work_TM2;//сработал таймер TM2 End; Так как внутри процедуры TimeProc не должны вызываться системные функции timeSetEvent и timeKillEvent, для создания таймера TM2 использована функция BuildTimer, а для удаления таймера TM1 – функцияDestroyTimer. В результате код программы, соответствующий поставленной задаче, будет выглядеть следующим образом: Var //описание идентификаторов таймеров TM1: UINT; TM2: UINT; Function GetWorkResult: Integer; Begin Result := Random(2); End; 9 Procedure Work_TM2; Var I : Integer; Begin I := 30000; While I <> 0 do I := I – 1; End; Procedure DestroyTimer(Var T_ID : UINT); Begin TimeKillEvent(T_ID); T_ID := 0; End; Function BuildTimer(uDelay : UINT; uResolution:UINT; fuEvent : UINT) : UINT; Begin Result := TimeSetEvent(uDelay,uResolution,@TimeProc,0,fuEvent); End; Procedure TimeProc(uTimerID,uMessage:UINT;dwUser,dw1,dw2:DWORD) stdcall; Begin If uTimerID = TM1 Then//сработал таймер с идентификатором TM1 Case GetWorkResult of 0:TM2 := BuildTimer(1000,1,TIME_ONESHOT); 1:DestroyTimer(TM1); End Else Work_TM2;//сработал таймер TM2 End; //начало основной программы Begin Randomize; TM1 := timeSetEvent(5000, 5, @TimeProc, 0, TIME_PERIODIC); //здесь подразумевается создание окон приложения End. Задача 2. Периодический таймер TM1 с интервалом срабатывания 10 секунд и разрешающей способностью 0,01 секунды, а также с автокорректировкой времени запуска (отсчёт следующего момента запуска таймера сдвигается на время выполнения процедуры или функции), срабатывает 5 раз и при срабатывании вызывает функцию 10 GetWorkResult, возвращаемое значение которой влияет на дальнейшие события следующим образом. Если функция GetWorkResult вернёт значение 0, то запускается периодический таймер TM2 (интервал срабатывания 3 секунды, максимальная разрешающая способность) на два срабатывания. Срабатывание таймера TM2 вызывает на выполнение функцию GetTM2. Если функция GetTM2 возвращает значение 0, то запускается одиночный таймер TM3 с интервалом срабатывания 1 секунда и максимальной разрешающей способностью. Срабатывание таймера TM3 вызывает на выполнение процедуру Work_TM3. Если функция GetTM2 возвращает значение, отличное от 0 (GetTM2 0), никаких действий производить не следует. Если функция GetWorkResult вернет значение 1, то запускается одиночный таймер TM3 (интервал срабатывания 1с, максимальная разрешающая способность), вызывающий по срабатыванию процедуру Work_TM3. Разрабатываемая программа должна содержать описание переменных, используемых в качестве идентификаторов таймеров; описание процедур и функций и основную программу. Функция GetWorkResult аналогична одноимённой функции из первой задачи. Работу функции GetTM2 реализуем с помощью обращения к счётчику равномерно распределённых случайных чисел: Function GetTM2: Integer; Begin Result := Random(3); End; В результате выполнения функции GetTM2 возвращаемое значение (Result) будет равно 0,1 или 2. Работа процедуры Work_TM3 аналогична работе процедуры Work_TM2, описанной в первой задаче. Сначала должен быть запущен таймер TM1. Для его установки будем использовать функцию TimeSetEvent следующего вида: TM1 := timeSetEvent(10000, 10, @TimeProc, 0, TIME_PERIODIC). Опишем процедуру вызова TimeProc, адрес которой был указан в качестве одного из аргументов функции TimeSetEvent: Procedure TimeProc(uTimerID,uMessage:UINT;dwUser,dw1,dw2:DWORD) stdcall; Begin 11 If uTimerID = TM1 Then//сработал первый таймер Begin C1 := C1 – 1; If C1 = 0 Then DestroyTimer(TM1); Case GetWorkResult of 0:Begin C2 := 2; TM2 := BuildTimer(3000,0,TIME_PERIODIC); End; 1:TM3 := BuildTimer(1000,0,TIME_ONESHOT); End; If C1 > 0 Then Begin DestroyTimer(TM1); TM1 := BuildTimer(10000,10,TIME_PERIODIC); End; End Else If uTimerID = TM2 Then//сработал второй таймер Begin If GetTM2 = 0 Then TM3 := BuildTimer(1000,0,TIME_ONESHOT); C2 := C2 – 1; If C2 = 0 Then DestroyTimer(TM2); End Else Work_TM3;//сработал третий таймер End; Так как внутри функции вызова TimeProc не должны вызываться системные функции timeSetEvent и timeKillEvent, для создания таймеров использована функция BuildTimer, а для удаления таймеров – функцияDestroyTimer. В результате код программы, соответствующий поставленной задаче, будет выглядеть следующим образом: Var //описание идентификаторов таймеров TM1: UINT; TM2: UINT; TM3: UINT; //описание счётчиков количества срабатываний таймеров C1, C2: Integer; 12 Function GetWorkResult: Integer; Begin Result := Random(2); End; Procedure Work_TM3; Var I : Integer; Begin I := 30000; While I <> 0 do I := I – 1; End; Function GetTM2: Integer; Begin Result := Random(3); End; Procedure DestroyTimer(Var T_ID : UINT); Begin TimeKillEvent(T_ID); T_ID := 0; End; Function BuildTimer(uDelay : UINT; uResolution:UINT; fuEvent : UINT) : UINT; Begin Result := TimeSetEvent(uDelay,uResolution,@TimeProc,0,fuEvent); End; Procedure TimeProc(uTimerID,uMessage:UINT;dwUser,dw1,dw2:DWORD) stdcall; Begin If uTimerID = TM1 Then//сработал первый таймер Begin Case GetWorkResult of 0:Begin C2 := 2; TM2 := BuildTimer(3000,0,TIME_PERIODIC); End; 1:TM3 := BuildTimer(1000,0,TIME_ONESHOT); 13 End; C1 := C1 – 1; If C1 = 0 Then DestroyTimer(TM1) Else Begin DestroyTimer(TM1); TM1 := BuildTimer(10000,10,TIME_PERIODIC); End; End Else If uTimerID = TM2 Then//сработал второй таймер Begin If GetTM2 = 0 Then TM3 := BuildTimer(1000,0,TIME_ONESHOT); C2 := C2 – 1; If C2 = 0 Then DestroyTimer(TM2); End Else Work_TM3;//сработал третий таймер End; //начало основной программы Begin Randomize; C1 := 5; TM1 := timeSetEvent(10000, 10, @TimeProc, 0, TIME_PERIODIC); //здесь подразумевается создание окон приложения End. Обычные таймеры Служба обычных таймеров не такая точная, как служба мультимедийных таймеров. Это объясняется тем, что после истечения указанного интервала времени задержки для данного таймера служба обычных таймеров посылает к окнам Microsoft Windows сообщение WM_TIMER. Так как точность таймера зависит от тактовой частоты системы и от частоты чтения приложением сообщений из очереди сообщений, то значение времени задержки только приблизительно. Приложение, использующее службу обычных таймеров, должно создавать таймер с помощью функции SetTimer. Каждый таймер имеет уникальный идентификатор. При создании таймера приложение может использовать своё уникальное значение. Формат функции SetTimer: Function SetTimer(handle: HWND; nIDEvent: UINT; uElapse: UINT; lpTimerFunc: TFNTimerProc): UINT. 14 Здесь параметр handle идентифицирует окно, которое будет связано с таймером; параметр nIDEvent определяет идентификатор таймера, отличный от нуля; nElapse – задержка времени в миллисекундах; параметр lpTimerFuncне используется. Возвращаемое функцией SetTimer значение в случае успеха – целое число, идентифицирующее новый таймер, а если функция потерпела неудачу, то возвращаемое значение – нулевое. Параметр handle является идентификатором окна, принадлежащим инициализирующему таймер приложению. Если этот параметр нулевой, то никакое окно не будет связано с таймером, и параметр nIDEvent игнорируется. Всякий раз, когда значение времени задержки для таймера истекает, система посылает сообщение WM_TIMER к окну (handle), связанному с таймером. Если идентификатор окна не был указан в параметрах SetTimer, то приложение, которое создавало таймер, должно само контролировать приход сообщения WM_TIMER и обрабатывать его. Формат сообщения WM_TIMER: WM_TIMER wTimerID – wParam; tmprc – lParam. Здесь параметр wTimerID определяет идентификатор таймера; параметр tmprc указывает на функцию вызова (не используется). Это сообщение считывается системной функцией DispatchMessage только тогда, когда никакие другие сообщения не находятся в очереди сообщений. Новый таймер, как только он создан, начинает отсчитывать указанный интервал времени. Приложение может изменить значение времени задержки, используя функцию SetTimer, и может удалить таймер, используя функцию KillTimer. Чтобы использовать ресурсы системы эффективно, приложение должно удалить таймеры, которые больше ему не нужны. Формат функции KillTimer: Function KillTimer(handle: HWND; nIDEvent: UINT): Boolean, Здесь handle – идентификатор окна, связанного с указанным таймером; параметр nIDEvent определяет таймер, подлежащий удалению. В случае успеха возвращаемое функцией KillTimer значение «истина», иначе возвращаемое значение «ложь». Значение параметра handle должно быть тем же самым, что и значение, которое было послано в параметре handle к функции SetTimer, создававшей таймер. Если идентификатор окна (handle), указанный в параметрах функции SetTimer, был не нулевым, то параметр nIDEvent функции KillTimer 15 должен быть таким же, что и посланный к SetTimer параметр nIDEvent. Если приложение вызвало функцию SetTimer с нулевым значением параметра handle, то параметр nIDEvent функции KillTimer должен быть равен значению, возвращенному функцией SetTimer. Функция KillTimer не удаляет сообщения WM_TIMER, уже помещённые в очередь сообщений. Рассмотрим задачу на применение службы обычных таймеров. Условие задачи. Необходимо создать периодический таймер TM1 с интервалом срабатывания 5 секунд, который запускает функцию GetWorkResult и, в зависимости от возвращённого этой функцией значения, программа выполняет следующие действия: если функция GetWorkResult вернёт значение 0, то запускается одиночный таймер TM2 с интервалом срабатывания 1 секунда, который в свою очередь вызывает на выполнение процедуру Work_TM2; если функция GetWorkResult вернёт значение 1, то таймер TM1 выключается; если возвращаемое функцией GetWorkResult значение отличается от 0 или 1, то никаких действий не производится. Пусть функция GetWorkResult будет возвращать одно из пяти значений: 0, 1, 2, 3, 4. Тогда работу функции GetWorkResult реализуем с помощью обращения к датчику равномерно распределённых случайных чисел: Function GetWorkResult: Integer; Begin Result := Random(5); End; В результате выполнения функции GetWorkResult возвращаемое значение (Result) будет равно 0, 1, 2, 3 или 4. Пусть процедура Work_TM2 выполняет следующие действия: Procedure Work_TM2; Var I : Integer; Begin I := 30000; While I <> 0 do I := I – 1; End; 16 Поскольку сначала должен быть запущен таймер TM1, для его установки будем использовать функцию SetTimer: SetTimer(SimpleWindow.Handle, tm1, 5000, nil); Это действие будет выполняться в момент создания окна, то есть внутри процедуры Procedure TSimpleWindow.FormCreate(Sender: TObject). При завершении работы приложения, то есть в момент уничтожения его основного окна, следует удалить используемые таймеры, для чего используется следующая процедура: Procedure TSimpleWindow.FormDestroy(Sender: TObject); Begin KillTimer(SimpleWindow.Handle, tm1); KillTimer(SimpleWindow.Handle, tm2); End; Обозначим процедуру GetTimerMes в качестве перехватчика сообщения WM_TIMER: Procedure GetTimerMes(var Msg: TWMTimer); message WM_TIMER/ Описание структуры TWMTimer приведено в приложении 1. Действия внутри перехватчика сообщения будут выглядеть следующим образом: Procedure TSimpleWindow.GetTimerMes(var Msg: TWMTimer); Begin Case Msg.TimerID of tm1: Begin//обработка срабатывания первого таймера Case GetWorkResult of 0: SetTimer(SimpleWindow.Handle, tm2, 1000, nil); 1: KillTimer(SimpleWindow.Handle, tm1); End; Msg.Result := 0;//возвращаем 0, т.к. удачно обработали End; tm2: Begin//обработка срабатывания второго таймера Work_TM2; KillTimer(SimpleWindow.Handle, tm2); Msg.Result := 0;//возвращаем 0, т. к. удачно обработали End; End; Так как служба обычных таймеров должна быть привязана к окну, то необходимо описать тип данных, к которому будет принадлежать окно. 17 В результате текст основного модуля программы, соответствующий поставленной задаче, будет выглядеть следующим образом: unit Simple; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; const //описание уникальных идентификаторов таймеров tm1 = 500; tm2 = 600; //описание типа данных, к которому будет принадлежать окно type TSimpleWindow = class(TForm) procedure FormCreate(Sender: TObject);//создание окна procedure FormDestroy(Sender: TObject);//уничтожение окна protected procedure GetTimerMes(var Msg: TWMTimer); message WM_TIMER; end; var SimpleWindow: TSimpleWindow;//окно implementation {$R *.DFM} Function GetWorkResult: Integer; Begin Result := Random(5); End; Procedure Work_TM2; Var I : Integer; Begin I := Random(3000000); While I <> 0 Do Dec(I); End; Procedure TSimpleWindow.GetTimerMes(var Msg: TWMTimer); Begin Case Msg.TimerID of tm1: Begin//обработка срабатывания первого таймера 18 Case GetWorkResult of 0: SetTimer(SimpleWindow.Handle, tm2, 1000, nil); 1: KillTimer(SimpleWindow.Handle, tm1); End; Msg.Result := 0;//возвращаем 0, т.к. удачно обработали End; tm2: Begin//обработка срабатывания второго таймера Work_TM2; KillTimer(SimpleWindow.Handle, tm2); Msg.Result := 0;//возвращаем 0, т.к. удачно обработали End; End; End; procedure TSimpleWindow.FormCreate(Sender: TObject); Begin Randomize; SetTimer(SimpleWindow.Handle, tm1, 5000, nil); End; procedure TSimpleWindow.FormDestroy(Sender: TObject); Begin KillTimer(SimpleWindow.Handle, tm1); KillTimer(SimpleWindow.Handle, tm2); End; End. Порядок выполнения работы Индивидуальным вариантом к лабораторной работе предусматривается решение двух заданий: одного – с использованием службы мультимедийных таймеров и другого – с использованием службы обычных таймеров. Номер варианта задания состоит из двух частей: номера варианта в виде римской цифры (см. раздел 5) и номера строки из таблицы, соответствующей указанному варианту, например, I-2 (указана служба МТ) и III-5 (указана служба ОТ). Порядок подготовки к работе: 1. Изучить службы мультимедийных и обычных таймеров для Microsoft Windows. 2. Согласно заданному варианту, руководствуясь положениями и примерами раздела 2, написать программу, соответствующую поставленным задачам. 19 Отчет по лабораторной работе должен содержать: 1. Вариант индивидуального задания с описанием требований к срабатыванию таймеров. 2. Коды программ, соответствующие решению поставленных индивидуальных задач для служб мультимедийных и обычных таймеров, аналогично кодам программ, приведенных в подразделах 1 и 2. 3. Временные диаграммы работы. 4. Выводы по проделанной работе. 20 Варианты индивидуального задания Вариант I Программное приложение должно в моменты времени t1, t2, t3 с точностью r1, r2, r3 запустить на выполнение процедуры p1, p2, p3. Моменты запуска и точность представлены в табл.1 в миллисекундах (max – максимальная точность, X – точность не указана). Номера вызываемых процедур соответствуют номерам строк табл. 4. В графе «Служба» указано: МТ – мультимедийные таймеры, ОТ – обычные таймеры. Таблица № 1 № t1 t2 t3 r1 r2 r3 p1 p2 p3 Служба 1 9000 7000 1500 50 35 max 1 5 6 МТ 2 400 6800 8900 max 35 35 2 3 4 МТ 3 6100 2300 2700 X X X 1 3 4 ОТ 4 2300 2500 6000 X X X 2 5 6 ОТ 5 2600 6500 7200 50 30 35 1 2 3 МТ 6 8400 8700 4500 X X X 2 3 4 ОТ 7 3200 5700 5900 X X X 1 3 6 ОТ 8 5700 6000 750 30 25 max 1 2 6 МТ 9 3000 900 7600 X X X 3 4 6 ОТ 10 7600 3300 4400 40 5 45 1 2 3 МТ 11 1300 6500 2200 X X X 2 3 6 ОТ 12 4700 3400 3300 45 20 max 1 2 4 МТ 13 6900 3700 2900 10 35 max 1 3 5 МТ 14 8600 2300 2500 X X X 3 4 6 ОТ 15 8100 3600 5900 30 max 40 1 4 5 МТ Вариант II Через каждые t1 миллисекунд с точностью r1 необходимо n раз запускать процедуру p1, после выполнения которой, спустя t2 миллисекунд с точностью r2, должна запуститься процедура p2. Исходные данные индивидуальных вариантов представлены в табл. 2. Номера вызываемых процедур соответствуют номерам строк табл. 4. 21 Таблица № 2 № t1 t2 r1 r2 n p1 p2 Служба 1 9600 4800 40 50 7 1 4 МТ 2 8800 2900 30 20 4 2 3 МТ 3 7500 6900 X X 3 2 5 ОТ 4 7800 1800 max 40 7 3 4 МТ 5 5300 2800 45 max 6 5 6 МТ 6 1400 700 X X 5 2 4 ОТ 7 7100 3300 35 50 9 1 6 МТ 8 5800 4400 X X 6 1 3 ОТ 9 3100 2400 X X 10 4 5 ОТ 10 4800 1900 20 35 9 2 5 МТ 11 3600 1100 X X 3 3 6 ОТ 12 3900 2700 max 5 7 1 3 МТ 13 8300 1700 X X 4 5 6 ОТ 14 9100 1200 X X 8 1 2 ОТ 15 4200 1300 25 max 6 3 5 МТ Вариант III Программное приложение должно в момент времени t1 с точностью r1 запустить на выполнение функцию p1. Если функция p1 вернёт значение z1, то от этого момента через интервал времени t2 с точностью r2 должна выполниться процедура p2, иначе, если функция p1 вернёт значение z2, то спустя t3 миллисекунд с точностью r3 должна выполниться процедура p3. Повторное выполнение функции p1 должно прекратиться, если она вернёт значение z3 либо после n срабатываний этой функции. Исходные данные индивидуальных вариантов представлены в табл. 3. Номера вызываемых процедур соответствуют номерам строк табл. 4. 22 Таблица № 3 p3 z1 z2 z3 n 4 6 0 3 - 5 Служба МТ 1 3 4 1 2 4 - ОТ max max 1 5 6 4 1 2 - МТ 2 3 4 2 3 - 7 МТ X 4 5 6 0 1 3 - ОТ 7500 6300 4500 max 35 45 1 2 3 0 2 3 - МТ 7 9700 9100 4300 X X X 3 5 6 2 4 - 7 ОТ 8 8900 7000 3800 X X X 1 4 5 0 1 - 5 ОТ 9 6600 4200 1800 50 25 max 2 3 6 1 3 4 - МТ 10 9900 5900 4900 max 5 10 2 4 5 3 0 - 7 МТ 11 9000 8000 2200 X X X 1 5 6 0 4 2 - ОТ 12 8300 4900 4200 40 10 5 1 2 5 2 3 - 9 МТ 13 8100 7900 7400 45 35 5 1 4 6 0 1 2 - МТ 14 9200 3400 1000 X X X 1 2 5 1 2 4 - ОТ 15 8500 3400 1200 45 5 max 4 5 6 0 3 - 5 МТ № t1 t2 t3 r1 r2 r3 p 1 p2 1 6200 5100 4900 25 30 max 2 2 8500 6500 4600 X X X 3 3200 2700 2900 5 4 5400 4100 3200 max 20 10 5 9900 6000 5500 X X 6 Коды наполнения вызываемых процедур p1, p2, p3. (2) Таблица № 4 № 1 1 2 Код 2 Var I, J: Cardinal; begin I := 1 + Random(30);J := GetTickCount; While GetTickCount - J < I do; result := random (5); end; Var I, J: Cardinal; begin I := 1 + Random(40);J := GetTickCount; While GetTickCount - J < I do; result := random (5); end; 23 Окончание табл. 4 1 3 4 5 6 2 Var I, J: Cardinal; begin I := 1 + Random(50);J := GetTickCount; While GetTickCount - J < I do; result := random (5); end; Var I, J: Cardinal; begin I := 1 + Random(60);J := GetTickCount; While GetTickCount - J < I do; result := random (5); end; Var I, J: Cardinal; begin I := 1 + Random(70);J := GetTickCount; While GetTickCount - J < I do; result := random (5); end; Var I, J: Cardinal; begin I := 1 + Random(80);J := GetTickCount; While GetTickCount - J < I do; result := random (5); end; Контрольные вопросы 1. 2. 3. 4. Перечислите основные службы таймеров в ОС Microsoft Windows. Классификация и особенности таймеров в Windows. Принципы работы с мультимедийными таймерами. Принципы работы с обычными таймерами. 24 Лабораторная работа №2 РЕШЕНИЕ ЗАДАЧИ «ВЗАИМОИСКЛЮЧАЮЩИЙ ДОСТУП» С ПОМОЩЬЮ МЕХАНИЗМА СЕМАФОРОВ Цель работы: 1. Получить представление об использовании механизма семафоров для решения проблемы взаимоисключающего доступа. 2. Ознакомиться с реализацией механизма семафоров в операционной системе Windows. Теоретический материал Постановка задачи В лабораторной работе предлагается реализовать программно процедуру взаимоисключающего доступа к критическому объекту. Под критическим объектом понимается некоторая переменная, принимающая целые положительные значения. Присвоением определенных значений указанной переменной управляют два потока, работающие в рамках одного приложения. Требуется исключить одновременные обращения потоков к критическому объекту, при этом необходимо, чтобы задержка любого потока вне его критической секции не влияла на работу другого потока. Решение должно быть симметричным для обоих потоков (оба потока равноправны). Семафоры в Windows Семафоры и операции над ними, как правило, реализуются в ядре операционной системы, где осуществляется управление сменой состояния процессов. Создание семафора Для создания семафора приложение должно вызвать функцию CreateSemaphore. Синтаксис функции CreateSemaphore: HANDLE CreateSemaphore(lpsa: LPSECURITY_ATTRIBUTES; cSemInitial: LONG; cSemMax: LONG; lpszSemName: LPTSTR); Параметр lpsa задаёт дескриптор защиты, отличный от стандартного дескриптора. Значение параметра lpsa NULL указывает на стандартные атрибуты защиты. Через параметры cSemInitial и cSemMax передаются, соответственно, начальное и максимальное значения счетчика, связанного с создаваемым семафором. Начальное значение счетчика cSemInitial должно быть больше или равно нулю и не должно превосходить максимальное значение счетчика, передаваемое через параметр cSemMax. 25 Параметр lpszSemName присваивает имя в виде строки. В дальнейшем это имя используется для получения описателя семафора из других процессов. Если семафор используется только для синхронизации задач, созданных в рамках одного приложения, можно создать безымянный семафор, указав в качестве параметра lpszSemName функции CreateSemaphore значение NULL. В том случае, когда необходимо синхронизовать задачи разных процессов, следует определить имя семафора. В случае удачного создания семафора функция CreateSemaphore возвращает его идентификатор. В случае возникновения ошибки возвращается значение NULL, при этом код ошибки можно узнать при помощи функции GetLastError. Так как имена семафоров доступны всем приложениям в системе, возможно возникновение ситуации, когда приложение пытается создать семафор с уже использованным именем. При этом новый семафор не создается, а приложение получает идентификатор для уже существующего семафора. Если возникла такая ситуация, функция GetLastError, вызванная сразу после функции CreateSemaphore, возвращает значение ERROR_ALREADY_EXISTS. Уничтожение семафора Для уничтожения семафора необходимо передать его идентификатор функции CloseHandle. Заметим, что при завершении процесса все созданные им семафоры уничтожаются автоматически. Увеличение значения счетчика семафора Для увеличения значения счетчика семафора приложение должно использовать функцию ReleaseSemaphore. Синтаксис вызова функции ReleaseSemaphore: BOOL ReleaseSemaphore(Semaphore: HANDLE; cRelease: LONG; lplPrevious LPLONG); Функция ReleaseSemaphore увеличивает значение счетчика семафора, идентификатор которого передается ей через параметр Semaphore, на значение, указанное в параметре cRelease. С помощью функции ReleaseSemaphore счётчик ресурсов, принадлежащий семафору, можно увеличивать, более чем на единицу единовременно. Параметр cRelease определяет, какими порциями должен освобождаться семафор. Заметим, что через параметр cRelease можно передавать только положительное значение, большее нуля. При этом, если в результате увеличения новое значение счетчика должно будет превысить максимальное значение, заданное при создании семафора, функция ReleaseSemaphore возвращает признак ошибки и не изменяет значение счетчика. 26 Предыдущее значение счетчика, которое было до использования функции ReleaseSemaphore, записывается в переменную типа LONG. Адрес этой переменной передается функции через параметр lplPrevious. Если функция ReleaseSemaphore завершилась успешно, она возвращает значение TRUE. При ошибке возвращается значение FALSE. Код ошибки в этом случае можно определить, как обычно, при помощи функции GetLastError. Функция ReleaseSemaphore используется обычно для решения двух задач. Во-первых, с помощью этой функции задачи освобождают ресурс, доступ к которому регулируется семафором. Они могут делать это после использования ресурса или перед своим завершением. Во-вторых, эта функция может быть использована на этапе инициализации мультизадачного приложения. Создавая семафор с начальным значением счетчика, равным нулю, главная задача блокирует работу задач, выполняющих ожидание этого семафора. После завершения инициализации главная задача с помощью функции ReleaseSemaphore может увеличить значение счетчика семафора до максимального, в результате чего известное количество ожидающих задач будет активизировано. Уменьшение значения счетчика семафора В программном интерфейсе операционной системы Windows нет функции, специально предназначенной для уменьшения значения счетчика семафора. Этот счетчик уменьшается, когда задача вызывает функцию ожидания WaitForSingleObject. Синтаксис вызова функции WaitForSingleObject: DWORD WaitForSingleObject(Semaphore: HANDLE; Milliseconds: DWORD); Функция WaitForSingleObject уменьшает значение счетчика семафора, идентификатор которого передается ей через параметр Semaphore. Параметр Milliseconds указывает интервал ожидания в миллисекундах. Если параметру Milliseconds присвоено значение infinite, то интервал ожидания освобождения принимается равным бесконечности. Если задача вызывает несколько раз функцию ожидания для одного и того же семафора, содержимое его счетчика каждый раз будет уменьшаться. Функция WaitForSingleObject используется, чтобы занять ресурс. Определение текущего значения счетчика семафора Единственная возможность определения текущего значения счетчика семафора заключается в увеличении этого значения функцией ReleaseSemaphore. Значение счетчика, которое было до увеличения, 27 будет записано в переменную, адрес которой передается функции ReleaseSemaphore через параметр lplPrevious. Заметим, что в операционной системе Windows не предусмотрено средств, с помощью которых можно было бы определить текущее значение семафора, не изменяя его. В частности, нельзя задать функции ReleaseSemaphore нулевое значение инкремента lplPrevious, так как в этом случае указанная функция просто вернет соответствующий код ошибки. Создание потоков в Windows Потоки создаются и выполняются в родительском адресном пространстве, то есть в адресном пространстве процесса, создавшего потоки, но каждому потоку даётся свой стек. Потоки могут обмениваться друг с другом информацией без ограничений. Для создания потока используется функция CreateThread. Синтаксис функции CreateThread: HANDLE CreateThread(lpsa: LPSECURITY_ATTRIBUTES; cbStack: DWORD; lpStartAddr: LPTHREAD_START_ROUTINE; lpvThreadParam: LPVOID; fdwCreate: DWORD; lpIDThread: LPVOID); Параметр lpsa – это указатель на структуру, задающую нестандартные дескрипторы защиты. Значение параметра lpsa NULL указывает на стандартные атрибуты защиты. Параметр cbStack указывает размер стека для создаваемого потока. Если указать нулевое значение, то размер стека по умолчанию будет равен 1Мб, причём адресное пространство стека будет лишь зарезервировано. Реально поток обычно получает одну страницу оперативной памяти и при её переполнении происходит выделение дополнительной памяти. Ограничение на резервирование памяти под стек необходимо для прекращения деятельности функций с бесконечной рекурсией. Параметр lpStartAddr указывает адрес функции потока, с которой должен будет начать работу создаваемый поток. Синтаксис функции должен иметь следующий вид: DWORD ThreadFunc(lpvThreadParam: LPVOID) { … return(dwResult); } Параметр, получаемый данной функцией ThreadFunc, есть четвёртый параметр функции создания потока lpvThreadParam. Через него можно передать некоторое 32-битное значение, или просто данные или указатель на структуру, содержащую расширенную информацию. 28 Параметр fdwCreate – дополнительные флаги управления потоком. Флаг – CREATE_SUSPENDED указывает системе создать поток и приостановить его выполнение. Возобновить выполнение потока может родительский поток вызовом функции ResumeThread(HANDLE), передав ей в качестве параметра описатель, возвращённый функцией создания потока. Вообще родительский поток может в любое время остановить поток вызовом функции SuspendThread(HANDLE), и возвратить его к выполнению приведённой выше функцией ResumeThread(HANDLE). В структуре управления потоком есть переменная SuspendCount, в которой запоминается количество вызовов SuspendThread, а вызов ResumeThread уменьшает эту переменную на единицу. Как только она примет нулевое значение, поток возобновляет выполнение. Последний параметр lpIDThread – адрес переменной типа двойного слова, в которую будет записан уникальный идентификатор потока. Описание программы «взаимоисключающий доступ» Механизм семафоров, обеспечивающий взаимоисключающий доступ, реализуется в среде Borland (Embarcadero) Delphi операционной системы Windows. Проект (Semafore.dpr) состоит из двух модулей: основного модуля Main.pasи модуля WriterThread.pas. В основном модуле необходимо прописать процедуры создания потоков и их начальные параметры, а также процедуры, обеспечивающие отображение работы программы (графику) в двух режимах: «Числовые параметры» и «Диаграмма работы». При инициализации основного модуля создается семафор Semaphore с параметрами: начальное значение счётчика равно 1, максимальное значение счётчика равно 1. Для создания семафора вызывается функция Semaphore := CreateSemaphore(nil, 1,1,'NameSemaphore'). При нажатии на кнопку «Создать потоки» создаются два потока, которые, получив соответствующие параметры, запускаются и работают, пока не выполнят функцию Execute (основная функция потока). Модуль WriterThread.pas имитирует работу потоков. Работа потоков должна быть оформлена в виде процедур Thread1.Execute и Thread2.Execute. Процедура Execute для каждого потока содержит следующие этапы: 1. Имитация работы потока вне критической секции в течение заданного промежутка времени. 2. Ожидание освобождения семафора (время ожидания вычисляется и выводится в поле «числовые параметры»). 3. Работа с критическим объектом. Время работы задается. 4. Освобождение семафора. 29 Примерный фрагмент модуля программы, содержащий процедуру Execute, представлен на рис. 1. Результаты работы программы отображаются в просмотровом окне в режимах "Числовые параметры" (рис. 2) и "Диаграмма работы" (рис. 3). procedure Thread1.Execute; var i,j,jj,kk: integer; x1,x2:systemtime; begin i:=1; randomize; repeat jj:=round(random(FSleepDuration)); - 1-й этап sleep(jj); getsystemtime(x1); WaitForSingleObject(Semaphore , infinite); - 2-й этап getsystemtime(x2); UpdateGraphic(1,round(jj/k),30,cllime); kk:=(x2.wMinute-x1.wMinute)*60000+(x2.wSecondx1.wSecond)*1000+(x2.wMilliseconds-x1.wMilliseconds); if kk>0 then UpdateGraphic(1,round(kk/k),20,clblue); if pos[3].X<pos[1].X then UpdateGraphic(3,(pos[1].X-pos[3].X),40,clYellow); j:=round(random(FWorkSemafor)); sleep(j); - 3-й этап x:=round(random(100))+1; inc(i); WriteText:='---1---'+IntToStr(Self.ThreadID)+'-----X='+inttostr(x)+'------Tраб.='+inttostr(jj)+'-------Tраб.кр.='+inttostr(j)+'-------Tож.='+inttostr(kk); UpdateGraphic(1,round(j/k),40,clred); UpdateGraphic(3,round(j/k),20,claqua); ReleaseSemaphore(Semaphore, 1, nil); -4-й этап Synchronize(UpdateListBox); until i>10; end; Рис. 1 30 В программе, отображающей процедуру Execute, используются переменные целого типа i, j, jj, kk, а также переменные системного времени (systemtime) x1 и x2. Переменная i=10 задает для каждого потока по 10 обращений к критической секции. Работа потока вне критической секции имитируется обращением к генератору равномерно распределенных случайных чисел FSleepDuration (1-й этап). Число, выработанное этим генератором, присваивается переменной jj, которая определяет длительность работы потока перед обращением к критическому объекту (sleep(jj)). В режиме вывода числовых параметров (рис. 2) значение переменной jj отображает переменная Tраб.. Момент обращения к семафору фиксируется в переменной x1 вызовом функции системного времени: getsystemtime(x1). Для доступа к критическому объекту (2-й этап) производится вызов функции WaitForSingleObject(Semaphore , infinite). Время ожидания освобождения семафора принимается равным бесконечности. Момент срабатывания семафора, обеспечивающего доступ процесса к критическому объекту, фиксируется в переменной x2 вызовом функции системного времени: getsystemtime(x2). Разность моментов времени x1 и x2 определяет интервал времени ожидания освобождения критической секции. Эта разность присваивается переменной kk и отображается в режиме "Числовые параметры" с помощью переменной Tож. (рис. 2). Рис. 2 Работа потока с критическим объектом (3-й этап) имитируется обращением к генератору равномерно распределенных случайных чисел FWorkSemafor. Число, выработанное этим генератором, присваивается 31 переменной j, которая определяет длительность работы потока с критической секцией. В режиме "Числовые параметры" (рис. 2) значение переменной j отображает переменная Tраб.кр. Новое значение переменной x, присваемое потоком критическому объекту, вырабатывается обращением к генератору равномерно распределенных случайных чисел в диапазоне от 1 до 100. Освобождение семафора (4-й этап) осуществляется вызовом функции ReleaseSemaphore(Semaphore, 1, nil). Фрагмент числовых характеристик, отображаемых в окне режима «Числовые параметры», представлен в табл. 1. Таблица 1 № потока 1 2 Идентификаторы потоков в операционной системе 916 864 X Tраб. Tраб.кр. Tож. 31 8 511 338 460 85 20 120 Здесь 916, 864 – текущие идентификаторы потоков в операционной системе, X – текущее значение защищённой переменной, Tраб. – время работы потока вне критической секции, Tраб.кр. – время работы потока в критической секции, Tож. – время ожидания освобождения семафора. Рис. 3. Фрагмент временной диаграммы Варианты индивидуального задания Предполагается, что каждый из двух взаимодействующих с критическим объектом потоков должен обратиться к нему последовательно три раза, при этом длительности работы потоков вне критической секции (Tраб) и в критической секции (Tраб.кр.) задаются вариантом индивидуального задания. Вариантом индивидуального задания (табл. 2) также задается значение числа X , записываемое процессом при его работе с критической секцией. 32 Таблица 2 № варианта 0 1 2 3 4 5 6 7 8 9 Траб 700 100 200 50 100 200 135 325 550 300 200 100 250 220 50 100 50 40 225 125 100 90 25 300 700 200 300 500 125 100 1 процесс Траб.кр. 100 150 50 30 75 100 100 200 300 100 50 75 120 125 25 50 100 20 25 40 60 50 10 20 200 100 150 100 50 25 Х 10 20 30 15 20 45 13 26 39 30 40 60 60 25 15 75 80 95 40 20 10 12 32 43 56 7 6 8 14 56 Траб 750 125 80 15 25 450 200 400 500 350 100 150 270 200 40 125 75 50 200 20 50 100 10 300 800 100 250 550 40 75 2 процесс Траб.кр. 300 250 400 100 200 300 175 275 250 75 75 50 100 55 10 75 25 20 100 20 50 30 10 50 300 50 125 150 20 60 Х 25 50 75 33 44 55 23 33 43 56 76 36 12 15 16 45 55 75 80 90 10 14 24 34 55 66 77 57 37 87 Порядок выполнения работы Нарисовать временные диаграммы работы потоков согласно индивидуальному варианту (см. рис. 3) и на их основании определить времена ожидания освобождения семафора. Подготовить листинги 33 программ procedureThread1.Execute и procedure Thread2.Execute, соответствующие индивидуальному варианту работы потоков. Изменения состоят в следующем: 1. Убирается цикл работы (переменная i и связанные с ней операторы). 2. При работе потока вне критической секции (1-й этап, рис. 1) переменной jj присваивается значение Траб, соответствующее длительности работы потока вне критической секции. 3. При работе потока с критической секцией (3-й этап, рис.1) переменной j присваивается значение Траб.кр., соответствующее длительности работы потока в критической секции в первом цикле, переменной xприсваивается заданное значение защищенной переменной. 4. Программа Execute должна повторять действия 2 и 3 три раза и включать соответствующие процедуры отображения хода работы. Вызвать файл Writer Thread.pas, содержащий исходный модуль, задающий работу потоков, и внести изменения в процедуры Thread1.Execute и Thread2.Execute. Откомпилировать измененный исходный модуль WriterThread.pas. Запустить на выполнение основной модуль Main.pas. Результаты решения просмотреть в режиме "Числовые параметры" и сопоставить с заданными условиями работы. Сделать выводы. Отчет по лабораторной работе должен включать: 1) Вариант индивидуального задания. 2) Временные диаграммы работы потоков, построенные при домашней подготовке. 3) Листинги фрагментов программ Thread1.Execute и Thread2.Execute, содержащие изменения согласно индивидуальному варианту задания. 4) Результаты решения, представленные в режиме «Числовые параметры». 5) Выводы по работе. Контрольные вопросы Определите термины «ресурс» и «разделяемый ресурс». В чем состоит проблема взаимного исключения? Какие участки программ называется критическими секциями? Перечислите требования, которым должны удовлетворять механизмы взаимоисключения. 5. Что понимается под состоянием взаимоблокировки? 1. 2. 3. 4. 34 6. Что представляет собой семафор? 7. Определите P-операцию над семафором. 8. Определите V-операцию над семафором. 9. Что означает неделимость P- и V-операций? 10. Как защитить критическую секцию семафором? 11. Какие действия выполняются операционной системой, если в коде задачи встречаются операции Р и V? 12. Как создается семафор в операционной системе Windows? 13. Как в операционной системе Windows производится увеличение и уменьшение значения счетчика семафора? 14. В чем разница между процессами и потоками? 15. Как решается задача взаимоисключения с помощью механизма семафора? 35 Лабораторная работа 3 УСТАНОВКА СИСТЕМЫ QNX6. РАБОТА В ОБОЛОЧКЕ PHOTON Цель работы – изучение процессов установки ОС QNX и среды разработки Momentics, а также изучение графической оболочки Photon microGUI. Теоретический материал Установка QNX Операционная система QNX является системой жесткого реального времени и ориентирована прежде всего на рынок встраиваемых систем. Для коммерческого использования доступны три семейства: QNX2 – предназначена для применения с процессорами Intel 286 и мало распространена в России, т. к. до 1991г. она была запрещена к вывозу из США; QNX4 – может применяться на процессорах не ниже Intel 386, последняя версия – 4.25, вышла в 2003 г.; QNX6 (или QNX Neutrino) может использоваться не только на x86совместимых ЭВМ, но и на PowerPC, MIPS32, ARM, Xscale и др. Она полностью поддерживает стандарт POSIX, последняя версия QNX Neutrino – 6.4. Комплект разработчика, в который входит QNX Neutrino и средства разработки приложений, имеет название QNX Momentics. В настоящее время выпущено несколько дистрибутивов QNX Momentics: Non-Commercial Edition (NC) – бесплатный ознакомительный комплект разработчика для некоммерческого использования; Standart Edition (SE) – пакет разработчика для коммерческого применения, позволяет формировать целевые системы для различных платформ; Professional Edition (PE) – расширенный пакет разработчика для коммерческого применения, дополненный интегрированной средой разработки Eclipse и расширенной базой данных примеров исходных программ и рядом дополнительных компонентов. Для использования QNX Momentics PE требуются: процессор не ниже Pentium III 700 Мгц (желательно Pentium IV не ниже 2 Ггц); ОЗУ – не менее 256 Мбайт (желательно 512 Мбайт); свободное дисковое пространство – не меньше 2 Гбайт. 36 QNX6 может быть установлена двумя способами – в отдельный раздел жесткого диска или в среду виртуальной машины (например, VMWare). При установке в собственный раздел жесткого диска перед началом установки необходимо выполнить дефрагментацию жесткого диска и с помощью любого дискового редактора (например Acronis DiskEditor или PartitionMagic) уменьшить размер одного из существующих разделов диска. Порядок инсталляции системы в отдельный раздел диска: провести загрузку компьютера с дистрибутивного диска QNX; выбрать вариант инсталляции QNX в отдельный раздел диска (F3 – Install QNX to a new disk partition); принять условия лицензии после появления на экране информации о лицензировании (F1 - accept); выбрать место размещения раздела QNX на диске (F1 – раздел может находиться в любом месте диска, F2 – раздел QNX должен находиться в пределах 8,4 Гбайт); выбрать объем раздела QNX (F1 – 100 % свободного места на диске; F2 – 50 %, F3 – 25 %, F4 – 12,5 %); выбрать вариант загрузчика операционной системы (F1 – загрузчик QNX в случае ее установки за пределами 8,4 Гбайт, F2 - загрузчик QNX в случае ее установки в пределах 8,4 Гбайт, F3 – оставить прежний загрузчик). Установка в среду виртуальной машины VMWare Server проводится следующим образом: – создать виртуальную машину QNX6.3, выбрав в предлагаемом списке операционных систем пункт «Other» и задав размер минимального дискового пространства не менее 2 Гб; – установить QNX6.3 c установочного диска или из предварительно созданного iso-образа этого диска, рекомендуемая последовательность ответов – F3, F1, F1, F1, F1, F3, F2, на все вопросы отвечаем Y. После завершения установки и перезагрузки необходимо: настроить параметры видеорежима (разрешение монитора и глубину цвета) и частоту обновления экрана; установить возможность ввода символов кириллицы для приложений графической оболочки Photon (Localization/Keyboard); вручную внести изменения в файл /etc/system/trap/.KEYBOARD.myname так, чтобы он содержал следующие строки en_US_101.kbd ru_RU_102.kbd 37 здесь myname – имя Вашего компьютера. После этого в системе будет зарегистрирован только один пользователь с именем root, не имеющий пароля. настроить параметры сетевого подключения (например, при работе в компьютерном классе необходимо на закладке Devices указать любой адрес из сети 192.168.200.0..24, а в качестве шлюза и Name-сервера на закладке Network указать адрес 192.168.200.254) и провести его проверку с помощью команды ping. настроить виртуальную машину для работы с USB – устройствами. Виртуальная машина позволяет гостевой ОС работать с реальными USB устройствами. Для этого необходимо дополнить файл конфигурации виртуальной машины, расположенный в указанном при инсталляции QNX каталоге (C:\Virtual Machines\%ИмяВиртМашины%\%ИмяВиртМашины%.vmx) следующими строками: usb.present = “TRUE” usb. autoConnect.device0 = “” uchi.newcore = FALSE Обратите внимание: некоторые из этих строк уже могут быть в Вашем файле конфигурации и при их повторении будет возникать ошибка. Подключение USB-устройства проводится в меню виртуальной машины выбором пункта VM/Removable Devices, при этом указанное устройство отключается от ОС Windows. Проверить подключение устройства можно с помощью команды mount. Работать в QNX можно двумя способами: в командной строке и в графической оболочке Photon. При работе в оболочке командная строка все равно доступна с помощью программы псевдотерминала pterm. Обратите внимание: в командном режиме работа с символами кириллицы невозможна, для решения этой проблемы необходимо использовать пакет SWD Cyrillic Pack. Работа в командном режиме Интерфейс командной строки является стандартным UNIXинтерфейсом и обеспечивается командным интерпретатором Korn Shell. При загрузке системы командный интерпретатор Korn Shell выполняет команды, содержащиеся в файле /etc/profile, затем команды, содержащиеся в файле $HOME /.profile (разумеется, если эти файлы существуют и доступны для чтения). Для того, чтобы при входе в систему автоматически устанавливались необходимые переменные, следует 38 откорректировать файл .profile, находящийся в вашем домашнем каталоге. Для этого можно воспользоваться редактором vi или его клонами (vim, elvis). Редактор запускается следующим образом: vi имя_файла. Если файл с именем имя_файла не существует, то он будет создан. Редактор vi может находиться в одном из двух режимов: в режиме ввода или в командном режиме. После запуска редактор находится в командном режиме. Переход из командного режима в режим ввода осуществляется нажатием клавиши <I> (input) или <А> (add). После нажатия клавиши <I> текст будет вводиться с текущей позиции курсора, а после нажатия клавиши <А> текст будет вводиться со следующей позиции после курсора. Возврат в командный режим осуществляется нажатием клавиши <Esc>. Сводка по основным командам редактора приведена в таблице 8 Таблица 8 Команда Назначение Позиционирование курсора H Перейти в начало файла L или G Перейти в конец файла {n}G Перейти на строку с номером n Работа с буфером yy Скопировать текущую строку в буфер {n}yy Скопировать n строк, начиная с текущей, в буфер dd Удалить текущую строку в буфер {n}dd Удалить n строк, начиная с текущей, в буфер p Вставить содержимое буфера после текущей строки P Вставить содержимое буфера перед текущей строкой Редактирование текста o Вставка пустой строки после текущей строки O Вставка пустой строки перед текущей строкой i Вставка произвольного текста перед курсором a Вставка произвольного текста после курсора cw Замена слова dw Удаление слова {n}dw Удаление n слов u Отмена действия последней команды Команды режима командной строки :w Сохранить изменения :w имя_файла Сохранить изменения в указанном файле :q Выход :q! Выход без сохранения :s/строка_1/строка_2 Поиск и замена строки «строка_1» на «строка_2» :help Вызов справки 39 Работа в графической оболочке Photon microGUI Графический интерфейс Photon содержит окна, поля редактирования, флажки, кнопки и напоминает интерфейс системы Windows. В правой части главного окна находится панель быстрого запуска приложений Shelf. Оболочка дает возможность удобного вызова следующих программ: файловый менеджер (pfm); текстовый редактор (ped); псевдотерминал для работы в режиме командной строки (pterm); программу просмотра документации (helpviewer); программу конфигурирования; интегрированную среду программирования. При запуске Photon выполняет скрипт, находящийся в файле $HOME/.ph/phapps, в котором можно перечислить приложения для автоматического запуска. Этот файл, конечно, должен иметь атрибут «исполняемый» для владельца. Интерфейс файлового процессора является классическим двухоконным. Для настройки параметров используется меню Edit/Preferences (F10). При этом можно задать необходимость вывода скрытых файлов (Hide ‘dot’ Files), подтверждение на выполнение операций копирования, перемещения или удаления файлов и т. д. Для настройки реакции на нажатие ENTER имеется меню Edit/ Associations (F11) , где в появившемся окне можно задать обработчики для открытия (Open), просмотра(View) или редактирования (Edit) файлов, имена которых соответствуют определенным шаблонам. Атрибуты любого файла можно просмотреть через пункт Inspect контекстного меню или F9. Текстовый редактор ped имеет стандартный набор функций: редактирование текста, работа с буфером, поиск и замена строк и т. д. В режиме настройки можно установить кодовую страницу, параметры шрифтов, режим переноса строк. Основы администрирования Информация о рабочих группах и пользователях системы QNX содержится в нескольких файлах (см. таблицу 9): 40 Таблица 9 Имя файла /etc/group /etc/passwd /etc/shadow Назначение Информация о рабочих группах Информация о пользователях Информация о паролях Структура записи файла groupname: reserved: group: member username: haspw: userid: group: comment: homedir: shell username: password: lastch: minch: maxch: warn: inact: expire: reserved Здесь обозначено: groupname – имя группы; reserved – зарезервировано для дальнейшего использования; group – имя группы; member – список имен пользователей, принадлежащих к данной группе; username – имя пользователя; haspw – признак наличия пароля (если поле не пустое, то в файле /etc/shadow хранится пароль); userid – идентификатор пользователя; comment – любая символьная строка, не содержащая символ «:»; homedir – имя домашнего каталога пользователя; shell – имя командного интерпретатора, вызываемого при входе пользователя; password – зашифрованный пароль пользователя; lastch – время последней модификации; minch – минимальное количество дней для модификации; maxch - максимальное количество дней для модификации; warn – количество дней для предупреждения; inact – максимальное количество дней между входами в систему; expire – дата истечения доступа. Все поля записей являются позиционными, т. е. если какое-либо поле, кроме последнего, не указывается, то символ – разделитель (двоеточие) должен обязательно присутствовать. Добавление новой группы проводится путем редактирования файла /etc/group с помощью редактора vi. Добавление нового пользователя проводится командой passwd, которая предложит администратору ввести регистрационные данные для этого пользователя. Удаление пользователя из системы проводится путем простого редактирования файлов /etc/passwd и /etc/shadow. При внесении изменений в файлы администрирования старые версии этих файлов сохраняются в файлах /etc/opasswd и /etc/oshadow. 41 Порядок выполнения работы 1. Дефрагментировать жесткий диск компьютера. 2. Установить QNX6 в созданный раздел размером не менее 2 Гбайт или в виртуальную машину VMWare. 3. Запустить QNX Installer и установить с дистрибутивного диска программное обеспечение разработчика QNX Momentics из репозитария fs/cd0/rep621. 4. С помощью программы конфигурирования настроить графический режим монитора (Graphics) , установить параметры сетевого соединения (Network) и включить возможность работы с кириллицей (Language). 5. С помощью текстового редактора vi создать текстовый файл и выполнить с ним все действия согласно табл. 8. 6. Войти в графическую оболочку Photon и выполнить следующие действия: 6.1 Установить для файлового процессора следующие режимы: – включить режим показа скрытых файлов; – для файлов типа .doc, .txt обеспечить при нажатии ENTER загрузку редактора ped. 6.2. Выполнить с помощью файлового процессора типовые операции над каталогами и файлами (создание, копирование, перемещение, удаление). 7. Создать с помощью редактора Ped текстовый файл и выполнить с ним все типовые операции редактирования текста (поиск, замена, работа с буфером и т.д.). Изучить настройки редактора. 8. Создать группу пользователей mygroup путем редактирования файла /etc/group и добавить в нее нового пользователя newuser, присвоив ему любой пароль. 9. Войти в систему под именем newuser и определить имя текущего каталога. 10. Удалите пользователя newuser из системы и попробуйте войти под его именем. Контрольные вопросы 1. Назовите основные этапы установки QNX. 2. Какие версии QNX Momentics существуют и чем они отличаются друг от друга. 3. Основные команды редактора vi. 4. Основные компоненты графической оболочки Photon. 5. Назначение и состав файлов администрирования системы. 42 Лабораторная работа 4 КОМАНДЫ ОПЕРАЦИОННОЙ СИСТЕМЫ QNX Цель работы – изучение командного режима ОС QNX Теоретический материал Общие сведения Все команды QNX исполняются командным интерпретатором и делятся на две группы – внутренние и внешние. Внутренние команды выполняются непосредственно интерпретатором, а внешние команды реализуются программами, поставляемыми вместе с операционной системой в виде отдельных исполняемых файлов. Определить тип команды можно с помощью команды type. Система QNX содержит несколько командных интерпретаторов (shell): Korn Shell (ksh) – стандартный интерпретатор; Embedded Shell (esh) – встраиваемый интерпретатор; Micro embedded Shell (uesh) – малый встраиваемый интерпретатор; Fat embedded Shell (fesh) – расширенный встраиваемый интерпретатор. Встраиваемые интерпретаторы предназначены для использования в системах реального времени с ограниченными ресурсами. Набор команд QNX аналогичен набору команд ОС LINUX, использующей интерпретаторы sh, bash и csh; команды могут содержать аргументы и ключи. Синтаксис команды удобно представить в обобщенной форме: <команда>::= имя команды [ключи] [аргументы] Имя команды указывает действие, которое будет выполняться, аргументы – это имена объектов, над которыми будет выполняться действие, а ключи уточняют действие команды. В качестве аргументов команды используются имена файлов и каталогов, в качестве ключей – одиночные символы, перед которыми обычно записывается символ «-» (дефис). Обязательным элементом в командной строке является только имя команды, аргументы и ключи могут отсутствовать. Ключи и аргументы команды разделяются пробелами. 43 Файловая система Файловая система QNX, так же как и в LINUX, характеризует полную совокупность всех файлов, каталогов и устройств. Отдельные части файловой системы могут находиться на различных физических устройствах, например, на нескольких жестких и гибких дисках (или в различных частях одного диска). Соответствующие фрагменты (поддеревья файловой системы) монтируются (присоединяются) в единую файловую систему командой mount, после чего пользователь может обращаться к любым доступным файлам, при этом в имени никак не отражается устройство, на котором файл находится или создается. Имена каталогов и файлов в QNX являются регистрозависимыми, т. е. имена newfile и NEWFILE обозначают разные файлы. Имена всех файлов могут быть абсолютными или относительными. Абсолютное имя начинается от корневого каталога файловой системы root (обозначается символом «/»), а относительное имя – от текущего каталога. Имя файла или каталога не может превышать 255 символов, а полное имя (с учетом пути доступа) – не более 1023 символов. Если имя файла начинается с символа «.» (точка), такой файл называется скрытым и его характеристики не будут выводиться при просмотре содержимого каталогов. Каждый объект файловой системы (файл или каталог) имеет определенные права доступа для каждой категории пользователей. Информация о правах доступа выводится командой ls –l. При этом самый первый символ строки, соответствующей каждому файлу, обозначает тип файла (дефис – это обычный файл, d – каталог, l – символическая ссылка, b – блок – ориентированный файл ). Далее в строке идут три группы, состоящие из трех символов каждая: первая группа определяет права владельца, вторая – права группы пользователей, третья – права остальных пользователей. В каждой из этих групп первый символ определяет право доступа для чтения, второй – для записи, третий – для выполнения файла. Если в какой-либо позиции строки выводится символ «-» (дефис), то соответствующее право доступа отключено. Управление правами доступа проводится с помощью команды chmod. В качестве аргументов команды указываются права доступа (может использоваться цифровое или символьное указание прав доступа) и имя файла или каталога. При цифровом указании каждая группа символов представляется 8-ричным кодом, при символьном – используется следующая система обозначений: категории пользователей – владелец объекта (u), группа пользователей (g), остальные пользователи (o), все пользователи (a); 44 права доступа – чтение (r) , запись (w), выполнение (x). Например, для добавления права чтения и записи в файл file1 членам группы пользователей и права чтения для всех остальных пользователей можно использовать команды chmod g+rw,o+r file_1 или chmod 764 file_1. Управление процессами Процесс – это выполняющаяся программа. Для каждого процесса можно выделить образ и метаданные. Образ процесса – это совокупность кода (команды программы) и данных (операнды команд). Метаданные – информация о процессе, которая хранится в структурах данных ОС. Командный интерпретатор выполняет команды последовательно одна за другой. Если необходимо запустить какую-либо команду до того, как завершится предыдущая команда, то надо использовать фоновый режим исполнения процесса. Для этого в конце команды указывается символ “&” , например для поиска файлов можно использовать команду find / – name mydoc* -print & Просмотр списка выполняющихся процессов проводится командой ps, удаление заданного процесса – командой kill. Подробная информация по всем процессам и потокам, существующим в системе, может быть получена с помощью команды pidin или интерактивной программы psin (аргументы процессов; переменные окружения, приоритеты, выделенные адреса памяти и т.д.). В командной строке можно записывать несколько команд, используя следующие разделители: ; – команды исполняются последовательно; | – команды исполняются последовательно, данные с выхода предыдущей команды передаются на вход следующей команды; && – команды исполняются последовательно до тех пор, пока одна из команд не сможет выполниться; || – команды исполняются последовательно до тех пор, пока одна из команд выполнится. Стандартные переменные командного процессора При работе в командной строке важную роль играют глобальные переменные, которые хранятся в специальной области памяти, называемой областью окружения. Список некоторых из этих переменных приведен в таблице 10. 45 Таблица 10 Имя переменной LOGNAME HOME PATH LD_LIBRARY_PATH PWD PS1 SHELL ? $ Назначение Имя пользователя Имя домашнего каталога пользователя Перечень каталогов для поиска исполняемых файлов Перечень каталогов для поиска динамических библиотек Имя текущего каталога Вид первичного приглашения Путь к командному процессору Код завершения последней выполненной команды Идентификатор текущего процесса Для работы с переменными окружения предназначены следующие команды из таблицы 11: Таблица 11 Команда Назначение Set вывод содержимого области окружения export экспорт переменной в дочерние процессы командного процессора echo вывод значения переменных Пример set export TOWN=Новосибирск echo $TOWN Обратите внимание: при обращении к переменной окружения необходимо перед именем поставить символ «$». Команды QNX Информация по наиболее часто используемым командам QNX приведена в таблице 12. Таблица 12 Команда 1 Touch Cp Mv cat Назначение Пример Действие 2 3 4 1. Команды для работы с файлами Создание пустого touch myfile Создает файл myfile в файла текущем каталоге Копирование файла cp file1 file2 Копирует файл file1 в фал file2 в текущем каталоге Переименование или mv file_1 file_2 Переименование файла перемещение файла file_1 в файл file_2 в текущем каталоге Вывод содержимого cat file1 Выводит содержимое файла на экран или cat file1 file2 > файла file1. конкатенация файлов file3 Объединяет содержимое файлов file1 и file2 в файл file3 46 Продолжение табл. 12 1 rm ln more wc sort find md cd rmdir rm -r ls pwd cp -r 2 Удаление файла 3 4 rm file1 Удаляет файл file1 из текущего каталога Создание ссылки на ln folder1/file1 Создает ссылку на файл файл intro folder1/file1 в текущем каталоге с именем intro. Постраничный вывод more myfile Выводит постранично файла файл myfile Вывод числа wc –c file1 Выводит число символов символов или строк в wc –l file2 в файле file1. файле Выводит число строк в файле file1 Сортировка записей sort –n –k 4 Сортировка записей файла myfile файла myfile по четвертому полю Поиск файлов find inf* Выводит имена всех файлов текущего каталога, имена которых начинаются на “inf” 2. Команды для работы с каталогами Создание каталога md folder1 Создает каталог folder1 в текущем каталоге Переход в заданный cd /root/alex Делает текущим каталог каталог cd .. /root/alex. cd Делает текущим родительский каталог. Делает текущим домашний каталог. Удаление каталога rmdir Удаляет каталог /tmp/myfolder /tmp/myfolder Удаление непустого rm –r mydir Удаляет каталог mydir каталога вместе со всеми его файлами Вывод содержимого ls –l Выводит текущий каталога каталог на экран в полном формате Вывод имени Pwd Выводит имя текущего текущего каталога каталога на экран Копирование cp –r mydir /tmp Копирует каталог mydir в каталога каталог /tmp 47 Продолжение табл. 12 3. Команды для работы с файловой системой chmod Управление правами chmod 755 file1 Для владельца файла file1 доступа включает все права, для группы и для всех других пользователей – права на чтение и выполнение, mount, Подключение mount Монтирование раздела umount (отключение) к /dev/hd0t77 /dev/hd0t77 в каталог файловой системе /fs/qnx4 /fs/qnx4 нового устройства <, >, >>, | Переназначение pwd > namedir Выводит имя текущего ввода и вывода, каталога в файл namedir организация конвейера команд du Вывод размера du Выводит общий размер каталога или файла текущего каталога и всех подкаталогов df Вывод информации df Выводит информацию по по использованию всем смонтированным дискового разделам файловой пространства системы смонтированных разделов 4. Управление сеансом работы пользователя passwd Изменение пароля Passwd Изменяет пароль пользователя пользователя в интерактивном режиме ps Вывод информации о ps Выводит информации о работающих всех работающих в процессах данный момент процессах kill Уничтожение Kill 1245 Уничтожает процесс с процесса заданным идентификатором who Вывод списка Who Выводит список активных активных пользователей пользователей id Вывод id Выводит идентификатор идентификатора пользователя пользователя 48 Окончание табл. 12 1 use pidin uname 2 Вывод справки по заданной команде 3 use ls Вывод подробной pidin информации о работающих процессах и потоках Вывод информации о uname -sr версии операционной системы 4 Выводит справку по команде ls Выводит подробную информацию о работающих процессах и потоках Выводит номер версии установленной операционной системы В командах при указании имен файлов можно использовать метасимволы: * – заменяет любое количество символов; ? – заменяет любой одиночный символ; [список символов] – заменяет одиночный символ из указанного списка или диапазона символов; Порядок выполнения работы 1. Определить имя текущего пользователя и текущего каталога. 2. Подключить к заданному узлу файловой системы гибкий диск mount –t dos /dev/fd0 <путь монтирования>. 3. Используя команды работы с файлами и каталогами, сделать следующее: создать на дискете каталог cop; скопировать в него несколько файлов с жесткого диска; вывести содержимое каталога cop на экран, изучить структуры записей каталога о файлах и подкаталогах и занести их в отчет; используя команду переназначения, записать каталог cop в файл dirdat корневого каталога дискеты; отсортировать содержимое файла dirdat по алфавиту, по размеру и по дате последнего изменения файлов; с помощью конвейера вывести имена подкаталогов Вашего домашнего каталога на экран; удалить два файла из каталога cop; удалить подкаталог cop;. удалить файл dirdat 49 4. Дайте возможность пользователям просматривать каталоги командой dir вместо ls. 5. Посмотреть информацию по активным процессам и потокам командами pidin и psin, сравнить эти команды. 6. Создать новую группу пользователей myguest путем редактирования файла /etc/group. 7. Создать нового пользователя в группе myguest, присвоить ему логин и пароль. Проверить вход в систему под именем нового пользователя. 8. Просмотреть значения глобальных переменных, занести их в отчет. Записать новую переменную town, присвоить ей значение «Новосибирск». 9. Изменить файл /etc/profile так, чтобы при каждой загрузке ОС дискета была смонтирована. 10. Размонтировать дискету. 11. Определить тип следующих команд: cd, find, ls, grep. 12. Очистить экран монитора (clear). 13. Определить номер версии операционной системы. Контрольные вопросы Спецификация файла, использование метасимволов "*", "?". Понятие внешних и внутренних команд. Синтаксис команд, основные соглашения об именах устройств. Имя каталога, имя пути. Синтаксис и назначение. Синтаксис и назначение команд работы с каталогами и файлами. Поясните по протоколу. 6. Синтаксис и назначение команд переназначения ввода/вывода и фильтрации. Поясните по протоколу работы. 1. 2. 3. 4. 5. 50 Лабораторная работа 5 КОМАНДНЫЕ СЦЕНАРИИ ОПЕРАЦИОННОЙ СИСТЕМЫ QNX Цель работы – изучение языка командных сценариев. Теоретический материал Общие сведения Командный сценарий (shell script, командный файл) – это исполняемый текстовый файл, состоящий из команд интерпретатора. Сценарий часто называют shell–программой и используют для администрирования и управления ОС. Командный язык интерпретатора QNX, в отличие от интерпретатора Windows, является языком высокого уровня, поддерживающим ввод-вывод переменных, ветвления (if-thenelse, case), циклы (for, while, until), работу с функциями, пошаговый режим отладки и т. д. Запуск сценария может проводиться несколькими способами: – запустить интерпретатор, указав ему в качестве аргумента имя файла сценария, например: sh myscript.sh; – запустить интерпретатор, связав стандартный поток ввода с именем файла сценария, например sh < myscript.sh; – выполнить сценарий в текущем экземпляре shell командой «точка», например: . myscript.sh; – установить для сценария файловый атрибут «исполняемый» и запустить как обычную программу: chmod 755 myscript.sh myscript.sh. При написании сценария необходимо соблюдать следующие требования: в первой строке обычно указывается имя интерпретатора, который будет исполнять сценарий, например #!bin/ksh в конце сценария обязательно вводится символ перевода строки (нажатие ENTER). Переменные и параметры сценария Имена переменных, используемых в сценариях, представляют собой последовательность букв, цифр и символов подчеркивания. Имя должно начинаться с буквы или символа подчеркивания. Переменные задаются сразу со значением (возможно с пустым) и всегда имеют строковый тип. 51 При объявлении переменной ее имя и значение должны быть записаны без пробелов относительно символа равенства. Например, создадим две переменные, присвоив переменной VAR_1 значение “245”, а переменной VAR_2 пустое значение: VAR_1=245 VAR_2= Если для переменной задается значение, содержащее пробелы, то нужно заключать его в кавычки: VAR_3=”program files” В качестве значения переменной можно присвоить результат выполнения команды QNX, указав имя команды в обратных кавычках (обычно этот символ находится на клавише для ввода символов «~» и «ё»): VAR_2=`pwd` Значения переменным можно задать в диалоговом режиме, например команда read A B C присваивает значения переменным A, B и C. Вывод значений переменных экран проводится командой echo с указанием символа “$” перед именем переменной: echo $VAR_3. При необходимости выполнения конкатенации значения переменной и произвольной символьной строки используются фигурные скобки: echo ${VAR_3}binary. Командный интерпретатор имеет возможность обработки переменных как целых чисел, выполняя операции сложения (+), вычитания (-), умножения (*), целочисленного деления (/) и получения остатка от деления (%): A=`expr $x + $y`; B=`expr $x - $y` C=`expr $x / $y`; D=`expr $x % $y`; E=`expr $x “*” $y` Обратите внимание: символ умножения должен быть взят в кавычки, т. к. это служебный символ интерпретатора, а имена переменных и знаки операций всегда разделяются пробелами. Все переменные доступны только в том процессе shell , в котором они были объявлены. Для того, чтобы к переменным можно было обращаться из дочерних процессов, их следует экспортировать. Возможны два способа экспорта: при создании переменной (например, export VAR_4=myfolder); после создания переменной (например, export VAR_4). Команда export без аргументов выводит список всех экспортированных переменных интерпретатора. Интерпретатор поддерживает определенный набор внутренних переменных, список которых приведен в таблице 13. 52 Сценарий может принимать аргументы, используя механизм формальных и фактических параметров. Для описания формальных параметров в программе используются специальные переменные, имена которых состоят из одной цифры в диапазоне от 0 до 9. При этом переменная 0 содержит имя сценария, переменная 1 – значение первого фактического параметра, переменная 2 – второго фактического параметра и т. д. Общее число параметров содержится в переменной #. Таблица 13 Переменная ? $ ! # * @ - Значение Код завершения последней команды (0 – нормальное завершение) PID текущего процесса shell PID последнего фонового процесса Число параметров, переданных в shell Список параметров shell в виде одной строки Список параметров shell в виде набора слов Флаги, передаваемые в shell Если число фактических параметров превышает число формальных параметров, то в сценарии можно использовать команду shift , которая сдвигает список формальных параметров относительно списка фактических параметров на заданное количество позиций. Операторы языка Korn Shell 1. Оператор TEST Оператор test проверяет выполнение заданного условия и возвращает логическое значение «истина» (0) или «ложь» (1). Общий формат оператора: test условие или [ условие ] Обратите внимание: вторая форма записи оператора предусматривает наличие обязательных пробелов между квадратными скобками и выражением условия. Если строка условия в команде test пустая, то возвращается значение 1. Возможны условия нескольких видов: 1) проверка файлов или переменных формат команды: test ключ имя_файла | имя_переменной ключ команды задает режим проверки и может принимать следующие значения -f – указанный файл является обычным файлом; -d – указанный файл является обычным каталогом; -с – указанный файл является символьным устройством; -r – указанный файл доступен для чтения; 53 -w – указанный файл доступен для записи; -s – указанный файл не является пустым; -n – указанная переменная имеет значение (не пустая); -z – указанная переменная не имеет значения. Примеры команды: test –f /usr/user1/file_1 возвращает 0, если файл /usr/user1/file_1 является обычным файлом; test –w /usr/user1/file_1 возвращает 0, если файл /usr/user1/file_1 доступен для записи; test –n $ALFA возвращает 0, если переменная ALFA имеет значение. 2) сравнение строк формат команды: test строка1 отношение строка2 операции отношения строк: = (равно) или != (не равно) . примеры команд: test $A = $B возвращает 0, если значение переменной A равно значению переменной B test $A != $B возвращает 0, если значение переменной A не равно значению переменной B Обратите внимание: операция отношения обязательно с двух сторон окружается пробелами. 3) сравнение целых чисел. формат команды: test число1 отношение число2 операции отношения чисел: -eq – равно; -ne – не равно; -gt – больше; -ge – больше или равно; -lt – меньше; -le – меньше или равно. 2. Оператор IF Условный оператор IF имеет следующий формат: if условие then список_операторов [elif условие then список_операторов ] [else список_операторов] fi Здесь условие – любой оператор, возвращающий значение true или false (обычно используется оператор test); список_операторов – операторы, разделенные точкой с запятой (;). Пример оператора: 54 if [ -f /tmp/myfile ] then cksum /tmp/myfile else ls /tmp > /tmp/myfile; cksum /tmp/myfile fi Оператор CASE Оператор множественного выбора CASE имеет следующий формат: Case строка in шаблон_1) список_операторов;; шаблон_2) список_операторов;; шаблон_n) список_операторов;; esac Пример оператора: echo –n ‘please enter symbol from A to C: ‘ read ENTRY case $ENTRY in A|a) echo ‘Apple’;; B|b) echo ‘Baby’;; C|c) echo ‘Coke’;; *) echo ‘Please type A, B or C ! ’;; esac Оператор FOR Оператор предопределенного цикла FOR имеет следующий формат: for имя [in список] do список_операторов done Если список значений для переменной не задан, то в качестве списка используется значение внутренней переменной @, содержащей список аргументов сценария (см. табл. 11). Пример оператора: list=”word 1 word2 word3 word4” for VAL in $LIST do echo $VAL done Операторы WHILE и UNTIL Операторы WHILE и UNTIL предназначены для создания циклов с неопределенным числом исполнения списка операторов. Список_операторов оператора WHILE исполняется только тогда, когда условие истинно, а для оператора UNTIL – когда условие ложно. Выход 55 из циклов проводится путем принудительно по команде break. Форматы операторов: while условие do список_операторов done контроля значения условия или until условие do список_операторов done Оператор TRAP Командный сценарий может перехватывать сигналы прерываний, генерируемых при возникновении различных ненормальных ситуаций (нажатие клавиши DEL, срабатывание команды KILL и т. д.), и обрабатывать их собственными средствами (за исключением сигнала 9 команды KILL). Для этих целей предназначен оператор TRAP. Формат оператора: trap “список_команд” список_сигналов Здесь список_команд – задает команды, которые будут выполняться при возникновении любого сигнала из списка_сигналов. Команды с списке_команд разделяются точкой с запятой (;), а сигналы в списке_сигналов разделяются пробелами. Пример оператора: trap “rm /tmp/*; echo Game is over; exit” 16 Здесь при получении сигнала 16 будут удалены все файлы из каталога /tmp, на экран выводится сообщение и завершается работа интерпретатора. Использование функций в сценариях Функции Korn Shell описываются следующим образом: имя_функции () ( список_операторов ) Функции поддерживают механизм формальных и фактических параметров. Формальные параметры являются локальными и имеют имена от 0 до 9. Если функция должна вернуть код завершения, то используется оператор return. 56 Порядок выполнения работы 1. Разработать сценарий для создания файла данных с информацией согласно таблице 14. Количество записей в файле – не менее 10, в скобках указано смещение поля относительно начала записи. Форматирование полей записи проводить путем циклического добавления необходимого числа пробелов, например: len_fam=` expr length $fam; while [ $len_fam –lt 20 ] do #записываем пробел без перевода строки echo –n “ “ >>$file_dat; len_fam=`expr $len_fam + 1; done Здесь len_fam – переменная, в которой хранится текущая длина введенного пользователем значения поля. Таблица 14 № Предметная варианта область 1 Студенческая группа 2 Расписание поездов 3 Туристическая фирма 4 5 6 7 8 9 10 Название поля Фамилия (0), год рождения (20), место рождения (25), средний балл (40). Номер поезда (0), пункт назначения (5), время отправления (30), время в пути (40). Номер тура (0), страна (5), город (20), продолжительность тура (40), дата отправления (45). Расписание Номер рейса (0), пункт назначения (10), самолетов время отправления (30), время в пути (40) Склад Код товара (0), группа товара (5), наименование товара (20), дата поступления (40), цена (45). Отдел кадров Фамилия (0), год рождения (20), место рождения (25), должность (45), адрес, телефон (55) Успеваемость Группа (0), фамилия (10), номер семестра (30), студентов название дисциплины (32), оценка (50). Договорной отдел Шифр договора (0), заказчик (10), источник финансирования (30), сумма договора (50), дата начала (60), дата окончания (70) Бухгалтерия Табельный номер (0), фамилия (7), оклад (30), надбавка (40), наличие налогового вычета (45), ГИБДД Госномер автомобиля(0), модель (10), год выпуска (25), тип двигателя (30), адрес регистрации (40), дата регистрации (65) 57 2. Разработать сценарий для поиска в файле данных заданной пользователем строки символов. 3. Разработать сценарий для сортировки файла данных по заданному пользователем полю записи. 4. Разработать сценарий, формирующий меню для опроса пользователя (пункты меню – ДОБАВЛЕНИЕ ДАННЫХ, ПОИСК ДАННЫХ, СОРТИРОВКА ДАННЫХ, ВЫХОД) и выполнить это меню с помощью соответствующих сценариев, разработанных в п.3.1 – 3.3 задания. Контрольные вопросы 1. Командный сценарий: определение, назначение. 2. Способы запуска командного сценария. 3. Способы передачи данных в командный сценарий. 4. Переменные в командном сценарии: типы, область видимости, допустимые операции. 5. Организация циклов в сценарии. 6. Организация ветвлений в сценарии. 7. Поясните алгоритм форматирования записей при выполнении п. 3.1 лабораторной работы. 58 Лабораторная работа 6 РАЗРАБОТКА ПРИЛОЖЕНИЙ В СРЕДЕ PHAB Цель работы – изучение технологии разработки приложений в среде Photon Application Builder. Теоретический материал Общие сведения Дистрибутив QNX комплектуется собственным инструментом для разработки приложений на C/C++ - Photon Application Builder (PhAB). Установка PhAB с дистрибутивного диска проводится в следующем порядке: запустить инсталлятор, встроенный в среду Photon (Installer); установить содержимое репозиториев QNX Installer for Neutrino и QNX Updater for Neutrino (выбрать режим Select All и нажать кнопку Install). Основные особенности среды PhAB: отсутствуют встроенный редактор исходных текстов и символьный отладчик; не генерируется "проект" в том смысле, как это делают, например, MS Visual CPP, Borland Delphi или Borland Builder. Поэтому PhAB не является интегрированной средой разработки в привычном смысле и его можно рассматривать как построитель графического интерфейса приложения (Graphic User Interface, GUI). PhAB избавляет разработчика от рутинной работы по отслеживанию размеров, взаимных положений, цветов и других характеристик GUI компонентов приложения, и позволяет привязать обрабатывающий код к GUI событиям (нажатия кнопок, перемещения окон, ввод с клавиатуры). Программный код, определяющий реакцию на события, разработчик прописывает традиционными методами на C/C++. Перечень основных понятий PhAB и соответствующих им понятий Borland IDE приведен в таблице 15. 59 Таблица 15 PhAB виджет (widget) callback ресурс (resource) свойство ресурса событие Определение GUI – компонент системы Photon Borland форма, компонент функция, обеспечивающая обработку события свойство виджета, которое может читаться или устанавливаться из программного кода обработчик событий, метод свойство компонента (property) значение свойства текущее значение ресурса внешнее воздействие на виджет (нажатие кнопки, перемещение мыши и т. д.) событие (event) Окно PhAB – содержит меню, панели инструментов, набор виджетов и контрольную панель (аналог инспектора объектов в Borland IDE). Контрольная панель имеет несколько закладок: Resources – для установки свойств ресурсов (активация с помощью F12); Callbacks – для связи виджета с функциями, определяющими реакцию на различные события (активация с помощью Shift + F12); Search Panel – для поиска заданной символьной строки (активация с помощью Ctrl + R). В отдельное окно с помощью клавиши F11 можно выводить содержимое буфера обмена Clipboard. PhAB имеет большое количество виджетов (кнопки, списки, меню и т. д.). При добавлении виджета в проект ему присваивается стандартное имя, совпадающее с именем класса, к которому принадлежит этот виджет. Если для виджета определяется реакция на событие через callback-функцию, то ему необходимо присвоить уникальное имя. Виджет, у которого не определена реакция на события, называется пассивным. Результатом работы генератора PhAB является набор файлов, включающий Makefile, файлы заголовков, определений и т.д.; обработка файлов проводится с помощью утилиты make. Методика построения приложений PhAB 1. Запуск PhAB Может проводиться двумя способами: кнопкой Launch (Launch → Development → Builder); с помощью командной строки: /usr/photon/appbuilder/ab 60 2. Создание модулей Разработка приложения пользовательского интерфейса начинается с создания модулей. Каждый модуль группирует взаимосвязанную информацию и позволяет пользователю особым образом взаимодействовать с этой информацией. PhAB предоставляет несколько типов модулей: window – обычно используется для формирования главного окна приложения; dialog – позволяет приложению обмениваться информацией с пользователем; menu – предоставляет пользователю команды; icon – определяет иконку приложения; picture – может быть использовано, например, для формирования графических изображений. Создание главного модуля приложения выполняется через меню File → New, при этом будет представлен на выбор список из 13 типовых видов окна главного модуля приложения. После выбора типа модуля приложения ему по умолчанию будет присвоено имя base, и на экране появится диалог с его свойствами. Основными свойствами модуля являются: имя главной инициализирующей функции (Initialization Function) , которая запускается до создания окна приложения и получает те же параметры argv и argc, что и функция main в консольных приложениях; имя функции инициализации главного окна (Setup Function), которая может вызываться до или после отображения окна на экране; возможность использования синтаксиса С++ в файлах приложения (флажок Scan Source Files For Prototypes, для С++ он должен быть отключен); возможность управления состоянием, размером и положением главного окна приложения. 3. Добавление виджетов Добавление виджета проводится путем выбора его из палитры виджетов и размещения его в окне модуля. Определение свойств виджета проводится с помощью редактора ресурсов через панель управления. 4. Прикрепление ответных реакций Для определения реакции на различные события виджету назначаются ответные реакции (CallBack), реализуемын в виде callback61 функций. Имя функции указывается на закладке CallBacks, код пишется на C/C++. Присоединение функций к виджету проводится на этапе компоновки приложения. В общем случае синтаксис имени callback-функции выглядит следующим образом: Class::Function@File.Type, где любой элемент записи, кроме Function, может отсутствовать. Здесь Function – имя функции, при генерации проекта PhAB добавляет в программный файл «заглушку» с указанным именем и набором параметров, требуемым для каждого класса виджета; File – имя файла исходного кода программы, в который будет помещена функция; Type – расширение имени файла («c» или «cpp»), по умолчанию – «c»; Class – используется только в С++ и определяет класс, членом которого является функция. При удалении или переопределении ответной реакции PhAB не удаляет callback-функцию, это необходимо делать вручную. Доступ к свойствам виджета из программного кода проводится с помощью функций PtSetResource и PtGetResource путем указания имени виджета и имени ресурса. Для виджета можно использовать предопределенные имена, присваиваемые PhAB: ABW_xxx и ABN_xxx. Здесь ABW_xxx – указатель на размещенный на экране виджет с именем xxx, а ABN_xxx – целочисленный идентификатор этого виджета в системе окон приложения. Например, для установки значения надписи в окне он может выполняться следующим образом: PtSetResource(ABW_xxx, Pt_ARG_TEXT_STRING, “результат равен ”, 0); 5. Генерация кода Фактическое создание проекта в PhAB происходит при первоначальном выборе из меню: File → Save As… При этом PhAB предлагает нам определить имя нового проекта, т.е. его местоположение в файловой системе. После сохранения проекта создается каталог с именем, присвоенным проекту. Для генерации кода приложения необходимо открыть диалог "Build+Run" (рис. 6.2), указать целевую платформу (Target Platform), установить необходимые параметры проекта и нажать кнопку Generate. В общем случае QNX позволяет создавать приложения для большого числа процессорных платформ, однако QNX Momentics NC генерирует приложения только для платформы Intel x86. 62 PhAB создаёт на верхнем уровне проекта 2 каталога: ./wgt – содержащий параметрические описания всех модулей приложения; для каждого модуля будет создан один файл описаний, имя которого совпадает с именем виджета модуля, а тип – wgtX, где X – это односимвольный определитель типа модуля: *.wgtw – window, *.wgtd – dialog, *.wgtm – menus (многоуровневые текстовые меню), *.wgtp – picture (база данных виджетов, в том числе – изображений), *.wgti – “иконки”, используемые приложением. Файлы этого каталога проекта генерируются PhAB при визуальной компоновке внешнего вида приложения (модифицируются при всякой визуальной коррекции GUI) и, несмотря на то, что они имеют текстовый формат, никогда не следует редактировать их вручную. ./src – каталог исходных описаний проекта, содержащий программную часть проекта (служебные файлы описаний, необходимые для сборки приложения, и файлы, разработанные программистом). Для каждой целевой платформы создаётся подкаталог с именем платформы (для x86 это- gcc_ntox86), где содержатся все специфические для этой платформы файлы: Makefile сборки с конкретными параметрами компилятора, объектные файлы, итоговые файлы исполнимого формата. Здесь находится (генерируется PhAB), как минимум, один программный файл: abmain.cc – файл, содержащий головную программу запуска приложения (или abmain.c для проекта на языке С). Файл abvars.h содержит ABN_* идентификаторы всех виджетов приложения, файл abdefine.h определяет их ABW_* имена, а abevents.h описывает все реакции на события. Обратите внимание: все файлы, имена которых начинаются с «ab», являются собственными файлами PhAB, и не должны подвергаться модификации. Попытка модификации этих файлов может привести к потере работоспособности приложения или к некорректному его поведению. Диалог "Build+Run" содержит файловый менеджер, позволяющий редактировать исходный код и выполнять операции с файлами без выхода из PhAB, а также дает возможность компилировать проект (Make), запустить откомпилированное приложение (Run) или использовать отладчик (Debug). PhAB всегда создаёт главный файл приложения с именем abmain.c, куда помещает его стартовый код. После завершения очередного этапа по развитию проекта для уменьшения размера исполняемого файла приложения рекомендуется перейти в каталог gcc_ntox86 и выполнить команду strip имя_проекта, которая удаляет из исполняемого файла всю отладочную информацию. 63 Пример разработки приложения PhAB Разработаем приложение для расчета контрольной суммы введенной символьной строки. 1. Создаем модуль главного окна (имя модуля – base, тип окна – Plain). 2. Добавим две кнопки (выбрав Button на закладке Widgets и “перетащив” их в окно виджета base), поля для ввода символьной строки и вывода результата и надписи, для того чтобы указать пользователю назначение этих полей. На закладке Resources – изменим атрибуты ресурсов с именами ButtonText (ресурс Pt_ARG_TEXT_STRING) и Font (ресурс Pt_ARG_TEXT_FONT). 3. Добавляем программную реакцию (callback) на нажатие кнопки (закладка Callbacks, событие Activation), определив функцию-обработчик, которую мы предписываем поместить в файл abmain.c 4. Выполняем генерацию кода проекта, задав ему имя Project1. 5. Редактируем файл abmain.c (добавляем в него функцию расчета контрольной суммы, разработанную в лабораторной работе № 2). Рассмотрим листинг, полученный в результате всех предыдущих манипуляций, дополненный кодом, необходимым для выполнения подсчёта контрольной суммы CRC16 для введённой символьной строки. /* Standard headers */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* Local headers */ #include "ablibs.h" #include "abimport.h" #include "proto.h" #include "abwidgets.h" #include "abdefine.h" #include "abevents.h" #include "ablinks.h" #include "abvars.h" int main ( int argc, char *argv[ ] ) { _Ap_.Ap_winpos.x = _Ap_.Ap_winpos.y = AB_LOCKED; _Ap_.Ap_winsize.y = _Ap_.Ap_winsize.x = AB_LOCKED; /* AppBuilder Initialization */ ApInitialize( argc, argv, &AbContext ); PgSetDrawBufferSize( 0xffff ); 64 /* Display main window */ ApLinkWindow( NULL, &AbApplLinks[0], NULL ); /* Loop until user quits application */ PtMainLoop( ); PtExit( 0 ); return 0; }; static const ApClassTab_t ClassTable[] = { { "PtWindow", &PtWindow }, { "PtButton", &PtButton }, { NULL, NULL } }; ApContext_t AbContext = { ClassTable, 1, AbWidgets }; int CRC16( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) { PtArg_t arg; unsigned short res; unsigned char i2=0; char str[50], *str2; int i=0; PtSetArg(&arg, Pt_ARG_TEXT_STRING, 0, 0); PtGetResources(ABW_PtTxt, 1, &arg); res=0xFFFF; str2=(char*)malloc(strlen((char*)arg.value)); str2[strlen((char*)arg.value)]='\0'; strcpy(str2, (char*)arg.value); for(i=0; i<strlen(str2); i++) { res=(res&0xFF00)+(int)(str2[i])^(res%0x100); for(i2=0; i2<8; i2++) { if(res&0x0001!=0) res=(res>>1)^0xA001; else res=(res>>1); } } PtSetResource(ABW_PtRes, Pt_ARG_TEXT_STRING, itoa(res, str, 10), 0); return( Pt_CONTINUE ); }; EXT( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) 65 { PtExit (EXIT_SUCCESS); return (Pt_CONTINUE); } Легко видеть, что кроме текста и шаблонов, созданных PhAB на этапе визуального проектирования, нам потребовалось добавить только код функции для расчета контрольной суммы и для закрытия приложения (этот текст выделен жирным шрифтом). Порядок выполнения работы Запустить PhAB. Создать окно приложения. Дополнить его нужными виджетами. В соответствии с вариантом задания написать необходимые callbackфункции. 5. Отладить приложение. Таблица 16 1. 2. 3. 4. № варианта 1 2 3 4 5 Задание Определить базовые адреса последовательных портов и вывести их на экран Вычислить простую контрольную сумму текста, введённого в форму Вычислить контрольную сумму LRC текста, введённого в форму Вычислить контрольную сумму CRC32 текста, введённого в форму Вычислить корни квадратного уравнения, коэффициенты которого введёны в форму Контрольные вопросы Каковы основные этапы построения приложений Photon в PhAB? Какие параметры устанавливаются в панели Resources? Какие параметры устанавливаются в панели Callbacks? Как происходит генерация проекта. Что является её результатом? Какие параметры Вы можете установить при генерации проекта? В каких случаях и почему можно не присваивать виджету уникальное имя? 7. Каким образом в приложении осуществляется связь виджетов с Callback-функциями? 8. Какие свойств имеет главный модуль приложения? 1. 2. 3. 4. 5. 6. 66 Литература 1. Агуров П. Последовательные интерфейсы ПК. Практика программирования. М.: Издательство: BHV. 2004. 2. Зыль С. Операционная система реального времени QNX: От теории к практике. СПб.: БХВ. Петербург, 2004. 192 с. 3. Зыль С. QNX Momentics: основы применения. СПб.: БХВ. Петербург, 2005. 256 с. 4. Несвижинский В. Программирование аппаратных средств в Windows. М.: Издательство: BHV. 2004. 5. Цилюрик О. И. QNX: создание приложений в PhAB. www.qnx.org.ru/article19.html 6. Даниленко В. Различные варианты физической компоновки программ, использование динамических и статических библиотек при создании приложений на С++ в RTOS QNX 6.2 http://qnxclub.net/files/articles/libraries/libraries.html. 7. Руководство программиста Photon. Справочная документация. 67 Приложение Описание типов данных _SYSTEMTIME, LongWord Тип LongWord Cardinal Word UINT DWORD Integer LongInt String TColor MMRESULT TFNTimeCallBack HWND TFNTimerProc Boolean _SYSTEMTIME TWMTimer TFarProc Принимаемое значение Формат 0…4294967295 32 бита, без знака 0…4294967295 32 бита, без знака 0…65535 16 бит, без знака 0…4294967295 32 бита, без знака 0…4294967295 32 бита, без знака –2147483648…2147483647 32 бита, со знаком –2147483648…2147483647 32 бита, со знаком строка clRed, clGreen, clBlue, … 0…4294967295 32 бита, без знака Type TFNTimeCallBack = procedure(uTimerID, uMessage : UINT; dwUser, dw1, dw2 : DWORD) stdcall; 0…4294967295 32 бита, без знака Pointer указатель True, False булевский тип _SYSTEMTIME = record wYear: Word; wMonth: Word; wDayOfWeek: Word; wDay: Word; wHour: Word; wMinute: Word; wSecond: Word; wMilliseconds: Word; end; TWMTimer = packed record Msg: Cardinal; TimerID: Longint; TimerProc: TFarProc; Result: Longint; end; Pointer указатель 68