Билет 01 - honeycarbs/bmstu-os-6sem GitHub Wiki
Управление внешними устройствами: специальные файлы устройств, адресация внешних устройств и их идентификация в системе, тип dev_t. Система прерываний: типы прерываний и их особенности. Прерывания в последовательности ввода-вывода - обслуживание запроса процесса на ввод-вывод (диаграмма). Быстрые и медленные прерывания. Обработчики аппаратных прерываний: регистрация в системе -- функция и ее параметры, примеры. Тасклеты — объявление, планирование (пример лаб. раб).
Управление внешними устройствами
Cпециальные файлы устройств
Специальные файлы устройств обеспечивают единообразный доступ к внешнему устройству, или переферии. Эти файлы обеспечивают связь файловой системы и драйверов. В отличие от обычных файлов, специальные файлы устройств в действительности являются только указателями на соответствующие драйверы устройств в ядре. Такая интерпретация специальных файлов обеспечивает доступ к внешнему устройству как к обычному файлу. Так же как и обычный файл, специальный файл устройства может быть открыт, закрыт, в него можно писать и из него можно читать.
Каждому внешнему устройству unix\linux стваит в соответствие как минимум один специальный файл. Обычно эти файлы можно увидеть в каталоге /dev корневой файловой системы. Подкаталог /dev/fd содержит файлы с именами 0,1,2, но в некоторых системах имеются файлы с именами /dev/stdin, /dev/stdout, /dev/stderr.
Система поддерживает 2 типа специальных файлов устройств:
- блочный(буферизуемый) - внешние запоминающие устройства;
- символьный(небуферизуемый) - все остальные.
В unix\linux связь имени специального файла с конкретным внешним устройством обеспечивает inode (индексный идентификатор). Взаимодействие прикладных программ с аппаратной частью компа под управлением ОС unix\linux осуществяется по следующей схеме:
УСТРОЙСТВО <--> ЯДРО --> ДРАЙВЕР <--> VFS <--> ПРИЛОЖЕНИЕ
Драйвер - Это часть кода ядра, которая предназначена для упраления конкретным внешним устройством. Драйверы в любой системе пишутся по правилам этой стсиемы на основании структур ядра, определенных в системе. Такие структуры перечисляют точки входа в драйвер и другие параметры. Задача драйвера - управлять внешним устройством. Драйвер должен преобразовывать данные поступающие от устройства и поступающие в устройство. Если драйверу нужно передать данные устройству, ему нужно передать их в формате, определенном на устройстве. Формат данных, полученных от устройства должен быть преобразован в формат, понятный приложению.
Драйверы бывают трех видов:
- встроенные - выполнение которых инициализируется при запуске системы. (IDE, материнская плата, последовательные\переллельные порты);
- драйвера, реализованные как загружаемые модули ядра (часто используется для упарвления такими устройствами как SCSI, звуковые, сетевые карты). Располагаются в /lib/modules. При инсатляции системы указывается перечент модулей, который будет подключатся на этапе загрузки. Список - в /etc/modules/. /etc/modules/modules.conf - перечень опций для таких модулей. Подключепние и работа: lsmod, insmod, rmmod, modprobe(автоматически загружает модули)
- Код, поделенный между ядром и специальной утилитой.
Идентификация внешних устройств
Для идентификации внешних спейиальных файлов устройств имеется соответсвующая система идентификации. Старший и младший(дополнительный) номера устройств - major, minor. Общий подход, как символьных так и блочных устройств.
Посмотреть старшие и младшие номера устройств -
ls -l
.
Старший и младший номера идентифицируют драйвер, который связан с устройством.
Соверменная ОС linux позволяет множеству драйверов разделять старшие номера. Но большинство устройств, которые можно видеть в /dev все еще огранизованы по принципу один старший номер на один драйвер. Младшие номера определяют конкретное устройство.
dev_t - внутреннее представление драйверов устройств.
Стандарт POSIX.1 определяет существование этого типа, но не оговаривает формат полей. Тип
dev_t
, определён в
<linux/types.h>
. Начиная с версии ядра 2.6.0, dev_t является 32-х разрядным, в котором 12 бит отведены для старшего номера и 20 - для младшего.
Код драйвера не должен интерпертировать эти значения. Он должен их использовать.
<linux\kdev_t.h>
C
MAJOR(dev_t dev);
MINOR(dev_t dev);
Если имеются старший и младший номера, то возможно обратное действие:
C
MKDEV(int major, int minor);
Одно из первых действий, которое драйвер должен сделать, когда устанавливается символьное устройство - получить один и более номеров устройств.
TODO: адресация внешних устройств (45.00 9 лек)
Система прерываний
Типы прерываний и их особенности
Основой работы ОС является система прерываний. В монолитном ядре все построено на прерываниях.
Классификация:
- Системные вызовы (аппаратные) - вызываются искусственно с помощью соответствующей команды из программы, предназначены для выполнения некоторых действий ОС (фактически запрос на услуги ОС), является синхронным событием.
- Аппаратные - возникают как реакция микропроцессора на физический сигнал от некоторого устройства (клавиатура, мышь и т. д.), по времени возникновения эти прерывания асинхронны, т. е. происходят в случайные моменты времени.
- От таймера
- От действий оператора
- От устройств ввода/вывода (посылается сигнал о завершении процесса вв/выв на контроллер прерываний).
- Исключения — являются реакцией микропроцессора на нестандартную ситуацию, возникшую внутри микропроцессора во время выполнения некоторой команды программы (деление на ноль, прерывание по флагу TF(трассировка)), являются синхронным событием.
- Исправимые - приводят к вызову определенного менеджера системы, в результате работы которого может быть продолжена работа процесса (пр. страничная неудача с менеджером памяти).
- Неисправимые — в случае сбоя или в случае ошибки программы (пр. ошибка адресации). В этом случае процесс завершается.
Аппаратные прерывания предназначены для информирования процессора. Идея аппаратных прерываний: процесс должен быть проинформирован о завершении ввода-вывода. Поскольку процессор выполняет какую-то другую работу, его работа организована следующим образом: В цикле выполнения каждой команды процессор проверяет наличие сигнала прерывания на своей выделенной ножке. Если сигнал прерывания пришел, то процессор переходит на обработку этого прерывания. Из 1 семестра: лабораторная работа по защищенному режиму. Прерывание от устройства ввода/вывода поступает, когда устройство завершило процесс ввода/вывода. Процессор освобождается от проверки флагов и может переключиться на другую работу. Метод требует включения в состав ОС контроллера прерываний. Контроллер прерываний посылает по шине управления сигнал. В конце выполнения каждой команды процессор проверяет входной сигнал с шины управления (если прерывания не замаскированы в ОС). Если получен сигнал - посылается ответный сигнал контроллеру прерываний, в ответ контроллер прерываний формирует и посылает вектор прерывания. Вектор передается по шине данных. Полученный вектор используется для процедуры обработки прерывания.
Прерывания в последовательности ввода-вывода

Быстрые и медленные прерывания
В современных linux-системах к быстрым прерываниям относятся только прерывания от системного таймера. Все осталные являются медленными. В версии 2.6.19 все флаги, связанные с прерываниями были радикально изменены. В старых версиях флаги имели приставку SA. В частности, SA_INTERRUPT (быстрые прерывания) в новых версиях заменен на флаг IRQF_TIMER.
Когда выполняются аппаратные прерывания, никакая другая работа не может быть выполнена. SMP (Симметричная многопроцессорность) - архитектура внесла в этот тезис некоторые изменения. В ней имеются равноправные процессоры, которые работают с общей памятью. Поскольку процессоров несколько и они выполняют работу параллельно, возникает ситуация: на процессоре, который выполняет обработчик возникшего прерывания, запрещены все прерывания. Для остальных процессоров запрещены прерывания по этой линии IRQ.
Обработчики аппаратных прерываний должны завершаться как можно быстрее. Если они будут выполняться длительное время, то это скажется на отзывчивости системы (быстродействии). Поэтому обработчики АП выполняют минимально необходимый набор действий. Поэтому обработчики прерываний делятся на две части: верхняя и нижняя половина. Аппаратное прерывание, которым является top half, также инициализирует выполнение отложенных действий для того чтобы система могла завершить обработку ввода-вывода.
В современном unix-linux различается 3 типа нижних половин:
- softirq — гибкие прерывания;
- tasklet;
- workqueue - очереди работ. Обработчики аппаратных прерываний являются одной из точек входа в драйвер.
Обработчики аппаратных прерываний
Регистрация в системе
Функция request_irq() предназначена для регистрации обработчика прерывания на определенной линии прерывания.
request_irq(
unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char *devname,
void *dev_id
);
Обработчики прерываний в драйверах устройств отвечают за взаимодействие с внешними устройствами на этапе передачи данных от устройств. Драйвер устройства регистрирует в системе один обработчик прерывания. Делается это с помощью функции request_irq(), которой в качестве параметра передается указатель на обработчик, обслуживающий конкретное прерывание: irqreturn_t (*handler)(int, void *). Эта функция будет вызвана при возникновении конкретного прерывания в системе. Прототип обработчика принимает три параметра и возвращает значение типа irqreturn_t. Тип irqreturn_t определяется следующим образом:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
Например:
static irqreturn_t intr_handler (int irq, void *dev) {
if(! /* проверка того, что обслуживаемое устройство запросило прерывание*/)
return IRQ_NONE;
/* код обслуживания устройства */
return IRQ_HANDLED;
}
Первым параметром функции request_irq() является unsigned int irq, который определяет номер прерывания. Для некоторых устройств, например унаследованных (legacy) PC устройств таких, как таймер или клавиатура, это значение обычно жестко определено. Для большинства других устройств эта величина подбирается или назначается динамически и программно. Функция вызывается всякий раз, когда возникает прерывание с соответствующим значением irq. Третий параметр – irqflags – может быть или нулем, или битовой маской одного или нескольких следующих флагов, которые используются только ядром как часть IRQ-обработчиков:
#define IRQF_SHARED 0x00000080 /* разрешить разделение линии IRQ несколькими устройствами */
#define __IRQF_TIMER 0x00000200 /* прерывание маркируется как прерывание по таймеру.*/
#define IRQF_PROBE_SHARED 0x00000100 /* устанавливается вызывающим, когда он предполагает возможные проблемы с совместным использованием. */
devname – имя устройства, связанного с прерыванием. Например, для клавиатуры это - "keyboard". Это имя используется в /proc/irq и /proc/interrupt.
dev_id – используется для разделения линий прерывания. Когда обработчик прерывания освобождается, dev_id предоставляет уникальный файл, позволяющий удалить с линии irq соответствующий обработчик прерывания. Без данного файла не будет известно, какой обработчик удалять с линии прерывания. Можно установить значение NULL, если линия прерывания не разделена.
Важно отметить, что функция request_irq() может блокироваться и поэтому не может быть вызвана из контекста прерывания.
Когда драйвер выгружается, необходимо отменить регистрацию соответствующего обработчика прерывания. Для этого существует функция:
void free_irq(unsigned int irq, void *dev_id);
Если указанная линия прерывания не является разделяемой, то функция free_irq() удаляет обработчик и отключает линию. Если линия прерывания разделяется, то обработчик, определённый dev_id удаляется, но линия прерывания будет отключена только при удалении последнего обработчика.
Тасклеты
Tasklet - частный случай реализации softirq, но в отличие от softirq, которые пишутся таким образом, чтобы одновременно в системе могло выполняться некоторое количество одних и тех же softirq (в полном объеме реализовано взаимоисключение), на tasklet накладывается следующее ограничение: обработчик тасклета в каждый конкретный момент времени может выполняться только на одном процессоре, т.е. один и тот же тасклет не может выполняться параллельно, в отличие от softirq. Поэтому в системах, в которых требуется быстрая реакция, предпочтение отдается softirq. В отличие от softirq, которые регистрируются при компиляции системы и их количество определено в системе, асклеты могут быть зарегистрированы как статически, так и динамически.
Обработчик АП инцииализирует отложенное действие, чтобы его выполнить, необходимо запланировать тасклет. Ограничение тасклета: тасклет не может блокироваться, т.е. в тасклетах нельзя использовать блокирующие примитивы. Если в тасклете используются общие с обработчиком прерывания или каким-то другим тасклетом данные, то необходимо использовать спинлоки.
#ifndef DECLARE_TASKLET_OLD
#define DECLARE_TASKLET_OLD(arg1, arg2) DECLARE_TASKLET(arg1, arg2, 0L)
#endif
void my_tasklet_handler(unsigned long data);
static irqreturn_t my_interrupt_handler(int irq, void *dev_id);
DECLARE_TASKLET_OLD(my_tasklet, my_tasklet_handler);
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
printk(KERN_INFO "== Called my_interrupt_handler\n");
if (irq == 1)
{
tasklet_schedule(&my_tasklet);
printk_tasklet_info("In interrupt handler");
printk(KERN_INFO "== Tasklet scheduled\n");
return IRQ_HANDLED;
}
else
return IRQ_NONE;
}
...
static int __init module_init(void)
{
request_irq(IRQ_NUM, my_interrupt_handler, IRQF_SHARED,
"my_interrupt_tasklet", &my_dev_id)
....
}
static void __exit module_exit(void)
{
tasklet_kill(&my_tasklet);
free_irq(IRQ_NUM, &my_dev_id);
}