- Исключение (exception) — возникает при выполнении некоторой инструкции в программе и требует дополнительных действий перед тем, как выполнить следующую инструкцию
Все же стоит отметить, что по задумке большинство исключений возврата не предполагают в принципе. Ошибка доступа к памяти, выравнивания, выполнения неверных инструкций - это именно ошибки. После них надо не возвращаться к уже сломанному коду, а останавливать его.
Георгий Курячий Нет. Мы уже многократно этот вопрос обсуждали, и я приводил некоторое количество примеров, из которых следовало, что исключения «по задумке» именно что предполагают возврат — и обработка доступа к памяти, и выравнивание. Вопрос же не в том, что чаще случается.
- RISC-V F: исключения FPU — «это другое™», они накапливаются в виде флагов в CSR-регистре fflags, и обрабатывать их надо явно.
А зачем его тогда здесь упоминать?
Георгий Курячий Чтобы явно разделить два разных термина, которые по недосмотру обозначаются одним словом.
- Характеристики ловушек
Не знаю, зачем эта классификация. Тем более что 2 и 3 по сути дублируют друг друга, а 4 и 5 очевидны из предназначения ловушки. Ну то есть было бы странно, если бы прерывание приема байта как-то портило регистры или ecall приема строки их наоборот не менял.
- Запрет и/или дисциплина обработки повторных ловушек (исключительных ситуаций внутри ловушки), возможно, специальный режим работы процессора
Вложенные прерывания с разным приоритетом - полезная штука. Правда, количество уровней приоритета обычно невелико. А для исключений - постепенный переход по привилегиям. То есть U-mode сделал ecall, его поймали на S-mode, сделали ecall, его поймали на M-mode и уже там обработали. Одна и та же машина, одни и те же регистры, даже стек может быть одним и тем же.
- Тесно связано с многозадачностью: если есть аппаратная поддержка «задач», объявляем одну такую задачу «ядром», которое будет обрабатывать ловушки
Чего?! Ловушки обрабатываются только собственно обработчиком. Это даже не задача, это просто функция. Максимум, что тут можно сделать - конечный автомат. Ну или перебрасывать данные в конкретную задачу.
Георгий Курячий Я не смог понять, как эта фраза связана с предыдущей.
- теперь обработчики выполняются только ядром, в котором доступны т. н. «привилегированные инструкции»
Скорее, наоборот: по умолчанию все ловушки и так обрабатываются на M-mode.
Георгий Курячий Это в конце концов, после изобретения M-mode и прочего, обработку ловушек можно туда перенести. Ну, я так и написал.
- а также обеспечение изоляции / ограничения доступа / разделения времени / …
Зачем здесь это? Изоляция делается через MPU/MMU вообще независимо от ловушек. Ограничение доступа - через уровни привилегий, опять-таки независимо от ловушек.
Георгий Курячий Затем, что здесь идёт речь именно об уровнях привилегий, см одну строку назад
- Есть примерно три способа реализовать интерфейс управления процессором:
По сути, всего два варианта: управлять ли специальными инструкциями (вообще без разницы, относить ли их к "сопроцессору" или к основному) или через запись в память (MMIO или CSR).
Георгий Курячий Не совсем. Идея в том, есть ли (1) отдельные инструкции управления процессором, и (2) отдельная доступная к изменению изнутри процессора логика. Получается три варианта, один из которых («нет, нет») — вырожденный.
- Номера регистров — не «адреса»:
Можно еще добавить, что идут не подряд. То есть это не адреса в непрерывной памяти, а именно разрозненный набор регистров.
- Атомарные (это важно) R/W инструкции типа I
Ну, вообще-то, я не припомню реальных ситуаций, когда здесь важна была бы атомарность. То есть она необходима в общем случае (если вдруг понадобится), но обычно не используется.
Георгий Курячий Атомарность необходима вообще всегда во избежание гонок. Любая ситуация, при которой мы сначала читаем из CSR, а затем на основании прочтённого что-то туда пишем, и за это время CSR успел поменяться, может оказаться неприемлемой.
- Регистры fflags (1) и frm (2) — это всего лишь биты регистра fcsr (3), изменения, сделанные в них, отражаются в fcsr, и наоборот.
Правильнее сказать даже отображение (mapping). Например, чтобы можно было поменять способ округления, не потеряв флаги. Обычным способом пришлось бы делать crsc + csrs, а так можно просто csrw.
- В RARS реализовано почти что шесть:
Можно добавить еще столбец как регистры называются в современном стандарте | Rars | Современный стандарт | Адрес в Rars | описание | |
|
|
|
| | cycle | mcycle/scycle/ucycle | 3072 | Количество тактов с момента старта | | time | mtime/stime/utime | 3073 | Абсолютное время (в абстрактных единицах) |
- Побочного эффекта по возможности следует избегать:
Побочного эффекта производителям железа (разработчикам ядер, ...) следует избегать.
- После обработки исключения управление надо передать на инструкцию, следующую сразу после прерванной (во избежание повторного исключения) Полностью отсутствует разделение прав доступа. Вы будете смеяться, но это абсолютно обычная ситуация для практически любого «железа» тоже.
Вот это совершенно точно не так. Более того, ecall как раз и существует только для того, чтобы пробить ограничение прав доступа. А комбинация M-mode + U-mode поддерживается даже в микроконтроллерах (кроме полутора уж совсем примитивных. Даже в Каракатицах, gd32vf103 / ch32v303 оба уровня привилегий в наличии).
Георгий Курячий Вы будете смеяться, но тут я совершенно точно цитирую одно из ваших замечаний. Уберу
- User Previous IE - устанавливается автоматически при входе в ловушку; предотвращает повторный вход.
Он не предотвращает повторных вход. Предотвращает как раз бит xIE. А xPIE говорит, разрешать ли ловушки при выходе.
- когда происходит вход в ловушку:
В xPIE записывается xIE. В xIE записывается ноль, что запрещает повторный вход.
При выходе соответственно в xIE записывается xPIE. В зависимости от настроек это может разрешить прерывания. И никто не запрещает записать в xIE / xPIE что-то руками. Кстати, если уровней привилегий больше 1, добавляются еще PPIE и т.д. - для вложенных исключений.
- В регистре CSR ucause (0x42, 42, Карл) отображается номер ловушки и её тип (прерывание или исключение):
Здесь же стоит написать, что номера исключений фиксированы стандартом RISC-V, а вот номера прерываний каждый производитель назначает по-своему. Но принцип тот же: в ₓcause записывается номер прерывания.
- 8.ENVIRONMENT_CALL (в «больших» архитектурах это значение соответствует уровню, на котором произошёл вызов: 8-Umode, 9-Smode, 10-Hmode, 11-Mmode)
Лучше разделить: 8. ECALL-U, исключение ecall, вызванное с U-уровня 9. ECALL-S 10. ECALL-H 11. ECALL-M, исключение ecall, вызванное с M-уровня
Георгий Курячий Если честно, прочёл это как предложение, заменить 8, 9, 10, 11 на 8, 9, 10, 11 — и не понял, зачем.
- Дисциплина оформления обработчика:
Первым пунктом можно добавить использование общего стека, как простейший случай. Уже потом, что есть специальный регистр ₓscratch, в который можно временно сохранить sp чтобы потом инициализировать его уже ядерным стеком. Или вовсе хранить в ₓscratch полностью готовый ядерный стек, просто переключая его с юзерским при необходимости.
Георгий Курячий Всё-таки хранение «стека обработчика в sp» — это не часть дисциплины.
- Дополнительная дисциплина для RARS:
И то и другое - не специфика Рарс.
Георгий Курячий Конечно. Речь идёт о дисциплине, а не о специфике.
- на каждом уровне выполнения свои регистры и своё адресное пространство, так что проблем со стеком нет
Регистры, естественно, общие. Это ведь часть ядра. Аппаратного переключения банков регистров в RISC-V нет. Разве что какой производитель решит от себя добавить, но я такого не видел.
Адресное пространство тоже никто не заставляет разделять, это ж не MIPS. Собственно, чтобы адресное пространство разделить, надо приложить известные усилия - настроить MMU/MPU.
- Пример тривиального обработчика, не соблюдающего конвенцию по сохранению контекста (пройти под отладчиком RARS):
Его стоит переместить повыше, сразу после описания uret. И начать лучше с простейшего варианта на юзерском стеке.
И от магических чиселок избавляться! Я вот наизусть не помню что там за 65-й CSR. Кстати, мне казалось, что их имена уже прошиты в ассемблер, даже дефайнить руками не требуется. Но даже если требуется, список не такой длинный, можно хоть копипастить в голову каждого файла.
- Это обработчик только исключений; ловушка и для прерываний, и для исключений одновременно должна быть сложнее
В реальности это что-то вроде
` uint32_t mcause = csr_read(mcause); if( mcause & (1<<31) ){
mcause &=~(1<<31); if(isr_vector[mcause] != NULL)isr_vector[mcause](); asm("mret");
}else{
- switch(mcause){
- case 8: exc_ecall_u(); break; // работу с mepc эти функции сделают сами case 11: exc_ecall_m(); break;
} `
Опять-таки, на Си просто нагляднее. Что здесь важно: что обаботчики прерываний сгруппированы в массив, из которого вызываются просто по индексу. И что исключения это именно исключения. Какие можем обработать, обрабатываем, все остальные - ошибки, не позволяющие продолжить работу.
Хм. Думаю, стоит взять пример обработчика от 1921вг015 (там тоже не поддерживается векторная обработка), привести ее плюс примерный перевод на ассемблер (ну так, для соответствия тематике курса).
- Стек ядра в плоской модели памяти RARS проще всего разместить «с самого верха»
Через sbrk даже проще. А еще лучше ядерный стек разместить сверху, а sbrk вызвать для юзерского. Это даже ближе к реальности.
- Такая технология называется «semihosting»
Это уже было на прошлой лекции. Довольно редкая задача, нет смысла ее описывать в нескольких местах.
- Идея в том, чтобы сразу составить таблицу обработчиков для каждого вида прерываний, взять аппаратно заданный номер прерывания, вычислить адрес соответствующего обработчика и перейти сразу туда. Такую сложную конструкцию имеет смысл делать для асинхронных ловушек (собственно, прерываний), отсюда и название.
В реальности первые индексы в таблице отводятся под исключения, благо их немного.
- Двойная косвенная адресация
Зачем? По сути это jump( mtvec + mcause)
Георгий Курячий Да вот нет, это то=то вроде jump(@(mtvec + mcause)), в смысле переход по адресу B, который содержится по адресу A — двойная косвенная адресация как она есть.
- Однако в небольших микроконтроллерах с плоской моделью памяти иногда делают так (хотя это и сложнее, чем схема, описанная ниже)
Делают и так, и эдак, и вообще непойми как. В том же ch32v303 реализованы ВСЕ ТРИ варианта: можно использовать один общий вектор, можно таблицу адресов и можно таблицу джампов.
- Традиционно все исключения — это прерывание № 0 (векторизовать исключения обычно смысла нет)
Это не так:
Георгий Курячий По стандарту это так «When MODE=Vectored, all synchronous exceptions into machine mode cause the pc to be set to the address in the BASE field, whereas interrupts cause the pc to be set to the address in the BASE field plus four times the interrupt cause number.»
Ах да, на фоне нескольких сотен векторов прерываний выделить десяток под исключения вообще никаких сложностей не представляет.
Даже в ch32v003, а это САМЫЙ слабый RISC-V контроллер, который я вообще видел, 38 векторов, из которых первые 12 отданы под исключения и тому подобное. В v303 (Каракатица) векторов уже 103. В gd32vf103 (более старая Каракатица) 115.
- Аккуратно отлавливать случаи других исключений, в т. ч. попытки вызова других несуществующих ecall — в этом случае RARS должен сам это исключение обрабатывать. Я сделал так:
К чему такие сложности?
Георгий Курячий К проверке Д/З.
` Unhandled_exception: j Unhandled_exception `
Это используется в реальных проектах. И оно же используется как заглушка для необъявленных прерываний.
Георгий Курячий И при проверке Д/З приедет тупой TL.
О прямом, непрямом и побочном эффекте. Если мы пишем в fsrm число 1, то прямой эффект - появление числа 1 в регистре (его можно считать), непрямой эффект - изменение поведения FPU. Получается, scratch - регистр, у которого есть только прямой эффект. А в gd32 есть нестандартный регистр mscratchcsrw. Если привилегии менялись, он срабатывает как mscratch, если нет - как nop. Пожалуй, это тоже только прямой эффект, хотя и нестандартный.
О таблице прерываний. Таблица адресов позволяет адресовать все 4 ГБ памяти, причем позиционно-независимо. Иногда это важно. Таблица переходов позволяет адресовать всего 2 МБ, но позиционно-независимо. Иногда бывает нужно и это. Единый обработчик позволяет более гибко всем этим управлять.
