Читайте также:
|
|
Практически в любой программе, независимо от ее содержания, встречаются участки, которые требуется выполнять (возможно, с небольшими изменениями) несколько раз по ходу программы. Такие повторяющиеся участки целесообразно выделить из общей программы, оформить в виде подпрограмм и обращаться к ним каждый раз, когда в основной программе возникает необходимость их выполнения.
Подпрограмма, в зависимости от выполняемых ею функций, может требовать передачи из вызывающей программы определенных данных (называемых аргументами, или параметрами), возвращать в вызывающую программу результаты вычислений или обходиться и без того, и без другого.
Программный счетчик (в литературе он сокращенно называется РС - Program Counter) представляет собой, ну назовем это так, некоторую переменную, содержащую текущий адрес памяти, в которой находится выполняемая в данный момент команда. Написанные нами программы при ассемблировании преобразуются в специальные коды, которые процессор считывает, расшифровывает и выполняет. Так вот эти коды записываются в так называемую память программ (CSEG - Code Segment). Каждая команда занимает от 2-х до 4-х байт в этой области. Все команды записываются последовательно одна за другой. И, соответственно, каждая из них имеет свой адрес. Если не используется никаких команд перехода, то выполнение их осуществляется по очереди - от первой к последующим. При выполнении последней снова выполняется первая. Программный счетчик отслеживает адрес каждой выполняемой команды, так что процессор знает, откуда он в данный момент берет данные. При использовании команд перехода мы, как вы помните по предыдущему шагу, используем метки. Так вот, контроллер абсолютно не знает ни о каких метках. Зато о них знает транслятор языка ассемблера. Они ему нужны для того, чтоб отсчитать разность между текущим положением команды перехода и команды, следующей за меткой. Эта разность и записывается в контроллер. Итак, при выполнении команды перехода программный счетчик прерывает свое непрерывное наращивание и изменяет свое текущее значение на величину, являющуюся аргументом команды перехода.
Теперь разберемся с понятием стека. Чтобы представить себе, что такое стек, обратимся к бытовой аналогии. Представим себе детскую пирамидку. Мы кладем сначала большой диск, затем поменьше, затем еще меньше, и в конце концов самый маленький. Если же нам надо разобрать эту самую пирамидку, мы снимаем сначала самый маленький диск, затем второй по величине, и последним снимаем самый большой. Так вот пирамидка представляет собой классический стек - это структура, в которой элемент, который добавляется первым, удаляется последним, и наоборот: элемент, добавленный последним, удаляется первым. В литературе еще используется англоязычное сокращение LIFO (Last In - First Out, последний вошел - первый вышел).
Какое же отношение имеет пресловутый стек к тому, что делается в нашем контроллере? Оказывается, самое непосредственное. Именно благодаря ему есть возможность применять подпрограммы. Рассмотрим этот процесс подробнее. В чем отличие вызова подпрограммы от обычной команды перехода? В том, что команда перехода не требует возвращения к тому месту, откуда был совершен переход, а вызов подпрограммы подразумевает, что после ее выполнение и выхода из подпрограммы, выполнение основной программы продолжится со следующей за вызовом строки. А поскольку одна и та же подпрограмма может быть вызвана из разных мест основной программы, или других подпрограмм, то адрес возврата каждый раз будет разным. Тут нам на помощь и приходит стек.
В момент вызова подпрограммы текущее положение программного счетчика сохраняется в верхушке стека, а в сам программный счетчик записывается адрес первой команды подпрограммы. После завершения подпрограммы выполняется специальная команда возврата, в результате которой в программный счетчик записывается значение из верхушки стека. И таким образом продолжается выполнение программы с того места, откуда было совершен вызов подпрограммы.
Подпрограмма может быть оформлена в виде процедуры, и тогда имя этой процедуры будет служить точкой входа в подпрограмму:
drawline proc ;Подпрограмма-процедура
... ;Тело подпрограммы
ret ;Команда возврата в вызывающую программу
drawline endp
С таким же успехом можно обойтись без процедуры, просто пометив первую строку программы некоторой меткой:
drawline: ;Подпрограмма, начинающаяся с метки
... ;Тело подпрограммы
ret ;Команда возврата в вызывающую программу
... ;Продолжение основной программы или другие подпрограммы
В любом случае вызов подпрограммы осуществляется командой call. Подпрограмма должна завершаться командой ret, служащей для возврата управления в ту точку, откуда подпрограмма была вызвана.
Вопросы использования подпрограмм, передачи в них параметров и возвращения результата будут рассмотрены в следующей главе. Здесь мы остановимся только на таких принципиальных архитектурных вопросах, как механизм выполнения и возможности команд call l и ret,. При этом надо иметь в виду, что синтаксические особенности и закономерности использования команд call и jmp во многом совпадают, и значительная часть пояснений к командам перехода справедлива и для команд вызова.
Команда вызова подпрограммы call может использоваться в 4 разновидностях. Вызов может быть:
· прямым ближним (в пределах текущего сегмента команд);
· прямым дальним (в другой сегмент команд);
· косвенным ближним (в пределах текущего сегмента команд через ячейку с адресом перехода);
· косвенным дальним (в другой сегмент команд через ячейку с адресом перехода).
Рассмотрим последовательно перечисленные варианты.
Прямой ближний вызов. Как и в случае прямого ближнего перехода, в команде прямого вызова в явной форме указывается адрес (смещение) точки входа в подпрограмму; в качестве этого адреса можно использовать как имя процедуры, так и имя метки, характеризующей точку входа в подпрограмму. В код команды, кроме кода операции E8h, входит смещение к вызываемой подпрограмме. В приведенном ниже примере подпрограмма оформлена в виде процедуры.
code segment
main proc ;Основная программа
…
call sub ;Код Е8 dddd
…
main endp
sub proc near ;Подпрограмма
…
ret ;Код С3
sub endp
code ends
Процедура-программа находится в том же сегменте команд, что и вызывающая программа. В коде команды dddd обозначает смещение в сегменте команд к точке входа в подпрограмму. При выполнении команды call l процессор помещает адрес возврата (содержимое регистра IP) в стек выполняемой программы (рис. 2.16), после чего к текущему содержимому IP прибавляет dddd. В результате в I IP P оказывается адрес подпрограммы. Команда ret, t, которой заканчивается подпрограмма, выполняет обратную процедуру - извлекает из стека адрес возврата и заносит его в IP IP.
Рис. 2.22 Участие стека в механизме вызова ближней подпрограммы.
Участие стека в механизме вызова подпрограммы и возврата из нее является решающим. Поскольку в стеке хранится адрес возврата, подпрограмма, сама используя стек, например, для хранения промежуточных результатов, обязана к моменту выполнения команды ret вернуть стек в исходное состояние. Команда ret, естественно, никак не анализирует состояние или содержимое стека. Она просто снимает со стека верхнее слово, считая его адресом возврата, и загружает это слово в указатель команд IP IP. Если к моменту выполнения команды ret t указатель стека окажется смещенным в ту или иную сторону, команда ret по-прежнему будет рассматривать верхнее слово стека, как адрес возврата, и передаст по нему управление, что неминуемо приведет к краху системы.
Прямой дальний вызов. Этот вызов позволяет обратиться к подпрограмме из другого сегмента. В код команды, кроме кода операции 9Ah,, входит полный адрес (сегмент плюс смещение) вызываемой подпрограммы. Обычно в исходном тексте программы с помощью описателя far ptr указывается, что вызов является дальним, хотя, если транслятор настроен на трансляцию в два прохода, этот описатель не обязателен. Структура программного комплекса, содержащая дальний вызов подпрограммы, может выглядеть следующим образом:
codel segment
assume CS:code1
main proc ;Основная программа
call far ptr subr ;Код 9А dddd ssss
…
main endp
codel ends
code2 segment
assume CS:code2
subr proc far ;Объявляем подпрограмму дальней
…
ret ;Код СВ - дальний возврат
subr endp
code2 ends
Процедура-подпрограмма находится в другом сегменте команд той же программы. В коде команды dddd обозначает относительный адрес точки входа в подпрограмму в ее сегменте команд, a ssss - се сегментный адрес. При выполнении команды call процессор помещает в стек сначала сегментный адрес вызывающей программы, а затем относительный адрес возврата (рис. 2.17). Далее в сегментный регистр CS S заносится ssss (у нас это значение code2), а в IP - IP - dddd (у нас это значение s subr). Поскольку процедура-подпрограмма атрибутом f far объявлена дальней, команда ret имеет код, отличный от кода аналогичной команды ближней процедуры и выполняется по-другому: из стека извлекаются два верхних слова и переносятся в IP и CS,, чем и осуществляется возврат в вызывающую программу, находящуюся в другом сегменте команд. В языке ассемблера существует и явное мнемоническое обозначение команды дальнего возврата - retf..
Рис. 2.17. Участие стека в механизме вызова дальней подпрограммы.
Косвенный ближний вызов. Адрес подпрограммы содержится либо в ячейке памяти, либо в регистре. Это позволяет, как и в случае косвенного ближнего перехода, модифицировать адрес вызова, а также осуществлять вызов не с помощью метки, а по известному абсолютному адресу. Структура программы с косвенным вызовом подпрограммы может выглядеть следующим образом:
code segment
main proc;Основная программа
…
call DS:subadr;Код FF 16 dddd
main endp
subr proc near;Подпрограмма
…
ret;Код СЗ
subr endp
code ends
data segment
…
subadr dw subr;Яейка с адресом подпрограммы
data ends
Процедура-программа с атрибутом near находится в том же сегменте, что и вызывающая программа, а ее относительный адрес в ячейке subadr в сегменте данных. В коде команды dddd обозначает относительный адрес слова subadr в сегменте данных. Второй байт кода команды (1 16h в данном примере) зависит от способа адресации. Косвенный вызов позволяет использовать разнообразные способы адресации подпрограммы:
call BX ;В ВХ адрес подпрограммы
call[BX] ;В ВХ адрес ячейки с адресом подпрограммы
call[BX][SI] ;В ВХ адрес таблицы адресов подпрограмм, в SI индекс в этой таблице.
tbl[SI] ;tbl - адрес таблицы адресов подпрограмм, в SI индекс в этой таблице
Косвенный дальний вызов. Отличается от косвенного ближнего вызова лишь тем, что подпрограмма находится в другом сегменте, а в ячейке памяти содержится полный адрес подпрограммы, включающий сегмент и смещение.
codel segment
main proc ;Основная программа
call dword ptr subadr ;Код FF IE dddd
…
main endp
codel ends
code2 segment
subr proc far ;Подпрограмма
…
ret ;Код СВ
subr endp
code2 ends
data segment
…
subadr dd subr ;Двухсловная ячейка с адресом подпрограммы
data ends
Процедура-подпрограмма с атрибутом far находится в другом сегменте команд той же программы, а ее полный двухсловный адрес - в ячейке subadr в сегменте данных. Второй байт кода команды (I IE E в данном примере) зависит от способа адресации. Косвенный дальний вызов, как и косвенный ближний, позволяет использовать различные способы адресации.
Язык ассемблера не содержит средств для работы с файлами. Если такая необходимость возникает, то программа должна содержать фрагменты кода, в которых производится обращение к средствам операционной системы, осуществляющим взаимодействие с файловой системой. Это лишний раз подтверждает тот факт, что в области взаимодействия с внешним миром программа на ассемблере оказывается привязанной как к конкретной аппаратной, так и конкретной операционной платформам. В сегодняшней ситуации программисту все еще приходится сталкиваться с необходимостью программирования для MS DOS. Поэтому изучение средств для работы с файлами этой операционной платформы не потеряло своей актуальности и эти средства в плане совместимости поддерживаются различными реализациями Windows. В реализации MS DOS 7.0 введена поддержка длинных имен файлов, используемых системой файлового ввода-вывода Win 32. Таким образом можно выделить три аспекта работы с файлами из программ на ассемблере:
В основе файловой системы MS DOS лежит древовидная структура каталогов. Корень этой структуры представляет собой совокупность ограниченного числа дескрипторов, описывающих файлы и каталоги (подкаталоги) следующего уровня. Подкаталог представляет собой файл особого типа, который содержит дескрипторы файлов и подкаталогов очередного нижележащего уровня. В отличие от корневого каталога количество дескрипторов в подкаталоге не ограничено и определяется только размером диска. Дескриптор представляет собой экземпляр структуры размером 32 байта. Поля этой структуры содержат различную информацию о файле: идентификатор файла и его характеристики — дата и время создания (модификации), номер начального кластера, длина файла и его атрибуты.
Для использования файла в программе необходимо выполнить следующие операции:
Создание, открытие, закрытие и удаление файла
Прежде чем использовать файл в программе, его необходимо открыть с помощью функции 3dh прерывания 21h. Если файл не существует, то перед открытием его нужно создать. Оба эти действия выполняются одной из следующих функций: 3ch, 5bh, 5ah, 6ch.
В конце работы с файлом его нужно закрыть. Но это действие не является обязательным, так как функция 4сп, которая завершает выполнение программы, в числе прочих действий выполняет и закрытие всех файлов.
Запись в файл производится функцией 40h с текущей позиции файлового указателя. Вход: АН = 40 h; ВХ = дескриптор файла; СХ = количество байтов для записи;
Чтение из файла в область памяти осуществляется функцией 3Fh.
Для переименования файла используется функция 56h.
Архитектура операционной системы
Архитектура ОС – это структурная организация и принципы построения ОС на основе различных программных модулей.
Обычно в состав ОС входят:
– исполняемые и объектные модули стандартных для данной ОС форматов;
– библиотеки разных типов;
– модули исходного текста программ;
– программные модули специального формата (например, загрузчик ОС, драйверы ввода-вывода);
– файлы конфигурации;
– файлы документации;
– модули справочной системы
и т. д.
Не существует единой архитектуры ОС, но существуют универсальные подходы к структурированию ОС.
Наиболее общим подходом к структуризации ОС является разделение всех ее модулей на две группы:
– ядро – модули, выполняющие основные функции ОС (управление памятью, устройствами ввода-вывода и т. д.);
– модули, выполняющие вспомогательные функции ОС.
Без ядра ОС является полностью неработоспособной и не может выполнить ни одну из своих функций.
В состав ядра входят:
– функции, решающие внутрисистемные задачи организации вычислительного процесса (такие функции недоступны для приложений);
– функции для поддержки приложений. Эти функции создают для приложений так называемую прикладную программную среду;
– функции, которые могут вызываться приложениями, так как приложения могут обращаться к ядру с запросами (системными вызовами) для выполнения каких-либо действий. Такие функции образуют интерфейс прикладного программирования[1] –ApplicationProgramInterface (API).
Вспомогательные модули ОС обычно подразделяются на следующие группы:
– утилиты –программы, решающие отдельные задачи управления и сопровождения компьютерной системы;
– системные обрабатывающие программы – текстовые или графические редакторы, компиляторы, компоновщики, отладчики;
– программы предоставления пользователю дополнительных услуг – специальный вариант пользовательского интерфейса, калькулятор, игры;
– библиотеки процедур различного назначения, упрощающие разработку приложений, например библиотека математических функций, функций ввода-вывода и т. д.
Модули ОС, оформленные в виде утилит, системных обрабатывающих программ и библиотек, обычно загружаются в оперативную память только на время выполнения своих функций, то есть являются транзитными (или диск-резидентными).
Вспомогательные модули ОС оформляются или в виде приложений, или в виде библиотек процедур.
Рис. 1 Взаимодействие между ядром и вспомогательными модулями ОС
Так как некоторые компоненты ОС оформлены как обычные приложения (в виде исполняемых модулей стандартного для данной ОС формата), то часто очень сложно разграничить модули ОС и приложения (рис.2).
|
Рис. 2 Нечеткость границы между ОС и приложениями
Аппаратура компьютера должна поддерживать как минимум два режима работы – пользовательский режим (usermode) и привилегированный режим, который также называют режимом ядра (kernelmode), или режимом супервизора [2] (supervisormode).
ОС или некоторые ее части работают в привилегированном режиме, а приложения – в пользовательском режиме. Так как ядро выполняет все основные функции ОС, то чаще всего именно ядро работает в привилегированном режиме (рис. 3). Иногда работа в привилегированном режиме служит основным определением понятия "ядро".
Рис. 3 Архитектура операционной системы с ядром в привилегированном режиме
Приложения ставятся в подчиненное положение за счет запрета выполнения в пользовательском режиме некоторых критичных команд, связанных с переключением процессора с задачи на задачу, управлением устройствами ввода-вывода, доступом к механизмам распределения и защиты памяти. Выполнение некоторых инструкций в пользовательском режиме запрещается категорически, другие инструкции запрещается выполнять только при определенных условиях. Например, инструкции ввода-вывода могут быть запрещены приложениям при доступе к контроллеру жесткого диска, который хранит данные, общие для ОС и всех приложений, но разрешены при доступе к последовательному порту, который выделен в монопольное владение для определенного приложения.
Дата добавления: 2014-12-19; просмотров: 33 | Поможем написать вашу работу | Нарушение авторских прав |