Такое ощущение, что это (бессмысленная классификация) сейчас больше запутывает.

Георгий Курячий Увы, всё это есть в спецификации и довольно часто используется в объяснениях.

На самом деле - только одного. Таймер как являлся периферией, так и является, ручная запись в *ip как воспринималась прерыванием от периферии, так и воспринимается. Опять совершенно ненужное разделение одной сущности на несколько.

Георгий Курячий Снова отсылаю к спецификации, где этих типов три. Это не говоря уже о том, что существуют глубокие различия между всеми тремя, о некоторых прямо говорилось в лекциях.

Как мы помним, прерывание это всегда событие от периферии, …

Георгий Курячий Или не от периферии, см выше про три типа. Может быть программное или от внутреннего таймера.

…ему без разницы какой инструкции не повезло выполняться в этот момент.

Георгий Курячий или не без разницы, если надо проанализировать эту инструкцию на предмет семихостинга, например.

Тут акцент немножко на другом: драйвер периферии может крутиться не в пространстве ядра (M-mode), а в пространстве операционной системы (S-mode). Соответственно и спустить привилегии нужно именно туда.

Георгий Курячий Подмена причины и следствия. Драйвер написали уже после того, как сделали железо. Он поэтому и крутится уровнем выше, что так написано в спецификации железки.

Зачем тогда биты MPIE и подобные? Вряд ли ведь только чтобы руками туда что-то записывать.

Георгий Курячий Это не про повторный вход в ловушку

Скорее, заставить программиста сохранить контекст и руками выставить MIE. А дальше уже аппаратура проверяет в каком состоянии процессор (U-mode, M-mode, M-mode в обработчике, какой приоритет у прерывания) и какое прерывание ждет выполнения.

Даже в ch32v003 (а более слабых risc-v контроллеров я не знаю) и то есть приоритеты прерываний.

Скорее, в регистрах контроллера прерываний. Это внешняя периферия, не входящая в ядро.

Георгий Курячий До тех пор, пока мы этих регистров не видим, нам остаётся только смотреть в *ip

Самое главное забыли - разрешить прерывания в самой периферии. В частности, указать какие именно события будут генерировать прерывания. Например, в UART один и тот же обработчик может вызываться при приему байта, при передаче байта, при ошибке приема. В таймерах все еще веселее: по переполнению, по сравнению каждого канала, по захвату, ... В общем, пока фантазия производителя не успокоится.

Здесь можно сказать словами, что примитивный стандарт из Rars, да даже из Risc-v это, конечно, хорошо. Но в реальности опять придется изучать как извращался тот конкретный производитель, с железом которого вам не повезло работать. Стандарт Risc-v либо слишком негибкий, либо слишком молодой, чтобы удовлетворять реальным задачам, вот все и "расширяют" его. В меру своей фантазии.

Георгий Курячий «Стандарт» RARS реально тупой и древний, а вот относительно RISC-V скорее наоборот — все "расширяют" его в меру своей фантазии просто потому, что не осиливают CLIC / PLIC со товарищи

Это лишнее уточнение. Без дополнительных настроек входв прерывание и так аппаратно блокирует повторный вход. Соответственно, требование проводить в обработчике как можно меньше времени, актуально всегда (ох как на радиофорумах любят любителей delay(), while(flag){}, ...). Ну а если хочется пострелять себе по ногам и разрешить вложенные прерывания - ну флаг в руки.

Ритуал завершения прерывания также может сильно отличаться.

Так, зачастую биты pending прерываний являются всего лишь отображением битов периферии. Сбросили флаг где-нибудь в TIMER8->STATR, сбросился бит прерывания. Еще веселее в случае прерываний по уровню на ножке. Буквально можно настроить, чтобы пока на 14-й ножке микросхемы высокий уровень, бит готовности прерывания равен 1. И никаким способом сбросить его нельзя. Поэтому если в прерывании нет кода запрета самого себя, код так и будет там крутиться. Ну или не запрета, а перенастройки - например, не по высокому уровню, а по спадающему фронту (лог.1 -> лог.0). Это, кстати, к вопросу о битах разрешения прерываний.

Это одно и то же. Единственное, что делает периферия это выставляет бит pending interrupt. А уж дальше контроллер прерываний или ядро этот бит читают, делают AND по маске разрешенных прерываний, проверяют MIE, проверяют приоритеты, ... И только в самом конце вызывают собственно прерывание.

Остается добавить: "вот таким способом мы изобрели драйвер устройства. Он занимается обработкой прерываний, подготовкой данных, настройкой периферии. А юзерский код только и может, что складывать в нужное место данные для обмена".

Георгий Курячий Строго наоборот. «Таким образом изобрели драйвер устройства» разве что в Plan5 или GNU HURD. В подавляющем большинстве операционных систем драйвера монолитные с логикой в пространстве ядра.

Кроме шуток, термина "драйвер" я на этой странице не нашел.

Георгий Курячий Потому что это термин операционной системы, не имеющий отношения к архитектуре. И термин, положа руку на сердце, неважнецкий — слишком нечёткий.

В реальности обычно указывают базовый адрес периферии и смещения отдельных регистров. Для "консоли rars" это будет

.equ TREM_BASE          0xFFFF0000
.equ TERM_CTL_IN        0
.equ TERM_IN            4
.equ TERM_CTL_OUT       8
.equ TERM_OUT           12

Экземпляров периферии обычно бывает больше одного. Тех же UART-ов обычно штуки две-три, таймеров минимум четыре. Поэтому проще указать, что DATA регистр всегда по смещению +4 (или сколько там) от базового адреса. Соответственно для UART1 это будет 0x40013800 + 4, а для UART5 0x40005000 + 4 (числа, если что, реальные, от ch32v307).

Кстати, зачастую DATA-IN и DATA-OUT это один и тот же регистр, просто периферия сама определяет происходит ли чтение или запись и реагирует по-разному. Либо подменяя операцию чтения в DATA чтением из буфера чтения (это штука чисто аппаратная, своего номера у него нет), либо подменяя запись в тот же DATA записью во внутренний буфер. Таким образом, прочитать только что записанное значение уже невозможно.

"Терминал Rars" похож на обычные аппаратные терминалы, которые подключались по COM-порту. Соответственно скорость обмена там хорошо если 9600 бод (а бывало и 2400 и даже меньше). Даже у контроллера ядро успевает выполнить сотни тысяч инструкций, пока терминал наконец передаст один байт.

Такое в реальности тоже бывает. Вот только если не вовремя "выставить бит готовности", получим вместо осмысленных данных какую-то кашу. Буквально на прошлой неделе наступил на эти грабли: проверял бит готовности USB1, а писал в USB0. Естественно, бит готовности не сбрасывался (я ж не в USB1 писал), вот и получалось, что начинает передаваться один массив, а заканчивает - другой.

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

Впрочем, для того же UART (терминал) я и сейчас пользуюсь именно прерываниями по приему отдельных байтов. Тут проблема в том, что терминал заточен под строки, а не под пакеты. То есть нельзя заранее указать сколько байтов ожидается. Вот и приходится использовать ручную реализацию кольцевого буфера. Но UART обычно штука медленная, там этого хватает.

На ассемблере еще куда ни шло. А вот на Си компилятору не скажешь "ты регистр x23 не трогай, в него прерывание полезет".

В реальности именно так и бывает. Если устройство готово, пишем в него напрямую, если нет, то в буфер. Можно, конечно, и в буфер, а потом разрешать прерывание (см. выше: флаг готовности мы снять не можем, он аппаратно равен 1 все то время, пока буфер на передачу пуст. Можем только запретить прерывание). Но это лишний вход в прерывание, лишнее копирование. Лишние такты.

Правильнее сказать "сэмулировать аппаратное прерывание". Насколько я помню, в Risv-v, в отличие от некоторых других архитектур, программных прерываний нет. Вместо них ecall?

Георгий Курячий См. выше в плане этой лекции — в RISC-V всего три класса прерываний, и один из них — программные.

LecturesCMC/ArchitectureAssembler2026/10_Interrupts/COKPOWEHEU (последним исправлял пользователь FrBrGeorge 2026-04-19 23:09:19)