Dec 03, 2013 02:11
Вспомнил один забавный факап, произошедший с одной стороны, из-за чрезмерной унификации, приведшей к избыточности, а с другой стороны из-за вынужденной экономии, приведшей к неполной реализации.
Как-то в молодости прикручивал я к замечательной супер-микро-ЭВМ БК-0011 контроллер винчестера. Это сама по себе забавная история и я её как-нибудь опишу, сейчас суть в другом. Контроллер был успешно прикручен, драйвер был успешно написан, точнее адаптирован из штатного DEC-овского драйвера путём переписывания кусков, использующих команды расширенной арифметики, которые процессор 1801ВМ1 не поддерживал.
(Ёлки-палки, я ещё помню время, когда существовали процессоры без команд умножения и деления!)
Всё бы хорошо, но драйвер иногда, на первый взгляд непредсказуемо, падал с прерыванием по вектору 4 (в архитектуре PDP-11 это исключительная ситуация отсутствия отклика на шине, т.е. мы в устройство чего-то пишем или читаем, а в ответ -- тишина). Разбор полётов показал, что драйвер падает при попытке ЗАПИСАТЬ на диск нечётное количество слов. При этом попытка ПРОЧИТАТЬ нечётное количество слов завершалась успешно!
Обмен с диском происходил секторами по 512 байт (256 слов), для этого у контроллера был буффер, который набивался последовательно через 16-разрядный регистр данных. Т.е. протокол простой: выставили регистры сектора, цилиндра и поверхности, набили 256 слов данных и после записи последнего слова контроллер побежал исполнять операцию: позиционироваться и писать в нужное место на диск. Если клиент драйвера хотел записать меньше слов, чем размер сектора, то драйвер должен был дополнить до целого сектора нулями. Код в драйвере был примерно такой (ассеблер PDP-11, думаю, понятен):
; R2 адрес данных для записи
; R4 длина. для простоты считаем, что не больше 256
; DRDATA адрес регистра данных контроллера
; DRSTAT адрес регистра состояния контроллера
@@1: MOV (R2)+, @#DRDATA
SOB R4, @@1 ; цикл по R4
@@2 TST @#DRSTAT
BMI @@3 ; бит 15 (т.е. знак) признак "ЗАНЯТ"
CLR @#DRDATA ; забиваем нулями, пока контроллеру не надоест
BR @@2
@@3:
Адрес прерывания указывал, что исключение возникает в момент выполнения команды CLR @#DRDATA в этом фрагменте. Т.е. в один прекрасный момент, мы пытаемся записать нулевое слово в регистр данных, а контроллер уже побежал выполнять команду и не реагирует на обращения к регистру данных. При записи нечётного количества слов, ага. А при записи чётного всё классно.
Тут нужно сказать пару слов о системе команд PDP-11. Это была очень хорошая система команд. Как сейчас модно говорить, ортогональная, чем-то отдалённо похожая на систему команд RISC, типа того же ARM. Все регистры (восемь штук, включая SP и PC) были равноправны, а все операнды адресовались одним из восьми способов относительно какого-нибудь регистра. Команды имели длину в одно слово формата (в 8-ичной записи)
CCCCAR для одноадресных команд,
CCARAR для двухадресных команд,
где C - код операции, A - код адресации (три бита, восемь типов), R - номер регистра (три бита, восемь регистров).
(безадресные и другие команды нас тут не интересуют)
Команда CLR (очистка, обнуление операнда), понятное дело, имеет именно такой формат. А какие у нас ещё есть однооперандные команды и что они делают?
Ну, типа
INC op -- прочитать операнд, инкрементировать и записать обратно
DEC op -- прочитать операнд, декрементировать и записать обратно
Принцип ясен, да?
CLR op -- прочитать операнд, обнулить и записать обратно
Т.е. команда CLR выполняла ДВА цикла шины: чтение и запись.
Это особенность именно чипа 1801ВМ1, как, во-первых, очень упрощённого и, во-вторых, спешно перепиленного совсем из другого проекта (этот чип разрабатывался сначала как однокристалка совсем другой архитектуры, в нём ещё много артефактов, например, программируемый таймер, который был доступен и активно использовался БК-шными игрушками, или призрачные регистры, доступные по чтению-записи, но не имеющие никакого эффекта). Структурно этот чип был матричным кристаллом и, видимо, под конкретную систему команд программировался стереотипно, для экономии, тратить лишние ячейки и связи для единственной команды, видимо, было слишком дорого, тем более, что ЛОГИЧЕСКИ это ничего не меняло. Почти ничего.
Дело в том, что разработчики контроллера тоже решили сэкономить. Они решили, что пользователь контроллера, скорее всего, будет человеком вменяемым, и если он перевёл контроллер в режим записи, то читать данные ему в голову не придёт (тем более, что никакого осмысленного результата это операции конструкцией контроллера и не предполагалось). А посему они просто не делали никакой разницы между циклами шины! Они на каждый цикл клали в буфер значение с шины данных. Т.е. команда CLR @#DRDATA записывала в буфер контроллера ДВА нулевых слова, одно на цикл чтения, другое на цикл записи, а общее количество исполненных команд CLR @#DRDATA было в два раза меньше, чем по алгоритму (благо, там цикл не по счётчику, а с предусловием) и если общее количество слов было нечётным, то последний цикл записи приводил к исключению. Э... я сказал: "два нулевых слова"? А, собственно, почему первое слово было нулевым? Ведь на шине не было НИКАКИХ сигналов, и процессор и регистр в этот момент были включены на приём. А потому что линии шины -- типа "открытый коллектор", и притянуты терминаторами к высокому уровню, а логика на шине принята инверсная.
Замена CLR @#DRDATA на MOV #0, @#DRDATA привела к тому, что все жили долго и счастливо. И я там был, мёд-пиво пил, на клаву текло, а в рот не попало.
ретро,
программизмы