Об адресах в компьютере. Часть 1.
Здесь Я постаряюсь подробно описать основные механизмы взаимодействия процессора и переферийных устройств, так как, на мой взгляд, эта тема плохо освещается в большинстве академических курсов.
Основные идеи
Процессор, программы для которого мы в большинстве случаев пишем, является сложным электронным устройством, но сам по себе он умеет совсем немного: исполнять свои инструкции и отправлять или считывать электрические сигналы со своих многочисленных контактов. Этого явно не достаточно для того чтобы такая система звалась компьютером. Как минимум требуется подключить память, иначе будет негде хранить данные и инструкции, а без них процессор не имеет смысла.
Но вот вопрос: как организовать подключение памяти и взаимодействие с ней? Для этого придумали невероятно простой и изящный механизм. Весь “внешний мир” процесса представляется ему как массив двоичных состояний - битов. Выше Я упомянул лишь память, но к процессору, как правило, подключаются много других устройств и красота такого механизма проявляется в абсолютной независимости процессора от типа подключаемого устройства. Все они(даже оперативная память, занимающая особое положение) представлены процессору как набор переключателей с двумя положениями. Ниже, я подробней расскажу об этом.
Чтобы иметь возможность взаимодействовать с конкретным битом требуется механизм их адресации. Сам способ адресации весьма прост: индексирование от 0 до максимального адреса. Само же взаимодействие заключается в двух возможных действиях со стороны процессора: чтение состояния с адреса и запись по адресу. Подробнее об этом позже.
Я здесь про отдельные биты рассказываю, но как вы знаете, современные компьютеры оперируют группами сразу из нескольких бит. Такая группа называется Minimum Addressable Unit (MAU) и на сегодняшний день равна 8 битам, но так было далеко не всегда. Здесь и здесь можно подробно прочитать об этом.
Массив всех MAU до которых способен дотянуться процессор называется физическим адресным пространством(почему оно физическое расскажу позже).
Используя 8 битный MAU, мы увеличиваем физическое адресное пространство в 8 раз при том же количестве адресов, в сравнении с использованием 1 бита в качестве MAU, но при этом усложняется и замедляется взаимодействие с отдельными битами. Это наиболее заметно при работе с массивами из boolean элементов. Синонимом MAU является байт
. Есть разные версии того почему размер байта(MAU) в итоге выбрали именно в 8 бит. Наиболее вероятная - это широко используемая 8-битная кодировка Extended ASCII.
Само физическое адресное пространство формируется адресными контактами процессора(для отдельно взятого процессора, это просто набор контактов, не имеющих смысла при отсутствии подключенных устройств). Желая взаимодействовать с конкретным адресом, процессор выводит его с их помощью, а подключенные к этим контактам устройства, уже распознают свой адрес. Подробнее об этом я расскажу в следующей статье.
Взаимодействие с переферией
Вернемся к переферийным устройствам. Хорошо, со стороны процессора мы имеем физическое адресное пространство, но как его “соединить” с внешними устройствами? Здесь также был придуман весьма изящный способ: каждое переферийное устройство обладает набором тех самых переключателей - битов. За каждым из этих переключателей стоит некое реальное действие. Самое простое - это обычное сохранение последнего записанного состояния, как это делает RAM.
В электронике, устройство хранящее цифровую информацию называется регистром
. Каждое переферийное устройство с которым взаимодействует процессор, имеет хотя бы один регистр с помощью которого они могут взаимодействовать(т.е обмениваться данными) с процессором. Регистры могут иметь разную разрядность, в зависости от контролируемой ими функции. Например, для светодиода необходимо и достаточно одного регистра разрядностью 1 бит. Именно регистры и занимают “места” в физическом адресном пространстве процессора.
Нужно вспомнить об вышеупомянутом MAU в 1 байт. Регистры непосредственно затронуты этой интересной особенностью и взаимодействовать с одним их битом за раз не получится: поэтому регистры
имеют размер кратный 1-му байту(даже там где это избыточно). Как правило, регистр состоит из битов объединенных общим предназначением. Наиболее яркий пример это оперативная память, которая, по сути, является одним большим регистром данных (да, регистры вовсе не ограничены длиной в 8, 32 или 64 бита). В других переферийных устройствах, например таймерах, есть т.н конфигурационные регистры с помощью которых его можно настроить, запустить или остановить.
Представьте, все это похоже на то как мы взаимодействуем с различными механизмами. Например СВЧ печь: различные кнопки управления на ней это регистры конфигурации, а внутреннее отделение печи это, своего рода, регистр данных в который мы загружаем сырую еду. Еще пример - школьная доска, которая является логическим аналогом оперативной памяти, будучи одним большим регистром данных, сохраняющим последнее записанное значение.
Более подробно рассмотрим одно из самых простых и распространенных переферийных устройств - порт ввода-вывода общего назначения (GPIO)
Он представляет из себя группу контактов на каждом из которых можно выводить логический уровень(0 или 1) или считывать его из вне. Для управления им необходимо 2 регистра: регистр данных(DR) и т.н Data Direction Register. Каждый бит этих регистров управляет соответствующим контактом: бит в DDR задает режим работы контакта(1 если на вывод, 0 если на ввод), а бит в DR содержит текущий логический уровень выводимый нами(DDR=1) или вводимый из вне(DDR=0). Вышеописанные регистры занимают свои места в физическом адресном пространстве нашего процессора. Теперь записи и чтения по этим адресам будут контролировать работу нашего устройства GPIO.
Давайте взглянем на происходящее несколько шире:
На вышепредставленной картинке в форме UML представлены(на сколько возможно) отношения между процессором, его физическим адресным пространством и переферией. Ключевой фигурой здесь является интерфейс Byte - как легко догадаться, он описывает интерфейс одного байта из адресного пространства. Адресное пространство, в таком случае, есть массив этих интерфейсов, а переферийные устройства реализуют некоторые интерфейсы из этого массива, то есть на места “виртуальных”, байтов встают реальные регистры. Здесь можно заметить одну интересную особенность: реализацию принципа инверсии зависимостей
в отношении процессора и переферии, и все благодаря механизму адресного пространства. В результате ни процессор ни переферия не зависят друг от друга, что дает свободу манёвра в плане различных взаимодействий между ними.
Немного про сам процессор
В Intel® 64 and IA-32 Architectures Software Developer’s Manual есть интересная схема, демонстрирующая то чем процессор предстаёт для исполняемой им программы:
Что не удивительно, здесь опять все заполнено знакомыми нам регистрами, а в правом верхнем углу…(ну вы поняли). Регистры процессора служат тем же целям, что и в переферийных устройствах - формируют его интерфейс. Отличие состоит в том, что процессор, очевидно, хорошо осведомлен о своём внутреннем устройстве и, поэтому, побайтовая адресация не требуется, адресовать его регистры можно целиком(т.е каждый адрес соответсвует целому регистру процессора, а не байту). Можно сказать, что совокупный набор регистров процессора составляет, своего рода, отдельное адресное пространство.