Modifydata; end
Программа делится на две основные части. Сначала проверяются условия, а затем производятся операции над данными. Процедура проверки условия не изменяет данных и поэтому не требует какой-либо специальной защиты доступа. Однако доступ к данным для модификации должен быть координирован между процессами. Если использовать семафоры, то их потребуется два: один - для контроля доступа в защищенную область с данными, а другой - для индикации изменения общих данных и, соответственно, необходимости повторной проверки условий. Применение первого семафора просто, а для второго необходимо следить за числом ожидающих процессов и обеспечить, что при изменении условия ожидающие процессы будут активизированы с целью его проверки, то есть генерацию сигналов семафора, число которых равно числу ожидающих процессов. Это решение неудовлетворительно из-за большого расхода машинного времени на многочисленные проверки, при этом в программе довольно легко ошибиться. Для решения этой проблемы была введена новая переменная синхронизации event (событие), с которой связаны операции await (ждать) и cause (вызвать). Процесс, выполнивший операцию await (event), остается в состоянии ожидания, пока значение переменной event не изменится. Это изменение контролируется с помощью операции cause. При наступлении события, то есть выполнении операции cause (event), освобождаются все ожидающие его процессы, в то время как в случае семафора освобождается лишь один процесс. Операции с событиями можно реализовать либо с помощью двоичной переменной, либо с помощью счетчика, при этом основные принципы остаются одинаковыми. Глагол to await имеет значение не только "ждать", но и "предстоять", то есть конструкцию await (А) можно трактовать, как "предстоит событие А". Глагол to cause означает "быть причиной", побудительным мотивом, "вызвать что-либо". Конструкция cause (А) интерпретируется как "вызвать событие А" (в литературе и в операционных системах иногда используются и другие названия). В противоположность семафору, переменную события нельзя использовать для защиты ресурса от конкурирующего доступа нескольких процессов, поскольку по определению она освобождает все ожидающие процессы. Вышеприведенная проблема решается с помощью переменной события и семафора, если все программы имеют следующий вид var mutex: semaphore; change: event; Begin while not condition do await (change); wait (mutex); (* обработка общих переменных *) signal (mutex); cause (change); end; При каждом изменении переменной event все процессы проверяют condition, и только те из них, для которых condition выполнено, могут продолжаться. Доступ к общему ресурсу защищен с помощью семафора mutex, при этом продолжается только один процесс. Это решение проще, чем основанное только на семафорах. Оно также более эффективно, поскольку процессы проверяют условия только тогда, когда это имеет смысл, т.е. после изменения значения соответствующих переменных. Важный тип события в системах реального времени связан с внешними прерываниями. Программа обработки - обработчик прерываний - ждет прерывания. Когда оно происходит, исполнение обработчика возобновляется.
Лекция 3.4. Обмен информацией между процессами 1. Общие области памяти. 2. Почтовые ящики. 3. Каналы. 4. Удаленный вызов процедур. 5. Сравнение методов синхронизации и обмена данными.
1. Общие области памяти Взаимодействующие процессы нуждаются в обмене информацией. Поэтому многозадачная операционная система должна обеспечивать необходимые для этого средства. Обмен данными должен быть прозрачным для процессов, т.е. передаваемые данные не должны изменяться, а сама процедура должна быть легко доступна для каждого процесса. Простейший метод - использование общих областей памяти, к которым разные процессы имеют доступ для чтения/записи. Очевидно, что такая область представляет собой разделяемый ресурс, доступ к которому должен быть защищен, например, семафором. Главное преимущество общих областей памяти заключается в том, что к ним можно организовать прямой и мгновенный доступ, например один процесс может последовательно записывать поля, а другой затем считывать целые блоки данных. При программировании на машинном уровне общие области размещаются в оперативной памяти по известным адресам. В языках высокого уровня вместо этого используются глобальные переменные, доступные нескольким дочерним процессам. Так, например, происходит при порождении потоков, для которых переменные родительского процесса являются глобальными и работают как общие области памяти. В случае возможных конфликтов доступа к общим областям они должны быть защищены семафорами. 2. Почтовые ящики Другой метод, позволяющий одновременно осуществлять обмен данными и синхронизацию процессов, - это почтовые ящики. Почтовый ящик представляет собой структуру данных, предназначенную для приема и хранения сообщений (рис. 1). Для обмена сообщениями различного типа можно определить несколько почтовых ящиков. Рисунок 1. - Работа почтового ящика
Во многих операционных системах почтовые ящики реализованы в виде логических файлов, доступ к которым аналогичен доступу к физическим файлам. С почтовыми ящиками разрешены следующие операции: создание, открытие, запись/чтение сообщения, закрытие, удаление. В некоторых системах поддерживаются дополнительные служебные функции, например счетчик сообщений в почтовом ящике или чтение сообщения без удаления его из ящика. Почтовые ящики размещаются в оперативной памяти или на диске и существуют лишь до выключения питания или перезагрузки. Если они физически расположены на диске, то считаются временными файлами, уничтожаемыми после выключения системы. Почтовые ящики не имеют имен подобно реальным файлам - при создании им присваиваются логические идентификаторы, которые используются процессами при обращении. Для создания почтового ящика операционная система определяет указатели на область памяти для операций чтения/записи и соответствующие переменные для защиты доступа. Основными методами реализации являются либо буфер, размер которого задается при создании ящика, либо связанный список, который, в принципе, не накладывает никаких ограничений на число сообщений в почтовом ящике. В наиболее распространенных реализациях процесс, посылающий сообщение, записывает его в почтовый ящик с помощью оператора, похожего на оператор записи в файл put_mailbox (# 1, message) Аналогично, для получения сообщения процесс считывает его из почтового ящика с помощью оператора вида get _mailbox (# 1, message) Запись сообщения в почтовый ящик означает, что оно просто копируется в указанный почтовый ящик. Может случиться, что в почтовом ящике не хватает места для хранения нового сообщения, то есть почтовый ящик либо слишком мал, либо хранящиеся в нем сообщения еще не прочитаны. При чтении из почтового ящика самое старое сообщение пересылается в принимающую структуру данных и удаляется из ящика. Почтовый ящик -это пример классической очереди, организованной по принципу FIFO. Операция чтения из пустого ящика приводит к различным результатам в зависимости от способа реализации — либо возвращается пустая строка (нулевой длины), либо операция чтения блокируется до получения сообщения. В последнем случае, чтобы избежать нежелательной остановки процесса, необходимо предварительно проверить число сообщений, имеющихся в данный момент в ящике. 3. Каналы Канал (pipe) представляет собой средство обмена данными между двумя процессами, из которых один записывает, а другой считывает символы. Этот механизм был первоначально разработан для среды UNIX как средство перенаправления входа и выхода процесса. В ОС UNIX физические устройства ввода/вывода рассматривают как файлы, а каждая программа имеет стандартное устройство ввода (вход) и стандартное устройство вывода (выход), клавиатуру и экран монитора - можно переопределить, например, с помощью файлов. Когда выход одной программы перенаправляется на вход другой, создается механизм, называемый каналом (в операционных системах для обозначения канала используется символ "|"). Каналы применяются в операционных системах UNIX, OS/9 и Windows NT в качестве средства связи между процессами (программами). Каналы можно рассматривать как частный случай почтового ящика. Различие между ними заключается в организации потока данных - почтовые ящики работают с сообщениями, то есть данными, для которых известны формат и длина, а каналы принципиально ориентированы на неструктурированные потоки символов. В некоторых операционных системах, однако, возможно определить структуру передаваемых по каналу данных. Обычно процесс, выполняющий операцию чтения из канала, ждет, пока в нем не появятся данные. В настоящее время операционные системы включают методы, позволяющие избежать блокировки программы, если это нежелательно с точки зрения ее логики. Операции над каналами эквивалентны чтению/записи физических файлов. Они включают функции, как определить, открыть, читать, записать, закрыть, удалить. Дополнительные операции могут устанавливать флаги режима доступа, определять размер буфера и т.д. Благодаря тому, что ввод/вывод в файл и на физические устройства и вход/выход процессов трактуются одинаково, каналы являются естественным средством взаимодействия между процессами в системах "клиент-сервер". Механизм каналов в UNIX может в некоторых случаях зависеть от протокола TCP/IP, а в Windows NT каналы работают с любым транспортным протоколом. Следует иметь в виду, что внешне простой механизм каналов может требовать больших накладных расходов при реализации, особенно в сетевых системах. 4. Удаленный вызов процедур Модель "клиент-сервер" построена на обмене сообщениями "регулярной" структуры, которые можно передавать, например, через механизм каналов. Однако основной процедурой обмена данными и синхронизации в среде "клиент-сервер" является удаленный вызов процедур (Remote Procedure Call - RPC). Последний может рассматриваться как вызов подпрограммы, при котором операционная система отвечает за маршрутизацию и доставку вызова к узлу, где находится эта подпрограмма. Нотация обращения к процедуре не зависит от того, является ли она локальной или удаленной по отношению к вызывающей программе. Это существенно облегчает программирование. В системе реального времени существенно, является RPC блокирующим или нет. Блокирующий RPC не возвращает управление вызывающему процессу, пока не закончит свою работу, например, пока не подготовит данные для ответа. Неблокирующие RPC возвращают управление вызывающей процедуре по истечении некоторого времени (time out) независимо от того, завершила ли работу вызываемая процедура; в любом случае вызывающая программа получает код, идентифицирующий результат выполнения вызова, - код возврата. Таким образом, неблокирующие RPC имеют важное значение, с точки зрения гарантии живучести системы. 5. Сравнение методов синхронизации и обмена данными Может показаться, что основные задачи, связанные с параллельным программированием, взаимным исключением, синхронизацией и коммуникациями между процессами, имеют мало общего, но, в сути - это просто разные способы достижения одной цели. Методы синхронизации можно использовать для организации взаимного исключения и коммуникаций. Аналогично, с помощью техники коммуникаций между процессами можно реализовать функции синхронизации и взаимного исключения процессов. Например, семафор эквивалентен почтовому ящику, в котором накапливаются сообщения нулевой длины, - операции signal и wait эквивалентны операциям put и get почтового ящика, а текущее значение семафора эквивалентно числу помещенных в почтовый ящик сообщений. Аналогично можно организовать взаимное исключение и защиту ресурсов с помощью почтовых ящиков. В этом случае сообщение выполняет функцию "маркера". Процесс, получивший этот "маркер", приобретает право входить в критическую секцию или распоряжаться ресурсами системы. При выходе из секции или освобождении ресурса процесс помещает "маркер" в почтовый ящик. Следующий процесс читает из почтового ящика, получает "маркер" и может войти в критическую секцию. Связь между разными подходами имеет практическое значение в том случае, если в системе применяется только один из них, а все остальные нужно строить на его основе. Современные операционные системы, поддерживающие многозадачный режим и операции в реальном времени, применяют все упомянутые методы. Передача сообщений и доступ к общим областям памяти медленнее, чем проверка и обновление семафора и переменной события, и требует дополнительных накладных расходов. Если есть выбор между различными методами синхронизации и взаимодействия, следует использовать тот из них, который лучше решает конкретную проблему - результирующая программа будет понятнее и, возможно, быстрее работать. Кроме того, весьма важно оценить, насколько эффективно в имеющейся программной среде реализуются конкретные решения. Следует избегать незнакомых и неестественных конструкций. В распределенных системах всегда существует риск потерять сообщение в сети. Если сетевая система сконфигурирована так, что она контролирует правильность передачи сообщения, и имеются средства для повторной передачи утраченных сообщений, то прикладная программа не должна осуществлять дополнительные проверки. Обычно нижний уровень операционной системы и процедуры сетевого интерфейса передают на более высокий уровень код возврата, который прикладная программа должна проверить, чтобы убедиться, была ли попытка успешной или нет, и при необходимости повторить ее. Если контроль не предусмотрен, например, используется служба IP без транспортного протокола TCP, то прикладная программа несет ответственность за проверку результата передачи. Эта операция сложнее, чем это кажется. Можно использовать сообщение, подтверждающее прием, но нет гарантии, что оно само, в свою очередь, не будет потеряно и отправитель не начнет новую передачу. Эта проблема не имеет общего решения - стратегии передачи сообщений должны в каждом случае рассматриваться индивидуально. Возможным решением является помечать и нумеровать каждое сообщение таким образом, чтобы отправитель и получатель могли следить за порядком передачи. Этот метод используется в некоторых типах коммуникационных протоколов.
Тема 4. Особенности программирования систем реального времени Лекция 4.1. Методы программирования в реальном времени. 1. Последовательное программирование и программирование задач реального времени 2. Среда программирования. 3. Структура программы реального времени. 4. Параллельное программирование, мультипрограммирование и многозадачность.
1. Последовательное программирование и программирование задач реального времени Программа представляет собой описание объектов - констант и переменных - и операций, совершаемых над ними. Таким образом, программа -это чистая информация. Ее можно записать на какой-либо носитель, например на бумагу или на дискету. Программы можно создавать и анализировать на нескольких уровнях абстракции (детализации) с помощью соответствующих приемов формального описания переменных и операций, выполняемых на каждом уровне. На самом нижнем уровне используются непосредственное описание - для каждой переменной указывается ее размер и адрес в памяти. На более высоких уровнях переменные имеют абстрактные имена, а операции сгруппированы в функции или процедуры. Программист, работающий на высоком уровне абстракции, не должен думать о том, по каким реальным адресам памяти хранятся переменные, и о машинных командах, генерируемых компилятором. Последовательное программирование (sequential programming) является наиболее распространенным способом написания программ. Понятие "последовательное" подразумевает, что операторы программы выполняются в известной последовательности один за другим. Целью последовательной программы является преобразование входных данных, заданных в определенной форме, в выходные данные, имеющие другую форму, в соответствии с некоторым алгоритмом - методом решения (рис. 1). Рисунок 1. - Обработка данных последовательной программой
Таким образом, последовательная программа работает как фильтр для исходных данных. Ее результат и характеристики полностью определяются входными данными и алгоритмом их обработки, при этом временные показатели играют, как правило, второстепенную роль. На результат не влияют ни инструментальные (язык программирования), ни аппаратные (быстродействие ЦП) средства: от первых зависят усилия и время, затраченные на разработку и характеристики исполняемого кода, а от вторых - скорость выполнения программы, но в любом случае выходные данные будут одинаковыми. Программирование в реальном времени (real-time programming) отличается от последовательного программирования - разработчик программы должен постоянно иметь в виду среду, в которой работает программа, будь то контроллер микроволновой печи или устройство управления манипулятором робота. В системах реального времени внешние сигналы, как правило, требуют немедленной реакции процессора. В сущности, одной из наиболее важных особенностей систем реального времени является время реакции на входные сигналы, которое должно удовлетворять заданным ограничениям. Специальные требования к программированию в реальном времени, в частности необходимость быстро реагировать на внешние запросы, нельзя адекватно реализовать с помощью обычных приемов последовательного программирования. Насильственное последовательное расположение блоков программы, которые должны выполняться параллельно, приводит к неестественной запутанности результирующего кода и вынуждает связывать между собой функции, которые, по сути, являются самостоятельными. В большинстве случаев применение обычных приемов последовательного программирования не позволяет построить систему реального времени. В таких системах независимые программные модули или задачи должны быть активными одновременно, то есть работать параллельно, при этом каждая задача выполняет свои специфические функции. Такая техника известна под названием параллельного программирования (concurrent programming). В названии делается упор на взаимодействие между отдельными программными модулями. Параллельное исполнение может осуществляться на одной или нескольких ЭВМ, связанных распределенной сетью. Программирование в реальном времени представляет собой раздел мультипрограммирования, который посвящен не только разработке взаимосвязанных параллельных процессов, но и временным характеристикам системы, взаимодействующей с внешним миром. Между программами реального времени и обычными последовательными программами, с четко определенными входом и выходом, имеются существенные различия. Перечислим отличия программ реального времени от последовательных программ: 1. Логика исполнения программы определяется внешними событиями. 2. Программа работает не только с данными, но и с сигналами, поступающими из внешнего мира, например, от датчиков. 3. Логика развития программы может явно зависеть от времени. 4. Жесткие временные ограничения. Невозможность вычислить результат за определенное время может оказаться такой же ошибкой, как и неверный результат ("правильный ответ, полученный поздно - это неверный ответ"). 5. Результат выполнения программы зависит от общего состояния системы, и его нельзя предсказать заранее. 6. Программа, как правило, работает в многозадачном режиме. Соответственно, необходимы процедуры синхронизации и обмена данными между процессами. 7. Исполнение программы не заканчивается по исчерпании входных данных - она всегда ждет поступления новых данных. Важность фактора времени не следует понимать как требование высокой скорости исполнения программы. Скорость исполнения программы реального времени должна быть достаточной для того, чтобы в рамках установленных ограничений реагировать на входные данные и сигналы и вырабатывать соответствующие выходные величины. "Медленная" система реального времени может великолепно управлять медленным процессом. Поэтому скорость исполнения программ реального времени необходимо рассматривать относительно управляемого процесса или необходимой скорости. Типичные приложения автоматизации производственных процессов требуют гарантированное время ответа порядка 1 мс, а в отдельных случаях - порядка 0.1 мс. При программировании в реальном времени особенно важными является эффективность и время реакции программ. Соответственно, разработка программ тесно связана с параметрами операционной системы, а в распределенных системах - и локальной сети. Особенности программирования в реальном времени требуют специальной техники и методов, не использующихся при последовательном программировании, которые относятся к влиянию на исполнение программы внешней среды и временных параметров. Наиболее важными из них являются перехват прерываний, обработка исключительных (нештатных) ситуаций и непосредственное использование функций операционной системы (вызовы ядра из прикладной программы, минуя стандартные средства). Помимо этого при программировании в реальном времени используются методика мультипрограммирования и модель "клиент-сервер", поскольку отдельный процесс или поток обычно выполняют только некоторую самостоятельную часть всей задачи. 2. Среда программирования Рассмотрим среду, в которой исполняются программы. Среда выполнения может варьироваться от мини-, персональных и одноплатных микрокомпьютеров и локальных шин, связанных с окружающей средой через аппаратные интерфейсы, до распределенных систем "клиент-сервер" с централизованными базами данных и доступом к системе высокопроизводительных графических рабочих станций. В комплексной системе управления промышленными и технологическими процессами может одновременно использоваться все перечисленное оборудование. Разнообразие аппаратной среды отражается и в программном обеспечении, которое включает в себя как программы, записанные в ПЗУ, так и комплексные операционные системы, обеспечивающие разработку и исполнение программ. В больших системах создание и исполнение программ осуществляются на одной и той же ЭВМ, а в некоторых случаях даже в одно время. Небольшие системы могут не иметь средств разработки, и программы для них должны создаваться на более мощных ЭВМ с последующей загрузкой в исполняющую систему. То же касается и микропрограмм, "зашитых" в ПЗУ оборудования производителем (firmware), - они разрабатываются на ЭВМ, отличной от той, на которой исполняются. Первой задачей программиста является ознакомление с программной средой и доступными инструментальными средствами. Проблемы, с которыми приходится сталкиваться, начинаются, например, с типа представления данных в аппаратуре и программах, поскольку в одних системах применяется прямой, а в других - инверсный порядок хранения бит или байт в слове (младшие байты хранятся в старших адресах). Таких тонкостей очень много, и опытный программист знает, как отделить общую структуру данных и код от технических деталей реализации в конкретной аппаратной среде. Важно как можно раньше выяснить функции, обеспечиваемые имеющейся средой, и возможные альтернативы. Например, микропроцессор Motorola 68000 имеет в своем наборе команд инструкцию test_and_set, и поэтому связь между задачами может осуществляться через общие области памяти. Операционная система VAX/VMS поддерживает почтовые ящики, и синхронизировать процессы можно с помощью механизма передачи сообщений. В UNIX и других операционных системах связь между процессами наиболее удобно осуществлять через каналы. При разработке программ для среды UNIX следует стремиться, с одной стороны, максимально эффективно использовать ее особенности, например стандартную обработку входных и выходных данных, а с другой - обеспечить переносимость между разными версиями UNIX. Из-за того, что многозадачные системы и системы реального времени разрабатываются коллективами программистов, необходимо с самого начала добиваться ясности, какие методы и приемы используются. Структурирование аппаратных и программных ресурсов, то есть присвоение адресов на шине и приоритетов прерываний для интерфейсных устройств, имеет важное значение. Неправильный порядок распределения ресурсов может привести к тупиковым ситуациям. Определение аппаратных адресов и относительных приоритетов прерываний не зависит от разрабатываемой программы. Поэтому оно должно быть произведено на ранней стадии и зафиксировано в техническом задании. Его не следует откладывать до момента непосредственного кодирования, так как в этом случае неизбежны конфликты между программными модулями и возникает риск тупиковых ситуаций. Правильным практическим решением является использование в программе только логических имен для физического оборудования и его параметров и таблиц соответствия между ними и реальными физическими устройствами. При этом изменение адреса шины или приоритета устройства требует не модификации, а в худшем случае только новой компиляции программы. Разумно также использовать структурированное и организационно оформленное соглашение о наименовании системных ресурсов и программных переменных. То же относится и к наименованию и определению адресов удаленных устройств в распределенных системах. Программы следует строить по принципам, применяемым в операционных системах, - на основе модульной и многоуровневой структуры, поскольку это существенно упрощает разработку сложных систем. Должна быть определена спецификация отдельных модулей, начиная с интерфейсов между аппаратными и программными компонентами системы. К основной информации об интерфейсах относится и структура сообщений, которыми будут обмениваться программные модули. Это не означает, что изменения в определении интерфейсов не могут вводиться после начала разработки программы. Но чем позже они вносятся, тем больше затрат потребует изменение кода, тестирование и т. д. С другой стороны, следует быть готовым к тому, что некоторые изменения спецификаций все равно будут происходить в процессе разработки программы, поскольку продвижение в работе позволяет лучше увидеть проблему. Следует принимать во внимание эффективность реализации функций операционной системы. Нельзя считать, что способ, которым в операционной системе реализованы те или иные услуги, дан раз и навсегда. Для проверки того, насколько хорошо удовлетворяются временные ограничения, желательно провести оценку, например с помощью эталонных тестовых программ. Если результаты тестов неприемлемы, то одним из решений может быть разработка программ, замещающих соответствующие стандартные модули операционной системы. Такое решение требует очень осторожного и дифференцированного подхода, в частности замещение может выполняться не всегда, а только для определенных процессов. 3. Структура программы реального времени Разработка программы реального времени начинается с анализа и описания задачи. Функции системы делятся на простые части, с каждой из которых связывается программный модуль. Например, задачи для управления движением манипулятора робота можно организовать следующим образом: – считать с диска описание траекторий; – рассчитать следующее положение манипулятора (опорное значение); – считать с помощью датчиков текущее положение; – вычислить необходимый сигнал управления; – выполнить управляющее действие; – проверить, что опорное значение и текущее положение совпадают в пределах заданной точности; – получить данные от оператора; – остановить робота в случае нештатной ситуации (например, сигнал прерывания от аварийной кнопки). Принципиальной особенностью программ реального времени является постоянная готовность и отсутствие условий нормального, а не аварийного завершения. Если программа не исполняется и не обрабатывает данные, она остается в режиме ожидания прерывания/события или истечения некоторого интервала времени. Программы реального времени - это последовательный код, исполняющийся в бесконечном цикле. В каком-то месте программы есть оператор, приостанавливающий исполнение до наступления внешнего события или истечения интервала времени. Обычно программа структурируется таким образом, что оператор end никогда не достигается while true do (*бесконечный цикл*) begin (*процедура обработки*) wait event at #2,28 (*внешнее прерывание*) (*код обработки*) … end; (*процедура обработки*) end. (*выход из программы; никогда не достигается*) При разработке каждого программного модуля должны быть четко выделены области, в которых происходит обращение к защищенным ресурсам, - критические секции. Вход и выход из этих областей координируется каким-либо методом синхронизации или межпрограммных коммуникаций, например с помощью семафоров. В общем случае, если процесс находится в критической секции, можно считать, что данные, с которыми он работает, не изменяются каким-либо другим процессом. Прерывание исполнения процесса не должно оказывать влияния на защищенные ресурсы. Это снижает риск системных ошибок. Аналогичные предосторожности необходимо соблюдать и для потоков, порождаемых как дочерние процессы главного процесса. Разные потоки могут использовать общие переменные породившего их процесса, и поэтому программист должен решить, защищать эти переменные или нет. Для гарантии живучести программы нештатные ситуации, которые могут блокировать или аварийно завершить процесс, должны своевременно распознаваться и исправляться - если это возможно - в рамках самой программы. В системах реального времени различные процессы могут обращаться к общим подпрограммам. При простейшем решении эти подпрограммы связываются с соответствующими модулями после компиляции. При этом в памяти хранится несколько копий одной подпрограммы. При другом подходе в память загружается лишь одна копия подпрограммы, но доступ к ней возможен из разных программ. Такие подпрограммы должны быть повторно входимыми - реентерабельными (reentrant), то есть допускать многократные вызовы и приостановку исполнения, которые не влияют друг на друга. Эти программы должны использовать только регистры процессора или память вызывающих процессов, то есть не иметь локальных переменных. В результате реентерабельный модуль, разделяемый несколькими процессами, можно прервать в любое время и продолжить с другой точки программы, поскольку он работает со стеком вызвавшего его процесса. Таким образом, реентерабельная процедура может оказаться одновременно в контексте нескольких различных процессов. Эффективность исполнения является одним из наиболее важных параметров систем реального времени. Процессы должны выполняться быстро, и часто приходится искать компромисс между ясностью и структурированностью программы и ее быстродействием. Жизненный опыт показывает, что если для достижения цели нужно чем-то пожертвовать, что обычно и делается. Не всегда возникает противоречие между структурностью и эффективностью, но если первое должно быть принесено в жертву второму, необходимо полностью документировать все принятые решения, иначе существенно осложняется дальнейшее сопровождение программы. 4. Параллельное программирование, мультипрограммирование и многозадачность Программирование в реальном времени требует одновременного исполнения нескольких процессов или задач на одной ЭВМ. Эти процессы используют совместно ресурсы системы, но более или менее независимы друг от друга. Мультипрограммирование (multiprogramming) или многозадачность (multitasking) есть способ одновременного исполнения нескольких процессов. Такого эффекта можно добиться как для одного, так и для нескольких процессоров: процессы исполняются либо на одном, либо на нескольких связанных между собой процессорах. В действительности многие современные вычислительные системы состоят из нескольких процессоров, связанных между собой либо сетью передачи данных, либо общей шиной. Для записи параллельных процессов можно использовать следующую нотацию
|