Главная страница
Навигация по странице:

  • Структуры данных для представления потоков

  • Структуры для представления действий

  • Извините, если не понятно

  • Создание отложенных действий

  • Обработчик отложенного действия

  • Планирование действий на выполнение

  • Ожидание завершения действий

  • Создание новых очередей отложенных действий

  • Второе издание


    Скачать 3.09 Mb.
    НазваниеВторое издание
    Дата08.09.2019
    Размер3.09 Mb.
    Формат файлаpdf
    Имя файлаLav_Robert_Razrabotka_yadra_Linux_Litmir.net_264560_original_254.pdf
    ТипДокументы
    #86226
    страница20 из 53
    1   ...   16   17   18   19   20   21   22   23   ...   53
    Очереди отложенных действий
    Очереди отложенных действий (work queue) — это еще один способ реализации отложенных операций, который отличается от рассмотренных ранее. Очереди дей- ствий позволяют откладывать некоторые операции для последующего выполнения потоком пространства ядра — отложенные действия всегда выполняются в контек- сте процесса. Поэтому код, выполнение которого отложено с помощью постановки в очередь отложенных действий, получает все преимущества, которыми обладает код, выполняющийся в контексте процесса. Наиболее важное свойство — это то, что выполнение очередей действий управляется планировщиком процессов и, соответ- ственно, выполняющийся код может переходить в состояние ожидания (sleep).
    Обычно принять решение о том, что необходимо использовать: очереди отло- женных действий или отложенные прерывания/тасклеты, достаточно просто. Если отложенным действиям необходимо переходить в состояние ожидания, то следует использовать очереди действий. Если же отложенные операции не могут переходить в состояние ожидания, то воспользуйтесь тасклетами или отложенными прерывани- ями. Обычно альтернатива использованию очередей отложенных действий — это создание новых потоков пространства ядра. Поскольку при введении новых потоков пространства ядра разработчики ядра обычно хмурят брови (а у некоторых народов это означает смертельную обиду), настоятельно рекомендуется использовать очере- ди отложенных действий. Их действительно очень просто использовать.
    Если для обработки нижних половин необходимо использовать нечто, что пла- нируется на выполнение планировщиком процессов, то воспользуйтесь очередями отложенных действий. Это единственный механизм обработки нижних половин, ко- торый всегда выполняется в контексте процесса, и, соответственно, единственный механизм, с помощью которого обработчики нижних половин могут переходить в состояние ожидания. Это означает, что они полезны в ситуациях, когда необходи- мо выделять много памяти, захватывать семафор или выполнять блочные операции ввода-вывода. Если для выполнения отложенных операций нет необходимости ис- пользовать поток ядра, то стоит подумать об использовании тасклетов.
    7
    Отсутствие строгой последовательности выполнения хорошо сказывается на производительности,
    но приводит к усложнению программирования. Конвертация обработчиков ВН в обработчики та- склетов, например, требует тщательного осмысления того, безопасно ли выполнять этот же код в то же самое время другим тасклетом? Однако после того как конвертация выполнена, это окупает- ся повышением производительности.
    Обработка нижних половин и отложенные действия 149

    Реализация очередей отложенных действий
    В своей наиболее общей форме подсистема очередей отложенных действий — это интерфейс для создания потоков пространства ядра, которые выполняют некоторые действия, где-то поставленные в очередь. Эти потоки ядра называются рабочими пото-
    ками (worker threads). Очереди действий позволяют драйверам создавать специальные рабочие потоки ядра для того, чтобы выполнять отложенные действия. Кроме того,
    подсистема очередей действий содержит рабочие потоки ядра, которые работают по умолчанию. Поэтому в своей общей форме очереди отложенных действий — это простой интерфейс пользователя для откладывания работы, которая будет выполне- на потоком ядра.
    Рабочие потоки, которые выполняются по умолчанию, называются events/n,
    где п—номер процессора. Для каждого процессора выполняется один такой поток.
    Например, в однопроцессорной системе выполняется один поток events/0. Б двух- процессорной системе добавляется еще один поток— events/1. Рабочие потоки,
    которые выполняются по умолчанию, обрабатывают отложенные действия, кото- рые приходят из разных мест. Многие драйверы, которые работают в режиме ядра,
    откладывают обработку своих нижних половин с помощью потоков, работающих по умолчанию. Если для драйвера или подсистемы нет строгой необходимости в соз- дании своего собственного потока ядра, то использование потоков, работающих по умолчанию, более предпочтительно.
    Тем не менее ничто не запрещает коду ядра создавать собственные потоки. Это может понадобиться, если в рабочем потоке выполняется большое количество вы- числительных операций. Операции, критичные к процессорным ресурсам или к высокой производительности, могут получить преимущества от использования от- дельного выделенного потока. Это также уменьшает нагрузку на потоки, работаю- щие по умолчанию, и предотвращает нехватку ресурсов для остальных отложенных действий.
    Структуры данных для представления потоков
    Рабочие потоки представлены с помощью следующей структуры workqueue_
    struct.
    /*
    * Внешне видимая абстракция для представления очередей отложенных действий представляет собой массив очередей для каждого процессора:
    */
    struct workqueue_struct {
    struct cpu_workqueue_struct cpu_wq [NR_CPUS] ;
    const char* name;
    struct list_head list;
    };
    Эта структура содержит массив структур s t r u c t cpu_workqueue_struct, пo одному экземпляру на каждый возможный процессор в системе. Так как рабочий по- ток существует для каждого процессора в системе, то для каждого рабочего потока,
    работающего на каждом процессоре машины, существует такая структура.
    150 Глава 7

    Структура cpu_workqueue_struct определена в файле kernel/workqueue.с и является основной. Эта структура показана ниже.
    /*
    * Очередь отложенных действий, связанная с процессором:
    */
    struct cpu_workqueue_struct {
    spinlock_t lock; /* Очередь для защиты данной структуры */
    long rernove_sequence; /* последний добавленный элемент
    (следующий для запуска ) */
    long insert sequence; /* следующий элемент для добавления */
    struct list_head worklist; /* список действий */
    wait_queue_head_t more_work;
    wait_queue_head_t work_done;
    struct workqueue_struct *wq; /* соответствующая структура workqueue_struct */
    task_t *thread; /* соответствующий поток */
    int run_depth; /* глубина рекурсии функции run_workqueue() */
    };
    Заметим, что каждый тип рабочих потоков имеет одну, связанную с этим типом структуру workqueue_struct. Внутри этой структуры имеется по одному экземпляру структуры c p u _ w o r k q u e u e _ s t r u c t для каждого рабочего потока и, следовательно,
    для каждого процессора в системе, так как существует только один рабочий поток каждого типа на каждом процессоре.
    Структуры для представления действий
    Все рабочие потоки реализованы как обычные потоки пространства ядра, кото- рые выполняют функцию w o r k e r _ t h r e a d (). После начальной инициализации эта функция входит в бесконечный цикл и переходит в состояние ожидания. Когда ка- кие-либо действия ставятся в очередь, поток возвращается к выполнению и выпол- няет эти действия. Когда в очереди не остается работы, которую нужно выполнять,
    поток снова возвращается в состояние ожидания. Каждое действие представлено с помощью структуры w o r k _ s t r u c t , определенной в файле .
    Эта структура показана ниже.
    struct work_struct {
    unsigned long pending; /* ожидает ли это действие на выполнение? */
    struct list_head entry; /* связанный список всех действий */
    void (*func)(void * ) ; /* функция-обработчик */
    void *data; /* аргумент функции-обработчика */
    void *wq_data; /* для внутреннего использования */
    struct timer_list timer; /* таймер, который используется для очередей отложенных действий с задержками */
    Обработка нижних половин и отложенные действия 151
    };

    Эти структуры объединены в связанный список, по одному списку на каждый тип очереди для каждого процессора. Например, для каждого процессора существу- ет список отложенных действий, которые выполняются потоками, работающими по умолчанию. Когда рабочий поток возвращается к выполнению, он начинает вы- полнять все действия, которые находятся в его списке. После завершения работы рабочий поток удаляет соответствующие структуры w o r k _ s t r u c t из списка. Когда список становится пустым, поток переходит в состояние ожидания.
    Давайте рассмотрим упрощенную основную часть функции worker_thread () .
    for (;;) {
    set_task_state(current, TASK_INTERRUPTIBLE);
    add_wait_queue(&cwq->more_work, &wait);
    if (list_empty(&cwq->worklist))
    schedule();
    else set_task_state(current, TASK_RUNNING);
    remove_wait_queue (&cwq->more_work, &wait);
    if (! list_empty (&cwq->worklist))
    run_workqueue(cwq);
    }
    Эта функция выполняет следующие действия в бесконечном цикле.
    • Поток переводит себя в состояние ожидания (флаг состояния устанавливает- ся в значение TASK_INTERRUPTIBLE), и текущий поток добавляется в очередь ожидания.
    • Если связанный список действий пуст, то поток вызывает функцию schedule ()
    и переходит в состояние ожидания.
    • Если список не пуст, то поток не переходит в состояние ожидания. Вместо это- го он устанавливает свое состояние в значение TASK_RUNNING и удаляет себя из очереди ожидания.
    • Если список не пустой, то вызывается функция run_workqueue () для выпол- нения отложенных действий.
    1
    Функция run_workqueue ()
    Функция run_workqueue () в свою очередь выполняет сами отложенные дей- ствия, как показано ниже.
    while (!list_empty(&cwq->worklist)) {
    struct work_struct *work;
    void (*f) (void * ) ;
    void *data;
    work = list_entry(cwq->worklist.next, struct work_struct, entry);
    f = work->func;
    data = work->data;
    list_del_init(cwq->worklist.next);
    clear_bit(0, &work->pending);
    f(data);
    }
    152 Глава 7

    Эта функция просматривает в цикле все элементы списка отложенных действий и выполняет для каждого элемента функцию, на которую указывает поле func соответ- ствующей структуры workqueue_struct. Последовательность действий следующая.
    • Если список не пустой, получить следующий элемент списка.
    • Получить указатель на функцию (поле func), которую необходимо вызвать, и аргумент этой функции (поле d a t a ) .
    • Удалить полученный элемент из списка и обнулить бит ожидания в структуре элемента.
    • Вызвать полученную функцию.
    • Повторить указанные действия.
    Извините, если не понятно
    Взаимоотношения между различными, рассмотренными в этом разделе структу- рами достаточно запутанные. На рис. 7.1 показана диаграмма, которая эти взаимоот- ношения поясняет.
    На самом верхнем уровне находятся рабочие потоки. Может существовать не- сколько типов рабочих потоков. Для каждого типа рабочих потоков существует один рабочий поток для каждого процессора. Различные части ядра при необходимости могут создавать рабочие потоки. По умолчанию выполняются только рабочие по- токи events (события). Каждый рабочий поток представлен с помощью структуры cpu_workqueue_struct. Структура w o r k q u e u e _ s t r u c t представляет все рабочие потоки одного типа.
    Рабочий поток
    Структура cpu_workqueue_struct
    Структура workqueue_struct
    По одному экземпляру на процессор
    Один экземпляр на каждый тип рабочих потоков
    Структура work_struct
    Один экземпляр на каждую функцию отложенного действия
    Рис. 7.1. Соотношения между отложенными действиями, очередями, дей-
    ствий и рабочими потоками
    Обработка нижних половин и отложенные действия
    153

    Например, давайте будем считать, что в дополнение к обычному типу рабочих потоков events был создан еще один тип рабочих потоков — falcon. Также имеется в распоряжении четырехпроцессорный компьютер. Следовательно, выполняется че- тыре потока типа events (соответственно, определено четыре экземпляра структуры c p u _ w o r k q u e u e _ s t r u c t ) и четыре потока типа falcon (для которых тоже опреде- лены другие четыре экземпляра структуры c p u _ w o r k q u e u e _ s t r u c t ) . Для потоков типа events определен один экземпляр структуры workqueue_struct, а для потоков типа falcon — другой экземпляр этой структуры.
    На самом нижнем уровне находятся отложенные действия. Драйвер создает от- ложенное действие, которой должно выполниться позже. Действия представлены структурами work_struct. Кроме других полей, эта структура содержит указатель на функцию, которая должна обработать отложенное действие. Отложенное действие отправляется на выполнение определенному потоку. Соответствующий поток перево- дится в состояние выполнения и выполняет отложенную работу.
    Большинство драйверов использует существующие по умолчанию рабочие пото- ки, которые называются events. Они просты в реализации и в использовании. Однако в некоторых, более серьезных ситуациях необходимо создавать новые специальные рабочие потоки. Например, драйвер файловой системы XFS создает два новых типа рабочих потоков.
    Использование очередей отложенных действий
    Использовать очереди действий просто. Сначала мы рассмотрим рабочие пото- ки, используемые по умолчанию, — events, а затем опишем создание новых типов ра- бочих потоков.
    Создание отложенных действий
    Первый этап — это создание самого действия, которое должно быть отложено.
    Для создания статической структуры на этапе компиляции необходимо использовать следующий макрос.
    DECLARE_WORK(name, void (*func) (void *), void *data);
    Это выражение создает структуру w o r k s _ t r u c t с именем name, с функцией- обработчиком func и аргументом функции-обработчика d a t a .
    Во время выполнения отложенное действие можно создать с помощью передачи указателя на структуру, используя следующий макрос.
    INIT_WORK(struct work_struct *work, void (*func)(void *),void *data);
    Этот макрос динамически инициализирует отложенное действие, на структуру которого указывает указатель work, устанавливая функцию-обработчик func и аргу- мент d a t a .
    Обработчик отложенного действия
    Прототип обработчика отложенного действия имеет следующий вид.
    void work_handler (void *data)
    154 Глава 7

    Рабочий поток выполняет эту функцию, и, следовательно, эта функция выполня- ется в контексте процесса. По умолчанию при этом вес прерывания разрешены и никакие захваченные блокировки не удерживаются. Ели это необходимо, то функция может переходить в состояние ожидания. Следует заметить, что несмотря на то, что обработчики отложенных действий и выполняются в контексте процесса, эти обра- ботчики не могут переходить в пространство пользователя, так как у потоков про- странства ядра нет адресного пространства пользователя. Ядро может обращаться в пространство пользователя, только когда оно выполняется от имени пользователь- ского процесса, который имеет адресное пространство пользователя, отображенное на память, как, например, в случае выполнения системного вызова.
    Блокировки между очередями отложенных действий и другими частями ядра осу- ществляются также, как и в случае любого другого кода, работающего в контексте процесса. Это позволяет сделать написание обработчиков отложенных действий до- статочно простым. В следующих двух главах это раскрывается более детально.
    Планирование действий на выполнение
    Теперь, когда отложенное действие создано, его нужно запланировать на выпол- нение. Для того чтобы поставить обработчик данного действия в очередь на выпол- нение потоками events, которые работают по умолчанию, необходимо просто вызвать следующую функцию.
    г schedule_work(&work);
    Действие планируется на выполнение немедленно и будет выполнено, как только рабочий поток events, работающий на данном процессоре, перейдет в состояние выполнения.
    Иногда необходимо, чтобы действие было выполнено не немедленно, а с неко- торой задержкой. В этом случае работа может быть запланирована на выполнение в некоторый момент времени в будущем. Для этого используется следующая функция.
    schedule_delayed_work (&work, delay);
    В этом случае действие, представленное структурой work_struct, с адресом
    &work, не будет выполнено, пока не пройдет хотя бы заданное в параметре delay количество импульсов таймера. О том, как использовать импульсы таймера для из- мерения времени, рассказывается в главе 10, "Таймеры и управление временем".
    Ожидание завершения действий
    Действия, поставленные в очередь, выполняются, когда рабочий поток возвра- щается к выполнению. Иногда нужно гарантировать, что, перед тем как двигаться дальше, заданный пакет отложенных действий завершен. Это особенно важно для загружаемых модулей, которые, вероятно, должны вызывать эту функцию, перед вы- грузкой. В других местах также может быть необходимо гарантировать, что нет ожи- дающих на выполнение действий, для предотвращения состояния конкуренции.
    Для этого есть следующая функция, которая позволяет ждать, пока очередь дей- ствий events не будет очищена.
    void flush_scheduled_work(void);
    Обработка нижних половин и отложенные действия 155

    Данная функция ожидает, пока все действия в очереди действий events не будут выполнены. В ожидании завершения всех заданий очереди, эта функция переводит вызывающий процесс в состояние ожидания. Поэтому ее можно вызывать только из контекста процесса.
    Заметим, что эта функция не отменяет никаких отложенных действий с задерж- ками. Любые действия, которые запланированы на выполнение с помощью функции schedule_delayed_work () и задержки которых еще не закончены, — не очищаются с помощью функций flush_scheduled_work (). Для отмены отложенных действий с задержками следует использовать функцию int cancel_delayed_work(struct work_struct *work);
    Эта функция отменяет отложенное действие, которое связано с данной структу- рой w o r k _ s t r u c t , если оно запланировано.
    Создание новых очередей отложенных действий
    Если для поставленных целей недостаточно очереди отложенных действий, ко- торая используется по умолчанию, то можно создать новую очередь действий и со- ответствующие рабочие потоки. Так как при этом создается по одному потоку на каждый процессор, то новые очереди действий необходимо создавать, только если необходима большая производительность за счет выделенного набора потоков.
    Новая очередь действий и связанные с ней рабочие потоки создаются с помощью простого вызова функции.
    struct workqueue_struct *create_workqueue(const char *name);
    Параметр name используется для того, чтобы присваивать имена потокам ядра.
    Например, очередь e v e n t s , которая используется по умолчанию, создается с помо- щью следующего вызова.
    struct workqueue_struct *keventd_wq = create_workqueue("events");
    При этом также создаются все рабочие потоки (по одному на каждый процессор),
    которые подготавливаются к выполнению работы.
    Создание отложенных действий выполняется одинаково, независимо от тина очереди. После того как действия созданы, могут быть использованы функции, ана- логичные функциям schedule_work() и s c h e d u l e _ d e l a y e d _ w o r k ( ) , которые от- личаются тем, что работают с заданной очередью действий, а не с очередью, ис- пользуемой по умолчанию.
    int queue_work struct workqueue_struct *wq, struct work_struct *work);
    int queue_delayed_work(struct workqueue_struct *wq,
    struct wesrk_struct *work, unsigned long delay);
    И наконец, ожидание завершения действий в заданной очереди может быть вы- полнено с помощью функции flush_workqueue(struct workqueue_struct *wq);
    Эта функция работает по аналогии с функцией f l u s h _ s c h e d u l e d _ w o r k ( ) , как описывалось ранее, за исключением того, что она ожидает, пока заданная очередь не станет пустой.
    156 Глава 7

    Старый механизм очередей заданий
    Так же как и в случае интерфейса ВН, который дал начало интерфейсам отложен- ных прерываний (softirq) и тасклетов (tasklet), интерфейс очередей действий возник благодаря недостаткам интерфейса очередей заданий (task queue). Интерфейс оче- редей заданий (который еще называют просто tq), так же как и тасклеты, не имеет ничего общего с заданиями (task), в смысле с процессами
    8
    . Все подсистемы, которые использовали механизм очередей заданий, были разбиты на две группы еще во вре- мена разработки серии ядер 2.5. Первая группа была переведена на использование тасклетов, а вторая— продолжала использовать интерфейс очередей заданий. Все,
    что осталось от интерфейса очередей заданий, перешло в интерфейс очередей отло- женных действий. Краткое рассмотрение очередей заданий, которым пользовались в течение некоторого времени, — это хорошее упражнение по истории.
    Интерфейс очередей заданий позволял определять набор очередей. Очереди имели имена, такие как scheduler queue (очередь планировщика), immediate queue
    (немедленная очередь) или timer queue (очередь таймера). Каждая очередь выпол- нялась в определенных местах в ядре. Поток пространства ядра keventd выполнял работу, связанную с очередью планировщика. Эта очередь была предшественником интерфейса очередей отложенных действий. Очередь таймера выполнялась при каж- дом импульсе системного таймера, а немедленная очередь выполнялась в нескольких местах, чтобы гарантировать "немедленное" выполнение. Были также и другие оче- реди. Кроме того, можно было динамически создавать новые очереди.
    Все это может показаться полезным, но на практике интерфейс очередей зада- ний приносил только неприятности. Все очереди были, по сути, оторваны от дей- ствительности. Единственной ценной очередью оказалась очередь планировщика,
    которая предоставляла единственную возможность, чтобы выполнять отложенные действия в контексте процесса.
    Еще одним преимуществом механизма очередей заданий была простота интер- фейса. Несмотря на большое количество очередей и разнообразие правил, по кото- рым они выполнялись, интерфейс был максимально прост. Все остальное, что каса- ется очередей заданий, необходимо было убрать.
    Различные использования очередей заданий были заменены другими механизма- ми обработки нижних половин; большинство — тасклетами. Осталось только то, что касалось очереди планировщика. В конце концов, код демона keventd был обобщен в отличный механизм очередей действий, который мы имеем сегодня, а очереди за- даний были полностью удалены из ядра.
    1   ...   16   17   18   19   20   21   22   23   ...   53


    написать администратору сайта