Взаимодействие с оперативной памятью
Эмуляторы, которые используются для демонстрации и практических занятий (RARS, QtRvSim или Ripes), не поддерживают имитацию работы контроллера шины, реализацию различных типов оперативной памяти и т. д. Как следствие, рассказ на эти темы останется чисто описательным, без возможности опробовать что-то на практике. Тем не менее механизмы одновременного доступа устройств к оперативной памяти и разделение реализаций памяти на динамическую и статическую используются в архитектурах ЭВМ повсеместно и, следовательно, требуют обоснования.
Доступ к оперативной памяти со стороны устройств
Эволюция термина «шина».
1. Классическая «фон-неймановская» ЭВМ не предусматривала дополнительной схемотехники для управлении внешними устройствами. Логически команды внешним устройствам отдавал центральный процессор, на котором для каждого устройства должны быть предусмотрены отдельные контакты. Пачки подключённых к ним проводов или наборы отпечатанных проводников и получили название «шины». Во времена первых ЭВМ внешние устройства производились если не поштучно, то уж точно малыми сериями и различными производителями. При этом все основные проблемы взаимодействия приходилось решать аппаратно на стороне процессора:
- Неудачное (или просто сложное) проектирование, в первую очередь — с точки зрения электрики и временных задержек
- Преобразование аналоговых данных
- Кодирование и декодирование для борьбы с шумом / помехами / потерями и т. п.
- Логика взаимодействия с устройством
Выяснилось, что даже самые простые протоколы взаимодействия с внешними устройствами не так уж просты (например, канал последовательной передачи данных RS232; по ссылке можно найти его описание и документацию).
Что из этих протоколов стоит реализовать:
- аппаратно на стороне устройства?
- аппаратно на стороне процессора?
- программно?
Попытка «распилить» такой переусложнённый центральный процессор приводила к созданию т. н. «канальных процессоров» (например, отдельный процессор ввода-вывода на диск) со своими системами команд и канальными программами.
2. Когда различных внешних устройств стало достаточно много, появилась необходимость выбора: как задать сколько и каких именно подключено к компьютеру? «Шина» помимо комплекта проводов обзавелась схемотехникой: появилось понятие «номер» (или «адрес») устройства. Для взаимодействия с таким устройством сначала надо было выставить адрес на управляющих входах микросхемы, после чего обмениваться данными с указанным устройством посредством единого канала
Мультиплексор (ключ, коммутатор) имеет несколько сигнальных входов, управляющий вход и один информационный выход
Демультиплексор имеет один сигнальный вход, управляющий вход и несколько информационных выходов
3. Отдельно старались решить проблему унификации доступа к разнообразным устройствам:
- Набора универсальных команд и логика их исполнения (протокол шины)
Стандартизация этого протокола, дабы поощрить производителей оборудования ему соответствовать
- Декодирование унифицированных команд на устройстве (контроллер устройства)
- Унификация протокола взаимодействия (контроллер шины)
Общая шина в различных вариациях (например, МПИ / Unibus) применялась на микро ЭВМ:
- Дёшево
- Всё ещё управляется непосредственно процессором
- Достаточно медленно (принцип каравана ☺), мешает «разгону» процессора и занимает его время
- Спецификация «на все случаи жизни» оказывается весьма сложной (см. документацию по ссылке из Википедии)
4. Проблема «каравана» решилась так: разделили общий поток данных на взаимодействие с «быстрой» оперативной памятью (и впоследствии — видеопамятью; т. н. «северный мост»), и с «медленными» внешними устройствами («Южный_мост_(компьютер)). При этом:
Внешние устройства могут работать с оперативной памятью в обход процессора (т. н. прямой доступ к памяти, или DMA)
- Весь комплекс стал схемотехнически весьма сложным (впрочем, как и процессоры)
5. Проблема «протокола на все случаи жизни» решилась введением протоколов более высокого уровня, наподобие USB, I²C и других
- Усложнение архитектуры
- Перенос логики в программную часть
- Аппаратная реализация низкоуровневого протокола взаимодействия, напоминающая компьютерную сеть
В том же USB есть несколько уровней, пакеты и т. п.
Кстати, происхождение термина «шина» не вполне очевидно. Русское «шина», скорее всего, восходит к шине — металлической обивке тележного колеса (которая в виде заготовки выглядела как железная или медная полоска); затем шинами стали называть всякие металлические полосы, в том числе электрические проводники, а уж за ними и пачки компьютерных проводов. Английское же «bus» принято объяснять «омнибусом», то есть «доставкой всего повсюду», однако, кажется, это анахронизм: первые шины назывались «bus» задолго до того, как стали доставлять всё и повсюду. Нам кажется более вероятной гипотезой происхождение «bus» от «Busbar» — то есть того же самого электрического проводника — и далее по накатанной дорожке к колёсной обивке, где и всплывает ожидаемый омнибус (буквально bus bar — шина для омнибуса).
Возможности шины
Проблема синхронизации: когда можно передавать / получать данные по шине?
- ⇒ Регистр готовности и поллинг
Проблема ожидания: что делать процессору, когда (пока) устройство не готово к взаимодействию?
- Поллинг: трата времени
- ⇒ Внешние прерывания:
- асинхронность
- выделенный обработчик
- скорее всего, привилегированный режим работы процессора (ядро или супервизор)
Проблема пересылки больших объёмов данных: надо ли процессору самому делать обмен данными между памятью и внешним устройством (это много и небыстро!)
- Более сложное MMIO (например, реальное использование отдельных областей ОЗУ под видеопамять)
прямой доступ к памяти (DMA):
- Устройство само обменивается данными с памятью, от процессора требуется только инициировать этот обмен и обработать прерывание по окончании
- Если данных много, а объёмы небольшие, это всё ещё нагрузка на CPU
⇒ Bus mastering (захват шины) — ПДП контроллеры на устройствах, самостоятельное управление шиной на время обмена (выставления адреса, запуск прерывания)
⇒ Проблема разделения доступа к памяти между устройствами
- Приоритеты устройств/прерываний
- DMA контроллер (несколько каналов для нескольких устройств, блокировка областей памяти на время передачи данных и т. п.)
ОЗУ как внешнее устройство
Отступление: виды оперативной памяти
Оперативное запоминающее устройство — RAM, random access memory, собственно оперативная память. Здесь «random» означает «произвольный доступ», поскольку скорость работы с любыми ячейками памяти в любом порядке одинакова.
Постоянное запоминающее устройство — ROM, read only memory. Хранит раз и навсегда записанную информацию, требует электропитания только для чтения. Пример — картриджи древних игровых консолей.
Программируемое запоминающее устройство (ППЗУ, флеш-память и т. п.) — очень широкий класс, позволяющий перезаписывать информацию в одной и той же ячейке от нескольких раз до нескольких миллионов раз, и хранить её при выключенном питании от нескольких часов до неограниченного времени.
- Может быть оформлено как внешнее устройство и подключено к внешней шине (SSD и т. п.)
Вообще граница между памятью и устройством хранения данных технологически зыбкая, проще всего повести её по тому, по какой шине — по шине памяти или по внешней — идёт взаимодействие.
Специфика работы с ОЗУ, даже по одной отдельной шине, состоит в том, что на каждое чтение/запись ячейки приходится ещё по одной команде записи — передача адреса этой ячейки.
Если оставить одну шину, придётся использовать что-то наподобие мультиплексора — сначала выставлять на шине адрес, затем ждать его декодирования, и затем уже записывать (или считывать) по этой же шине содержимое памяти по этому адресу.
⇒ Обычно используют две шины — адресную и шину данных, это позволяет существенно распараллелить две операции; кроме того разрядность шин данных и адреса может быть неодинаковой.
Такт работы с ОЗУ
Запись:
- Выставить адрес на адресной шине
- Выставить записываемое слово на шине данных
- Дать команду записи
- Дождаться окончания операции
Чтение:
- Выставить адрес на адресной шине
- Дать команду чтения
- Дождаться конца операции
- Скопировать прочитанное слово с шины данных
Статическая и динамическая память
В течение курса мы несколько раз упоминали, что работа с оперативной памятью без дополнительных оптимизаций существенно медленнее, чем работа с регистрами процессора. Это свойство даже легло в основу архитектуры RiscV — вся обработка данных (арифметичские операции, логика и т. п.) делается на регистрах, а к памяти обращаются только для чтения или записи.
Дело в том, что регистры процессора и ячейки оперативной памяти как правило отличаются на уровне физической реализации
Попробуем смоделировать ячейку для хранения одного бита информации с помощью транзисторов.
- Поддерживает две операции: чтение и запись
- Операция чтения имеет один двоичный выход
- повторное чтение без записи всегда приводит к одинаковому результату
- Операция записи имеет один двоичный вход
- после записи некоторого значения операции чтения возвращают это значение
Раньше в электронике использовались лампы, теперь — полупроводники. Диод проводит электричество только «в одну сторону». Два последовательно соединённые диода не проводят электричества. Однако если собрать их в одном корпусе (транзистор) и подать «отпирающее» напряжение (на контакт «управление»), между входом и выходом потечёт ток.
Обратное управление (отсутствие тока на выходе при наличии сигнала) обеспечивается инвертором:
Здесь при отсутствии сигнала ток течёт через выход (считается 1), а при наличии отпирается транзистор, и из-за разности сопротивлений на втором контакте транзистора и на выходе сигнал падает (считается 0).
Из двух инверторов можно собрать триггер — схему, которая способна хранить состояние.
Схема работы триггера:
- В начальном состоянии инвертор-0 находится в состоянии 0, инвертор-1 — в состоянии 1. Находящийся в состоянии 1 инвертор «запирает» второй. На выходах активен сигнал 0
- При подаче сигнала на вход Reset запирается и переходит в состояние 0 инвертор-1, тем самым отпирая инвертор-0
- Таким образом, триггер переходит в состояние, при котором на выходах активен сигнал 1, даже после того, как на входе Reset сигнал пропадает
- При подаче сигнала на вход Set запирается и переходит в состояние 1 инвертор-0, тем самым отпирая инвертор-1. Это переводит триггер в начальное состояние.
На базе триггера легко уже организовать ячейку памяти, хранящую один бит, присоединив ко входам и одному выходу по «ключу». Ключ — это транзистор, который «отпирает» проход сигнал, если на его управляющий контакт подаётся напряжение. Второй выход триггера не используется.
- Для того, чтобы сработала операция записи или чтения, надо подать напряжение на строку
- Чтение будет состоять в том, чтобы получить сигнал с выхода 1 триггера
- Запись будет состоять в том, чтобы подать сигнал на один из входов триггера
Достоинства и недостатки статической памяти:
+ Скорость: не медленнее переключения триггера ⇒ быстрее только кванты
- - Сложность (в нашей схеме 7 транзисторов, ещё бывает 6 и 8) ⇒ низкая плотность и дороговизна
- ? Энергопотребление: в момент переключения ⇒ чем больше частота обращений, тем горячее
Смоделируем ячейку для хранения одного бита конденсатором.
- Запись 1 — это зарядка конденсатора
- Запись 0 — это разрядка конденсатора
- Чтение — это результат разрядки конденсатора (если он не был заряжен — 0, если был — 1)
- ⇒ после каждого чтения требуется запись
Прототип ячейки памяти на базе конденсатора:
- Если конденсатор разряжен, транзистор закрыт, и на выходе 1
- Если конденсатор заряжен, транзистор открыт, и сигнал на выходе резко падает (считается 0)
- Подача напряжения на вход «Запись» соответствует записи 0 в ячейку
- Запись в ячейку 1 произойдёт сама собой, когда конденсатор разрядится (достаточно не перезаписывать его после чтения)
Для разрешения операций чтения и записи оборудуем ячейку двумя ключами:
Немного конспирологии: тиристорная память, и почему её нет?
Адресация
Для организации массива ячеек памяти нам понадобятся два типа устройств:
Дешифратор. В нашем случае — устройство, преобразующее число в двоичной системе счисления в число в единичной системе счисления. Иными словами, получая набор из нулей и единиц на n входных контактах, соответствующий числу k в двоичном представлении, дешифратор активирует k-й из 2n выходных контактов, а остальные сбрасывает в 0.
Мультиплексор. В нашем случае — устройство, получающее набор из нулей и единиц на n управляющих контактах, соответствующий числу k в двоичном представлении, на единственном выходе выдающее тот же сигнал, что и на k-м из 2n входных контактов. Попросту говоря, устройство, позволяющее считать сигнал с k-го входа.
Рассмотрим массив емкостью в 2m+n однобитных ячеек памяти. Чтобы избежать создания 2m+n каналов к каждой ячейке, расположим их в виде матрицы. Часть адреса (номера ячейки) будет дешифровываться в общую для m ячеек строку, а другая часть — в общий для n ячеек столбец. Таким образом, чтение будет происходить сразу из всех ячеек строки, после чего потребуется повторная запись в каждую из них.
attachment:20170323-5-CellArray.dia.svg
При операции записи в ячейку специальный мультиплексор каждого столбца выбирает, что записывать в неё — только что прочитанное значение или только что записанное (сигнал инвертируется, потому что, как мы помним, заряд конденсатора означает запись 0). Этот же механизм обеспечивает запись после чтения. Наконец, из значений, прочитанных из каждого столбца, отдельный мультиплексор выбирает требуемое для операции чтения.
Конденсаторы довольно быстро разряжаются сами собой — с учётом того, что в действительности специального конденсатора в динамической ячейке нет, используется собственная ёмкость транзистора. Так что время от времени приходится «вхолостую» перезаписывать содержимое всех ячеек памяти (это называется «цикл регенрерации»). Регенерация нужна не так часто, как запись после чтения, но операция с памятью — причём как минимум со всей строкой! — во время регенерации невозможна.
Без своевременной регенерации ячейка раскует неожиданно разрядиться и начать показывать 1 вместо 0. Любители т. н. «разгона» компьютеров знают: если понизить частоту регенерации памяти в аппаратных настройках, система станет работать чуть-чуть быстрее, но оперативная память может начать сбоить — это и есть те самые единицы.
Выше описана организация массива динамической памяти. Динамические ячейки памяти требуют массовой операции — перезарядки — поэтому использовать их поодиночке особого смысла нет.
Почему «память медленная»?
Массив динамической памяти существенно медленнее статической ячейки:
- Частота ядра процессора в несколько раз превышает частоту системной шины
- Скорость работы памяти зависит от скорости разрядки/зарядки конденсатора (а не от срабатывания триггера)
- Во время регенерации ячейка недоступна
- Цикл доступа на чтение к ячейке памяти из массива:
- выставить адрес,
- дешифровать номер строки и столбца,
- (дождаться конца регенерации, если вдруг),
- считать данные (включает в себя работу мультиплексора),
- вернуть значение
В более сложных системах могут использоваться трёх- и четырёхмерные массивы (банк → страница банка → строка → столбец), это также замедляет общее время.
Статическую память тоже можно собрать в массив, снабдив его дешифраторами и мультиплексором — это, разумеется, увеличит количество тактов на одну операцию, но разница в частоте обращения и отсутствие нужды в регенерации и записи после чтения всё равно сделают такой массив существенно более быстрым (а также существенно более дорогим!), чем массив динамических ячеек.
Статической обычно бывает память внутри процессора — это в первую очередь регистры, которых так мало, что можно себе позволить отдельные каналы (провода) до каждого из них, и кеш-память — как правило, первого уровня, самая быстрая и самая маленькая — та, что на процессоре.
Разница в скорости доступа к регистрам и к ячейкам оперативной памяти без учёта различных оптимизаций, может достигать нескольких порядков.