
Лекция 14
5.3. Поток управления
Поток управления - это последовательность, в которой выполняются команды программы. При отсутствии переходов и вызовов процедур команды выбираются из последовательных ячеек памяти. Вызов процедуры изменяет поток управления: выполняемая в данный момент процедура останавливается, и начинается выполнение вызванной процедуры. Существует несколько видов процедур, отличающихся способом организации вызова: подпрограммы, сопрограммы, ловушки и прерывания. Мы рассмотрим их в данном разделе.
5.3.1. Последовательный поток управления и переходы
Большинство команд программы не меняет поток управления. После выполнения очередной такой команды счетчик команд увеличивается на ее длину, затем выбирается и выполняется следующая команда в памяти. В таких случаях счетчик команд представляет собой линейную функцию времени, которая увеличивается на среднюю длину команды за средний промежуток времени.
Если программа содержит переходы, то счетчик команд уже не является монотонно возрастающей функцией времени. В результате последовательность выполнения команд из самой программы не видна. Чем труднее увидеть последовательность выполнения, тем больше вероятность возникновения ошибок. Такое наблюдение (Дейкстра) дало толчок революции в программировании, одним из нововведений которой было устранение оператора goto более структурированными формами потока управления, например циклами while. Тем не менее такие программы компилируются в программы машинного уровня, которые могут содержать многочисленные переходы, поскольку реализация операторов if, while и других структур языков высокого уровня требует совершения переходов.
5.3.2. Подпрограммы
Один из наиболее важных способов структурирования программ представляет подпрограмма. С одной стороны, вызов подпрограммы изменяет поток управления, но в отличие от команды перехода после выполнения задачи управление возвращается к точке вызова. С другой стороны, тело подпрограммы можно рассматривать как определение новой команды на более высоком уровне. С этой точки зрения вызов процедуры можно считать отдельной командой, даже если процедура сложна.
Особый интерес представляет рекурсивная подпрограмма. Это подпрограмма, которая вызывает сама себя непосредственно либо посредством цепочки других подпрограмм. С помощью рекурсивной подпрограммы легко решается, например, древняя задача о “Ханойской башне”.
5.3.3. Сопрограммы
В обычной последовательности вызовов существует различие между вызывающей и вызываемой процедурами. Пусть есть процедура A, которая вызывает процедуру В. После полного выполнения В управление возвращается в А к точке вызова. Если процедура A вызывает процедуру В много раз, то процедура В каждый раз начинается с начала, а процедура А уже никогда больше с начала не начинается. Когда A вызывает B, она использует команду вызова процедуры (CALL), которая помещает адрес возврата в доступное место, например, в вершину стека. Затем она заносит адрес процедуры B в счетчик команд, чтобы окончательно оформить вызов. Для выхода из процедуры B используется команда (RETURN), которая выталкивает адрес возврата из стека и помещает его в счетчик команд.
Режим работы сопрограмм состоит в том, что A и B более или менее равноправны. Они попеременно вызывают друг друга. Управление каждый раз возвращается к точке последнего вызова. Когда процедура А передает управление процедуре В, оно переходит не к началу В (за исключением первого раза), а к тому месту, на котором произошел предыдущий вызов А.
Сопрограммы обычно используются для того, чтобы производить (псевдо) параллельную обработку данных на одном процессоре. Каждая сопрограмма работает псевдопараллельно с другими сопрограммами, как будто у нее есть собственный процессор. Такой подход упрощает программирование приложений специального класса. Он также полезен для проверки программного обеспечения, которое будет работать на мультипроцессоре.
Обычные команды (CALL и RETURN) не подходят для вызова сопрограмм. Для этого подошла бы команда замены вершины стека на счетчик команд. Такая команда встречается редко, и в большинстве случаев ее приходится моделировать имеющимися командами.
5.3.4. Ловушки
Ловушка (trap) - это особый тип вызова процедуры, который происходит при определенном событии, созданном выполняемой программой. Один из примеров такого события - переполнение. В большинстве процессоров, если результат выполнения арифметической операции превышает самое большое допустимое число, срабатывает ловушка. Это выражается в переходе потока управления на некоторую фиксированную ячейку памяти. В этой ячейке находится команда вызова специальной процедуры (обработчика системных прерываний), которая выполняет определенное действие, например печатает сообщение об ошибке.
Существенно то, что этот вид прерывания вызывается некоторым исключительным событием, созданным самой программой и обнаруженным аппаратным обеспечением или микропрограммой. Ловушки экономят время и память по сравнению с регулярной проверкой кода условия под контролем программы.
Ловушку можно реализовать путем регулярной проверки, выполняемой микропрограммой (или аппаратным обеспечением). Если обнаружено переполнение, адрес ловушки загружается в счетчик команд. Таким образом, то, что является ловушкой на одном уровне, может находиться под контролем программы на более низком уровне. Проверка на уровне микропрограммы требует меньше времени, чем проверка под контролем пользовательской программы, поскольку она может выполняться одновременно с каким-либо другим действием. Кроме того, такая проверка экономит память, т.к. она может присутствовать только в одном месте, например, в основном цикле микропрограммы, независимо от того, сколько арифметических команд встречается в основной программе.
Наиболее распространенные события, которые могут вызывать ловушки, - это переполнение и исчезновение значащих разрядов при операциях с плавающей точкой; переполнение при операциях с целыми числами; нарушения защиты; неопределяемый код операции; переполнение стека; попытка запустить несуществующее устройство ввода-вывода; попытка вызвать слово из ячейки с нечетным адресом и деление на 0.