06 сем Семинар 8 Tasklet 31.05.21 - chrislvt/OS GitHub Wiki

В современных ОС принято классифицировать прерывания следующим образом:

  1. системные вызовы
  2. исключения
  3. аппаратные прерывания

При этом системные вызовы и исключения являются синхронными событиями по отношению к выполняемому коду (программе).
Аппаратные прерывания - асинхронные события, которые не зависят от выполняемой в текущий момент работы.

Возникновение аппаратных прерываний связанно с архитектурными особенностями современных вычислительных систем, в которых реализована идея распараллеливания функций. В 3ем поколении ЭВМ, а именно при разработке IBM 360, была предложена канальная архитектура, в основе которой лежит идея распараллеливания функций. Процессор был освобожден от задачи управления внешними устройствами. Задача управления внешними устройствами была возложена на спец процессоры, которые получили название каналы.

X86 имеют шинную архитектуру. В шинной архитектуре задачу управления внешними устройствами выполняют спец устройства - контроллеры. Контроллер обычно входит в состав самого внешнего устройства (девайса).
Адаптеры - устройства, которые управляют внешними устройствами, но находятся на материнской плате.
И контролеры, и адаптеры являются программно-управляемыми устройствами, как и каналы.

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

Надо сказать, что уже полноценная система прерывания появилась в IBM360. В книгах того времени выделяют 5 типов прерываний, но только потому что аппаратные прерывания делились на 3 группы. Мы тоже их делим - прерывания от системного таймера, от девайсов и группа действий оператора (она уже не имеет того значения как в IBM360, но тем не менее остались какие-то ситуации/ Пример windows ctrl+ld когда вызывают таск мененджер).

Аппаратное прерывание в любой системе (Windows, Linux) всегда выполняются на очень высоком уровне приоритетов.

В Windows прерывания находятся выше DPS Dispatch, то есть мы изучали, что в Widows уровень приоритета ядра 0, а все действия в ядре выполняются по IRQL. Наивысшие уровни IRQL имеют аппаратные прерывания, выше всех аппарптных прерываний - прерывание от системного таймера. Прерывания power предполагают, что необходимо сохранить работоспособность системы, чтобы систему можно было восстановить.

В Linux, Unix не так, там для ядра выделен определенный диапазон приоритетов и аппаратные прерывания выполняются на высочайших уровнях приоритетов. В тех системах, с которыми мы работаем, не поддерживаются ложные прерывания. При возникновении аппаратного прерывания на процессоре, на котором начинает выполняться обработчик данного прерывания, запрещены все прерывания.

Поскольку аппаратные прерывания выполняются на очень высоком уровне IRQL и уровне приоритета, и в наших системах обработка аппаратных прерываний построена на запрете прерываний, причем прерывания запрещаются на текущем процессоре, на остальных процессорах запрещаются прерывания по данной линии IRQ.
Даже если пришло аппаратное прерывание более высокого уровня, оно другое прерывание вытеснить не может. Естественно это все выполняется по стековому алгоритму, то есть выход из этой ситуации выполняется по LIFO. В тех системах которые мы используем это не используется. (можно почитать у Медника-Донована если найдёте).

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

Все прерывания в тех системах, с которыми мы имеем дело, кроме прерывания от системного таймера делятся на 2 части: top half и bottom half. Это историческое название. Механизмы менялись - название сохранилось.

Начиная с версии 2.6.19, было изменено именование флагов для аппаратных прерываний. Старое именование SA, новое - IRGF. В старых был SA_INTERRUPT, этот флаг обозначал быстрое прерывание.
Быстродействие наших систем с тех пор значительно увеличилось, это привело к более явному отличию быстродействия процессора и внешних устройств. Внешние устройства всегда медленные и осталось единственное быстрое прерывание - таймер. Соответственно существует флаг IRQF_TIMER.
Быстрые прерывания выполняются от начала до конца, а медленные прерывания делятся на 2 части - обработчик аппаратного прерывания и отложенное действие. То есть, завершает обработку прерывания отложенное действие.
Таймер - условно только называется быстрым прерыванием. На таймер возложены важнейшие системные функции, и сам таймер инициализирует большое количество отложенных действий, но эти действия определяют функционирование системы. Сам таймер работает независимо и может инициализировать необходимое в системе действие по времени.

В современных системах существует 3 механизма реализации отложенных действий:

  • softirq,
  • tasklet,
  • очереди работ.

Прототипы функций, связанных с обработчиком аппаратного прерывания, взяты из ядра 2.6.37. Это такое этапное ядро, это уже SMP. Выглядит это следующим образом:

typedef interrupt_t(*irq_handler_t)(int, void*);

int request_irq(unsigned int irq, int_handler handler, unsigned long flags, const char *name, void *dev); // регистрация обработчика прерывания на указанной линии IRQ.

int irq - номер линии irq,
handler - обработчик аппаратного прерывания
Флагов много, но самый замечательный флаг, который мы используем, это IRQF_SHARED, который указывает, что данная линия прерываний может разделяться.
Параметр name - символьная строка ascii символов. Это то имя обработчика прерывания, которое мы увидим в системе.
указатель void dev говорит о том, что прерывания всегда связаны с конкретным устройством. Драйвер устройства может иметь только 1 обработчик прерывания. Данный параметр очень важен так как когда мы завершаем обработку, нам надо освободить системы от handler соответственно есть функция free_irg(int irg, void *dev); Это указатель void и сюда мы можем передать все что угодно, в том числе наш хендлер.

В старом варианте этой функции было typedef interrupt_t(*irq_handler_t)(int, void*, struct pt_regs*); Начиная с ядра 2.6.37 этот аргумент был выкинут.

Системный вызов

DOS Windows Linux

int21h int2Eh int80h

Фактически это перевод системы в режим ядра. При переходе системы в режим ядра в стеке сохраняется содержимое регистров для того чтобы затем можно было продолжить прерванную программу. Что значит сохраняется в стеке? Стек находится в оперативной памяти, соответственно содержимое регистров сохраняется в оперативной памяти. В системе даже существует понятие цикл обращения к памяти - это медленное действие. Прерывания возникают очень часто, фактически они вызывают зло, но в архитектурах, с которыми мы работаем, от этого зла невозможно избавиться. В современных системах эти вызовы int были заменены SYSENTER. Смысл изменения состоит только в том, что, как говорят, вектор прерывания забили в железо. В процессоре появились специальные регистры, в которых сохраняется аппаратный контекст. То есть сохранение аппаратного контекста забили в железо и struct pt_regs* стала не нужна.

Тасклеты являются одним из типов softirq. В системе может быть 32 softirq, но существует в настоящее время 10. Шестой приоритет имеет тасклет softirq. Поскольку тасклеты являются одним из softirq, фактически нет смысла включать в перечень softirq свои softirq. Все softirq определяются статически во время компиляции, если мы пытаемся добавить softirq, то нужно перекомпилировать ядро. Если мы пишем softirq по уровню ниже тасклета, то в этом нет смысла, тогда нужно писать тасклет.

Существуют отличия между softirq и тасклетами, тасклет - это реализация softirq, но softirq могут масштабироваться на процессоры. т.е. одновременно выполняться на разных процессорах. В связи с этим к softirq предъявляются высокие требования по взаимоисключению.
Один и тот же тасклет параллельно выполняться не может. Тасклет всегда выполняется в единственным экземпляре, но тасклет является атомарным. А в softirq должно быть взаимоисключение. Они могут выполняться параллельно, т.е. может осуществлять параллельный доступ к разделяемым данным.
Фактически прервать softirq может только аппаратное прерывание. В связи с этим тасклет блокироваться не может, как и softirq, то есть в них должны использоваться команды test_and_set. Это видно в ядре, в реализациях. Или как говорят spinlock.

В отличие от softirq тасклеты могут регистрироваться как статически, так и динамически.

СТАТИЧЕСКИ

Статический тасклет декларируется макросом DECLARE_TASKLET(<name>, <обработчик>, data);
name - имя тасклета типа struct tasklet_struct

с тасклетами связана структура

struct tasklet_struct{
	struct tasklet_struct *next;
	unsigned long state;
	...
	atomic_t count;
	void (*tune)(unsigned long);
	unsigned long data;
} my_tasklet;

DECLARE_TASKLET_DISABLED(-//-)

ДИНАМИЧЕСКИ

Тасклет может регистрироваться динамически с помощью ф-ии tasklet_init(<name>, <обработчик>, data)

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

Все эти функции возвращают void.

void tasklet_scheduler(struct tasklet_struct *t);

void tasklet_hi_sheduler(-//-);

void tasklet_hi_sheduler_first(-//-);

Все эти функции похожи. Отличия : tasklet_scheduler использует отложенное действие, то есть softirq TASKLET_SOFTIRQ (приоритет 6), а tasklet_hi_sheduler (приоритет 0) использует HI_SOFTIRQ.
Несмотря на то, что в структуре struct tasklet_struct имеется поле next (очередь), один и тот же тасклет одновременно находиться 2 раза в этой очереди не может. Эта очередь - очередь планирования. Надо помнить что любое выполняемое в ядре действие является потоком. На такслете определены не только функции планирования, но тасклет может быть активирован и деактивирован. То есть на нём определены следующие функции:

void tasklet_disable(struct tasklet_struct*)

void tasklet_enable(-//-)

void tasklet_disabke_nosync(-//-)

Существует функция taskelt_kill(struct tasklet_struct*) и tasklet_kill_immediately(struct tasklet_struct*, int cpu)
cpu - указание процессора, с которого удаляется тасклет.
immediately - тасклет будет удален в любом случае. Обычная функция ждет, пока тасклет выполнится.

Задание на лр по тасклетам:

В этой лр должен быть обработчик аппаратного прерывания interrupt handler и тасклет - то есть обработчик тасклета, отложенное действие. Обработчик аппаратного прерывания регистрируем с помощью requestIRQ. В ините тасклет можно инициализировать статически, но лучше динамически. В обработчике прерывания мы инициализированный тасклет ставим в очередь на выполнение и передаем указатель на структуру struct tasklet_struct. В обработчике тасклета мы вызываем свою функцию show. Из лабы по проц seqfile.h. Для того, чтобы вывести с помощью seq_printf state тасклета, count и data. Соответственно должна быть функция open, в которой вызываем функцию single_open. Это уточненное задание на тасклеты.

Выводим в юзермод, не в принтк.

⚠️ **GitHub.com Fallback** ⚠️