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

3222

.pdf
Скачиваний:
5
Добавлен:
15.11.2022
Размер:
3.54 Mб
Скачать

Пример. Адресация относительно счетчика команд

Вычислите значение непосредственного операнда и запишите машинный код для инструкции bne в программе, приведенной ниже.

0x40 loop: add $t1, $a0, $s0

0x44 lb $t1, 0($t1)

0x48 add $t2, $a1, $s0 0x4C sb $t1, 0($t2) 0x50 addi $s0, $s0, 1 0x54 bne $t1, $0, loop 0x58 lw $s0, 0($sp)

Решение. На рис. 2.19 показан машинный код инструкции bne. Ее целевой адрес ветвления равен 0x40 и находится на 6 инструкций раньше, чем значение PC + 4 (0x58), поэтому непосредственный операнд в этом случае равен – 6.

Рис. 2.19. Машинный код для инструкции bne из примера 6.8

Псевдопрямая адресация

При прямой адресации адрес перехода задаётся внутри инструкции. Инструкции безусловного перехода j и jal в идеале могли бы использовать прямую адресацию для определения 32-битного целевого адреса перехода (англ.: jump target address, JTA), указывающего адрес инструкции, которая будет выполнена следующей.

К сожалению, в формате инструкций типа J нет достаточного количества бит для того, чтобы задать полный 32битный адрес перехода. Шесть старших бит инструкции занимает код операции (поле opcode), поэтому для адреса перехода остаётся только 26 бит.

91

К счастью, два младших бита адреса перехода (JTA1:0) всегда должны быть равны нулю, потому что все инструкции выровнены по словам.

Следующие 26 бит адреса перехода (JTA27:2) берутся из поля addr инструкции. Четыре старших бита адреса перехода (JTA31:28) берутся из четырёх старших бит значения PC + 4. Такой способ адресации называется псевдопрямым.

В приведенном ниже примере кода показана инструкция jal, использующая псевдопрямую адресацию. Целевой адрес безусловного перехода этой инструкции равен 0x004000A0, а ее машинный код показан на рис. 2.20. Четыре старших и два младших бита целевого адреса перехода отбрасываются, а оставшиеся биты помещаются в 26-битное поле адреса (addr).

Пример. Вычисление целевого адреса перехода

0x0040005C jal sum

...

0x004000A0 sum: add $v0, $a0, $a1

Процессор вычисляет целевой адрес перехода инструкции типа J, добавляя два нуля в конец и четыре старших бита значения PC + 4 в начало 26-битного поля addr.

Рис. 2.20. Машинный код инструкции jal

Так как четыре старших бита адреса перехода берутся из значения PC + 4, то длина такого перехода ограничена.

Все инструкции типа J (j и jal) используют псевдопрямую адресацию. Обратите внимание, что инструкция безусловного перехода по регистру (jr) не является инструкцией типа J. Она является инструкцией типа R и выполняет переход по 32битному адресу, находящемуся в регистре rs.

92

2.8. Компиляция, ассемблирование и запуск программы

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

Мы начнем с рассмотрения карты памяти MIPS, описывающей расположение кода, данных и стека в памяти. Затем на примере покажем этапы выполнения кода программы.

Карта памяти

Так как архитектура MIPS использует 32-битные адреса, то размер адресного пространства составляет 232 байта = 4 гигабайта (Гбайт). Адреса слов кратны 4 и располагаются в промежутке от 0 до 0xFFFFFFFC. На рис. 2.21 изображена карта памяти MIPS.

Адресное пространство разделено на четыре части, или сегмента: сегмент кода, сегмент глобальных данных, сегмент динамических данных и зарезервированный сегмент.

Рис. 2.21. Карта памяти MIPS

93

Сегмент кода

Сегмент кода (англ.: text segment) содержит машинные команды исполняемой программы. Его размер достаточен для размещения почти 256 Мбайт кода. Обратите внимание, что четыре старших бита адреса в сегменте кода всегда равны нулю, что позволяет использовать инструкцию j для перехода по любому адресу в программе.

Сегмент глобальных данных

Сегмент глобальных данных (англ.: global data segment) содержит глобальные переменные, которые, в отличие от локальных переменных, находятся в области видимости всех функций программы. Глобальные переменные инициализируются при загрузке программы, но до начала ее выполнения. В программе на языке Си эти переменные объявляются вне функции main и доступны для всех функций. Размер этого сегмента позволяет разместить 64 Кбайт глобальных переменных.

Доступ к глобальным переменным осуществляется при помощи глобального указателя ($gp), который инициализируется значением 0x100080000. В отличие от указателя стека ($sp), $gp не меняется во время выполнения программы. Любая глобальная переменная доступна при помощи 16-битного положительного или отрицательного смещения относительно $gp. Во время ассемблирования смещение уже известно, так что для доступа к глобальным переменным можно использовать режим базовой адресации с константными смещениями.

Сегмент динамических данных

Сегмент динамических данных (англ.: dynamic data segment) содержит стек и кучу. В момент запуска программы этот сегмент не содержит данных – они динамически выделяются и освобождаются в нем в процессе выполнения программы. Сегмент динамических данных – это самый большой сег-

94

мент памяти, используемый программой; его размер составляет почти 2 Гбайт.

Cтек используется для сохранения и восстановления регистров, используемых функциями, а также хранения локальных переменных, таких как массивы. Стек растет вниз от верхней границы сегмента динамических данных (0x7FFFFFFC), а доступ к кадрам стека осуществляется в режиме очереди LIFO («последним пришел – первым ушел»).

Куча (англ.: heap) хранит блоки памяти, динамически выделяемые программе во время работы. В языке C выделение памяти осуществляется функцией malloc; в C++ и Java для этого служит функция new. Как и в случае кучи одежды на полу комнаты в общежитии, данные, находящиеся в куче, можно использовать и выбрасывать в произвольном порядке. Куча растет вверх от нижней границы сегмента динамических данных. Если стек и куча прорастут друг в друга, данные программы могут быть повреждены. Функция выделения памяти стремится избежать этой ситуации. Она возвращает ошибку нехватки памяти (англ.: out-of-memory error), если свободной памяти недостаточно для размещения новых динамических данных.

Зарезервированный сегмент

Зарезервированный сегмент используется операционной системой и не может использоваться непосредственно программой. Часть зарезервированной памяти используется для прерываний и для отображения устройств ввода-вывода в адресное пространство.

Трансляция и запуск программы

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

95

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

Рис. 2.22. Этапы трансляции и запуска программы

Пример Компиляция программы

Код на языке высокого уровня int f, g, y; // global variables

int main(void)

{

f = 2; g = 3;

y = sum(f, g); return y;

96

}

 

 

int sum(int a, int b) {

return (a + b);

 

}

 

 

Код на языке ассемблера MIPS

.data

 

f:

 

 

g:

 

 

y:

 

 

.text

 

 

main:

 

addi $sp, $sp, −4 # make stack frame

sw

$ra, 0($sp)

# store $ra on stack

addi $a0, $0, 2

# $a0 = 2

sw

$a0, f

# f = 2

addi $a1, $0, 3

# $a1 = 3

sw $a1, g

 

# g = 3

jal sum

# call sum function

sw

$v0, y

# y = sum(f, g)

Iw

$ra, 0($sp)

# restore $ra from stack

addi $sp, $sp, 4

# restore stack pointer

jr

$ra

# return to operating system

sum:

 

 

add

$v0, $a0, $a1 # $v0 = a + b

jr $ra

 

# return to caller

Этап 1: Компиляция Компилятор транслирует код высокого уровня в код на

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

97

ются метки. Места для их хранения будут определены ассемблером. На данный момент они остаются в коде в виде символов.

Этап 2: Трансляция Ассемблер транслирует код на языке ассемблера в объ-

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

0x00400000 main: addi $sp, $sp, −4

0x00400004

sw

$ra, 0($sp)

0x00400008

addi $a0, $0, 2

0x0040000C

sw

$a0, f

0x00400010

addi $a1, $0, 3

0x00400014

sw

$a1, g

0x00400018

jal sum

0x0040001C

sw

$v0, y

0x00400020

lw

$ra, 0($sp)

0x00400024

addi $sp, $sp, 4

0x00400028

jr

$ra

0x0040002C sum: add $v0, $a0, $a1 0x00400030 jr $ra

Имена и адреса символов хранятся в таблице символов. Таблица символов для нашего примера приведена в табл. 2.2. Адреса символов заполняются после первого прохода, когда адреса меток уже известны.

Глобальным переменным присваиваются адреса из сегмента глобальных данных, начиная с адреса 0x10000000. Во время второго прохода ассемблер генерирует машинный код.

98

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

Код на машинном языке и таблица символов сохраняются в объектном файле.

 

 

Таблица 2.2

 

Таблица символов

Символ

 

Адрес

f

 

0x10000000

g

 

0x10000004

y

 

0x10000008

main

 

0x00400000

sum

 

0x0040002C

Этап 3: Компоновка Большие программы обычно содержат много файлов.

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

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

В нашем примере только один объектный файл, поэтому никакого перемещения не требуется. На рис. 2.23 показан полученный исполняемый файл.

99

Рис.2.23. Исполняемый файл

Он состоит из трех секций: заголовка, сегмента кода и сегмента данных. Заголовок исполняемого файла содержит информацию о размерах сегмента кода (т.е. об объеме кода) и размерах сегмента данных (т.е. о количестве глобально объявленных данных). Все размеры приведены в байтах. Команды в сегмент кода приведены в том же порядке, в котором они расположены в памяти.

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

Например, первая команда sw $a0, 0x8000 ($gp) присваивает значение 2 глобальной переменной f, которая размещена в памяти по адресу 0x10000000. Помните, что смещение 0x8000

– это 16-битное число со знаком, которое после знакового расширения до 32 битов прибавляется к базовому адресу, находящемуся в регистре $gp. Таким образом, $gp + 0x8000 =

100

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]