Дневники чайника. Чтива 0, виток0

Системы счисления и устройство памяти.
Второй день

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

Только целые и только положительные.

Возможно, вы проходили эту тему в школе, и кто-то из вас даже что-то помнит, но начинать нужно именно отсюда.

Нас будут интересовать 3 системы счисления - dec, bin, hex.

Десятичная - Decimal (Dec или буква "d")

Aрабская система - она называется десятичной, потому что в ней используются 10 символов.

0,1,2,3,4,5,6,7,8,9

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

Запомни, юнга! В космосе нет верха, нет низа - это всё условности. И то, что у тебя десять пальцев на руках, это всего лишь исключение. У наших бинарных братьев всего два пальца, они смеются над тобой - урод десятипалый :). У них есть на это право, их больше и они старше. С Бинарниками надо дружить, иначе корабль собьют на подходе к первой же станции.

Двоичная система счисления - Binary (Bin или буква "b")

Нетрудно догадаться, что двоичная система имеет всего два символа 0 и 1.

Компьютер - это очень простой прибор, в нём есть только выключатели - биты (вкл. =1, выкл. =0).

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

11111111 - это байт
01010101 - и это байт
00000000 - и это тоже байт

Бит может иметь значение 0 или 1.

Байт - это 8 бит, и он может иметь значения от 0000 0000 - ноль, до 1111 1111 - 255 в десятичной системе (пробелы для читаемости). Получается, что у байта 256 значений (всегда считается вместе с нулевым).

биты       dec-цифры     |    биты       dec-цифры
00000001 = 1             |    00001011 = 11
00000010 = 2  !          |    00001100 = 12
00000011 = 3             |    00001101 = 13
00000100 = 4  !          |    00001110 = 14
00000101 = 5             |    00001111 = 15
00000110 = 6             |    00010000 = 16  !
00000111 = 7             |    00010001 = 17
00001000 = 8  !          |    00010010 = 18
00001001 = 9             |    00010011 = 19
00001010 = 10            |    00010100 = 20
И так до 11111111 = 255.

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

Как вы думаете, почему я выделил 2,4,8,16?

Правильно, это "круглые" цифры. В десятичной системе они, конечно, не круглые, но в двоичной получается 10,100,1000,10000. Поэтому десятичная система для компьютерных вычислений не очень подходит. Вместо неё используется...

Шестнадцатиричная система счисления - Hexadecimal (Hex или буква "h")

Имеет целых 16 символов. Чтоб не придумывать новые символы, в hex используются буквы латинского алфавита.

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F  - это цифры

Я приравняю все hex-символы к десятичным значениям.

h d    h d    h d     h d 
0=0    4=4    8=8     C=12
1=1    5=5    9=9     D=13
2=2    6=6    A=10    E=14
3=3    7=7    B=11    F=15

В этой системе счисления ноль справа прибавляется при умножении на 16 (десятичных).

Лишние нули слева от числа значения не имеют, так же, как и в математике.

Однако если число начинается с буквы (A-F), ноль слева нужен при наборе программ. Иначе как компилятор будет определять, что началось число? А чтобы не путать числа в разных системах и писать при этом коротко, пишут:

d - десятичные значения

01,02,03,04,05,06,07,08,09,10d,11d,12d,13d,14d,15d,16d,17d,18d,19d,20d...

h - шестнадцатиричные значения

01,02,03,04,05,06,07,08,09,0Ah,0Bh,0Ch,0Dh,0Eh,0Fh,10h,11h,12h,13h,14h...

b - двоичные значения

0,1,10b,11b,100b...

Вот примеры:

01   *  16d = 10h     (получается 16d)
10h  *  16d = 100h    (получается 256d)
100h *  16d = 1000h   (получается 4096d)


1    *  10h  = 10h
10h  +  10h  = 20h
10h  *  10h  = 100h
100h +  100h = 200h

10b  *  10b  = 100b

Удобно, правда? А вот так?

10d + 10h = 1Ah или 26d

Неудобно. Поэтому всегда ВСЕ ВЫЧИСЛЕНИЯ ДЕЛАЙТЕ В ОДНОЙ СИСТЕМЕ!

Сам я никогда не перевожу из hex в dec и в bin в уме или на листочке, для этого есть калькулятор. И мне знакома эта растерянность перед новыми цифрами. Но я и не рассчитываю, что стало понятно хоть что-то. Просто вы должны знать, что системы счисления hex & bin существуют. Через месяц практики вы привыкнете к шестнадцатиричной системе как к родной. А вот двоичная будет использоваться только в пределах четырёх байт. На экране монитора мне лишь изредка приходится видеть биты как "01011010", хотя часто их очень не хватает.

Теперь ещё раз про байт.

bin-числа   hex-числа
00001000  = 08
00010000  = 10h
00100000  = 20h
01000000  = 40h
10000000  = 80h
...
11111111b = FFh

В байт умещаются ровно два разряда hex-системы счисления! Именно так мы и будем видеть байты. Вспомните наш нулевой эксперимент:

         байты в hex                     символы в кодировке DOS (Р - русская буква)
90 41 90 41 90 90 41 41 42 43 44       |    РAРAРРAABCD

Теперь вы понимаете, что я имел в виду, сказав: "90 здесь 144". Правильнее было бы сказать 90h = 144d.

Байт это 8 бит, и что самое главное, байт - минимально адресуемая ячейка памяти.

Если нужно прочитать информацию, например, из бита 900, то нам нужно обратиться к 112-му байту и посмотреть в нём бит номер 4.

      | Адрес в байтах    |    Информация в БИТАХ
      |                   |    76543210 - номера бит (разряд)
------|-------------------|-----------------------------------------------
 111d | 0000006F          |    00000000
 112d | 00000070          |    000?0000
 113d | 00000071          |    00000000
 114d | 00000072          |    00000000

Конечно же, в компьютере физически биты не разделяются пробелами. Вся оперативная память, например, - сплошной поток выключателей :).

Но при отображении биты обычно разделяют на:
байты - 8 бит, две hex-цифры, или
тетрады - 4 бита, одна hex-цифра.

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

Хотя так информацию мы видеть практически не будем. Вместо битов везде будут hex-байты, вот так:

        Адрес в байтах    |    Информация в БАЙТАХ
       -------------------|---------------------------------------
        0000006F          |    00
        00000070          |    00
        00000071          |    00
        00000072          |    00

или вот так:

        Адрес в байтах    |    Информация в БАЙТАХ
       -------------------|---------------------------------------
        0000006F          |    00 00 00 00

Здесь вынужден заметить: адреса в файле и адреса в оперативной памяти - это совершенно разные вещи.

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

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

Да простят меня профи за такую вольность.

Юнга, после обеда я научу тебя писать дельные программы для вспомогательного бортового оборудования. Ты, конечно, пуст, как первая ступень, и ни черта не понял за сегодня, но у меня нет времени рассусоливать, нас давно ждут.

Первая полезная программа

Что там у нас дальше по учебнику? Этого вам пока не надо... Этого я и сам ещё не знаю... Тут слишком много умностей... Нет, пожалуй, продолжу, как предложил Олег Калашников. Пожалуй, лучший подход для любителей практики.

Эксперимент 01 (prax01.com)

Я по-прежнему подразумеваю, что вы используете WinXP и пример должен работать.

Создайте файл с расширением "com" (напомню в FAR'e - Shift+F4). Назвав файл, напечатайте в нём любую букву или цифру, ну, допустим, "1". Сохраните файл (в FAR'e - Esc).

Нет, это ещё не программа, этот файл выполнять не нужно. Откройте в Hiew'e.

Сейчас вы видите 1, если нажать "F4" (Mode), то, как и в тот раз, вы увидите байт в hex-виде. F4 еще раз покажет дизассемблерный код. Если в файле единица, то выглядеть код будет так:

Адреса    Байты         Имена    Операнды
00000000: 31            xor    [bx][si],ax

В отличие от команды nop, которую вы уже видели, большинство команд используют предметы для действия.

Предмет, с (или над) которым производится действие, называется операнд.

Операнды в ассемблере для Интел-совместимых процессоров принято разделять запятыми. То есть в некоторых системах или в других языках программирования пишут:
AX xor 44
или вполне может быть такая форма записи:
44,55 xоr AX
Но в x86 ассемблере принято писать так:

xor AX,44

где AX - операнд 1 (он же приёмник), 
  а 44 - операнд 2 (он же источник).

Из всего этого главное сейчас усвоить, что операндов не больше трёх (чащё всего 2), они разделяются запятыми и идут после имени команды. Давайте писать настоящую программу на ассемблере.

В Hiew'e (когда вы видите дизассемблерный код нашего файла) нажмите F3 и затем Enter. Теперь можно набирать программу на ассемблере (символ "1" в файле должен стереться). Каждая инструкция вводится Enter'ом и превращается в строку, если нет явной ошибки. Пробелы нужны только для удобства, поэтому неважно, сколько их. Пишите как хотите, строчными или прописными буквами, но только по-англицки. :)

Вот код программы, его нужно набрать:

mov  ah,9
mov  dx,10Dh
int  21h
mov  ah,10h
int  16h
int  20h

Когда всё напишете, нажмите один раз Esc, чтобы прекратить ассемблирование, и F9, чтобы сохранить файл.

Это был весь код программы, которая должна выводить строку на экран! Круто, правда? Только не хватает самой строки.

Для того, чтоб вписать строку, нужно открыть файл в текстовом редакторе (в FAR'e - F4).

Допишите после всех закорючек (только не сотрите ничего) любую текстовую строку и в конце поставьте знак $.

Это может выглядеть примерно так:

_?_?
_?_?_?_?_Good Day!$

Закорючки будут другие, но вид такой. Сохраните программу. Откройте снова в Hiew'e.

Адреса    Маш.команды Команды Асма      комментарии
          Байты       Имена Операнды   

00000000: B409        mov   ah,009    ; Поместить значение 9 в регистр AH (параметр1)
00000002: BA0D01      mov   dx,0010D  ; Поместить адрес текстовой строки в DX (параметр2)
00000005: CD21        int   021       ; Вызвать подпрограмму, в которой
                                      ; отработает функция вывода текста на экран (AH=09)

00000007: B410        mov   ah,010    ; Поместить значение 10h в регистр AH (параметр1)
00000009: CD16        int   016       ; Вызвать подпрограмму ожидания нажатия клавиши
0000000B: CD20        int   020       ; Подпрограмма завершения

0000000D: 47          inc   di
0000000E: 6F          outsw
0000000F: 6F          outsw
00000010: 64204461    and   fs:[si][61],al
00000014: 7921        jns   000000037  ---X
00000016: 24          and   al,000

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

Видите, начиная с адреса в файле 0000000Dh, появились команды, которые вы не писали, это всего лишь строка текста. Её процессор выполнять не будет только потому, что перед строкой текста стоит код завершения (int 20).

Запустите программу (можно из проводника)... Если компьютер с вами поздоровался - я вас тоже поздравляю! Значит, у вас есть шанс научить его делать и более сложные вещи.
Вы увидите окно DOS-приложения с текстом:
Good Day!
Нажатие на любую клавишу вызовет выход из программы.

Если же этого не произошло - не расстраивайтесь. Перепроверьте всё несколько раз, может быть, вы опечатались. Прочитайте "Аннотацию" в последней главе или комментарии. Я пока ничего подобного не написал, но, возможно, когда-нибудь придётся. Ведь у нас нет гарантии, что новые твАрения MS или других "рук" не изменят ситуацию в худшую сторону. Хотя, будем надеяться, что программа заработает и на новых OS'ях и процессорах.

"$" не выводится. Хм, интересно :/ Это условный символ конца строки?

Да, но в windows мы будем использовать нулевой байт (00h) для этой же цели.

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

Прямо так и вижу следующие "почему":
Почему mov?
Почему ah?
Почему 9?
И вообще, что это за подпрограммы-прерывания int 16, int 21, int 20.

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

На самом деле вывод строки на экран без специальной DOS-функции ничуть не сложнее. Мы используем именно такой способ из-за того, что он наиболее схож с программированием под Win. Здесь было бы аккуратнее и быстрее выводить на экран без специальной подпрограммы DOS-функций.

Но ДОС в прошлом, а нас ждёт Win32.

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

Мы завтра весь день будем искать ответ на вопрос "Почему ah", так как этот "почему" - самый важный во всём ассемблере. Серьёзно!

Bitfry

<<предыдущая глава     следующая глава>>

Вернуться на главную

Hosted by uCoz