Справочник от Автор24
Поделись лекцией за скидку на Автор24

Системное программирование

  • 👀 602 просмотра
  • 📌 524 загрузки
Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Системное программирование» docx
Системное программирование Системное программное обеспечение — комплекс программ, которые обеспечивают управление компонентами компьютерной системы, такими как процессор, оперативная память, устройства ввода-вывода, сетевое оборудование, выступая как «межслойный интерфейс», с одной стороны которого аппаратура, а с другой — приложения пользователя. В отличие от прикладного программного обеспечения, системное не решает конкретные практические задачи, а лишь обеспечивает работу других программ, предоставляя им сервисные функции, абстрагирующие детали аппаратной и микропрограммной реализации вычислительной системы, управляет аппаратными ресурсами вычислительной системы. Системное программирование — создание системного программного обеспечения. Системный программист — программист, специализирующийся на системном программировании. Отнесение того или иного программного обеспечения к системному условно, и зависит от соглашений, используемых в конкретном контексте. Как правило, к системному программному обеспечению относятся операционные системы[⇨], утилиты[⇨], системы программирования[⇨], системы управления базами данных[⇨], широкий класс связующего программного обеспечения. Одним из языков системного программирования является Ассемблер. Язык Ассемблер — это низкоуровневый язык программирования для компьютеров или других программируемых устройств, он специфичен для конкретной компьютерной архитектуры центрального процессора, что отличает его от большинства высокоуровневых языков программирования, которые обычно портативны среди разных систем. Язык Ассемблер преобразуется в исполняемый машинный код с помощью служебной программы, называемой ассемблером, такой как NASM, MASM и т. д. Каждый персональный компьютер имеет микропроцессор, который управляет арифметической, логической и контрольной активностью. Каждая семья процессоров имеет свой собственный набор инструкций для обработки различных операций, таких как получения ввода с клавиатуры, отображение информации на экране и выполнения различных других работ. Этот набор инструкций называется «инструкции машинного языка» ('machine language instructions'). Процессор понимает только инструкции машинного языка, которые являются строками из единиц и нулей. При этом машинный язык слишком непонятный и сложный для использования его в разработки программного обеспечения. И низкоуровневый язык Ассемблер предназначен для определённый групп процессоров, он представляет различные инструкции в символическом коде и более понятной форме. Преимущества языка Ассемблер Знание языка ассемблера позволяет понять: • Как программы взаимодействуют с ОС, процессором и BIOS; • Как данные представлены в памяти и других внешних устройствах; • Как процессор обращается к инструкции и выполняет её; • Как инструкции получают доступ и обрабатывают данные; • Как программа обращается к внешним устройствам. Другие преимущества использования ассемблера: • Программы на нём требует меньше памяти и времени выполнения; • Это упрощает сложные аппаратные задачи; • Подходит для работ, в которых время выполнения является критичным; • Он наиболее подходит для написания подпрограмм обработки прерываний и других программ, полностью находящихся в оперативной памяти. Системы счисления Основные характеристики аппаратной составляющей ПК Каждый компьютер содержит процессор и оперативную память. Процессор содержит регистры — компоненты, которые содержат данные и адреса. Для выполнения программы, система копирует её с устройства постоянного хранения во внутреннюю память. Процессор выполняет инструкции программы. Фундаментальной единицей компьютерного хранилища является бит. Он может быть в состоянии Включён (1) или Выключен (0). Группа из восьми связанных битов составляет байт, из которых семь бит используются для данных, а ещё один используется для контроля чётности. Согласно правилу чётности, количество битов, которые Включены (1) в каждом байте, всегда должно быть чётным. То есть бит чётности имеет значение 1, если у соответствующего байта количество 1-х битов нечётно. 0 — если иначе (чётно). Таким образом, бит чётности используется для того, чтобы сделать количество битов в байте чётным. Если соотношение является нечётным, система предполагает, что произошла ошибка соотношения (хотя и редко), которая могла быть вызвана неисправностью оборудования или электрическими помехами. Процессор поддерживает следующие размеры данных - • Word: 2-байтовый элемент данных • Doubleword: a 4-байтовый (32 бита) элемент данных • Quadword: 8-байтовый (64 бита) элемент данных • Paragraph: 16-байтовая (128 бита) область • Kilobyte: 1024 байт • Megabyte: 1,048,576 байт Двоичная система счисления В каждой системе счисления используются позиционные обозначения, то есть каждая позиция, в которой записана цифра, имеет различное позиционное значение. Каждая позиция — это степень базы, которая равна 2 для двоичной системы счисления, и эти степени начинаются с 0 и увеличиваются на 1. В таблице приведены позиционные значения для 8-битного двоичного числа, где все биты установлены в положение ON (Включено). Значение бита 1 1 1 1 1 1 1 1 Значение позиции как степень основания 2 128 64 32 16 8 4 2 1 Номер бита 7 6 5 4 3 2 1 Значение двоичного числа, как и в десятичном, зависит от составляющих его цифр и расположения этих цифр. Но в двоичном числе используются только цифры 1 и 0, и расположение цифр имеет другое значение степени. Первая цифра, как и в десятичном числе, может означать 0 или 1. Вторая цифра (смотрим число справа на лево) может означать 2 (если этот бит установлен на 1) или 0 (если бит установлен на 0). Третья цифра (смотрим число справа на лево) может означать 4 (если этот бит установлен на 1) или 0 (если бит установлен на 0). И так далее. В десятичном числе значение каждого символа нужно умножить на 10 в степени порядкового номера этой цифры за минусом единицы. То есть число 1337 это 1 * 103 + 3 * 102 + 3 * 101 + 7 * 100 = 1337 В двоичной системе всё точно также, только вместо десятки в степени порядкового номера за минусом единицы, нужно использовать двойку — вот и всё! Допустим число 110101 и нужно узнать, сколько это будет в десятичной системе счисления, для этого достаточно выполнить следующее преобразование: 1 * 25* + 1 * 24 + 0 * 23 + 1 * 22 + 0 * 21 + 1 * 20 = 1 * 32 + 1 * 16 + 0 * 8 + 1 * 4 + 0 * 2 + 1 * 1 = 53 Итак, значение бинарного числа основывается на наличии битов 1 и их позиционном значении. Поэтому значение числа 11111111 в двоичной системе является: 1 + 2 + 4 + 8 +16 + 32 + 64 + 128 = 255 Кстати, это то же самое, что и 28 - 1. Шестнадцатеричная система счисления Шестнадцатеричная система счисления использует основание 16. Цифры в этой системе варьируются от 0 до 15. По соглашению, буквы от A до F используются для представления шестнадцатеричных цифр, соответствующих десятичным значениям с 10 по 15. Шестнадцатеричные числа в вычислениях используются для сокращения длинных двоичных представлений. По сути, шестнадцатеричная система счисления представляет двоичные данные, деля каждый байт пополам и выражая значение каждого полубайта. В следующей таблице приведены десятичные, двоичные и шестнадцатеричные эквиваленты – Десятичное число Двоичный вид Шестнадцатеричный вид 1 1 1 2 10 2 3 11 3 4 100 4 5 101 5 6 110 6 7 111 7 8 1000 8 9 1001 9 10 1010 A 11 1011 B 12 1100 C 13 1101 D 14 1110 E 15 1111 F Чтобы преобразовать двоичное число в его шестнадцатеричный эквивалент, разбейте его на группы по 4 последовательные группы в каждой, начиная справа, и запишите эти группы в соответствующие цифры шестнадцатеричного числа. Пример — двоичное число 1000 1100 1101 0001 эквивалентно шестнадцатеричному - 8CD1 Чтобы преобразовать шестнадцатеричное число в двоичное, просто запишите каждую шестнадцатеричную цифру в её 4-значный двоичный эквивалент. Пример - шестнадцатеричное число FAD8 эквивалентно двоичному - 1111 1010 1101 1000 Отрицательные двоичные числа Компьютерные процессы действуют по своей логике и своим алгоритмам. И привычные операции вычитания, деления, умножения выполняются удобным для микропроцессора способом. Удобством для арифметических действий в процессоре обусловлено то, как записываются отрицательные двоичные числа. Вы должны помнить из курса информатики, что в одном байте содержится 8 бит. Но старший бит используется для установки знака. Чтобы правильно прочесть число, а также правильно поменять его знак, нужно выполнять следующие правила: Во-первых, если старшие биты (крайние слева), равны нулю, то их иногда не записывают. Например, восьмибитное число 10 (в десятичной системе счисления оно равно 2), также можно записать как 0000 0010. Обе эти записи означают число 2. Если старший бит равен нулю, то это положительное число. Например, число 110. В десятичной системе счисления это 6. Данное число является положительным или отрицательным? На самом деле, однозначно на этот вопрос можно ответить только, зная разрядность числа. Если это восьмиразрядное число, то его полная запись будет такой: 0000 0110. Как можно увидеть, старший бит равен нулю, следовательно, это положительное число. Для трёхбитовых чисел было бы справедливо следующее: Десятичное значение Двоичное значение трёхбитового числа со знаком (в представлении Дополнительный код)  000 1 001 2 010 3 011 -4 100 -3 101 -2 110 -1 111 Как можно понять после анализа предыдущей таблицы, для смены знака недостаточно просто поменять единицу на ноль — для преобразования числа в отрицательное, а также для чтения отрицательного числа существуют особые правила. Отрицательные двоичные числа записываются без знака минус и для получения этого же числа со знаком минус (то есть для получения числа в Дополненном коде) нужно выполнить два действия: 1. нужно переписать его полную форму с противоположным значением битов (то есть для единиц записываются нули, а для нулей записываются единицы) 2. и затем добавить к этому числу 1. Пример: Число 53 00110101 Замена битов на противоположные 11001010 Добавляем 1 00000001 Число -53 11001011 На русском языке такая форма записи называется Дополнительный код, в англоязычной литературе это называется Two's complement. Примеры восьмибитного двоичного числа в Дополнительном коде (старший бит указывает на знак): Десятичное значение Двоичное значение трёхбитового числа со знаком (в представлении Дополнительный код)  0000 0000 1 0000 0001 2 0000 0010 126 0111 1110 127 0111 1111 −128 1000 0000 −127 1000 0001 −126 1000 0010 −2 1111 1110 −1 1111 1111 Ещё примеры: Десятичное представление Двоичное представление (8 бит) (в виде Дополнительного кода) 127        0111 1111        1        0000 0001        0        0000 0000        -0        ---        -1        1111 1111        -2        1111 1110        -3        1111 1101        -4        1111 1100        -5        1111 1011        -6        1111 1010        -7        1111 1001        -8        1111 1000        -9        1111 0111        -10        1111 0110        -11        1111 0101        -127        1000 0001        -128        1000 0000        Числа в дополненном коде удобно применять для вычитания — это будет показано далее. Для преобразования отрицательного числа, записанного в дополнительном коде, в положительное число, записанное в прямом коде, используется похожий алгоритм. Рассмотрим пример с числом -5. Запись отрицательного восьмибитного числа: 1111 1011 Инвертируем все разряды отрицательного числа -5, получая таким образом: 0000 0100 Добавив к результату 1 получим положительное число 5 в прямом коде: 0000 0101 И проверка через сложение с дополнительным кодом 0000 0101 + 1111 1011 = 1 0000 0000, десятый разряд выбрасывается, то есть получается 0000 0000, то есть 0. Следовательно, преобразование выполнено правильно, так как 5 + (-5) = 0. Двоичная арифметика Следующая таблица иллюстрирует четыре простых правила для двоичного сложения: (i) (ii) (iii) (iv)       1 1 1 1 +0 +0 +1 +1 =0 =1 =10 =11 Эту таблицу нужно читать по столбцам сверху вниз. В первом столбце складываются 0 и 0 — в результате получается 0. Во втором примере складываются 1 и 0 (или 0 и 1 — без разницы), в результате получается 1. В третьем столбце складываются две единицы — в результате в текущей позиции получается 0, но на одну позицию влево добавляется единица. Если в этой позиции уже есть единица — то применяется это же правило, то есть в позиции пишется 0, и 1 передаётся влево. В четвёртом примере складываются три единицы — в результате, в текущей позиции записывается 1, и ещё одна 1 передаётся влево. Пример: Десятичные Двоичные 60 00111100 +42 00101010 102 01100110 Вычитание. Для вычитания число, которое вычитается, записывается в форме Дополнительного кода, а затем эти два числа складываются. Пример: Вычесть 42 из 53 Число 53 00110101 Число 42 00101010 Инвертируем биты 42 11010101 Добавляем 1 00000001 Число -42 11010110 Выполняем операцию: 53 - 42 = 11 00110101 + 11010110 = 100001011, то есть = 00001011 Бит который вызывает переполнение — крайней левый, девятый по счёту, просто отбрасывается. Адресация данных в памяти Процесс, посредством которого процессор управляет выполнением инструкций, называется циклом fetch-decode-execute (выборки-декодирования-выполнения) или циклом выполнения (execution cycle). Он состоит из трёх непрерывных шагов - • Извлечение инструкции из памяти • Расшифровка или идентификация инструкции • Выполнение инструкции Процессор может одновременно обращаться к одному или нескольким байтам памяти. Давайте рассмотрим шестнадцатеричное число 0725H (буква H означает, что это шестнадцатеричное число). Для этого числа потребуется два байта памяти. Байт старшего разряда или старший значащий байт — 07, а младший байт — 25. Процессор хранит данные в последовательности обратного байта, то есть байт младшего разряда хранится в низком адресе памяти и байт старшего разряда в старшем адресе памяти. Таким образом, если процессор переносит значение 0725H из регистра в память, он сначала перенесёт 25 на нижний адрес памяти и 07 на следующий адрес памяти. Когда процессор получает числовые данные из памяти для регистрации, он снова переворачивает байты. Есть два вида адресов памяти: • Абсолютный адрес — прямая ссылка на конкретное место. • Адрес сегмента (или смещение) — начальный адрес сегмента памяти со значением смещения. Основы синтаксиса Ассемблера Программу на языке Ассемблер можно разделить на три раздела: • Раздел data • Раздел bss • Раздел text Раздел data Раздел data используется для объявления инициализированных данных или констант. Эти данные не изменяются во время выполнения. В этом разделе вы можете объявить различные постоянные значения, имена файлов или размер буфера и т. д. Синтаксис объявления раздела data: 1 section.data Раздел BSS Секция bss используется для объявления переменных. Синтаксис объявления раздела bss: 1 section.bss Раздел text Раздел text используется для хранения самого кода. Этот раздел должен начинаться с объявления global _start, которое сообщает ядру, где начинается выполнение программы. Синтаксис объявления раздела text: 1 2 3 section.text    global _start _start: Комментарии Комментарий на ассемблере начинается с точки с запятой (;). Он может содержать любой печатный символ, включая пробел. Он может появиться в строке сам по себе, например: 1 ; Эта программа отображает сообщение на экране или в той же строке вместе с инструкцией, например: 1 add eax, ebx     ; добавляет ebx к eax Операторы Ассемблера Программы на ассемблере состоят из трёх типов операторов: • Исполняемые инструкции или инструкции, • Директивы ассемблера или псевдооперации (pseudo-ops), и • Макросы. Исполняемые инструкции или просто инструкции говорят процессору, что делать. Каждая инструкция состоит из кода операции (opcode). Каждая исполняемая инструкция генерирует одну инструкцию на машинном языке. Директивы ассемблера или псевдооперации говорят ассемблеру о различных аспектах процесса сборки. Они не являются исполняемыми и не генерируют инструкции машинного языка. Макросы — это в основном механизм подстановки текста. Синтаксис операторов ассемблера Операторы языка ассемблера вводятся по одной инструкции в каждой строке. Каждое утверждение имеет следующий формат: 1 [label]   мнемоника   [операнды]   [;комментарий] Поля в квадратных скобках являются необязательными. Основная инструкция состоит из двух частей: первая — это имя инструкции (или мнемоника), которая должна быть выполнена, а вторая — операнды или параметры команды. Ниже приведены некоторые примеры типичных операторов языка ассемблера. 1 2 3 4 5 6 7 8 9 10 11 12 13 INC COUNT        ; Увеличить переменную памяти COUNT   MOV TOTAL, 48    ; Перемести значение 48 в                  ; переменную памяти TOTAL                         ADD AH, BH       ; Добавить содержимое регистра                  ; BH в регистр AH                         AND MASK1, 128   ; Выполнить операцию AND на переменной                  ; MASK1 и 128                         ADD MARKS, 10    ; Добавить 10 к переменной MARKS MOV AL, 10       ; Перенести значение 10 в регистр AL Пример section .text    global _start     ;должно быть объявлено для линкера (linker) (ld)       _start:             ;показывает линкеру точку входа    mov  edx,len     ;длина сообщения    mov  ecx,msg     ;сообщение для записи    mov  ebx,1       ;файловый дескриптор (stdout - стандартный вывод)    mov  eax,4       ;номер системного вызова (sys_write)    int  0x80        ;вызов ядра          mov  eax,1       ;номер системного вызова (sys_exit)    int  0x80        ;вызов ядра   section .data msg db 'Всем привет!', 0xa  ;строка для печати len equ $ - msg     ;длина строки Сегменты памяти Модель сегментированной памяти делит системную память на группы независимых сегментов, на которые ссылаются указатели, расположенные в регистрах сегментов. Каждый сегмент используется для хранения данных определённого типа. Один сегмент используется для хранения кодов команд, другой — для хранения элементов данных, а третий — для программного стека. В свете вышеизложенного можно выделить различные сегменты памяти, такие как: • Сегмент Data. Он представлен разделом .data и .bss. Раздел .data используется для объявления области памяти, где хранятся элементы данных для программы. Этот раздел не может быть расширен после объявления элементов данных, и он остаётся статическим во всей программе. Раздел .bss также является разделом статической памяти, который содержит буферы для данных, которые будут объявлены позже в программе. Эта буферная память заполнена нулями. • Сегмент Code. Он представлен разделом .text. Он определяет область в памяти, в которой хранятся коды команд. Это также фиксированная зона. • Stack — этот сегмент содержит значения данных, передаваемые функциям и процедурам в программе. Ассемблер: регистры (Registers) Операции процессора в основном связаны с обработкой данных. Эти данные могут быть сохранены в памяти и доступны оттуда. Однако чтение данных из памяти и её сохранение в памяти замедляет процессор, поскольку включает сложные процессы отправки запроса данных через шину управления и в блок хранения памяти и получения данных по одному и тому же каналу. Для ускорения работы процессора процессор включает в себя несколько мест хранения внутренней памяти, называемых регистрами (registers). Регистры хранят элементы данных для обработки без необходимости доступа к памяти. Ограниченное количество регистров встроено в чип процессора. Регистры процессора В архитектуре IA-32 имеется десять 32-разрядных и шесть 16-разрядных процессорных регистров. Регистры сгруппированы в три категории: • Общие регистры, • Регистры управления и • Сегментные регистры. Общие регистры далее делятся на следующие группы: • Регистры данных, • Регистры указателя и • Индексные регистры. Регистры данных Четыре 32-битных регистра данных используются для арифметических, логических и других операций. Эти 32-битные регистры можно использовать тремя способами: • Как полные 32-битные регистры данных: EAX, EBX, ECX, EDX. • Нижние половины 32-битных регистров могут использоваться как четыре 16-битных регистра данных: AX, BX, CX и DX. • Нижняя и верхняя половины вышеупомянутых четырёх 16-битных регистров могут использоваться как восемь 8-битных регистров данных: AH, AL, BH, BL, CH, CL, DH и DL. Некоторые из этих регистров данных имеют конкретное применение в арифметических операциях. AX — основной аккумулятор; он используется во вводе/выводе и большинстве арифметических инструкций. Например, в операции умножения один операнд сохраняется в регистре EAX или AX или AL в соответствии с размером операнда. BX известен как базовый регистр, поскольку его можно использовать при индексированной адресации. CX известен как регистр подсчёта, так как регистры ECX, CX хранят счётчик циклов в итерационных операциях. DX известен как регистр данных. Он также используется в операциях ввода/вывода. Он также используется с регистром AX вместе с DX для операций умножения и деления с большими значениями. Регистры указателя Регистры указателя являются 32-разрядными регистрами EIP, ESP и EBP и соответствующими 16-разрядными правыми частями IP, SP и BP. Есть три категории регистров указателей: • Указатель инструкций (IP) — 16-битный регистр IP хранит адрес смещения следующей команды, которая должна быть выполнена. IP вместе с регистром CS (как CS:IP) даёт полный адрес текущей инструкции в сегменте кода. • Указатель стека (SP) - 16-разрядный регистр SP обеспечивает значение смещения в программном стеке. SP в сочетании с регистром SS (SS:SP) относится к текущей позиции данных или адреса в программном стеке. • Базовый указатель (BP) — 16-битный регистр BP в основном помогает ссылаться на переменные параметра, передаваемые подпрограмме. Адрес в регистре SS объединяется со смещением в BP, чтобы получить местоположение параметра. BP также можно комбинировать с DI и SI в качестве базового регистра для специальной адресации. Индексные регистры 32-разрядные индексные регистры ESI и EDI и их 16-разрядные крайние правые части. SI и DI, используются для индексированной адресации и иногда используются для сложения и вычитания. Есть два набора указателей индекса: • Исходный индекс (SI) — используется в качестве исходного индекса для строковых операций. • Указатель назначения (DI) — используется как указатель назначения для строковых операций. Регистры управления Регистр указателя 32-битной инструкции и регистр 32-битных флагов рассматриваются как регистры управления. Многие инструкции включают сравнения и математические вычисления и изменяют состояние флагов, а некоторые другие условные инструкции проверяют значение этих флагов состояния, чтобы перенести поток управления в другое место. Популярные биты флага: • Флаг переполнения (OF) — указывает на переполнение старшего бита (крайнего левого бита) данных после арифметической операции со знаком. • Флаг направления (DF) — определяет направление влево или вправо для перемещения или сравнения строковых данных. Когда значение DF равно 0, строковая операция принимает направление слева направо, а когда значение равно 1, строковая операция принимает направление справа налево. • Флаг прерывания (IF) — определяет, будут ли игнорироваться или обрабатываться внешние прерывания, такие как ввод с клавиатуры и т. д. Он отключает внешнее прерывание, когда значение равно 0, и разрешает прерывания, когда установлено значение 1. • Trap Flag (TF) — позволяет настроить работу процессора в одношаговом режиме. Программа DEBUG устанавливает флаг прерывания, чтобы можно было пошагово пройтись по инструкциям — по одной инструкции за раз. • Флаг знака (SF) — показывает знак результата арифметической операции. Этот флаг устанавливается в соответствии со знаком элемента данных после арифметической операции. Знак указывается старшим левым битом. Положительный результат очищает значение SF до 0, а отрицательный результат устанавливает его в 1. • Нулевой флаг (ZF) — указывает результат арифметической операции или операции сравнения. Ненулевой результат очищает нулевой флаг до 0, а нулевой результат устанавливает его в 1. • Вспомогательный флаг переноса (AF) — содержит перенос с бита 3 на бит 4 после арифметической операции; используется для специализированной арифметики. AF устанавливается, когда 1-байтовая арифметическая операция вызывает перенос из бита 3 в бит 4. • Флаг чётности (PF) — указывает общее количество 1-битов в результате, полученном в результате арифметической операции. Чётное число 1-бит очищает флаг чётности до 0, а нечётное число 1-битов устанавливает флаг чётности в 1. • Флаг переноса (CF) — содержит перенос 0 или 1 из старшего бита (крайнего слева) после арифметической операции. Он также хранит содержимое последнего бита операции shift или rotate. В следующей таблице указано положение битов флага в 16-битном регистре флагов: Флаг:         O D I T S Z   A   P   C Номер бита: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 Сегментные регистры Сегменты — это специальные области, определённые в программе для хранения данных, кода и стека. Есть три основных сегмента: • Сегмент Code — содержит все инструкции, которые должны быть выполнены. 16-битный регистр сегмента кода или регистр CS хранит начальный адрес сегмента кода. • Сегмент Data — содержит данные, константы и рабочие области. 16-битный регистр сегмента данных или регистр DS хранит начальный адрес сегмента данных. • Сегмент Stack — содержит данные и адреса возврата процедур или подпрограмм. Он реализован в виде структуры данных стека. Регистр сегмента стека или регистр SS хранит начальный адрес стека. Помимо регистров DS, CS и SS существуют и другие регистры дополнительных сегментов — ES (дополнительный сегмент), FS и GS, которые предоставляют дополнительные сегменты для хранения данных. При программировании на ассемблере программе необходим доступ к ячейкам памяти. Все области памяти в сегменте относятся к начальному адресу сегмента. Сегмент начинается с адреса, равномерно делимого на 16 или в шестнадцатеричном виде числа 10. Таким образом, крайняя правая шестнадцатеричная цифра во всех таких адресах памяти равна 0, что обычно не сохраняется в регистрах сегментов. Сегментные регистры хранят начальные адреса сегмента. Чтобы получить точное местоположение данных или инструкции в сегменте, требуется значение смещения (или смещение). Чтобы сослаться на любую ячейку памяти в сегменте, процессор объединяет адрес сегмента в регистре сегмента со значением смещения местоположения. Ассемблер: Переменные NASM предоставляет различные директивы определения (define directives) для резервирования места для хранения переменных. Директива определения ассемблера используется для выделения пространства хранения. Его можно использовать для резервирования, а также для инициализации одного или нескольких байтов. Выделение пространства хранения для инициализированных данных Синтаксис для оператора распределения памяти для инициализированных данных: 1 [имя-переменной]    директива-определения    начальное-значение   [,начальное-значение]... Где имя-переменной — это идентификатор для каждого пространства хранения. Ассемблер связывает значение смещения для каждого имени переменной, определённого в сегменте данных. Существует пять основных форм директивы определения: Директива Цель Размер хранения DB Определить Byte выделяет 1 байт DW Определить Word выделяет 2 байта DD Определить Doubleword выделяет 4 байта DQ Определить Quadword выделяет 8 байта DT Определить Ten Bytes выделяет 10 байта Ниже приведены некоторые примеры использования директив определения. 1 2 3 4 5 6 choice      DB  'y' number      DW  12345 neg_number  DW  -12345 big_number  DQ  123456789 real_number1    DD  1.234 real_number2    DQ  123.456 Внимание: • Каждый байт символа хранится как его значение ASCII в шестнадцатеричном формате. • Каждое десятичное значение автоматически преобразуется в его 16-разрядный двоичный эквивалент и сохраняется в виде шестнадцатеричного числа. • Процессор использует little-endian порядок байтов. • Отрицательные числа преобразуются в его представление Дополнительный код. • Короткие и длинные числа с плавающей запятой представлены с использованием 32 или 64 бит соответственно. Следующая программа показывает использование директивы определения: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 section .text    global _start          ;нужно указать для линкера (gcc)       _start:                   ;показывает линкеру точку входоа    mov  edx,1         ;длина сообщения    mov  ecx,choice        ;сообщение для записи    mov  ebx,1         ;файловый дескриптор (stdout)    mov  eax,4         ;номер системного вызова (sys_write)    int  0x80          ;вызов ядра      mov  eax,1         ;номер системного вызова (sys_exit)    int  0x80          ;вызов ядра   section .data choice DB 'y' Когда приведённый выше код компилируется и выполняется, он даёт следующий результат: 1 y Выделение дискового пространства для неинициализированных данных Директивы резервирования используются для запаса места для неинициализированных данных. Директивы резервирования принимают один операнд, который определяет количество единиц пространства, которое будет зарезервировано. Каждая директива определения имеет связанную директиву резервирования. Существует пять основных форм директив резервирования: Директива Цель RESB Зарезервировать Byte RESW Зарезервировать Word RESD Зарезервировать Doubleword RESQ Зарезервировать Quadword REST Зарезервировать 10 байт Множественность определений Можете иметь несколько операторов определения данных в программе. Например: 1 2 3 choice    DB    'Y'          ;ASCII of y = 79H number1   DW    12345    ;12345D = 3039H number2    DD  12345679  ;123456789D = 75BCD15H Ассемблер выделяет непрерывную память для нескольких определений переменных. Множественность инициализаций Директива TIMES позволяет выполнить несколько инициализаций к одному и тому же значению. Например, массив с именем marks размера 9 может быть определён и инициализирован на начальное значение ноль с помощью следующего оператора: 1 marks  TIMES  9  DW  0 Директива TIMES полезна при определении массивов и таблиц. Пример на повторение section .text global _start ;нужно указать для линкера (gcc) _start: ;говорит линкеру о точке входа ;первая строка текста mov edx,len ;длина сообщения mov ecx,msg ;сообщение для записи mov ebx,1 ;файловый дескриптор (stdout) mov eax,4 ;номер системного вызова (sys_write) int 0x80 ;вызов ядра ;вторая строка текста с повторением mov edx,len1 ;длина сообщения mov ecx,msg1 ;сообщение для записи mov ebx,1 ;файловый дескриптор (stdout) mov eax,4 ;номер системного вызова (sys_write) int 0x80 ;вызов ядра mov eax,1 ;номер системного вызова (sys_exit) int 0x80 ;вызов ядра section .data msg db 'Сейчас будет повторение',0xa ;сообщение len equ $ - msg ;длина сообщения msg1 times 6 db 'hello!!! ' ;сообщение с количеством повторений len1 equ $ - msg1 ;длина сообщения Ассемблер: Режимы адресации Большинство инструкций на ассемблере требуют обработки операндов. Адрес операнда предоставляет место, где хранятся данные, подлежащие обработке. Некоторые инструкции не требуют операнда, в то время как некоторые другие инструкции могут требовать один, два или три операнда. Когда инструкции требуется два операнда, первый операнд обычно является пунктом назначения, который содержит данные в регистре или ячейке памяти, а второй операнд является источником. Источник содержит либо данные для доставки (немедленная адресация), либо адрес (в регистре или памяти) данных. Как правило, исходные данные остаются неизменными после операции. Три основных режима адресации: • Адресации на регистр • Немедленная адресация • Адресация на память Адресации на регистр В этом режиме адресации регистр содержит операнд. В зависимости от инструкции регистр может быть первым операндом, вторым операндом или обоими. Например: 1 2 3 MOV DX, TAX_RATE   ; Регистр в первом операнде MOV COUNT, CX      ; Регистр во втором операнде MOV EAX, EBX       ; Оба операнда в регистрах Поскольку обработка данных между регистрами не требует памяти, она обеспечивает самую быструю обработку данных. Немедленная адресация Непосредственный операнд имеет постоянное значение или выражение. Когда инструкция с двумя операндами использует немедленную адресацию, первый операнд может быть регистром или ячейкой памяти, а второй операнд является непосредственной константой. Первый операнд определяет длину данных. Например: 1 2 3 4 BYTE_VALUE  DB  150    ; Определена величина byte WORD_VALUE  DW  300    ; Определена величина word ADD  BYTE_VALUE, 65    ; Добавлен немедленный операнд 65 MOV  AX, 45H           ; Немедленная константа 45H передана на AX Адресация на память Когда операнды указываются в режиме адресации на память, требуется прямой доступ к основной памяти, обычно к сегменту данных. Этот способ адресации приводит к более медленной обработке данных. Чтобы найти точное местоположение данных в памяти, нужен начальный адрес сегмента, который обычно находится в регистре DS, и значение смещения. Это значение смещения также называется действующим адресом (effective address). В режиме прямой адресации значение смещения указывается непосредственно как часть инструкции, обычно указывается именем переменной. Ассемблер вычисляет значение смещения и поддерживает таблицу символов, в которой хранятся значения смещения всех переменных, используемых в программе. При прямой адресации в памяти один из операндов ссылается на ячейку памяти, а другой операнд ссылается на регистр. Например: 1 2 ADD BYTE_VALUE, DL  ; Добавляет регистр в ячейку памяти MOV BX, WORD_VALUE  ; Операнд из памяти добавлен в регистр Прямая адресация со смещением Этот режим адресации использует арифметические операторы для изменения адреса. Например, посмотрите на следующие определения, которые определяют таблицы данных: 1 2 BYTE_TABLE DB  14, 15, 22, 45      ; Таблица bytes WORD_TABLE DW  134, 345, 564, 123  ; Таблица words Следующие операции обращаются к данным из таблиц в памяти в регистрах: 1 2 3 4 MOV CL, BYTE_TABLE[2]   ; Получает 3й элемент BYTE_TABLE MOV CL, BYTE_TABLE + 2  ; Получает 3й элемент BYTE_TABLE MOV CX, WORD_TABLE[3]   ; Получает 4й элемент WORD_TABLE MOV CX, WORD_TABLE + 3  ; Получает 4й элемент WORD_TABLE Косвенная адресация на память В этом режиме адресации используется способность компьютера Segment:Offset (Сегмент:Смещение). Обычно для этой цели используются базовые регистры EBX, EBP (или BX, BP) и регистры индекса (DI, SI), закодированные в квадратных скобках для ссылок на память. Косвенная адресация обычно используется для переменных, содержащих несколько элементов, таких как массивы. Начальный адрес массива хранится, скажем, в регистре EBX. В следующем фрагменте кода показано, как получить доступ к различным элементам переменной. 1 2 3 4 5 6 MY_TABLE TIMES 10 DW 0  ; Выделено 10 words (по 2 байта) каждое ; инициализировано на 0 MOV EBX, [MY_TABLE]     ; Эффективный адрес MY_TABLE в EBX MOV [EBX], 110          ; MY_TABLE[0] = 110 ADD EBX, 2              ; EBX = EBX +2 MOV [EBX], 123          ; MY_TABLE[1] = 123 Инструкция MOV Инструкция MOV, используется для перемещения данных из одного пространства хранения в другое. Инструкция MOV принимает два операнда. Синтаксис Синтаксис инструкции MOV: 1 MOV  пункт_назначения, источник Инструкция MOV может иметь одну из следующих пяти форм: 1 2 3 4 5 MOV  регистр, регистр MOV  регистр, непосредственное_значение MOV  память, непосредственное_значение MOV  регистр, память MOV  память, регистр Обратите внимание, что: • Оба операнда в операции MOV должны быть одинакового размера • Значение исходного операнда остаётся неизменным Инструкция MOV порой вызывает двусмысленность. Например, посмотрите на утверждения: 1 2 MOV  EBX, [MY_TABLE]  ; Эффективный адрес MY_TABLE в EBX MOV  [EBX], 110       ; MY_TABLE[0] = 110 Не ясно, нужно ли переместить байтовый эквивалент или словесный эквивалент числа 110. В таких случаях целесообразно использовать спецификатор типа (type specifier). В следующей таблице приведены некоторые общие спецификаторы типов: Спецификатор типа Байты BYTE 1 WORD 2 DWORD 4 QWORD 8 TBYTE 10 Пример section .text global _start ;нужно объявить для линкера _start: ;указывает компоновщику точку входа ;пишем имя 'Kate Rose' mov edx,10 ;длина сообщения mov ecx, name ;сообщение для записи mov ebx,1 ;файловый дескриптор (stdout) mov eax,4 ;номер системного вызова (sys_write) int 0x80 ;вызов ядра mov [name], dword 'Mary Rose' ; изменение имени Nuha Ali ;запись имени 'Mary Rose' mov edx,9 ;длина сообщения mov ecx,name ;сообщение для записи mov ebx,1 ;файловый дескриптор (stdout) mov eax,4 ;номер системного вызова (sys_write) int 0x80 ;вызов ядра mov eax,1 ;номер системного вызова (sys_exit) int 0x80 ;вызов ядра section .data name db 'Kate Rose' Ассемблер: Константы NASM предоставляет несколько директив, определяющих константы. Особое внимание обратим на три директивы: • EQU • %assign • %define Директива EQU Директива EQU используется для определения констант. Синтаксис директивы EQU следующий: 1 ИМЯ_КОНСТАНТЫ EQU выражение Пример: 1 TOTAL_STUDENTS equ 50 Затем можно использовать это постоянное значение в коде, например: 1 2 mov  ecx,  TOTAL_STUDENTS cmp  eax,  TOTAL_STUDENTS Операндом оператора EQU может быть выражение: 1 2 3 LENGTH equ 20 WIDTH  equ 10 AREA   equ length * width Приведённый фрагмент кода определит AREA как 200. Пример SYS_EXIT equ 1 SYS_WRITE equ 4 STDIN equ 0 STDOUT equ 1 section .text global _start ;нужно продекларировать чтобы использовать gcc _start: ;говорит линкеру точку входа mov eax, SYS_WRITE mov ebx, STDOUT mov ecx, msg1 mov edx, len1 int 0x80 mov eax, SYS_WRITE mov ebx, STDOUT mov ecx, msg2 mov edx, len2 int 0x80 mov eax, SYS_WRITE mov ebx, STDOUT mov ecx, msg3 mov edx, len3 int 0x80 mov eax,SYS_EXIT ;номер системного вызова (sys_exit) int 0x80 ;вызов ядра section .data msg1 db 'Привет, студент!',0xA,0xD len1 equ $ - msg1 msg2 db 'Начинается знакомство с ', 0xA,0xD len2 equ $ - msg2 msg3 db 'программированием на Ассемблере! ' len3 equ $- msg3 Ассемблер: Управление файлами Система рассматривает любые входные или выходные данные как поток байтов. Есть три стандартных файловых потока: • Стандартный ввод (stdin), • Стандартный вывод (stdout) и • Вывод ошибок (stderr). Файловый дескриптор Файловый дескриптор — это 16-разрядное целое число, назначаемое файлу в качестве идентификатора файла. Когда создаётся новый файл или открывается существующий файл, дескриптор файла используется для доступа к файлу. Файловый дескриптор стандартных файловых потоков — stdin, stdout и stderr — равны 0, 1 и 2 соответственно. Файловый указатель Файловый указатель определяет местоположение для последующей операции чтения/записи в файле в виде байтов. Каждый файл рассматривается как последовательность байтов. Каждый открытый файл связан с указателем файла, который задаёт смещение в байтах относительно начала файла. Когда файл открыт, указатель файла устанавливается в ноль. Системные вызовы обработки файлов В следующей таблице кратко описаны системные вызовы, связанные с обработкой файлов. %eax Имя %ebx %ecx %edx 2 sys_fork struct pt_regs - - 3 sys_read unsigned int char * size_t 4 sys_write unsigned int const char * size_t 5 sys_open const char * int int 6 sys_close unsigned int - - 8 sys_creat const char * int - 19 sys_lseek unsigned int off_t unsigned int Шаги, необходимые для использования системных вызовов: • Поместите номер системного вызова в регистр EAX. • Сохраните аргументы системного вызова в регистрах EBX, ECX и т. д. • Вызовите соответствующее прерывание (80h). • Результат обычно возвращается в регистр EAX. Создание и открытие файла Для создания и открытия файла выполните следующие задачи: • Поместите номер 8 системного вызова sys_creat() в регистр EAX. • Поместите имя файла в регистр EBX. • Поместите права доступа к файлу в регистр ECX. Системный вызов возвращает дескриптор файла созданного файла в регистр EAX, в случае ошибки код ошибки находится в регистре EAX. Открытие существующего файла Чтобы открыть существующий файл, выполните следующие задачи: • Поместите номер 5 системного вызова sys_open() в регистр EAX. • Поместите имя файла в регистр EBX. • Поместите режим доступа к файлу в регистр ECX. • Поместите права доступа к файлу в регистр EDX. Системный вызов возвращает дескриптор файла созданного файла в регистре EAX, в случае ошибки код ошибки находится в регистре EAX. Среди режимов доступа к файлам чаще всего используются: только чтение (0), только запись (1) и чтение-запись (2). Чтение из файла Для чтения из файла выполните следующие задачи: • Поместите номер 3 системного вызова sys_read() в регистр EAX. • Поместите дескриптор файла в регистр EBX. • Поместите указатель на входной буфер в регистр ECX. • Поместите размер буфера, то есть количество байтов для чтения, в регистр EDX. Системный вызов возвращает количество байтов, считанных в регистре EAX, в случае ошибки код ошибки находится в регистре EAX. Запись в файл Для записи в файл выполните следующие задачи: • Поместите номер 4 системного вызова sys_write() в регистр EAX. • Поместите дескриптор файла в регистр EBX. • Поместите указатель на выходной буфер в регистр ECX. • Поместите размер буфера, т.е. количество байтов для записи, в регистр EDX. Системный вызов возвращает фактическое количество байтов, записанных в регистр EAX, в случае ошибки код ошибки находится в регистре EAX. Закрытие файла Для закрытия файла выполните следующие задачи: • Поместите номер 6 системного вызова sys_close() в регистр EAX. • Поместите дескриптор файла в регистр EBX. Системный вызов возвращает, в случае ошибки, код ошибки в регистре EAX. Обновление файла Для обновления файла выполните следующие задачи: • Поместите номер 19 системного вызова sys_lseek() в регистр EAX. • Поместите дескриптор файла в регистр EBX. • Поместите значение смещения в регистр ECX. • Поместите референтную позицию для смещения в регистр EDX. Исходная позиция может быть: • Начало файла — значение 0 • Текущая позиция — значение 1 • Конец файла — значение 2 Системный вызов возвращает, в случае ошибки, код ошибки в регистре EAX. Пример Следующая программа создаёт и открывает файл с именем myfile.txt и записывает текст «Привет!» в этом файле. Далее программа читает файл и сохраняет данные в буфере с именем info. Наконец, он отображает текст как сохранённый в info. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 section .text    global _start         ;нужно декларировать для использования gcc  _start:                  ;точка входа для линкера    ; создаём файл    mov  eax, 8    mov  ebx, file_name    mov  ecx, 0777        ;чтение, запись и выполнение всеми    int  0x80             ;выхов ядра          mov [fd_out], eax          ; пишем в файл    mov  edx,len          ;количество байт    mov  ecx, msg         ;сообщение для записи    mov  ebx, [fd_out]    ;файловый дескриптор    mov  eax,4            ;номер системного вызова (sys_write)    int  0x80             ;вызов ядра          ; закрываем файл    mov eax, 6    mov ebx, [fd_out]    int  0x80             ;вызов ядра            ; вывод сообщения о том, что закончена запись в файл    mov eax, 4    mov ebx, 1    mov ecx, msg_done    mov edx, len_done    int  0x80          ; открываем файл для чтения    mov eax, 5    mov ebx, file_name    mov ecx, 0             ;доступ только для чтения    mov edx, 0777          ;чтение, запись и выполнение для всех    int  0x80          mov  [fd_in], eax          ; читаем из файла    mov eax, 3    mov ebx, [fd_in]    mov ecx, info    mov edx, 200    int 0x80          ; закрываем файл    mov eax, 6    mov ebx, [fd_in]    int  0x80          ; выводим info    mov eax, 4    mov ebx, 1    mov ecx, info    mov edx, 200    int 0x80             mov  eax,1             ;номер системного выхова (sys_exit)    int  0x80              ;выхов ядра   section .data    msg db 'Привет!'    len equ  $-msg      msg_done db 'Записано в файл', 0xa    len_done equ $-msg_done          file_name db 'myfile.txt'   section .bss    fd_out resb 1    fd_in  resb 1    info resb  200 Результат выполнения программы: 1 2 Записано в файл Привет от HackWare.ru! Ассемблер: Управление памятью Системный вызов sys_brk() предоставляется ядром для выделения памяти без необходимости её перемещения позже. Этот вызов выделяет память прямо за изображением приложения в памяти. Эта системная функция позволяет вам установить максимальный доступный адрес в разделе данных. Этот системный вызов принимает один параметр, который является наибольшим адресом памяти, который необходимо установить. Это значение сохраняется в регистре EBX. В случае ошибки sys_brk() возвращает -1 или возвращает отрицательный код ошибки. В следующем примере демонстрируется динамическое распределение памяти. Пример Следующая программа выделяет 16 КБ памяти с помощью системного вызова sys_brk() - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 section .text    global _start         ;нужно декларировать для использования gcc    _start:                  ;указываем линкеру точку входа      mov  eax, 45      ;sys_brk    xor  ebx, ebx    int  80h      add  eax, 16384   ;количество байт для резервирования    mov  ebx, eax    mov  eax, 45      ;sys_brk    int  80h          cmp  eax, 0    jl   exit    ;выйи, если ошибка    mov  edi, eax     ;EDI = самый высокий доступный адрес    sub  edi, 4       ;указание на последнее DWORD     mov  ecx, 4096    ;количество выделенных DWORD    xor  eax, eax     ;очищаем eax    std           ;назад    rep  stosd            ;повторить для всей выделенной области    cld           ;поместить DF флаг в нормальное состояние          mov  eax, 4    mov  ebx, 1    mov  ecx, msg    mov  edx, len    int  80h      ;печатаем сообщение   exit:    mov  eax, 1    xor  ebx, ebx    int  80h       section .data msg     db  "Выделено 16 кб памяти!", 10 len     equ $ - msg Результат работы приведённого выше кода: 1 Выделено 16 кб памяти!
«Системное программирование» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ

Тебе могут подойти лекции

Смотреть все 55 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot