Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Организация пула и прерывания
Когда драйвер устройства посылает команды в управляемое устройство, существует два способа определения успешности выполнения команды: можно организовать пул устройства или использовать прерывания устройства. При организации пула устройства драйвер устройства периодически проверяет устройства для проверки того, что команда успешно доставлена. Из-за того что драйвер устройства является частью ядра, при прямой организации пула присутствует риск заставить ядро ожидать завершения операции пула. Пул драйвера устройства может быть организован через таймер. Когда драйвер устройства хочет заполнить пул устройства, он помещает в планировщик вызов функции из драйвера устройства. Эта функция выполняет проверку устройства без остановки ядра. Перед тем как углубиться в подробности работы прерываний ядра, мы должны объяснить основные методы блокировки доступа к критическим секциями в коде ядра - циклическим блокировкам. Циклические блокировки работают с помощью установки специального флага для некоторых значений перед вхождением в код критической секции и сбрасывания этого значения после оставления кода критической секции. Циклическая блокировка должна применяться, когда контекст задачи не блокируется, что и происходит в коде ядра. Давайте рассмотрим код циклической блокировки для архитектур х86 и РРС. include/asm-i386/spinlock.h 32 #define SPIN_LOCK_UNLOCKED (spinlock_t) { 1 SPINLOCK_MAGIC_INIT } 34 #define spin_lock_init (x) do { * (x) = SPIN_LOCK_UNLOCKED; } while(O) 43 #define spin_is_locked(x) (*(volatile signed char *) (& (x)-> lock) < = 0) 44 #define spin_unlock_wait(x) do { barrier(); } while(spin_is_locked(x)) include/asm-ppc/spinlock.h 25 idefine SPIN_LOCK_UNLOCKED (spinlock_t) { 0 SPINLOCK_DEBUG_INIT } 27 #define spin_lock_init (x) do { * (x) = SPIN_LOCK_UNLOCKED; } while(O) 28 #define spin_is_locked(x) ((x)-> lock! = 0) while(spin_is_locked(x)) 29 tdefine spin_unlock_wait(x) do { barrier(); } while(spin_is_locked(x)) На архитектуре х86 флаг циклической блокировки устанавливается в 1 если она снята, а на РРС в 0. Отсюда видно, что при написании драйверов вам нужно использовать Написание кода предоставляемый макрос вместо сырых значений для кроссплатформенной совместимости. Задачи, которые хотят получить блокировку, в пустом цикле последовательно проверяют значение специального флага до тех пор, пока оно не станет меньше 0, т. е. выполняется циклическое ожидание в задаче. См. sipn__unlock_wait () в двух блоках кода. Циклические блокировки для драйверов обычно используются при обработке прерываний, когда коду ядра необходимо выполнить критическую секцию без прерывания другими прерываниями. В предыдущих версиях ядра Linux использовались функции cli () и sti () для отключения и включения прерываний. Как и в 2.5.28, cli () и sti() поэтапно заменены циклическими блокировками. Новый способ выполнения разделов кода ядра, которые не могут быть прерваны - следующий: Documentation/cli-sti-removal.txt
spinlock_t driver_lock = SPIN_LOCK_UNLOCKED; struct driver_data; irq_handler (...) { unsigned long flags; spin_lock_irqsave(& driver_lock, flags); driver_data.finish = 1; driver_data.new_work = 0; spin__unlock_irqrestore (& driver__lock, flags) ioctl_func (...) { spin_lock_irq(& driver_lock); driver_data.finish = 0; driver_data.new_work = 2; spin__unlock_irq(& driver_lock); Глава 10 • Добавление вашего кода в ядро Строка 8 Перед началом кода критической секции прерывание сохраняется во флаг и выполняется блокировка driver_lock. Строки 9-12 Этот код критической секции может выполняться только одной задачей одновременно. Строка 27 Эта строка заканчивает код критической секции. Восстанавливается состояние прерываний и разблокируется driver_lock. С помощью spin_lock_irq_save () [и spin_lock_irq_reatore () ] мы проверяем, что прерывание отключено перед запуском обработчика прерывания и осталось отключенным после его завершения. Когда ioctl__f unc () блокирует driver_lock, могут запускаться другие вызовы ircj__handler (). Поэтому нам нужно убедиться, что критическая секция в ioctl_ f unc () завершается как можно быстрее, для того чтобы гарантировать минимальное время ожидания irq_handler (), обработка прерывания верхнего уровня. Давайте рассмотрим последовательность создания обработчика прерывания и его высокоуровневого обработчика (см. нижнюю половину, использующую очередь выполнения в подразд. 10.2.5) #define mod_num_tries 3 static int irq = 0; int count = 0; unsigned int irqs = 0; while ((count < mod_num_tries) & & (irq < = 0) ) { irqs = probe_irq_on(); /* Заставляет устройство запустить прерывание. Некоторая задержка, требуемая на подтверждение получения прерывания */ irq = probe_irq_off(irqs); /* If irq < 0 получение множественных прерываний. If irq == 0 нет полученных прерываний. */ count++; } if ((count == mod_num__tries) & & (irq < =0) ) { printk(*Couldn't determine interrupt for %s\n" / MODULE_NAME); } Написание кода Этот код будет частью раздела инициализации драйвера устройства и завершится неудачно, если прерывания не будут обнаружены. Теперь, когда у нас есть прерывание, мы можем зарегистрировать это прерывание и наш высокоуровневый обработчик прерывания в ядре: retval = request_irq(irq, irq_handler, SA__INTERRUPT, DEVICE_NAME, NULL); if (retval < 0) { printk(*Request of IRQ %n failed for %s\n", irq, MODULE_NAME); return retval; } request_irq () имеет следующий прототип: arch/ i38б/kernel/irq.с 590 /** 591 * request__irq - выделение строки прерывания 592 * @irq: строка прерывания для выделения 593 * ©handler: Функция, вызываемая при наступлении IRQ 594 * @irqflags: тип флага прерывания 595 * ©devname: ascii имя для обрабатываемого устройства 596 * @dev_id: cookie передаваемое в функцию-обработчик 622 int request__irq(unsigned int irq, 623 irqreturn_t (*handler) (int, void *, struct pt_regs *), 624 unsigned long irqflags, 625 const char * devname, 626 void *dev_id) Параметр irqflags может иметь значение ord следующего макроса: • SAJSHIRQ для разделяемого прерывания; • SA__INTERRUPT для отключения локальных прерываний во время выполнения handler; • SA_SAMPLE_RANDOM если прерывание является источником энтропии. dev_id должно равняться NULL, если прерывание неразделяемое, и, если оно все-таки разделяемое, обычно адресует структуру данных устройства, так как это значение получает handler. Глава 10 • Добавление вашего кода в ядро В этом месте полезно вспомнить, что любое запрашиваемое прерывание нужно освободить при выходе из модуля с помощью f ree_irq (): arch/ i386/kernel/irq.c 669 /** 67 0 * free_irq - освобождение прерывания 671 * @irq: линия прерывания для освобождения 672 * @dev_id: идентификатор устройства для освобождения 682 */ 684 void free_irq(unsigned int irq, void *dev_id) Если dev_id разделяет irq, модуль должен быть уверен, что прерывания выключены перед вызовом этой функции. Кроме того, f ree_irq () никогда нельзя вызывать из контекста прерывания. Вызов f ree_irq () в модуле - стандартный способ очистки. [См. spin_lock_irq () и spin__unlock_irq ().] В данной точке мы зарегистрировали наш обработчик прерывания и связали его с irq. Теперь нам нужно написать настоящий обработчик верхнего уровня, определяемый как irq_handler (). void irq_handler(int irq, void *dev_id, struct pt_regs *regs) { /* См. выше код циклической блокировки */ /* Копирование данных прерывания в очередь выполнения для обработчика нижней половины */ schedule_work( WORK_QUEUE ); /* Освобождение spin_lock */ } Если вам нужен быстрый обработчик прерывания, вы можете использовать тасклет вместо очереди выполнения: } 10.2 Написание кода 10.2.5 Очереди выполнения и тасклеты Основную работу в обработчике прерываний обычно выполняет очередь выполнения. В предыдущем подразделе мы рассмотрели, как обработчики прерываний верхнего уровня копируют нужные им данные из прерывания в структуры данных и затем вызывают schedule_work (). Для запуска задачи из очереди выполнения она должна быть помещена в work__struct. Для объявления структуры выполнения во время компиляции используется макрос DECLARE_WORK (). Например, следующий код помещает в наш модуль инициализацию структуры выполнения и связывает ее с функцией и данными. struct bh_data_struct { int data_one; int *data_array; char *data_text; } static bh__data_struct bh_data; static DECLARE_WORK(my_mod_work, my_mod_bh, & bh_data); static void my_mod_bh(void *data) { struct bh_data_struct *bh_data = data; /* весь замечательный код нижней половины */ } Обработчик верхней половины настраивает все требуемые данные через my_mod_bh в bh_data и затем вызывает schedule_work(my_niod_work). schedule_work () - это функция, доступная для любого модуля; тем не менее это значит, что работающий планировщик помещает «события» в общую очередь выполнения. Некоторые модули могут желать обладать собственными очередями выполнения, но требуемые для этого функции экспортируются только для GPL-совместимых модулей. Поэтому, если вы хотите оставить ваш модуль проприетарным, вы должны использовать общую очередь выполнения. Очередь выполнения создается с помощью макроса create__workqueue (), ко kernel/workqueue.с 3 04 struct workqueue_struct *_____ create_workqueue(const char *name, Глава 10 • Добавление вашего кода в ядро name может иметь длину до 10 символов. Если single thread равно 0, ядро создает поток workqueue для процессора; если single thread равен 1, ядро создает единственный поток single thread для всей системы. Структуры выполнения создаются тем же способом, который был описан ранее, но они помещаются в отдельную очередь выполнения с помощью queue_work () вместо schedule_work (). kernel/workqueue.с 97 int fastcall queue_work(struct workqueue_struct *wq, struct work_struct *work) 98 { wq - это специальная очередь выполнения, созданная с помощью create_workqueue (). work - это структура выполнения, помещаемая в wq. Другие функции очереди выполнения можно найти в kernel /workqueue. с, включая следующее: • queue_work_delayed(). Проверяет, что функция структуры выполнения вызывается по истечении указанного количества мгновений. • flush_workqueue(). Заставляет вызывающего подождать до тех пор, пока завершится работа планировщика с очередью. Обычно используется при выходе из драйвера устройства. • destroy_workqueue(). Обрабатывает и затем освобождает очередь выполнения. Похожие функции schedule_work_delayed() и flush_sheduled_work() существуют и для общей очереди выполнения. Популярное:
|
Последнее изменение этой страницы: 2016-03-25; Просмотров: 666; Нарушение авторского права страницы