Эта книга распространяется в надежде на то, что она будет вам полезна, но без каких-либо гарантий, в том числе и без подразумеваемых гарантий высокого спроса или пригодности для специфических целей


Глава 11. Обработка прерываний 11.1. Обработка прерываний



страница18/18
Дата21.11.2016
Размер1.25 Mb.
Просмотров4090
Скачиваний0
1   ...   10   11   12   13   14   15   16   17   18

Глава 11. Обработка прерываний

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


Везде, кроме предыдущей главы, все наши действия в ядре сводилось к ответам на разные запросы от процессов, к работе со специальными файлом, посылке команд ioctl или запуску системных вызовов. Однако работа ядра не может сводится только к обработке запросов. Еще одна немаловажная задача -- это работа с аппаратурой компьютера.

Существует два типа взаимодействий между CPU и остальной аппаратной частью компьютера. Первый -- передача команд аппаратным средствам, второй -- прием ответов от аппаратуры. Второй тип взаимодействия -- прерывания, является наиболее тяжелым в обработке, потому что прерывания возникают тогда, когда это удобно устройству, а не CPU. Аппаратные устройства обычно имеют весьма ограниченный объем ОЗУ, и если не считать поставляемую ими информацию немедленно, то она может потеряться.

В Linux аппаратные прерывания называются IRQ (сокращенно от Interrupt ReQuests -- Запросы на Прерывание). [14] Имеется два типа IRQ: "короткие" и "длинные". "Короткие" IRQ занимают очень короткий период времени, в течение которого работа операционной системы будет заблокирована, а так же будет невозможна обработка других прерываний. "Длинные" IRQ могут занять довольно продолжительное время, в течение которого могут обрабатываться и другие прерывания (но не прерывания из того же самого устройства). Поэтому, иногда бывает благоразумным разбить выполнение работы на исполняемую внутри обработчика прерываний (т.е. подтверждение прерывания, изменение состояния и пр.) и работу, которая может быть отложена на некоторое время (например постобработка данных, активизация процессов, ожидающих эти данные и т.п.). Если это возможно, лучше объявлять обработчики прерывания "длинными".

Когда CPU получает прерывание, он останавливает любые процессы (если это не более приоритетное прерывание, тогда обработка пришедшего прерывания произойдет только тогда, когда более приоритетное будет завершено), сохраняет некоторые параметры в стеке и вызывает обработчик прерывания. Это означает, что не все действия допустимы внутри обработчика прерывания, потому что система находится в неизвестном состоянии. Решение проблемы: обработчик прерывания определяет -- что должно быть сделано немедленно (обычно что-то прочитать из устройства или что-то послать ему), а затем запланировать обработку поступившей информации на более позднее время (это называется "bottom halves" -- "нижние половины") и вернуть управление. Ядро гарантирует вызов "нижней половины" так быстро, насколько это возможно. Когда это произойдет, то наш обработчик -- "нижняя половина", уже не будет стеснен какими-то рамками и ему будет доступно все то, что доступно обычным модулям ядра.

Устанавливается обработчик прерывания вызовом request_irq. Ей передаются номер IRQ, имя функции-обработчика, флаги, имя для /proc/interrupts и дополнительный параметр для обработчика прерываний. Флаги могут включать SA_SHIRQ, чтобы указать, что прерывание может обслуживаться несколькими обработчиками (обычно, по той простой причине, что на одном IRQ может "сидеть" несколько устройств) и SA_INTERRUPT, чтобы указать, что это "короткое" прерывание. Эта функция установит обработчик только в том случае, если на заданном IRQ еще нет обработчика прерывания, или если существующий обработчик зарегистрировал совместную обработку прерывания флагом SA_SHIRQ.

Во время обработки прерывания, из функции-обработчика прерывания, мы можем получить данные от устройства и затем, с помощью queue_task_irq,tq_immediate и mark_bh(BH_IMMEDIATE), запланировать "нижнюю половину". В ранних версиях Linux имелся массив только из 32 "нижних половин", теперь же, одна из них (а именно BH_IMMEDIATE) используется для обслуживания целого списка "нижних половин" драйверов. Вызов mark_bh(BH_IMMEDIATE) как раз и вставляет "нижнюю половину" драйвера в этот список, планируя таким образом ее исполнение.


11.2. Клавиатура на архитектуре Intel


Материал, рассматриваемый в оставшейся части этой главы, может быть применим исключительно к архитектуре Intel. На других платформах код примера работать не будет.

Было очень трудно выбрать тип драйвера, который можно было бы использовать в качестве примера в этой главе. С одной стороны пример должен быть достаточно полезным, он должен работать на любом компьютере и быть достаточно выразительным. С другой стороны, в ядро уже включено огромное количество драйверов практически для всех общеизвестных и широкораспространенных устройств. Эти драйверы не смогли бы совместно работать с тем, что я собирался написать. Наконец я принял решение представить в качестве примера -- обработчик прерываний от клавиатуры, но для демонстрации работоспособности кода сначала придется отключить стандартный обработчик прерываний от клавиатуры, а так как этот символ объявлен как static (в файле drivers/char/keyboard.c), то нет никакого способа восстановить обработчик. Поэтому, прежде чем вы дадите команду insmod, перейдите в другую консоль и дайте команду sleep 120; reboot, если ваша файловая система представляет для вас хоть какую-нибудь ценность.

Этот пример захватывает обработку IRQ 1 -- прерывание от клавиатуры на архитектуре Intel. При получении прерывания обработчик читает состояние клавиатуры (inb(0x64)) и скан-код нажатой клавиши. Затем, как только ядро сочтет возможным, оно вызывает got_char (она играет роль "нижней половины"), которая выводит, через printk, код клавиши (младшие семь бит скан-кода) и признак "нажата/отпущена" (8-й бит скан-кода -- 0 или 1 соответственно).

Пример 11-1. intrpt.c

/*

* intrpt.c - Обработчик прерываний.



*

* Copyright (C) 2001 by Peter Jay Salzman

*/

/*


* Standard in kernel modules

*/

#include /* Все-таки мы работаем с ядром! */



#include /* Необходимо для любого модуля */

#include /* очереди задач */

#include /* Взаимодействие с планировщиком */

#include /* определение irqreturn_t */

#include
#define MY_WORK_QUEUE_NAME "WQsched.c"
static struct workqueue_struct *my_workqueue;
/*

* Эта функция вызывается ядром, поэтому в ней будут безопасны все действия

* которые допустимы в модулях ядра.

*/

static void got_char(void *scancode)



{

printk("Scan Code %x %s.\n",

(int)*((char *)scancode) & 0x7F,

*((char *)scancode) & 0x80 ? "Released" : "Pressed");

}
/*

* Обработчик прерываний от клавиатуры. Он считывает информацию с клавиатуры

* и передает ее менее критичной по времени исполнения части,

* которая будет запущена сразу же, как только ядро сочтет это возможным.

*/

irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)



{

/*


* Эти переменные объявлены статическими, чтобы имелась возможность

* доступа к ним (посредством указателей) из "нижней половины".

*/

static int initialised = 0;



static unsigned char scancode;

static struct work_struct task;

unsigned char status;
/*

* Прочитать состояние клавиатуры

*/

status = inb(0x64);



scancode = inb(0x60);
if (initialised == 0) {

INIT_WORK(&task, got_char, &scancode);

initialised = 1;

} else {


PREPARE_WORK(&task, got_char, &scancode);

}
queue_work(my_workqueue, &task);


return IRQ_HANDLED;

}
/*

* Инициализация модуля - регистрация обработчика прерывания

*/

int init_module()



{

my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);


/*

* Поскольку стандартный обработчик прерываний от клавиатуры не может

* сосуществовать с таким как наш, то придется запретить его

* (освободить IRQ) прежде, чем что либо сделать.

* Но поскольку мы не знаем где он находится в ядре, то мы лишены

* возможности переустановить его - поэтому компьютер придется перезагрузить

* после опробования этого примера.

*/

free_irq(1, NULL);


/*

* Подставить свой обработчик (irq_handler) на IRQ 1.

* SA_SHIRQ означает, что мы допускаем возможность совместного

* обслуживания этого IRQ другими обработчиками.

*/

return request_irq(1, /* Номер IRQ */



irq_handler, /* наш обработчик */

SA_SHIRQ,

"test_keyboard_irq_handler",

(void *)(irq_handler));

}
/*

* Завершение работы

*/

void cleanup_module()



{

/*


* Эта функция добавлена лишь для полноты изложения.

* Она вообще бессмысленна, поскольку я не вижу способа

* восстановить стандартный обработчик прерываний от клавиатуры

* поэтому необходимо выполнить перезагрузку системы.

*/

free_irq(1, NULL);



}
/*

* некоторые функции, относящиеся к work_queue

* доступны только если модуль лицензирован под GPL

*/

MODULE_LICENSE("GPL");




Глава 12. Симметричная многопроцессорность


Один из самых простых и самых дешевых способов увеличения производительности -- это разместить на материнской плате несколько микропроцессоров. Каждый из процессоров может играть свою собственную роль (асимметричная многопроцессорная обработка -- ASMP) или же они все работают параллельно, организуя вычисления таким образом, при котором и операционная система, и приложения могут использовать любой доступный процессор (симметричная многопроцессорная обработка -- SMP). Примером асимметричной многопроцессорной ОС может служить NetWare SFT III, использующая зеркальное отображение серверов, в двухпроцессорном сервере первый процессор занимается предоставлением услуг, а второй - операциями ввода/вывода. Асимметричная многопроцессорноя обработка более эффективна, но она требует точного распределения ролей между процессорами, что практически невозможно на универсальных ОС, подобных Linux. С другой стороны, симметричная многопроцессорная обработка менее эффективна, но относительно проста в реализации (здесь, под словами "относительно проста", вовсе не подразумевается, что это действительно просто).

В симметричной многопроцессорной среде микропроцессоры совместно используют одну и ту же память, в результате код, работающий на одном CPU может изменять содержимое памяти, используемой другим CPU. Здесь уже нет уверенности, что переменная, которой вы присвоили некоторое значение, в предыдущей строке программы, все еще имеет то же самое значение -- код, исполняемый на другом CPU может его поменять. Очевидно, что я привел невероятный пример.

На самом деле, в случае обычных процессов, такой проблемы не существует. Как правило, в каждый конкретный момент времени, процесс может исполняться только на одном CPU. [15] Но ядро может выполнять одновременно разные процессы на разных CPU.

В ядрах версии 2.0.x это не было большой проблемой, поскольку ядро работало под защитой одной большой спин-блокировки (spinlock). Это означает, что когда один процессор работает в привилегированном режиме, а другой собирается войти в этот режим (например в случае системного вызова), то он вынужден ждать, пока первый процессор не выйдет в пользовательский режим. Это делает SMP в Linux безопасной, но малоэффективной.

Начиная с ядра версии 2.2.x стал возможным одновременный выход нескольких процессоров в привилегированный режим. Это обстоятельство вы должны знать и помнить.

Глава 13. Заключение


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

Стандартные библиотеки. Вы не должны использовать функции из стандартных библиотек языка C. Используйте только те функции, которые предоставляются ядром (большинство из них вы найдете в /proc/kallsyms).

Запрет прерываний. Если вы запретите прерывания на короткий срок, то ничего страшного не произойдет. Но если вы забудете их разрешить, то система "зависнет" и перезагрузить ее можно будет только кнопкой выключения питания.

Не суйте голову в пасть тигру! Вероятно это предупреждение излишне, но тем не менее...


Примечания


[1]

В ранних версиях ядра он назывался kerneld.

[2]

Если вы предполагаете проводить эксперименты с ядром, то чтобы избежать перезаписи существующих файлов модулей вы можете изменить значение переменнойEXTRAVERSION в Makefile ядра. В результате все модули ядра будут записываться в отдельный каталог.

[3]

Я не компьютерный гений, я простой физик!

[4]

Это - далеко не то же самое, что и "встраивание всех модулей в ядро", хотя идея та же.

[5]

Делается это в соответствии с принятыми соглашениями. Однако, при разработке драйвера устройства, на период отладки, размещать файл устройства в своем домашнем каталоге -- наверное не такая уж и плохая идея. Единственное -- не забудьте исправить место для размещения файла устройства после того, как отладка будет закончена.

[6]

В версиях ядра 2.0 и 2.2 это делалось автоматически, если в качестве номера inode передавалось нулевое значение.

[7]

Различие здесь состоит в том, что файловые операции работают с файлом непосредственно, а функции, работающие с inode, предоставляют способ сослаться на файл, например создание ссылок на файл.

[8]

Обратите внимание, здесь смысл терминов "чтение" и "запись" также имеют обратный смысл. Так операция чтения передает данные от процесса ядру, а операция записи -- в обратном направлении, от ядра к процессу.

[9]

Не совсем верно. Вы не сможете передать функции ioctl, например структуру, но передать указатель на структуру -- безусловно возможно.

[10]

Самый простой способ оставить файл открытым -- это дать команду tail -f

[11]

Это означает, что процесс продолжает свою работу в привилегированном режиме -- поскольку процесс был приостановлен во время работы системного вызова, который еще не закончил свою работу. Процесс даже не "подозревает" о том, что кто-то еще, кроме него, использовал процессор в промежутке между обращением к системному вызову и возвратом из него.

[12]

Именно по этой причине использовался вызов функции wait_event_interruptible. Можно было бы использовать wait_event, но тогда задачу невозможно будет прервать по Ctrl-C во время ожидания.

[13]

Tty (от англ. TeleTYpe - телетайп) -- первоначально обозначал комбинацию клавиатура-печатающее устройство предназначенное для взаимодействия с Unix-системой, на сегодняшний день -- это абстракция текстового потока, используемого программами Unix, независимо от того, является ли он физическим терминалом, окном xterm на дисплее, сетевым подключением или чем то иным.

[14]

Это стандартное понятие для архитектуры Intel, на которой начинал разрабатываться Linux.

[15]

За исключением многопоточных процессов. В этом случае разные потоки одного процесса могут исполняться одновременно на разных процессорах.

Каталог: files
files -> Основная часть 1 История создания школы
files -> Методические рекомендации по проведению Дня Знаний, посвященного Году кино в РФ
files -> Подросток и компьютерные игры
files -> Программа духовно-нравственного развития и воспитания обучающихся на уровне среднего общего образования
files -> Правила закаливания… Выпуск №1. Чтоб улыбка сияла. Мама первый стоматолог
files -> О существовании значения игры преследования
files -> Учебное пособие по нейрохирургии. Часть I. Краткая история нейрохирургии. Черепно-мозговая травма санкт-Петербург 2015


Поделитесь с Вашими друзьями:
1   ...   10   11   12   13   14   15   16   17   18


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал