Skip to content

Вопросы к РК1

Andrey Sapozhkov edited this page Jun 27, 2022 · 13 revisions

Здесь ответы на вопросы предыдущих годов. За них я не отвечаю.

Теория

1. Регистры общего назначения.

Регистры общего назначения - группа регистров, доступная для чтения/записи основными командами.

Предназначены для временного хранения данных, записи параметров машинных команд, арифметической обработки и т.д.

Существует всего 4 РОН: AX, BX, CX, DX. Каждый содержит в себе 16 бит и делится на 2 части по 8 бит - старшую (high, H) и младшую (low, L).

Обращаться можно как к регистру целиком, так и к его половинам по отдельности.

Назначения регистров общего назначения:

  • AX (AH + AL): аккумулятор - умножение, деление, обмен с устройствами ввода/вывода (команды ввода и вывода);
  • BX (BH + BL): базовый регистр в вычислениях адреса, часто указывает на начальный адрес (называемый базой) структуры в памяти;
  • CX (CH + CL): счетчик циклов, определяет количество повторов некоторой операции;
  • DX (DH + DL): определение адреса ввода/вывода, так же может содержать данные, передаваемые для обработки в подпрограммы.

2. Сегментные регистры. Адресация в реальном режиме. Понятие сегментной части адреса и смещения.

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

Реальный режим работы - режим совместимости современных процессоров с 8086.

Доступен 1 Мб памяти (220 байт), то есть разрядность шины адреса - 20 разрядов.

Физический адрес получается сложением адреса начала сегмента (на основе сегментного регистра) и смещения.

Сегментный регистр хранит в себе старшие 16 разрядов (из 20) адреса начала сегмента. 4 младших разряда в адресе начала сегмента всегда нулевые. Говорят, что сегментный регистр содержит в себе номер параграфа начала сегмента.

Память в реальном режиме работы процессора - пример.

|     Номер параграфа начала сегмента     |
 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
            |              Смещение               |

[SEG]:[OFFSET] => физический адрес:

  1. SEG необходимо побитово сдвинуть на 4 разряда влево (или умножить на 16, что тождественно)
  2. К результату прибавить OFFSET
5678h:1234h =>
  56780
+  1234
= 579B4

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

Распространённые пары регистров: CS:IP (указатель на следующую команду), DS:BX (указатель на некоторую переменную), SS:SP (указатель на вершину стека).

3. Регистры работы со стеком.

Пара регистров для работы со стеком: SS:SP.

SS - сегментный регистр, отвечающий за сегмент стека

SP - указатель на вершину стека

4. Структура программы. Сегменты.

Структура программы на языке ассемблера:

  • Модули (файлы исходного кода)
    • Сегменты (описание блоков памяти)
      • команды процессора;
      • инструкции описания структур данных, выделения памяти для переменных и констант;
      • макроопределения.

Сегмент - блок памяти размером до 64 Кб.

Любая программа состоит из сегментов.

Виды сегментов:

  • сегмент кода (CS)
  • сегмент данных (DS и дополнительные ES, FS, GS)
  • сегмент стека (SS)

Описание сегмента - директива SEGMENT:

имя SEGMENT [READONLY] [выравнивание] [тип] [разрядность] [‘класс’]
...
имя ENDS

Параметры директивы SEGMENT.

Выравнивание:

  • BYTE (сегмент может начинаться с произвольного адреса)
  • WORD (Адрес начала сегмента кратен 2 байтам)
  • DWORD (Адрес начала сегмента кратен 4 байтам)
  • PARA (сегмент располагается в начале параграфа, т.е. адрес начала сегмента кратен 16 байтам) - по умолчанию
  • PAGE (сегмент располагается в начале страницы, т.е. адрес начала сегмента кратен 256 байтам)

Тип:

  • PUBLIC (сегменты с одним именем будут располагаться в памяти друг за другом независимо от порядка объявления)
  • STACK (сегмент будет использоваться под стек (сегмент стека), все такие сегменты будут объединяться в один, увеличивая общий стек)
  • COMMON (сегменты будут накладываться, т.е. начинаться с одного и того же адреса; память выделится по размеру наибольшего из сегментов; метки, на которые ссылаются сегменты, будут указывать на одни и те же ячейки памяти)
  • AT (нужен аргумент - номер параграфа начала сегмента; сегмент будет загружаться в память по некоторому фиксированному постоянному адресу, независимо от расположения остальных сегментов)
  • PRIVATE (сегмент не объединяется с другими) - по умолчанию

Класс - любая метка, взятая в одинарные кавычки. Сегменты одного класса будут расположены в памяти друг за другом.

5. Прерывание 21h. Примеры ввода-вывода.

Прерывание - особая ситуация, когда выполнение текущей программы приостанавливается и управление передаётся программе-обработчику возникшего прерывания.

Виды прерываний:

  • аппаратные (асинхронные) - события от внешних устройств;
  • внутренние (синхронные) - события в самом процессоре, например, деление на ноль;
  • программные - вызванные командой INT;

Прерывание DOS 21h.

  • Аналог системного вызова в современных ОС.
  • Используется наподобие вызова подпрограммы.
  • Номер функции передаётся через AH.

Прерывание DOS - вывод на экран в текстовом режиме.

Функция Назначение Вход Выход
02 Вывод символа в stdout DL = ASCII-код символа -
09 Вывод строки в stdout DS:DX - адрес строки, заканчивающейся символом $ -

Прерывание DOS - ввод с клавиатуры.

Функция Назначение Вход Выход
01 Считать символ из stdin с эхом - AL - ASCII-код символа
06 Считать символ без эха, без ожидания, без проверки на Ctrl+Break DL = FF -
07 Считать символ без эха, с ожиданием и без проверки на Ctrl+Break - AL - ASCII-код символа
08 Считать символ без эха - AL - ASCII-код символа
10 (0Ah) Считать строку с stdin в буфер DS:DX - адрес буфера Введённая строка помещается в буфер
0Bh Проверка состояния клавиатуры - AL=0, если клавиша не была нажата, и FF, если была
0Ch Очистить буфер и считать символ AL=01, 06, 07, 08, 0Ah -

Примеры:

mov ah, 01h
int 21h        ; Ввод символа, его код теперь лежит в al

mov dl, al
mov ah, 02h
int 21h        ; Вывод символа, код которого лежит в dl

6. Стек. Назначение, примеры использования.

Структура данных стек:

  • LIFO/FILO (last in, first out) - последним пришёл, первым ушёл
  • Сегмент стека - область памяти программы, используемая её подпрограммами, а также (вынужденно) обработчиками прерываний
  • SP - указатель на вершину стека
  • В x86 стек "растёт вниз", в сторону уменьшения адресов (от старших адресов к младшим) (от конца сегмента к началу). В таком случае удобно определять переполнение стека, т.е. нужно просто отследить момент, когда SP стал равен нулю. Если бы этой механики не было, то приходилось бы где-то хранить размер стека.
  • При запуске программы SP указывает на конец сегмента.

Команды непосредственной работы со стеком.

Каждая такая команда делает сразу несколько действий (работают за несколько тактов процессора; на аппаратном уровне разбиты на атомарные команды):

PUSH:

  1. Уменьшает указатель вершины стека (регистр SP) на размер источника (того, что мы кладём в стек)
  2. Записывает значение из источника по адресу SS:SP

POP:

  1. Считывает значение из вершины стека (с адреса SS:SP) и записывает его в приёмник
  2. Увеличивает указатель вершины стека (регистр SP) на размер приёмника (того, что мы достаём из стека)

Команды:

  • PUSH <источник> - поместить данные в стек. Уменьшает SP на размер источника и записывает значение по адресу SS:SP.
  • POP <приёмник> - считать данные из стека. Считывает значение с адреса SS:SP и увеличивает SP.
  • PUSHA - поместить в стек регистры AX, CX, DX, BX, SP, BP, SI, DI.
  • POPA - загрузить регистры из стека (SP игнорируется)
  • PUSHF - поместить в стек содержимое регистра флагов
  • POPF - загрузить регистр флагов из стека

7. Регистр флагов.

Регистр FLAGS.

16 битов - отдельные флажки.

0   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15
CF  -   PF  -   AF  -   ZF  SF  TF  IF  DF   OF     IOPL    NT   -

Некоторые флаги напрямую менять нельзя, они меняются сами

TF, IF, IOPL, NT - системные флаги

  • CF (carry flag) - флаг переноса (при выходе за рязрядную сетку)
  • PF (parity flag) - флаг чётности
  • AF (auxiliary carry flag) - вспомогательный флаг переноса
  • ZF (zero flag) - флаг нуля
  • SF (sign flag) - флаг знака
  • TF (trap flag) - флаг трассировки
  • IF (interrupt enable flag) - флаг разрешения прерываний
  • DF (direction flag) - флаг направления
  • OF (overflow flag) - флаг переполнения (изменение знакового разряда)
  • IOPL (I/O privilege flag) (появился в 286 процессоре) - уровень приоритета ввода-вывода
  • NT (nested task) (появился в 286 процессоре) - флаг вложенности задач

Команды управления флагами.

  • STC/CLC/CMC - установить/сбросить/инвертировать CF
  • STD/CLD - установить/сбросить DF (Direction Flag); Если он равен нулю (по умолчанию), то обработка строк будет идти слева направо, т.е. по возрастанию адресов памяти, иначе наоборот
  • LAHF - загрузка флагов состояния в AH
  • SAHF - установка флагов состояния из AH
  • CLI/STI - запрет/разрешение прерываний (т.е. сброса/установки IF - Interruption Flag). Разумеется речь идёт не про программные, а про аппаратные прерывания.

8. Команды условной и безусловной передачи управления.

JMP <операнд> - команда безусловной передачи управления.

  • Передаёт управление в другую точку программы (на другой адрес памяти), не сохраняя какой-либо информации для возврата.
  • Операнд - непосредственный адрес (вставленный в машинный код), адрес в регистре или адрес в переменной.

Пример:

    ...
    XOR AX, AX
    MOV BX, 5
label1:
    INC AX
    ADD BX, AX
    JMP label 1

Команды условных переходов J..

(Зубков С. В. Assembler для DOS, Windows, Unix, глава 2)

  • Переход типа short или near
  • Обычно используются в паре с CMP
  • Термины “выше” и “ниже” - при сравнении беззнаковых чисел
  • Термины “больше” и “меньше” - при сравнении чисел со знаком

Виды условных переходов.

Часть 1:

Команда Описание Состояние флагов для выполнения перехода
JO Есть переполнение OF = 1
JNO Нет переполнения OF = 0
JS Есть знак SF = 1
JNS Нет знака SF = 0
JE, JZ Если равно/если ноль ZF = 1
JNE, JNZ Не равно/не ноль ZF = 0
JP/JPE Есть чётность / чётное PF = 1
JNP/JPO Нет чётности / нечётное PF = 0
JCXZ CX = 0 -

Часть 2:

Команда Описание Состояние флагов для выполнения перехода Знаковый
JB
JNAE
JC
Если ниже
Если не выше и не равно
Если перенос
CF = 1 Нет
JNB
JAE
JNC
Если не ниже
Если выше или равно
Если нет переноса
CF = 0 Нет
JBE
JNA
Если ниже или равно
Если не выше
CF = 1 или ZF = 1 Нет
JA
JNBE
Если выше
Если не ниже и не равно
CF = 0 и ZF = 0 Нет

Часть 3:

Команда Описание Состояние флагов для выполнения перехода Знаковый
JL
JNGE
Если меньше
Если не больше и не равно
SF <> OF Да
JGE
JNL
Если больше или равно
Если не меньше
SF = OF Да
JLE
JNG
Если меньше или равно
Если не больше
ZF = 1 или SF <> OF Да
JG
JNLE
Если больше
Если не меньше и не равно
ZF = 0 и SF = OF Да

9. Организация многомодульных программ.

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

PUBLIC идентификатор

Описывает идентификатор, как доступный из других модулей.

EXTRN определение[,определение].

Указывает, что идентификатор определен в другом модуле. Определение описывает идентификатор и имеет следующий формат:

имя:тип

"Имя" - это идентификатор, который определен в другом модуле. "Тип" должен соответствовать типу идентификатора, указанному при его определении, и может быть следующим: NEAR, FAR, PROC, BYTE, WORD, DWORD, DATAPTR, CODEPTR, FWORD, PWORD, QWORD, TBYTE, ABS или именем структуры.

10. Подпрограммы. Объявление, вызов.

CALL - вызов процедуры, RET - возврат из процедуры

CALL <операнд>

  • Она похожа на PUSH (но немного сложнее): сохраняет адрес следующей за ней команды в стеке (той, которая физически за ней стоит в памяти, а не та, которая вызывается при переходе в подпрограмму), уменьшает SP (в случае ближнего перехода на 2 байта, в случае дальнего - на 4 байта) и записывает по этому адресу значение IP либо CS:IP, в зависимости от размера аргумента. Далее управление программы передаётся на значение операнда (в регистр IP записывается смещение подпрограммы, а в случае дальнего перехода оно заносится ещё и в CS:IP; насчёт дальнего перехода не уверен, там не очень чёткая формулировка была)

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

  • Передаёт управление на значение аргумента.

RET/RETN/RETF <число>

Делает действия, обратные команде CALL:

  • Загружает из стека адрес возврата в регистр IP или, в случае дальнего перехода, в CS:IP
  • Увеличивает SP (на 2/4 байта в случае ближнего/дальнего перехода)

RETN/RETF - ближний/дальний (NEAR/FAR) возврат

Если указан операнд, его значение будет дополнительно прибавлено к SP для очистки стека от параметров.

Параметры в подпрограмму передаются через стек.

Использование стека подпрограммами

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

Включает в себя:

  • Параметры
  • Адрес возврата (обязательно)
  • Локальные переменные

Соглашения о вызовах - договорённости о том, какая сторона будет очищать стек и т.д..

Пример объявления процедуры:

MyProc proc near ; указываем тип перехода
    ...
Myproc endp

Возможные переходы:

  • short - не может прыгнуть дальше -128..127 байт от команды вызова процедуры (на практике не используется, но вроде как формально возможен)
  • near - можем прыгать сюда ближним переходом (в пределах того же сегмента, метка занимает 2 байта)
  • far - можем прыгать сюда дальним переходом (в пределах всей программы, метка занимает 4 байта)

11. Арифметические команды.

Двоичная арифметика - ADD, ADC, SUB, SBB.

ADC, SBB нужны для работы с большими числами, не помещающимися в регистры.

  • ADD, SUB не делают различий между знаковыми и беззнаковыми числами.
  • ADC <приёмник>, <источник> - сложение с переносом. Складывает приёмник, источник и флаг CF.
  • SBB <приёмник>, <источник> - вычитание с займом. Вычитает из приёмника источник и дополнительно - флаг CF.

Пример: сложение и вычитание 32-разрядных чисел: два числа, разбитые на старшие и младшие половинки, лежат в парах регистров DX и AX, BX и CX.

add ax, cx ; Складываем младшие половинки (могло произойти переполнение с установкой флага CF)
adc dx, bx ; Складываем старшие половинки с учётом флага CF, не прописывая руками никаких условных операторов.
sub ax, cx ; Вычитаем младшие половинки (разность может быть отрицательной, тогда установится флаг CF)
sbb dx, bx ; Вычитаем старшие половинки с учётом флага CF, не прописывая руками никаких условных операторов.

Эти команды могут выставлять все арифметические флаги.

Арифметические флаги - CF, OF, SF, ZF, AF, PF.

Команды умножения - IMUL, MUL, IDIV, DIV.

Умножение чисел со знаком:

  • IMUL <источник>
  • IMUL <приёмник>, <источник> - не из нотации процессора 8086, а из последующих
  • IMUL <приёмник>, <источник1>, <источник2> - не из нотации процессора 8086, а из последующих

Целочисленное деление со знаком:

  • IDIV <источник>

Результат округляется в сторону нуля, знак остатка совпадает со знаком делимого.

INC, DEC - инкремент, декремент.

INC <приёмник>
DEC <приёмник>
  • Увеличивает/уменьшает приёмник на 1.
  • В отличие от ADD, не изменяет CF.
  • OF, SF, ZF, AF, PF устанавливаются в соответствии с результатом.

NEG - команда изменения знака

NEG <приёмник>
  • Переводит число в дополнительный код и прибавляет к нему 1, чтобы получить число с противоположным знаком.

О двоично-десятичных числах.

  • Неупакованное двоично-десятичное число - байт от 00h до 09h.
  • Упакованное двоично-десятичное число - байт от 00h до 99h (цифры A..F не задействуются).
  • При выполнении арифметических операций необходима коррекция: 19h + 1 = 1Ah => 20h
inc al
daa

Логический, арифметический, циклический сдвиг - SAR, SAL, SHR, SHL, ROR, ROL, RCR, RCL.

Арифметический сдвиг (число сдвигается с учётом знака, т.е. знак сохраняется: -8>>2 = -4): SAR (Shift Arithmetic Right), SAL (Shift Arithmetic Left).

Логический сдвиг (знак не сохраняется, т.е. знаковый бит сдвигается так же, как и все остальные): SHR (SHift Right), SHL (SHift Left).

Циклический сдвиг: ROR (Rotate Operand Right), ROL (Rotate Operand Left).

Циклический сдвиг через перенос: RCR (Rotate through Carry Right), RCL (Rotate through Carry Left).

  • SAL тождественна SHL.
  • SHR зануляет старший бит (знак), SAR - сохраняет.
  • ROR, ROL - циклический сдвиг вправо/влево.
  • RCR, RCL - циклический сдвиг через CF (в операции участвует не 16 бит, а 17 - при сдвиге вправо младший бит попадёт в CF, а CF - в старший бит, при сдвиге влево всё произойдёт наоборот).

12. Команды побитовых операций.

Операции над битами и байтами - BT, BTR, BTS, BTC, BSF, BSR, SRTcc.

База - адрес битовой строки.

Смещение - номер бита, который следует считать.

Команды:

  • BTS <база>, <смещение> - считывает во флаг CF значение из битовой строки.
  • BTS <база>, <смещение> - установить бит в 1.
  • BTR <база>, <смещение> - сбросить бит в 0.
  • BTC <база>, <смещение> - инвертировать бит.
  • BSF <приёмник>, <источник> - прямой поиск бита (от младшего разряда к старшему, т.е. справа налево).
  • BSR <приёмник>, <источник> - обратный поиск бита (от старшего разряда к младшему, т.е. слева направо).
  • SETcc <приёмник> - выставляет приёмник (1 байт) в 1 или 0 в зависимости от условия, аналогично Jcc.

13. Команды работы со строками.

Строка-источник - DS:SI, строка-приёмник - ES:DI.

За один раз обрабатывается один байт (слово).

Команды:

  • MOVS/MOVSB/MOVSW <приёмник>, <источник> - копирование (На самом деле эта команда не имеет параметров. Она всегда пишет данные из DS:SI в ES:DI)
  • CMPS/CMPSB/CMPSW <приёмник>, <источник> - сравнение и выставление флага по его результатам
  • SCAS/SCASB/SCASW <источник> - сканирование источника (всегда это DS:SI), сравнение с AL/AX и выставление флага по его результатам
  • LODS/LODSB/LODSW <источник> - чтение (в AL/AX)
  • STOS/STOSB/STOSW <приёмник> - запись (из AL/AX)

Каждая такая команда делает сразу несколько действий (работают за несколько тактов процессора; на аппаратном уровне разбиты на атомарные команды):

  1. Читает из памяти по адресу источника (например DS:SI; SI - Source Index) 1, 2 или 4 байта (в зависимости от команды)
  2. Увеличивает индексы SI и DI на 1, 2 или 4
  3. Если команда связана с записью, то произойдёт запись 1, 2 или 4 байт по адресу приёмника (ES:DI; DI - Destination Index)

Среди перечисленных команд есть те, которые работают и с источником, и с приёмником, и те, которые работают только с источником или только с приёмником.

Эти команды работают только с отдельными байтами, машинными словами или двойными словами. Чтобы работать со всей строкой, нужно использовать префиксы.

Префиксы: REP/REPE/REPZ/REPNE/REPNZ

Применение: пишем префикс перед командой (через пробел), тогда команда будет выполняться в цикле со счётчиком CX, как команда loop.

Префиксы REPE/REPZ/REPNE/REPNZ дополнительно будут контролировать состояние флага ZF (т.е., например, REPZ будет выполнять цикл до тех пор, пока CX не равен 0 и ZF равен 0; остальное по аналогии).

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

14. Прерывания. Обработка прерываний.

Прерывание - особая ситуация, когда выполнение текущей программы приостанавливается и управление передаётся программе-обработчику возникшего прерывания.

Виды прерываний:

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

Таблица векторов прерываний в реальном режиме работы процессора.

Откуда процессор берёт адреса обработчиков прерываний?

Вектор прерывания — номер, который идентифицирует соответствующий обработчик прерываний.

Векторы прерываний объединяются в таблицу векторов прерываний, содержащую адреса обработчиков прерываний.

Свойства:

  • Располагается в самом начале оперативной памяти, начиная с нулевого физического адреса.
  • Доступно 256 прерываний (всего аппаратных, программных и внутренних).
  • Каждый вектор занимает 4 байта - полный адрес (2 байта для сегментной части адреса + 2 байта для смещения).
  • Размер всей таблицы - 1 Кб.

Срабатывание прерывания.

Что делает процессор при срабатывании прерывания (любого):

  1. Приостановка выполнения текущей программы
  2. Сохранение в текущий стек значение регистра флагов и полного адреса возврата (адреса следующей команды) - 6 байт. Это действие аналогично команде CALL
  3. Нахождение в таблице векторов прерываний программы-обработчика прерывания
  4. Передача управления по адресу обработчика из таблицы векторов, т.е. значения регистров CS и IP обновляются на 4 байта, которые хранятся в таблице векторов прерываний
  5. Возвращение управления текущей программе после окончания обработки прерывания

Реентерабельность - возможность повторно срабатывания прерываний, когда оно уже работает

Зачем запрещать прерывания:

(ведь на обычные программы они вообще-то не влияют)

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

15. Работа с портами ввода-вывода.

Порты ввода-вывода - механизм взаимодействия программы, выполняемой процессором, с устройствами компьютера.

Устройства могут сами о себе что-то сообщать с помощью прерываний, а если наша программа хочет что-то им сообщить, как-то настроить или какие-то данные из них получить, то для этого нужны команды IN и OUT.

Порт - отдельное адресное пространство (которое никак не пересекается ни с оперативной памятью, ни с постоянной), на которое отображаются внешние устройства.

Например, какое-то количество байт будет соответствовать клавиатуре, какое-то количество байт - таймеру, какое-то количество байт - жёсткому диску и т.д.

Номер порта - участок этого адресного пространства (в котором все внешние устройства объединены по отношению к процессору)

IN - команда чтения данных из порта ввода.

OUT - команда записи в порт вывода.

  • Пример:
IN al, 61h    ; читаем один байт состояния из порта ввода-вывода 61h
OR al, 3      ; выставляем младшие 2 бита в единицы
OUT 61h, al   ; отправляем полученное значение обратно в этот порт (мб мы как-то повлияли на работу устройства)
Clone this wiki locally