Режим опроса
В соответствии с этой методикой, процессор периодически проверяет (опрашивает) регистр состояния устройства, пока значение регистра не просигнализирует об окончании операции ввода/вывода. Мы уже сталкивались с подходом, основанным на опросе, в главе 5: когда процессор пытается получить занятый спин-блокировку, он периодически опрашивает переменную, пока ее значение не станет нулевым. Однако опрос, выполняемый в отношении операций ввода/вывода, обычно очень трудоемкий, потому что драйверу нужно еще не забывать проверять, не истекло ли время ожидания. Простой пример опроса выглядит так:
for (;;) {if (read_status(device) & DEVICE_END_OPERATION) break; if (—count == 0) break;}
Переменная count, проинициализированная до входа в цикл, уменьшается на каждом шаге и может быть использована для реализации грубого механизма проверки времени ожидания. Более точный механизм можно реализовать, читая значения счетчика тиков jiffies на каждом шаге цикла и сравнивая его со старым значением, прочитанным до входа в цикл ожидания.
Если время, необходимое для завершения операции ввода/вывода, относительно велико, скажем, порядка нескольких миллисекунд, эта схема становится не эффективной, потому что процессор непроизводительно тратит драгоценные машинные циклы, ожидая окончания операции. В таких случаях предпочтительно волевым решением освобождать процессор после каждой Операции опроса, вставив Внутрь ЦИКЛа ВЫЗОВ фуНКЦИИ schedule .
Режим прерывания
Режим прерывания можно использовать, только если контроллер ввода/вывода способен просигнализировать по линии IRQ об окончании операции.
Мы продемонстрируем работу режима прерывания в простейшем случае. Предположим, мы хотим реализовать драйвер для простого символьного устройства ввода. Когда пользователь делает системный вызов read для соответствующего файла устройства, команда на ввод отправляется управляющему регистру устройства. По истечении некоторого времени устройство помещает один байт данных в свой входной регистр. Затем драйвер устройства возвращает этот байт в качестве результата системного вызова read .
Это типичный случай, в котором предпочтительно реализовать драйвер с использованием режима прерываний. В сущности, такой драйвер состоит из двух функций:
- f oo read — реализует метод read файлового объекта;
- foo interrupt — обрабатывает прерывание.
Функция f oo read вызывается, когда пользователь читает файл устройства:
ssize_t foo_read(struct file filp, char buf, size_t count,
loff_t ppos){
foo_dev_t foo_dev = filp->private_data; if (down_interruptible(&foo_dev->sem) return -ERESTARTSYS; foo_dev->intr = 0;
outb(DEV_FOO_READ, DEV_FOO_CONTROL_PORT);
wait_event_interruptible(foo_dev->wait, (foo_dev->intr ==1)); if (put_user(foo_dev->data, buf)) return -EFAULT;
UP(&foo_dev->sem); return 1;}
Драйвер устройства использует специализированный дескриптор типа foo dev t, который включает в себя семафор sem, защищающий аппаратное устройство от попыток одновременного обращения, очередь ожидания wait, флаг int г, устанавливаемый, когда устройство выдает прерывание, и однобайтовый буфер data, в который пишет обработчик прерываний и из которого читает метод read. Вообще, все драйверы ввода/вывода, применяющие прерывания, пользуются структурами данных, к которым обращаются как обработчик прерываний, так и методы read и write. Адрес дескриптора foo dev t обычно хранится в поле private data файлового объекта, принадлежащего файлу устройства, или в глобальной переменной.
Функция f oo read выполняет следующие операции:
- Получает семафор foo_dev->sem, чтобы никакой другой процесс не обратился к устройству.
- Сбрасывает флаг int г.
- Выдает устройству ввода/вывода команду на чтение.
- Выполняет макрос wait_event_interruptible, Чтобы приостановить про- цесс, пока флаг int г не станет равным 1. По прошествии некоторого времени наше устройство выдает прерывание, сигнализируя, что операция ввода/вывода завершена, и данные находятся в соответствующем порте dev foo data port. Обработчик прерываний устанавливает флаг int г, чтобы возобновить выполнение процесса. Когда планировщик решает продолжить выполнение процесса, выполняется вторая часть функции f oo read :
1. Копирует символ, находящийся в переменной foo_dev->data, в адресное пространство пользователя.
2. Освобождает семафор foo_dev->sem и завершается.
Ради простоты мы не проверяем, истекло ли время ожидания. Вообще говоря, проверка времени ожидания реализуется с помощью статических или динамических таймеров. Таймер должен быть установлен в соответствующее значение непосредственно перед началом операции ввода/вывода и удален, когда операция завершится.
Рассмотрим КОД функции foo_interrupt :
irqreturn_t foo_interrupt(int irq, void dev_id, struct pt_regs )
{
foo->data = inb(DEVLFOO_DATA_PORT); foo->intr = 1;
wake_up_interruptible(foo_dev->wait); return 1;}
Обработчик прерываний считывает символ с входного регистра нашего устройства и записывает его в поле data дескриптора foo dev t драйвера устройства, на которое указывает глобальная переменная foo. Затем он устанавливает флаг int г И вызывает функцию wake_up_interruptible, чтобы возобно- вить выполнение процесса, заблокированного в очереди foo->wait.
Обратите внимание, что ни один из трех параметров в обработчике прерывания не используется. Это довольно распространенная ситуация.
Предыдущая страница | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | Следующая страница