Проблема
При инициировании приемо-передачи данных от центрального компьютера контроллеру, программа в контроллере, написанная полностью на ассемблере и не содержащая чужого кода, повисала. Время, через которое происходило зависание зависило от двух факторов: частоты приемо-передачи и НЕКОЕГО СЛУЧАЙНОГО ФАКТОРА. То есть при зафиксированной частоте приемо-передачи, время, через которое система могла зависнуть, варьировалось от секунд до нескольких минут.
Анализ и решение вкратце
Система зависала из-за порчи системных данных моим обработчиком прерывания от ком-порта.
Порча данных происходила из-за того, что сегмент данных в момент работы моего обработчика не соответствовал сегменту, с которым работает (должна работать) моя программа.
Несоответствие сегмента данных происходило из-за того, что параллельно с моей программой каждые 55 мс вызывался обработчик прерывания от таймера 0, написанный фирмой Advantech, в котором разрешались вложенные прерывания до восстановления регистра сегмента данных.
Во время работы обработчика прерывания от таймера 0 после разрешения прерываний происходило прерывание от ком-порта и включался мой обработчик, который имел дело с некорректным сегментом данных, т.к. последний не был еще восстановлен.
Однако, такая проблема актуальна только для процессоров RDC R8800, т.к. Amd188Em точно следуют своей спецификации, в которой сказано, что после разрешения прерываний выполняется как минимум еще одна инструкция, которая в нашем случае как раз и должна успевать восстановить регистр сегмента данных.
Поэтому на контроллерах ADAM-5510, в которых стоит Amd188Em данная проблема отсутствует. Процессоры же RDC R8800, которые стоят в контроллерах ADAM-5510M, ADAM-5510E данную спецификацию НАРУШАЮТ, тем самым не являясь полным аналогом Amd188Em.
Поскольку изменить обработчик фирмы Advantech невозможно (обработчик защищен от записи), то существует несколько решений:
- устанавливать регистр сегмента данных в обработчике прерывания от ком-порта принудительно в нужное значение;
- запретить вообще прерывания от таймера 0, если он не используется в программе;
- написать свой разработчик и перенаправлять вектор на него до того, как будут разрешаться все остальные прерывания и запускаться главный цикл программы;
ПРИ ИСПОЛЬЗОВАНИИ СТАНДАРТНЫХ БИБЛИОТЕК ПРОБЛЕМА ОТСУТСТВУЕТ, т.к. стандартный обработчик от ком-порта принудительно устанавливает требуемый сегмент данных.
ПРИ ПРОГРАММИРОВАНИИ БЕЗ ИСПОЛЬЗОВАНИЯ БИБЛИОТЕК ПРОБЛЕМА - некорректное поведение инструкции sti и соответственно стандартного обработчика прерывания от таймера 0, входящего в состав операционной системы - РАСПРОСТРАНЯЕТСЯ НА ВСЕ ОБРАБОТЧИКИ ПРЕРЫВАНИЯ И НЕ ОБЯЗАТЕЛЬНО МОЖЕТ БЫТЬ СВЯЗАНА С КОМ-ПОРТОМ.
Основные выводы
1. Инструкция STI процессора RDC R8800 НАРУШАЕТ СПЕЦИФИКАЦИЮ Amd188Em.
2. Стандартный обработчик прерывания от таймера 0, входящий в состав ОС фирмы Advantech не учитывает данный факт и может приводить к некорректному поведению пользовательских программ, которые полагаются на корректное значение регистра сегмента данных ds.
Вопросы к аудитории и экспертам
1) Сталкивался ли кто-либо с подобным?
2) Есть ли у кого-либо замечания по поводу написанного (возможно я что-то не так понимаю или где-то ошибаюсь)?
В случае необходимости могу выслать полностью исходники на ассемблере + документацию. Программа в com-формате занимает 2.5 кБ.
Замечания
В процессе сужения области поиска на контроллере ADAM-5510E из-за очередного зависания контроллер приказал долго жить - теперь после его включения при загрузке появляется надпись "Sector not found. Error finding drive A. Abort, Retry, Fail?"
Все остальные испытания были продолжены на контроллере ADAM-5510M.
Поэтому как только в процессе анализа проблемы стало ясно, что зависания происходят из-за некорректного сегмента данных, зависания были заменены на включение индикатора COMM, чтобы не испортить еще один контроллер, а именно: если сегмент данных в обработчике прерывания от ком-порта не соответствовал корректному, включался индикатор COMM, после чего сегмент данных устанавливался в корректное значение принудительным образом.
Более подробно
1. Программа системы управления была максимально сокращена до минимально необходимой части, были оставлены:
обработчик прерывания от ком-порта;
функции вывода данных на терминал;
главный цикл был модифицирован, чтобы обеспечить приемо-передачу по принципу: ответ заданной строкой на любой полученный символ.
В результате тестовая программа также зависала через случайный промежуток времени.
2. После последовательного сужения области поиска было найдено, что система перестает виснуть (1.5 часа работала без сбоя, после чего была мною остановлена), если в обработчике прерывания от ком-порта принудительно устанавливать регистр сегмента данных в корректное значение.
Мой обработчик прерывания от ком-порта использует внутренние данные, расположенные в том же сегменте, что и код, т.е. ds = cs, где
ds - регистр адреса сегмента данных,
cs - регистр адреса сегмента кода.
При этом мой обработчик НИКАК НЕ НАСТРАИВАЛ РЕГИСТР СЕГМЕНТА ДАННЫХ, полагая, что он совпадает с регистром сегмента кода.
Данное предположение работает до тех пор, ПОКА НЕТ ЧУЖОГО РАБОТАЮЩЕГО КОДА.
Отсюда был сделан вывод, что кто-то изменяет регистр сегмента данных перед вызовом моего обработчика.
3. Были проанализированы управляющие регистры для всех источников прерывания, и выяснено, что одновременно с моей программой работают 2 таймера: таймер 0 и таймер 2.
Таймер 2 является опорным для таймера 0, срабатывая каждые 258 мкс. При этом прерывание для таймера 2 не генерится (соответственно отсутствует обработчик).
Обработчик прерывания от таймера 0 вызывается каждые 55 мс (213 * 258 мкс) и обновляет так называемый регистр тиков таймера, а также вызывает пользовательский обработчик таймера 1Ch (DOS), в котором прописана одна инструкция iret. (Пользователь DOSа может переопределить обработчик 1Ch).
После дизассемблирования дампа области памяти, где находится обработчик прерывания от таймера 0, было получено:
;----------------------------------------------------------------
; timer0_isr
;----------------------------------------------------------------
;
; Обработчик прерывания от таймера 0
;
; 0040:006C - 4-байтовый счетчик тиков таймера
; 0040:0070 - флаг переполнения таймера
;
; константа 1800B0h = 1573040 определяет число тиков в сутки,
; за секунду происходит ~18.2 тика или иначе тик происходит
; каждые 55 мс.
;
;----------------------------------------------------------------
PROC timer0_isr
push ax
push cx
push ds
mov ax, 0040h
mov ds, ax ; СЕГМЕНТ ДАННЫХ МЕНЯЕТСЯ на 0040
; 0040 - СИСТЕМНАЯ ОБЛАСТЬ
inc [ WORD ds:006Ch ]
jnz @@skip_inc_hi
inc [ WORD ds:006Eh ]
@@skip_inc_hi:
mov cx, [ ds:006Eh ]
cmp cx, 18h
jl @@skip_reset_count
jg @@reset_count
mov cx, [ ds:006Ch ]
cmp cx, 0B0h
jle @@skip_reset
@@reset_count:
call timer0_reset_count
inc [ WORD ds:0070h ]
@@skip_reset_count:
int 1Ch ; НЕЯВНО ЗАПРЕЩАЮТСЯ ВСЕ ПРЕРЫВАНИЯ
push dx
mov dx, 0FF22h
mov ax, 8000h
out dx, ax
pop dx
sti ; РАЗРЕШАЮТСЯ ВЛОЖЕННЫЕ ПРЕРЫВАНИЯ
; ТОЧКА КРАХА!!?
pop ds ; ВОССТАНАВЛИВАЕТСЯ СЕГМЕНТ ДАННЫХ
; НО СОГЛАСНО ДОКУМЕНТАЦИИ НА МИКРОПРОЦЕССОР AMD188EM ОБРАБОТЧИК ВЛОЖЕННОГО ПРЕРЫВАНИЯ
; ПОЛУЧИТ УПРАВЛЕНИЕ ТОЛЬКО *ПОСЛЕ* ВЫПОЛНЕНИЯ СЛЕДУЮЩЕЙ (!) ЗА КОМАНДОЙ STI ИНСТРУКЦИИ!!!
pop cx
pop ax
iret
ENDP timer0_isr
;----------------------------------------------------- timer0_isr
;----------------------------------------------------------------
; timer0_reset_count
;----------------------------------------------------------------
;
; Обнуляет счетчик тиков таймера.
;
;----------------------------------------------------------------
PROC timer0_reset_count
xor ax, ax
mov [ ds:006Ch ], ax
mov [ ds:006Eh ], ax
retn
ENDP timer0_reset_count
;--------------------------------------------- timer0_reset_count
Из приведенного кода видно, что регистр сегмента данных восстанавливается ПОСЛЕ ТОГО КАК РАЗРЕШАЮТСЯ ВЛОЖЕННЫЕ ПРЕРЫВАНИЯ! ОДНАКО НИ ОДИН ОБРАБОТЧИК ПРЕРЫВАНИЯ НЕ МОЖЕТ БЫТЬ ВЫЗВАН СРАЗУ ПОСЛЕ ИНСТРУКЦИИ STI СОГЛАСНО ДОКУМЕНТАЦИИ НА МИКРОПРОЦЕССОР.
Т.к. на контроллере ADAM-5510 стоит микропроцессор AMD188EM, то там
зависание не наблюдается. Однако, на контроллере ADAM-5510M стоит другой микропроцессор RDC R8800 - аналог (?) AMD188EM.
4. Для начала, чтобы убедиться в том, что сегмент данных портится обработчиком прерывания от таймера, при старте был отключен таймер 0. Зависания прекратились.
Далее, чтобы убедиться в том, что крах (точнее начало краха) происходит именно там, где указано выше в листинге, в обработчик прерывания от ком-порта были добавлены инструкции (выключение таймера 0 при старте программы при этом было убрано):
если ds изменился (ds <> cs), то
включить индикатор COMM
сохранить значение ds (_ds = ds)
извлечь из стека адрес возврата и запомнить его в _cs:_ip
установить флаг краха
сделать ds корректным (ds = cs)
конец если
В главном цикле, в случае появления флага краха, в терминал выводились _ds, _cs:_ip
В результате испытания, через случайный промежуток времени система "повисла" - зажгла индикатор COMM (повисание не произошло, т.к. ds устанавливался в корректное значение принудительно), а в терминале был выведен адрес ТОЧКИ КРАХА, т.е. того места с которого был вызван обработчик прерывания от ком-порта.
ds оказался равным 0040, а точка краха пришлась на инструкцию pop ds, т.е. перед тем как выполнить инструкцию pop ds в обработчике прерывания от таймера 0, обработчик прервался и был вызван мой обработчик от ком-порта, который уже имел дело с некорректным ds.
При некорректном ds любое изменение переменной программы (например, заполнение буфера получаемыми данными, ведение счетчика принятых данных и т.д.) происходит не в том сегменте данных, т.е. происходит порча данных в системной области, т.к. ds указывает на системные данные. В результате система повисает.
Данные испытания проводились многократно. Система всегда "зависала" именно в вышеуказанной точке краха.
Дополнения (уточнения)
1. Вся программа была написана на ассемблере и транслировалась в com-формат.
2. ТОЧКА КРАХА является условным названием, т.к. очевидно, что в данной точке никакого краха или зависания не происходит, а лишь начинается "дорога к смерти", поскольку регистр ds еще не восстановлен. Повиснет система гораздо позже - все зависит от того, куда записываются данные в обработчике прерывания от ком-порта, и насколько важные данные они затрут.
3. Данная проблема отсутствует на ADAMах-5510, т.к. там стоят микроконтроллеры Amd188Em, которые (к счастью) соответствуют документации (по крайней мере в том, что касается инструкции STI).
4. Данная проблема актуальна также и для ADAM-5510E, т.к. там то же ядро, что и в ADAM-5510M.