Завершения

Предыдущая  Содержание  Следующая V*D*V

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

 

struct semaphore sem;

 

init_MUTEX_LOCKED(&sem);

start_external_task(&sem);

down(&sem);

 

Внешняя задача может затем по завершении своей работы вызвать up(&sem).

 

Как оказалось, семафоры не лучший инструмент для использования в этой ситуации. При нормальном использовании код, пытающийся блокировать семафор, находит, что семафор доступен почти всё время; если есть значительные соперничество за семафор, страдает производительность и схема блокировки должна быть пересмотрена. Так что семафоры были сильно оптимизированы для "подходящих" случаев. Однако, при использовании для общения при завершении задачи показанным выше способом поток, вызывающий down, почти всегда вынужден ждать; соответственно, пострадает производительность. При использовании этого способа семафоры могут также стать предметом (неприятного) состояния гонок, если они объявлены как автоматические переменные. В некоторых случаях семафор мог бы исчезнуть до того, как процесс, вызывающий up, закончит с ним работу.

 

Эти опасения побудили добавить в ядре версии 2.4.7 интерфейс "завершения" (completions). Ожидание завершения является легковесным механизмом с одной задачей: позволить одному потока рассказать другому, что работа выполнена. Чтобы воспользоваться механизмом завершения, ваш код должен подключить <linux/completion.h>. "Завершение" может быть создано с помощью:

 

DECLARE_COMPLETION(my_completion);

 

Или, если "завершение" должно быть создано и проинициализировано динамически:

 

struct completion my_completion;

/* ... */

init_completion(&my_completion);

 

Ожидание "завершения" является простым делом вызова:

 

void wait_for_completion(struct completion *c);

 

Обратите внимание, что эта функция выполняет непрерываемое ожидание. Если ваш код вызовет wait_for_completion и никто никогда не завершит задачу, результатом будет неубиваемый процесс. (* На момент написания, патчи, добавляющие прерываемые версии, были в обращении, но не были объединены с главной веткой.)

 

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

 

void complete(struct completion *c);

void complete_all(struct completion *c);

 

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

 

"Завершение", как правило, одноразовое устройство; оно используется однажды, а затем отбрасывается. Однако, вполне возможно повторное использование структур "завершения", если об этом правильно позаботиться. Если complete_all не используется, структура "завершения" может быть использована снова без каких-либо проблем, пока нет никакой двусмысленности в том, о каком событии в настоящее время просигнализировано. Но если вы используете complete_all,  вы должны переинициализировать структуру "завершения" перед повторным использованием. Этот макрос:

 

INIT_COMPLETION(struct completion c);

 

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

 

В качестве примера того, как может быть использовано "завершение", рассмотрим модуль complete, который включён в исходник примера. Этот модуль определяет устройство с простой семантикой: любой процесс, пытающийся прочитать из устройства будет ждать (используя wait_for_completion), пока в устройство пишет какой-нибудь другой процесс. Код, который реализует это поведение:

 

DECLARE_COMPLETION(comp);

 

ssize_t complete_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)

{

    printk(KERN_DEBUG "process %i (%s) going to sleep\n",

    current->pid, current->comm);

    wait_for_completion(&comp);

    printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);

    return 0; /* EOF */

}

 

ssize_t complete_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)

{

    printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",

    current->pid, current->comm);

    complete(&comp);

    return count; /* успешно, избегаем повтора */

}

 

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

 

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

 

void complete_and_exit(struct completion *c, long retval);

Предыдущая  Содержание  Следующая