Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Textnew2

.pdf
Скачиваний:
13
Добавлен:
06.02.2018
Размер:
1.48 Mб
Скачать

А.Н. ФЛОРЕНСОВ

АППАРАТНО ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ

Учебное пособие

ОМСК 2004

1

ВВЕДЕНИЕ

Вгосударственном стандарте Российской Федерации от 2000 года для специальности 220100 (Вычислительные машины, комплексы, системы и сети) содержание учебной дисциплины "Системное программное обеспечение" задается перечислением следующих базовых вопросов: "пользовательский интерфейс операционной среды; управление задачами; управление памятью; управление вводом-выводом; управление файлами; пример современной операционной системы; программирование в операционной среде; ассемблеры; мобильность программного обеспечения; макроязыки; трансляторы; формальные языки и грамматики, типы грамматик; вывод цепочек; конечный и магазинный автоматы, распознаватели и преобразователи, построение автомата по заданной грамматике; структура компиляторов и интерпретаторов, лексический, синтаксический и семантический анализаторы, генератор кода; распределение памяти, виды переменных; статическое и динамическое связывание; загрузчики; функции загрузчика; настраивающий и динамический загрузчики; подключение библиотек". Как видим одним из принципиальных вопросов, требующих изучения будущими специалистами по информатике и вычислительной техники, является изучение даже не одного какого-то ассемблера, а более широкое их изучение. Аналогичным образом изучение ассемблеров включено в ту же учебную дисциплину и для специальности 220200 (Автоматизированные системы обработки информации и управления).

К настоящему времени на книжном рынке России присутствует немало книг и учебных пособий по программированию на ассемблеру [1-2,5-13,17-20 ]. Характерным свойством практически всех этих изданий является их базирование на ассемблере, созданного еще четверть века назад и предназначенным исключительно для семейства процессоров Intel8086. Модификации указанных ассемблеров позже стали охватывать и возможности 32-битных моделей процессоров, но содержание практически всех учебных пособий строятся в архаической теперь последовательности: вначале детально излагается ассемблер и основанные на нем средства доступа к операционной системе MS DOS, а потом, по мере накопления опыта, читателя переводят на освоение особенностей 32-битной архитектуры процессоров и приемы программирования на ассемблере для операционных системе типа MS Windows.

ВXXI веке однозадачная операционная система MS DOS, бывшая такой актуальной и, казалось, удобной более двадцати лет назад, окончательно устарела. Тем ни менее инерция мышления и действий преподавания настолько велика, что обучение организуется по старым путям. Отчасти это обусловлено тем, что программирование на языке ассемблера для операционных систем типа MS Windows требует значительно большего числа деталей и, с содержательной стороны, требует сознательного владения рядом понятий, которые единовременно дать читателям или слушателям невозможно из-за большого их суммарного объема.

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

2

привести, например, пособие [3], которое другие разделы государственного стандарта излагает очень систематически и подробно. Можно отметить и явное отворачивания от ассемблеров современных педагогов, мышление которых сформировалось под довлеющим влиянием формальных систем. Для последних близким и естественным кажется изучение и использование языков программирования высокого уровня (чем выше уровень, тем лучше!), но от ассемблеров они отворачиваются, как от чего-то низменного и недостойного.

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

Изучение ассемблеров для полноценных специалистов в информатике совершенно необходимо по той причине, что с помощью их человек-создатель (называемый на современном языке "инженер") способен почувствовать и уяснить, что информация в материальном мире обязательно имеет место расположения, которое для нее необходимо где-то выделить! Практически к настоящему времени известны три уровня информационных понятий, имеющих операционный характер. (Операционными считаются такие понятия, которые можно подвергать операциям, достаточно универсальным, чтобы не зависеть от субъекта или наблюдателя.)

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

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

3

там). Более того, этот прием очень часто практически используется на уровне аппаратуры! Настойчивые попытки заставить выполнятся программу со стороны человека, владеющего только первыми двумя из перечисленных уровней операционных понятий, в ряде случае подобны усилиям разместит ее в "прокрустовом ложе" технической реализации. Если до сих пор такие попытки не являются "камнем преткновения", то исключительно по той причине, что основные создатели программного обеспечения начала XXI века учились и осваивались еще десять-двадцать лет назад, когда было естественным понимание не только машины Тьюринга, но и особенностей программирования на ассемблере.

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

С учетом сделанных выше замечаний об архаизме традиционных путей изучения ассемблера, предлагаемый далее учебный курс будет базироваться на ассемблере NASM - Netwide Assembler. Этот ассемблер, предлагаемый как программный пакет

ссоответствующей документацией [22], представляет собой свободно распространяемый продукт. Последнее обстоятельство должно быть особенно существенным для современных российских вузов, так как традиционные ассемблеры фирм Microsoft и Borland/Inprise являются коммерческими продуктами и далеко не каждый из российских вузов может позволить себе их приобретение.

Еще более существенной особенностью NASM оказывается его многоплатформенность: с помощью ее можно разрабатывать и выполнять программы как в ОС MS Windows и старой MS DOS, так и в ОС Linux, приобретающей сейчас все большее значение как в образовании, так и в корпоративных сетях. Хотя программирование на основе ассемблера не обеспечивает мобильности в пределах семейства ни только UNIX, но и более узкого семейства Linux, начальное знакомство

сассемблером оказывается проще всего осуществить именно на основе операционной системы Linux для PC компьютеров (компьютеров на процессорах, совместимых с Intel386). Это обусловлено тем, что указанная реализация Linux использует 32-битную архитектуру процессора и имеет простой доступ через прерывания к функциям ядра этой ОС.

Освоив основные языковые средства и приемы программирования в архитектуре Intel386 (называемой также архитектурой IA32), оказывается уже не сложным перейти на особенности доступа к системным функциям операционных систем типа MS Windows, что и делается в следующей части пособия. Для настойчивых

4

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

1. ОСНОВЫ ПРОГРАММИРОВАНИЯ НА АССЕМБЛЕРЕ

1.1. Принципы построения ассемблерных программ

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

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

5

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

Под языком программирования ассемблер в настоящее время понимается язык программирования, задающий действия отдельных команд - условными (мнемоническими) обозначениями и позволяющий использовать символические обозначения обрабатываемых данных. Характерным для него является соответствие: одна элементарная конструкция языка (команда ассемблера) - одна машинная команда, получаемая в результате трансляции. Естественно, что этот язык аппаратно зависим и аппаратно ориентированн.

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

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

2.Каждое действие процессоре, задаваемого ассемблером на отдельной строке, называется командой (в некоторых ассемблерах - инструкцией).

3.Собственно действие команды задается мнемокодом (сокращенным удобным для запоминания наименованием) на английском языке (последнее сложилось исторически из-за лидерства англоязычных стран с конца 60-х в области компьютерной техники).

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

5.В одной команде ассемблера задается лишь одна операция преобразования (одно сложение, умножение, сдвиг двоичного кода и т.п.).

6.Для указания места перехода при выполнении операций управления ходом выполнения программы используются метки, в качестве которых применяются символические имена (идентификаторы) с последующими за ними символом "двоеточие".

7.Для удобства введения комментариев, в том числе и с начала строки, используется символ "точка с запятой". (Напомним, что в современных языках высокого уровня он применяется как разделитель операндов или признак конца операнда. Так как в ассемблере каждая команда занимает точно одну строку, то с этой целью

вассемблере подобный разделитель совершенно не нужен.)

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

6

команды. Значительная часть из них аналогична по семантике операторам раздела описания программ в языках высокого уровня.

9. В общем случае команда ассемблера имеет вид

МЕТКА МНЕМОКОД ОПЕРАНДЫ КОММЕНТАРИИ

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

1.2. Понятие архитектуры компьютера

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

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

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

Архитектура процессора состоит из:

1.Внутренних узлов хранения информации в процессоре, явно указываемых или используемых в машинных командах. Эти узлы называют регистрами программиста.

2.Способов доступа к ячейкам внешней по отношению к процессору адресуемой памяти (так называемой основной памяти компьютера).

7

3.Описания форматов и функций машинных команд.

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

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

Одним из фундаментальных понятий современной вычислительной техники и системного программирования является понятие адреса. Адрес информационного объекта - это порядковый номер ячейки памяти, начиная с которой размещается в основной памяти эта информация. Понятие адреса относится как к данным, так и к командам (адрес команды). Для обеспечения большой гибкости процессоры, начиная с 60-х годов, используют в качестве элементарной адресуемой ячейки основной памяти байт (группу из 8 бит). Адресовать информацию размером менее байта

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

Для операндов, размещаемых в основной памяти, информация машинного кода команды определяет адрес, называемый обычно эффективным или исполнительным. В современных архитектурах компьютеров исполнительный адрес отличается от действительного адреса информации в памяти. Сделано это для возможности перемещения программ. Действительно, если бы такого отличия не было, то при необходимости разместить исполняемую программу с другого реального места памяти потребовалось бы ее переделать: либо перетранслировать с учетом новой информации о размещении при выполнении, либо скорректировать те части машинных кодов, которые определяют исполнительный адрес. Решением явилось введение и использование специальных регистров, называемых обычно сегментными или, ранее, базовыми регистрами программы. Эти регистры предназначены для хранения информации, исходя из которой определяется действительный адрес начала программы в памяти - в общем случае части программы, называемой сегментом. Тогда при перемещении программы в памяти достаточно скорректировать лишь содержимое регистра сегмента (сегментов) программы. В настоящее время этот подход используется во всех архитектурах процессоров, исключая лишь простейшие микропроцессоры.

1.3. Регистры программиста в IA32

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

8

упражнения, а в данном случае также разрабатывать и исследовать конкретные программы. На момент написания пособия наиболее распространены компьютеры, являющиеся потомками компьютеров IBM PC и основанные на процессорах фирмы Intel. Все они используются в режиме 32-битной обработки данных. Соответствующая архитектура для краткости обозначается IA32. Именно она взята в качестве практической базы дальнейшего изучения программирования на ассемблере.

Полное описание этой архитектуры занимает сотни страниц самого лаконичного текста, поэтому для его детального изложения следует обратиться к учебным пособиям [5-7,19]. Здесь же будет приведена минимальная информация, без которой невозможно дальнейшее изложение частных вопросов системного программирования.

Множество регистров программиста в архитектуре IA32 разделяется на регистры общего назначения, обозначаемые мнемониками EAX, EBX, ECX, EDX, регистры указателей и индексов с обозначениями ESI, EDI, EBP, ESP, регистр признаков (флажков) EFlags, регистр указателя команд EIP и сегментные регистры. Все эти регистры, кроме сегментных, 32-битные, причем все перечисленные регистры, кроме сегментных, могут использоваться и частично, а именно могут применяться только их младшие половины. Эти половины обозначаются, соответственно, AX, BX, CX, DX, SI, DI, BP, SP, Flags, IP. Младшие половины 16-битных регистров общего назначения могут к тому же использоваться по частям - по составляющим байтам. Именно, младшие половины этих частей обозначаются, соответственно, AH, BH, CH, DH (от англ. слова High), младшие - AL, BL, CL, DL (от англ. слова Low). Происхождение наименования регистров восходит к названию их основной программной функции: AX - аккумулятор (accumulator - накапливающий - основной регистр), BX - базовый (base register), CX регистр счетчика (counter), DX - регистр данных (data). (Второй символ наименования в обозначении регистров общего назначения появился из английского слова eXtented в связи с тем, что эти регистры исторически являются расширением регистров A, B, C, D архитектуры отдаленного предшественника IA32 - микропроцессоров I80). Мнемоника DI расшифровывается как Distignation Index (индекс назначения), SI - Source Index (индекс источника). Регистры BP и SP являются аббревиатурой Base Pointer и Stack Pointer, а IP - Instruction Pointer. Заметим, что 16-битная разрядность сегментных регистров сохранена и в архитектуре I32, в которой все остальные регистры обязательно 32-битные.

Отличительной особенностью архитектуры IA32 является специализация регистров общего назначения - ситуация, практически не имеющая аналогов в конкурирующих семействах и связанная с прямой преемственностью базового процессора I86 по отношению к процессору I80, а также со стремлением разработчиков аппаратуры из фирмы Intel в условиях технических ограничений конца 70-х годов реализовать в нем возможно большее число функций. (Всего в процессоре I8086 было около 29000 транзисторов).

Регистр EAX является наиболее универсальным. Некоторые операции выполняются только с операндами, находящимися в EAX. Предшественник регистра EBX -

9

регистр BX - служил в прежней архитектуре в основном для хранения базового адреса массива или структуры данных (относительно начала сегмента), но мог использоваться и для непосредственного хранения операндов, например, при сложении. Регистр ECX наиболее специализирован и во многих случаях изменяется в результате побочного эффекта при выполнении команд, машинный код которых не содержит указания на этот регистр; используется в основном как счетчик в циклах с параметром, который по умолчанию хранится в этом регистре. Многие команды в конце своего выполнения уменьшают значение этого счетчика (регистра) на 1 или 2. Специализированные функции регистров EDI, ESI, EBP и ESP будут описаны позже.

С учетом перспектив неизбежного в будущем перехода на другую архитектуру процессора следует подчеркнуть, что специализация регистров скорее исключении чем правило. Чаще всего в других архитектурах процессоров имелось от 8 до 16 регистров действительно общего назначения, хотя в ряде архитектур один-два из них зарезервированы за указателем команд и указателем стека. Иногда основные регистры делят на две части: регистры данных и адресные регистры, из которых только вторые могут использоваться при построении сложных способов адресации, а первые - лишь содержать данные.

1.4. Описание сегментной структуры программы

Действительная структура программы в машинных кодах включает так называемые сегменты памяти. Эта структура невидима из языков высокого уровня, но является неизбежной и даже благодарной реальностью машинных структур данных. Основное назначение таких сегментов в современных архитектурах - обеспечить защитой от ошибочного доступа машинные коды команд и области данных. (В область команд запрещается что-либо записывать в процессе выполнения программы, а из области данных управляющему устройству процессора запрещается читать коды, автоматически интерпретируя их как коды машинных команд.) Получается, что, как минимум, машинная программа должна иметь два сегмента: сегмент машинных кодов (кодов команд) и сегмент данных. Заметим, что только в однозадачной ОС MS-DOS, где не было или не использовались указанные выше средства защиты сегментов друг от друга, можно было все машинные коды программы - и данные, и коды команд - размещать в одном единственном сегменте, причем все сегменты в этой ОС могли иметь максимальный размер всего в 64 Кбайт.

Для описания сегментов служит специальное обозначение директивы, обозначаемые словом SEGMENT, причем в этом случае может использоваться запись служебного слова как строчными, так и прописными буквами. (Синонимом этого служебного слова является слово SECTION, которое можно всегда использовать для обозначения сегмента.) В простейшем случае директива сегмента имеет единственный аргумент, который задает имя описываемого сегмента. С учетом ориентации ряда ОС на язык Си, как язык соглашений взаимодействия с системой, рекомендуе-

10

Соседние файлы в предмете Операционные системы