06 сем Лекция 6 - chrislvt/OS GitHub Wiki
Одна из основных задач компьютера - передача информации от внешних устройств в память и из памяти во внешние устройства. Вся работа системы построена на прерываниях.
Система прерываний, её современное представление это три типа прерываний:
- Первый тип: системные вызовы
вызов api - application function intefrace.Это те функции которые операционная система предоставляет в распоряжение пользователя. В системе есть соверешенно определенная последовательность операций, система предоставляет api для того чтобы мы(пользователь) могли запросить сервис системы. Это определенные функции, их набор определен и стандартизован (в частности стандартом POSIX).
- Второй тип: исключения
Это исключительные ситуации которые система может перехватывать. Они определены. Все исключения перечислены.
- Третий тип: аппаратные прерывания
Если первые два типа прерывания являются синхронными событиями, то есть они воздникают в процессе выполненения программы, то аппаратные прерывания в системе являются абсолютно асинхронными событиями. Их возникновение не зависит ни от каких действий выполняемых в системе. Инициируется аппаратное прерывание API - системным вызовом, но собственно аппаратное прерывание, задачей которого является информирование процессора о возникновении прерывания, это абсолютно асинхронное событие. Такая схема возникла в результате реализации идеи распараллеливания функций о котором мы говорили в самом начале курса лекций, когда управление внешними устройствами было передано в канальной архитектуре каналам, а шинной архитектуре контроллерам или адаптерам.
Контроллером называется устройство которое как правило входит в состав внешнего устройства. Адаптером называется устройство, которое как правило находится на материнской плате.
Это программно-управляемые устройства. Они получают в результате работы драйвера устройства, который вызвается в результате возникновения системного вызова. Контроллеру передаётся команда, после этого процесор отключается, он не управляет операцией ввода вывода. И возникает в системе задача информировать о том что операция ввода/вывода завершена. И реализуется это с помощью соответствующей схемы.
По Цирюлику
Схемы обработки прерывания это принципиально архитектурно зависимое действие, связанное с аппаратной реализацией и в простейшем случае с контроллером прерываний. Несмотря на то что в современных системах схемы с контроллерами прерываний та же с APIC заменена MSI(Message Signaled Interrupts) всёравно общая схема остаётся неизменна. Даже в случае асинхронного ввода/вывода по завершению опперации ввода вывода контроллер устройства сигнализирует о том что операция завершена в результате должен быть активизирован обработчик прерывания от устройства и этот обработчик как правило входит в состав драйвера.
Схему эту мы рисовали. ЕЕ повторять она не будет, но на экзамене спросит)))))))))))))))))))))). Надо опять прочитать вспомнить про адресацию аппаратных прерываний в защищённом режиме. Н.Ю. выдаст материал, но предупреждает что этот материал будет на экзе. Там же в этом материале даётся схемы обработки с MSI. В этой схеме реализуется идея обратой совместимости. Там присутсвует и pic и apic.
- приходит прерывание на контроллер прерываний
- что-то происходит с регистрами на контроллере прерывания (как-то сохраняется какое irq было)
- отправляется сигнал процессору о прерывании (по шине управления)
- процессор в конце каждого такта проверяет налич прерываний, если есть то переключается на его выполнение (сохранение аппаратного контекста) и посылает контроллеру сигнал о готовности обработать его (по шу)
- контроллер получив сигнал, формирует вектор прерывания = базовое смещение + №irq и оттправляет его по шине данных
- процессор получает данный вектор и в таблице IDT находит дескриптор прерывания (базовый адрес из idtr + вектор прерывания)
- по полученному дескриптор находиться адрес обработчика прерываний: из селектора находиться начальный адрес сегмента обработчика прерывания (через gdtr + селектор + происходит проверка прав доступа) и к нему добавляется смещение
Важнейшим моментом нашего обсуждения является диаграма из Show (приведена в конце файла 5 моделей ввода/вывода) она явно демонстрирует действия которые выполняются когда приложение запрашивает ввод/вывод с точки зрения обобщения этой информации. Запрос ввода вывода является запросом сервиса системы. Система должна обслужить запрос ввода вывода. Это те же самые API функци (open, read, write, release). В результате инициализируется операция ввода/вывода, это значит что контроллеру посылается команда на управление операцией ввода/вывода, процессор отключается от дальнейшего управления операцией ввода/вывода и управление берет на себя специальное устройство (в шинной архитектуре это контроллер). По завершению формируется сигнал, задачей которого является информирование процессора о завершении операции ввода/вывода. Эта информация будет доведена до процессора с помощью вектора прерывания или MSI, и в том и в другом случае будут выполняться процессором действия по переключению на обработчик прерываний. В первом семестре говорили, что в конце выполнения каждой команды процессор проверяет наличие сигнала прерывания на выделенной ножке. MSI этот момент опускает, поэтому в основном все и рассказывают схему с pic и apic, так как она наиболее ярко раскрывает как же всё-таки система работает с аппаратными прерываниями.
Важнейшим моментом обработки прерываний с точки зрения программного обеспечения такой обработки являеся время, небходимое для выполнения обработчика прерывания. Мы видели в первом семестре, что обработчики прерываний выполняются на очень высоких уровнях в Windows IRQL, а в Unix/Linux на очень высоких уровнях приоритета в ядре. В системах с которым мы работаем, в SMP архитектуре, то есть многопроцессорных системах не реализованы вложенные прерывания. Аппаратные прерывания обрабатываются следующим образом: на том процессоре, на котором выполняется обработчик аппаратного прерывания запрещены все прерывания. На остальных процессорах запрещены прерывания по данной линии IRQ. То есть пока не завершится обработчик данного прерывания, например один процессор вообще не может ничего делать. Очевидно, что такая ситуация не может существовать в системе длительное время. То есть обработчик прерывания должен завершаться как можно быстрее. Поэтому в системах с которыми мы с вами работаем, строго говоря в Unix/Linux реализовано деление обработчиков прерываний на 2 части. Историческим названием такого деления является деление на верхнюю и нижнюю половины. Это буквальный перевод - top half и bottom half. Это название продолжает существовать, хотя реализация нижних половин менялась и в настоящее время это 3 типа обработчиков нижних половин о которых мы будем говорить.
Для того чтобы система начала обрабатывать соответствующее прерывание (аппаратное) необходимо зарегестрировать обработчик аппаратного прерывания.(часто слово аппаратного опускается, если говорят обработчик прерывания, то имеется ввиду обработчик аппаратного прерывания). В частности в материале Цирюлика приводится прототип взятый из ядра 2.6.7. Есть и более ранний прототип, но приведем именно этот. Функции определены в бибилиотеке linux/interrupt.h
typedef irqreturn_t(*irq_handler_t)(int, void*);
В более ранних версиях существует и третий параметр - struct pt_regs*.
typedef irqreturn_t(*irq_handler_t)(int, void*, pt_regs*);
В современнных системах с развитием технологий сохранение регистров процессора выполняется уже не программно, а забито в железе. Поэтому они выкинули этот параметр, так как железо работает безальтернативно.
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id);
- Первый параметр - unsigned int irq определяет номер прерывания. Мы с номерами знакомы по простейшей схеме IRQ0 - таймер, IRQ1 - клава ps пополам и тд.
Для legasy ps device (наследованных от таких ps девайсов)таких как таймер или клавиатура, эта величина обычно устанавливается аппратно(хард-коды). Для большинства других устройств устанавливается программно или динамически. Посмотреть прерывания, номера, обработчики можно опять же с помощью виртуальной файловой системы proc. Для этого надо набрать /proc/interupt.
- Второй параметр - irq_handler_t handler это указатель на актуальный обработчик прерывания. Очевидно, что этот обработчик вызывается по той схеме которую мы с вами обсуждали при возникновении прерывания, например от внешнего устройства и в защищённом режиме через таблицу IDT(Interrupt Descriptor Table) через соответствующий gate(шлюз). В результате сложения базового адреса сегмента в котором находится обработчик прерывания и смещения формируется адрес обработчика и процессор переключается на выполнение конкретного обработчика прерываний.
Мы видим что возвращается указатель на struct irq return t, который рассмотрим на семинаре.
- Третий параметр - irqflags или просто флаги. Начиная с версии ядра 2.6.19 старое именование флагов которое начиналось буквами SA__..., было заменено на новое IRQF__... .
Различаются следующие флаги:
Есть флаги которые устанавливают обработку прерывания по уровню сигнала это level_triggered и edge_triggered. Нам эти флаги не очень интересны, но они присутстствуют в мануалах. Речь идёт об уровне сигнала, если level_triggered то по достижению определеногого уровня сигнала, если edge_triggered то по фронту (например, по заднему).
В версии ядра 4.10 определены следующие флаги
#defne IRGF_SHARED 0x00000080
#define --//-- IRGF_PROBE_SHARED 0x00000100
# --//-- IRQF_TIMER 0x00000200
# --//-- IRQF_PERCPU 0x00000400
...
IRGF_SHARED флаг указыващий что линия IRQ может разделяться. То есть флаг указывает на разрешение разделения (совместного использования) линии IRQ.
IRGF_PROBE_SHARED устанавливается если есть возможность нестыковок при совместном использовании. (идет проба, можно разделять или нельзя)
IRQF_TIMER этот флаг заменил флаг SA__INTERRUPT который определял быстрые прерывания. То есть в системе прерывания делились и делятся на быстрые и медленные. Быстрое прерывание отличается тем что его обработчик выполняется от начала до конца и в современных системах осталось одно быстрое прерывание - прерывание от системного таймера. Оно определяется как прерывание от таймера и всё. Обработчик от системного таймера не может вызывать какие-то действия какие-то подпрограммы. Он может только инициализировать какие-то действия в системе как отложенные. То есть ставить соответсвующие действия в системе в очередь. Это можно делать разными способами, например устанавливать флаг, анализируя который система придет к необходиомости выполнения каких-либо действий. Или путём изменения состояния со слип на r, но это очень проблематично так как предполагает постановки в очередь процессору.
IRQF_PERCPU - флаг, который указывает что прерывание закреплено за определенным процессором. Напоминаю, у нас многопроцессорные системы с SMP архитектурой. Когда появилась многопроцессорность ядро было переписано, появились новые флаги.
Старые флаги были очень интересны, например был SA_SAMPLE_RANDOM. Этот флаг указывал что прерывания, генерируемые определенным устройством, должны вносить свой вклад в так называемый пул энтропии ядра, то есть для образования случайных чисел. Поскольку аппаратные прерывания в системе являются абсолютно асинхронными событиями, то это можно использовть для генерации абсолютно случайных чисел.
-
Четвертый параметр const char *devname - ascii текст, то есть имя устройства связанного с данным прерыванием. Например если это прерывание от клавиатуры, то можно сюда вставить строку keyboard. Это символьное имя используется в /proc/irq и как уже говорили в /proc/interrupts.
-
Пятый параметр void *dev_id - у него тип void, может указывать на всё что угодно. Но прежде всего этот параметр используется для разделения линии прерываний. Когда обработчик прерываня освобождается, то есть вызывается функция free irq, dev_id обеспечивает уникальные cookie файлы чтобы выполнить удаление только нужного обработчика прерывания с линии прерывания. Как уже сказали имеет тип void, может указывать на всё что угодно, но обычно в драйверах используется как указатель на структуру, специфичную для устройства. В случае успешного завершения функция request_irq возвращает 0. Если значение ненулевое, то возникла ошибка и handler не регистрируется. Обычная ошибка -EBUSY.
handler - обработчик. В системе принято делить прерывания на быстрые и медленные, но в современных системах быстрым прерыванием является только прерывание от системного таймера. Не потому что оно мнгновенное, а потому что на нём лежат очень важные функции и он инициализирует целый ряд отложенных действий в системе.
Все обработчики аппаратных прерываний от девайсов являются медленными и делятся на 2 части которые историчеки принято исторически называть top half и bottom half. В Unix каждому прерыванию принято назначать уровень приоритета прерывания - IPL(interrupt priority level). Регистор состояния процессора обычно содержит битовые поля в которых хранится текущий, а иногда и предыдущий IPL. В некоторых системах IPL = 0 это низший уровень, в других системах высший. В ОС BSD значение IPL изменяется от 0 до 31.Обработчик прерывания может выполнятся в один и тот же момент только одним процессором, при этом все прерывания на данном процессоре запрещены. На других процессорах запрещено прерывание по данной линии прерывания.
Верхние и нижние половины.
Для того чтобы максимально сократить время обработки так называемых медленных прерываний, они делятся на 2 части: верхнюю и нижнюю.
Верхняя половина(top half) запускается в результате получения сигнала прерывания процессором. Основной задачей обработчика прерывания является сохранение данных, полученных от устройства( по шине данных ), в буфере ядра и заканчивается обработчик прерывания инициализацией выполнения нижней половины различными способами. Такая инициализация нижней половины зависит от типа нижней половины.
В настоящее время в Unix/Linux различается 3 типа отложенных действий: softirq, tasklets(тасклеты), queue work(очереди работ). Все новые схемы реализации (верхних и нижних половин) построены на выполнении обработчика нижней половины отдельным потоком ядра. softirq определяются статически во время компиляции ядра. В файле <linux/interrupt.h> определена структура struct softirq_action.
ядро 4.10
struct softirq_action
{
/* ф-я которая должна выполняться */
void(*action)(struct softirq_action*);
};
В ядре в версии 2.6.37 у структуры было ещё поле данных. В очень многих работах из интернета приводится именно старая структура.
В файле <kernel/softirq.c> определен массив из 32х экземпляров структуры softirq_action, то есть
static struct softirq_action softirq_vec[NR_SOFTIRQS]
где NR_SOFTIRQS - число задействованных номеров = 32. Следственно имеется возможность создать 32 обработчика softirq. В настоящее время определено 10 обработчиков.
Табличка
(добавить в табличку HI_SOFTIRQ - высокоприоритетные softirq.)
RCU должен быть последим в softirq.
Когда ядро выполняет обработчик отложенного прерывания, то функция action вызывается с указателем на соответствующую структуру softirq_action в качестве параметра, например XXX_softirq содержит указатель на элемент массива softirq_vec:
softirq_vec[NR_SOFTIRQS]
char *softirq_to_name[NR_SOFTIRQS] = {"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCKTOPOLL", "TASKLET", "SHED", "HRTIMER", <>, "RCU"};
Таким образом мы видим, что индекс определяет имя softirq. Если добаляется новый softirq, то обновляется массив softirq_to_name. При этом добавить новый уровень обработчика softirq можно только перекомпилировав ядро. Приоритет устанавливается таким образом, что softirq с меньшими номерами имеют более высокий приоритет. Это видно из самих названий. Очеидно, что новый softirq должен иметь уровень хотябы на единицу меньше tasklet_softirq (?) иначе нет смысла определять новый уровень так как можно ипользовать тасклет.