Различия между версиями 2 и 3
Версия 2 от 2019-04-19 08:12:18
Размер: 21958
Редактор: FrBrGeorge
Комментарий:
Версия 3 от 2019-04-19 08:12:29
Размер: 21912
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 2: Строка 2:
=== О прерываниях вообще ===

Прерывания

  • Прерывание — сигнал, сообщающий процессору о наступлении какого-либо внешнего события.
    • асинхронны: могут произойти в любое время в любом месте выполнения программы (ср. исключения: могут возникнуть только при выполнении конкретных инструкций)
    • позволяют освободить cpu от активного ожидания: программа спокойно вычисляет (не отвлекаясь на опрос готовности устройства), а когда устройство, наконец, себя проявит, результаты его активности быстро обрабатываются
    • обрабатываются так же, как и другие события, — обработчиком прерываний (как правило, частью ядра ОС)

  • Проблемы возникающие при обработке прерываний:
    • распознавание природы прерывания — что за устройство и что с ним случилось
      • где-то должна появляться об этом информация (аппаратно!)
    • прерывания нужно быстро обработать
    • во одно и то же время может случиться несколько прерываний
      • про каждое из них надо знать, что оно случиолось
  • Маскирование прерываний.
    • Прерывания, в зависимости от возможности запрета, делятся на:
      • маскируемые — прерывания, которые можно запрещать установкой соответствующих битов в регистре маскирования прерываний;
      • немаскируемые — обрабатываются всегда, независимо от запретов на другие прерывания.
  • Приоритеты обслуживания прерываний: если выясняется, что произошло несколько, какое обрабатывать первым? А если за время обработки произойдёт ещё несколько?

Обработчик прерываний

Прерывания, в отличие от исключений, могут возникать в произвольное время (например, прерывание ввода зависит от того, когда человек нажал на кнопку). Прерывания в Mars обрабатываются тем же кодом, что и исключения — специальным обработчиком по адресу 0x8000180. Обработка прерываний управляется сопроцессором 0 и его регистрами

  • Регистр Status:

bits

31-16

15-8

7-5

4

3,2

1

0

target

unused

Int. mask

unused

K/U

unused

Exception level

Int enable

  • Interrupt mask - Mаска прерываний. Бит равен 1, если соответствующее прерывание разрешено.
  • При возникновении прерывания будет установлен соответствующий бит регистра Cause (если прерывание маскировано — не произойдёт ничего)
  • Kernel/User - режим ядра/режим пользователя; не поддерживается Mars; всегда 1.
  • Exception level - устанавливается автоматически при исключении; предотвращает повторный вход.
  • Interrupt enable - глобальное разрешение обработчика прерываний (0 - отключить)

    • Выполнение кода и переход на обработчик выполняться не будет. в Mars обязательно отключать в начале обработчика прерываний

    • Если за время, пока прерывания отключены, внешние устройства проявляют активность, и соответствующие прерывания не маскированы, после включения прерываний произойдёт вход в обработчик, и поле Pending interrupts регистра Cause будет содержать все соответствующие биты
    • Так в «настоящих» MIPS. В Mars выставлять более одного бита умеет только устройство «консоль»

    Регистр Cause:

31

30-16

15-8

7

6-2

1-0

Br

unused

Pending interrupts

unused

Exception code

unused

  1. Br - 1 если исключение произошло на инструкции в слоте задержки перехода.
  1. Pending interrupts - ожидающие (ещё не обработанные) прерывания.
  2. Exception code - код исключения. Прерывания, в отличие от исключений, могут возникать в произвольное время (например, прерывание ввода зависит от того, когда человек нажал на кнопку). Прерывания в Mars обрабатываются тем же кодом, что и исключения — специальным обработчиком по адресу 0x8000180.
    • Нужно сохранять все используемые регистры, кроме $k0 и $k1 (все, включая $at)

  3. Нельзя пользоваться стеком (а вдруг он испорчен, или прерывание произошло во время его изменения?)
    • Можно предусмотреть отдельный стек ядра (скажем, от 0xfffeeffc вниз) и пользоваться им (тогда $sp тоже сохраняется)

  4. Нужно различать исключения (поле Exception code регистра Cause ненулевое) и прерывания (поле EXC нулевое)
    • возврат из исключения по eret требует прибавить 4 к значению EPC (ошибочную инструкцию выполнять повторно обычно не надо)

    • возврат из прерывания по eret не требует увеличения EPC (инструкция по этому адресу ещё не выполнена)

  5. В Mars нужно как можно быстрее запретить обрабатывать исключения, чтобы исключение не случилось в ядре

    • Значит, в обработчике надо проводить как можно меньше времени — не все устройства Mars умеют выставлять бит в регистре, когда обработчик запрещён (бит IE регистра Status равен 0)

    • Если в поле Pending interrupts приехало несколько битов, значит, произошло несколько прерываний, и все надо обработать (или игнорировать)

  6. Перед выходом из обработчика
    • Очистить регистр Cause (биты прерываний и тип исключения)
    • Разрешить обработку прерываний
    • (бит EXL в Mars очистит инструкция eret)

    Программа, использующая прерывания, должна их включать: выставлять 1 в биты разрешения прерываний и во все нужные позиции маски прерываний, а также переводить используемые врешние устройства в режим работы по прерыванию:
            mfc0    $a0 $12                 # read from the status register
            ori     $a0 0xff11              # enable all interrupts
            mtc0    $a0 $12                 # write back to the status register
    
            li      $a0 2                   # enable keyboard interrupt
            sw      $a0 0xffff0000
    Сам обработчик событий по адресу 0x800000180, таким образом, обычно состоит из следующих частей:
    • Запрет вызова обработчика (бит IE)
  7. Сохранение всех регистров
  8. Вычисление типа исключений (0 — прерывание)
    • Переход на обработчик соответствующего исключения или на обработчик прерываний
  9. Обработчик прерываний:
    • Выяснение источника/ов прерывания (биты Pending)
    • Обработка всех случившихся прерываний (порядок определяется программно)
  10. Обработчик исключения
    • Обработка исключения :)

    • Вычисление нового EPC
  11. Восстановление всех регистров
  12. Разрешение вызова обработчика
  13. eret

    Замечание: как и во время обработки прерывания, во время системного вызова прерывания или вообще запрещены, или накапливаются (делаются «Pending») без вызова обработчика. Поэтому задача обработчика — как можно быстрее принять решение, что делать с прерыванием и вернуть управление пользовательской программе.

    • В обработчике выполняются только действия, необходимые для получения данных по прерыванию (чтение регистров, управление устройствами и т. п.)
  14. Вся логика обработки данных остаётся в программе пользователя
  15. При соблюдении некоторой дисциплины программирования можно вообще запретить пользовательской программе обращаться к внешним устройствам, оставив эти операции ядру, обрабатывающему прерывани

Пример: консоль Mars

Консоль Mars («Keyboard and Display MMIO Simulator») — воображаемое устройство, осуществляющее побайтовый ввод и вывод. Вернее окошко — «дисплей», куда выводятся байты, а нижнее — «клавиатура» (для удобства набираемый на клавиатуре текст отображается в этом окошке).

atttachment:Console.png

Консоль имеет следующие регистры ввода-вывода

0xffff0000

RcC

Управляющий регистр ввода

RW

0 бит — готовность, 1 бит — прерывание

0xffff0004

RcD

Регистр данных ввода

R

введённый байт

0xffff0008

TxC

Управляющий регистр вывода

RW

0 бит — готовность, 1 бит — прерывание

0xffff000c

TxD

Регистр данных вывода

W

необязательные координаты курсора, байт для вывода

  • Операции ввода и вывода в консоли возможно только если бит готовности равен 1. Если бит готовности нулевой в управляющем регистре ввода, значит, клавиша ещё не нажата, а если в управляющем регистре вывода — символ всё ещё выводится, следующий выводить нельзя (ну медленное устройство, в жизни так сплошь и рядом!). Как обычно, устройство заработает только после нажатия кнопки «Connect to MIPS». Простой пример чтения с клавиатуры при помощи поллинга. Удобно рассматривать с низкой скоростью работы эмулятора (3-5 тактов в секунду).
    loop:   lb      $t0 0xffff0000          # готовность ввода
            andi    $t0 $t0 1               # есть?
            beqz    $t0 loop                # нет — снова
            lb      $a0 0xffff0004          # считаем символ
            li      $v0 11                  # выведем его
            syscall
            b       loop
    Чуть более сложный пример с выводом, в котором видна проблема переполнения. Вывод начинает работать (точнее, бит готовности выставляется в первый раз) только после нажатия «Reset» (подозреваю, что это — тоже пример из «реальной жизни»: при включении питания многие устройства находятся в неопределённом состоянии и требуют инициализации).
            li      $t1 0
    loop:   beqz    $t1 noout               # выводить не надо
    loopi:  lb      $t0 0xffff0008          # готовность вывода
            andi    $t0 $t0 1               # есть?
            beqz    $t0 loopi               # а надо! идём обратно
            sb      $t1 0xffff000c          # запишем байт
            li      $t1 0                   # обнулим данные
    noout:  lb      $t0 0xffff0000          # готовность ввода
            andi    $t0 $t0 1               # есть?
            beqz    $t0 loop                # нет — снова
            lb      $t1 0xffff0004          # считаем символ
            b       loop

    Выставляя ползунок «Delay Length» в большое значение, мы заставляем консоль долго не давать готовности по выводы (в течение, скажем, 20 инструкций). Пока программа находится в половинке вывода (цикл loopi:), она не успевает за вводом.

    Задание: пронаблюдать, что происходит с регистрами ввода, когда пользователь много наживает на клавиатуре, а программа не успевает считать.

    • TODO — нижеследующее в отдельную страницу

    Главное свойство консоли она может инициировать прерывания в момент готовности ввода или вывода. Устанавливая в 1 первый бит в регистре RcC, мы разрешаем консоли возбуждать прерывание всякий раз, как пользователь нажал на клавишу. Устанавливая в 1 первый бит регистра TxC, мы разрешаем прерывание типа «окончание вывода». И в том, и в другом случае прерывание возникает одновременно с появлением бита готовности (нулевого) в соответствующем регистре. Таким образом, вместо постоянного опроса регистра мы получаем однократный вызов обработчика в подходящее время.

    Рассмотрим пример очень грязного обработчика прерывания от клавиатуры, который ничего не сохраняет, не проверяет причину события и номер прерывания. Зато по этому коду хорошо видна асинхронная природа работы прерывания. Рекомендуется выставить ползунок Mars «Run speed» в низкое значение (например, 5 раз в секунду).

            li      $a0 2                   # разрешим прерывания от клавиатуры
            sw      $a0 0xffff0000
            li      $a0 0
    loop:   beqz    $a0 loop                # вечный цикл
            beq     $a0 0x1b done           # ESC — конец
            li      $v0 11                  # выведем символ
            syscall
            li      $a0 0                   # затрём $a0
            j       loop
    done:   li      $v0 10
            syscall
    
    .ktext  0x80000180                      # очень грязный код обработчика
            lw      $a0 0xffff0004          # считаем символ
            eret

    В примере ниже «полезные вычисления» делает подпрограмма sleep (на самом деле ничего полезного), время от времени проверяя содержимое ячейки 0 в глобальной области. Это лучше, чем модифицировать регистр или метку, определяемую пользовательской программой. Обработчик клавиатурного прерывания (для простоты — не проверяя, клавиатурное ли оно) записывает в эту ячейку код нажатой клавиши. TODO проверить таки источник прерывания и нарисовать что-то \|/-

    .text
            .globl main
    main:
            mfc0    $a0 $12                 # read from the status register
            ori     $a0 0xff11              # enable all interrupts
            mtc0    $a0 $12                 # write back to the status register
    
            li      $a0 2                   # enable keyboard interrupt
            sw      $a0 0xffff0000
    
    here:
            jal     sleep
            lw      $a0 ($gp)               # print key stored in ($gp)
            beq     $a0 0x1b done           # ESC terminates
            li      $v0 1
            syscall
            j       here
    done:   li      $v0 10
            syscall
    
    .eqv    ZZZ     100000
    sleep:  li      $t0 ZZZ                 # Do nothing
    tormo0: subi    $t0 $t0 1
            blez    $t0 tormo1
            j       tormo0
    tormo1: jr      $ra
    
    .ktext  0x80000180                      # kernel code starts here
    
            mfc0    $k0 $12                 # !! disable interrupts
            andi    $k0 $k0 0xfffe          # !!
            mtc0    $k0 $12                 # !!
    
            move    $k1 $at                 # save $at. User programs are not supposed to touch $k0 and $k1
            sw      $v0 s1                  # We need to use these registers
            sw      $a0 s2                  # not using the stack
    
            mfc0    $k0 $13                 # Cause register
            srl     $a0 $k0 2               # Extract ExcCode Field
            andi    $a0 $a0 0x1f
            bne     $a0 $zero kexc          # Exception Code 0 is I/O. Only processing I/O here
    
            lw      $a0 0xffff0004          # get the input key
            sw      $a0 ($gp)               # store key
            li      $a0 '.'                 # Show that we handled the interrupt
            li      $v0 11
            syscall
            j       kdone
    
    kexc:   mfc0    $v0 $14                 # No exceptions in the program, but just in case of one
            addi    $v0 $v0 4               # Return to next instruction
            mtc0    $v0 $14
    kdone:
            lw      $v0 s1                  # Restore other registers
            lw      $a0 s2
            move    $at $k1                 # Restore $at
            mtc0    $zero $13
    
            mfc0    $k0 $12                 # Set Status register
            ori     $k0 0x01                # Interrupts enabled
            mtc0    $k0 $12                 # write back to status
    
            eret
    
    .kdata
    s1:     .word 10
    s2:     .word 11
    Символами "!!" отмечены инструкции, при выполнении которых в MARS может возникнуть фатальный повторный вход в обработчик события.

LecturesCMC/ArchitectureAssembler2019/09_Interrupts (последним исправлял пользователь FrBrGeorge 2019-05-17 15:28:53)