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

  • Объявление тасклетов

  • Написание собственной функции-обработчика тасклета

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

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


    Скачать 3.09 Mb.
    НазваниеВторое издание
    Дата08.09.2019
    Размер3.09 Mb.
    Формат файлаpdf
    Имя файлаLav_Robert_Razrabotka_yadra_Linux_Litmir.net_264560_original_254.pdf
    ТипДокументы
    #86226
    страница19 из 53
    1   ...   15   16   17   18   19   20   21   22   ...   53
    Планирование тасклетов на выполнение
    Запланированные (scheduled) на выполнение тасклеты (эквивалент сгенериро- ванных отложенных прерываний)
    6
    хранятся в двух структурах, определенных для каждого процессора: структуре t a s k l e t _ v e c (для обычных тасклетов) и структуре t a s k l e t _ h i _ v e c (для высокоприоритетных тасклетов). Каждая из этих структур —
    это связанный список структур t a s k l e t _ s t r u c t . Каждый экземпляр структуры t a s k l e t _ s t r u c t представляет собой отдельный тасклет.
    Тасклеты могут быть запланированы на выполнение с помощью функций t a s k l e t _ s c h e d u l e () и t a s k l e t _ h i _ s c h e d u l e (), которые принимают единствен- ный аргумент— указатель на структуру тасклета— t a s k l e t _ s t r u c t . Эти функции очень похожи (отличие состоит в том, что одна использует отложенное прерывание с номером TASKLET_SOFTIRQ, а другая — с номером HI_SOFTIRQ). К написанию и ис- пользованию тасклетов мы вернемся в следующем разделе. А сейчас рассмотрим дета- ли реализации функции t a s k l e t _ h i _ s c h e d u l e (), которые состоят в следующем.
    • Проверяется, не установлено ли поле s t a t e в значение TASKLET_STATE_
    SCHED. Если установлено, то тасклет уже запланирован на выполнение и функ- ция может возвратить управление.
    • Сохраняется состояние системы прерываний и запрещаются прерывания на локальном процессоре. Это гарантирует, что ничто на данном процессоре не будет мешать выполнению этого кода.
    • Добавляется тасклет, который планируется на выполнение, в начало связанно- го списка структуры t a s k l e t _ v e c или t a s k l e t _ h i _ v e c , которые уникальны для каждого процессора в системе.
    6
    Это еще один пример плохой терминологии. Почему отложенные прерывания (softirq) генериру- ются (rise), а тасклеты (lasklet) планируются (schedule)? Кто знает? Оба термина означают, что об- работчики нижних половин помечаются как ожидающие на выполнение и в скором времени будут выполнены.
    142 Глава 7

    • Генерируется отложенное прерывание с номером TASKLET_SOFTIRQ или НI_
    SOFTIRQ, чтобы в ближайшее время данный тасклет выполнился при вызове функции d o _ s o f t i r q ( ) .
    • Устанавливается состояние системы прерываний в первоначальное значение и возвращается управление.
    При первой же удобной возможности функция d o _ s o f t i r q ( ) выполнится, как это обсуждалось в предыдущем разделе. Поскольку большинство тасклетов помеча- ются как готовые к выполнению в обработчиках прерываний, то, скорее всего, функ- ция d o _ s o f t i r q () вызывается сразу же, как только возвратится последний обработ- чик прерывания. Так как отложенные прерывания с номерами TASKLET_SOFTIRQ
    или HI_SOFTIRQ к этому моменту уже сгенерированы, то функция d o _ s o f t i r q ()
    выполняет соответствующие обработчики. Эти обработчики, а также функции t a s k l e t _ a c t i o n () и t a s k l e t _ h i _ a c t i o n () являются сердцем механизма обработ- ки тасклетов- Давайте рассмотрим, что они делают.
    • Запрещаются прерывания, и получается весь список t a s k l e t _ v e c или tasklet_
    hi_vec для текущего процессора.
    • Список текущего процессора очищается путем присваивания значения нуль указателю на него.
    • Разрешаются прерывания (нет необходимости восстанавливать состояние си- стемы прерываний в первоначальное значение, так как этот код может выпол- няться только в обработчике отложенного прерывания, который вызывается только при разрешенных прерываниях).
    • Организовывается цикл по всем тасклетам в полученном списке.
    • Если данная машина является многопроцессорной, то нужно проверить не выполняется ли текущий тасклет на другом процессоре, то есть проверить не установлен ли флаг TASLET_STATE_RUN. Если тасклет уже выполняется, то его необходимо пропустить и перейти к следующему тасклету в списке (вспомним,
    что только один тасклет данного типа может выполняться в любой момент времени).
    • Если тасклет не выполняется, то нужно установить флаг TASLET_STATE_RUN,
    чтобы другой процессор не мог выполнить этот тасклет.
    • Проверяется значение поля c o u n t на равенство нулю, чтобы убедиться, что тасклет не запрещен. Если тасклет запрещен (поле count не равно нулю), то нужно перейти к следующему тасклету, который ожидает на выполнение.
    • Теперь можно быть уверенным, что тасклет нигде не выполняется, нигде не будет выполняться (так как он помечен как выполняющийся на данном про- цессоре) и что значение поля c o u n t равно нулю. Необходимо выполнить об- работчик тасклета. После того как тасклет выполнился, следует очистить флаг
    TASLET_STATE_RUN и поле s t a t e .
    • Повторить описанный алгоритм для следующего тасклета, пока не останется ни одного тасклета, ожидающего выполнения.
    Обработка нижних половин и отложенные действия 143

    Реализация тасклетов проста, но в то же время очень остроумна. Как видно, все тасклеты реализованы на базе двух отложенных прерываний TASKLET_SOFTIRQ и
    HI_SOFTIRQ. Когда тасклет запланирован на выполнение, ядро генерирует одно из этих двух отложенных прерываний. Отложенные прерывания, в свою очередь, об- рабатываются специальными функциями, которые выполняют все запланированные на выполнение тасклеты. Эти специальные функции гарантируют, что только один тасклет данного типа выполняется в любой момент времени (но тасклеты разных типов могут выполняться одновременно). Вся эта сложность спрятана за простым и ясным интерфейсом.
    Использование тасклетов
    В большинстве случаев тасклеты — это самый предпочтительный механизм, с по- мощью которого следует реализовать обработчики нижних половин для обычных аппаратных устройств. Тасклеты можно создавать динамически, их просто использо- вать, и они сравнительно быстро работают.
    Объявление тасклетов
    Тасклеты можно создавать статически и динамически. Какой вариант лучше вы- брать, зависит от того, как необходимо (или желательно) пользователю обращаться к тасклету: прямо или через указатель. Для статического создания тасклета (и соот- ветственно, обеспечения прямого доступа к нему) необходимо использовать один из двух следующих макросов, которые определены в файле < l i n u x / i n t e r r u p t s ,h>;
    DECLARE_TASKLET(name, func, data)
    DECLARE_TASKLET_DISABLED(name, func, data);
    Оба макроса статически создают экземпляр структуры s t r u c t _ t a s k l e t _ s t r u c t с указанным именем (name). Когда тасклет запланирован на выполнение, то вызывает- ся функция func, которой передается аргумент d a t a . Различие между этими макро- сами состоит в значении счетчика ссылок на тасклет (поле count). Первый макрос создает тасклет, у которого значение поля c o u n t равно нулю, и, соответственно,
    этот тасклет разрешен. Второй макрос создает тасклет и устанавливает для него зна- чение поля count, равное единице, и, соответственно, этот тасклет будет запрещен.
    Можно привести следующий пример.
    DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);
    Эта строка эквивалентна следующей декларации.
    struct tasklet_struct rny_tasklet = { NULL, 0, ATOMIC_INIT(0),
    tasklet_handler, d e v ) ;
    В данном примере создается тасклет с именем m y _ t a s k l e t , который разрешен для выполнения. Функция t a s k l e t _ h a n d l e r будет обработчиком этого тасклета.
    Значение параметра dev передается в функцию-обработчик при вызове данной функции.
    Для инициализации тасклета, на который указывает заданный указатель s t r u c t t a s k l e t _ s t r u c t * t — косвенная ссылка на динамически созданную ранее структу- ру, необходимо использовать следующий вызов.
    t a s k l e t _ i n i t ( t , t a s k l e t _ h a n d l e r , dev); /* динамически, а не статически */
    144 Глава 7

    Написание собственной функции-обработчика тасклета
    Функция-обработчик тасклета должна соответствовать правильному прототипу.
    void tasklet_handler(unsigned long data)
    Так же как и в случае отложенных прерываний, тасклет не может переходить в состояние ожидания (блокироваться). Это означает, что в тасклетах нельзя исполь- зовать семафоры или другие функции, которые могут блокироваться. Тасклеты так- же выполняются при всех разрешенных прерываниях, поэтому необходимо принять все меры предосторожности (например, может понадобиться запретить прерывания и захватить блокировку), если тасклет имеет совместно используемые данные с об- работчиком прерывания. В отличие от отложенных прерываний, ни один тасклет не выполняется параллельно самому себе, хотя два разных тасклета могут выполняться на разных процессорах параллельно. Если тасклет совместно использует данные с обработчиком прерывания или другим тасклетом, то необходимо использовать соот- ветствующие блокировки (см. глапу 8, "Введение в синхронизацию выполнения кода ядра" и главу 9, "Средства синхронизации в ядре").
    Планирование тасклета на выполнение
    Для того чтобы запланировать тасклет на выполнение, должна быть вызвана функция t a s k l e t _ s c h e d u l e (), которой в качестве аргумента передается указатель на соответствующий экземпляр структуры t a s k l e t _ s t r u c t .
    tasklet_schedule(&my_tasklet) ; /* отметить, что тасклет my_tasklet ожидает на выполнение */
    После того как тасклет запланирован на выполнение, он выполняется один раз в некоторый момент времени в ближайшем будущем. Если тасклет, который запла- нирован на выполнение, будет запланирован еще раз до того, как он выполнится,
    то он также выполнится всего один раз. Если тасклет уже выполняется, скажем, на другом процессоре, то будет запланирован снова и снова выполнится. Для оптими- зации тасклет всегда выполняется на том процессоре, который его запланировал на выполнение, что дает надежду на лучшее использование кэша процессора.
    Указанный тасклет может быть запрещен с помощью вызова функции t a s k l e t _
    d i s a b l e ( ) . Если тасклет в данный момент времени выполняется, то эта функция не возвратит управление, пока тасклет не закончит выполняться. Как альтернативу можно использовать функцию t a s k l e t _ d i s a b l e _ n o s y n c ( ) , которая запрещает ука- занный тасклет, но возвращается сразу и не ждет, пока тасклет завершит выполне- ние. Это обычно небезопасно, так как в данном случае нельзя гарантировать, что тасклет не закончил выполнение. Вызов функции t a s k l e t _ e n a b l e () разрешает тасклет. Эта функция также должна быть вызвана для того, чтобы можно было ис- пользовать тасклет, созданный с помощью макроса DECLARE_TASKLET_DISABLED (),
    как показано в следующем примере.
    tasklet_disable(&my_tasklet); /* тасклет теперь запрещен */
    /* Мы можем делать все, что угодно, зная, что тасклет не может выполняться. */
    tasklet_enable(&my_tasklet); /* теперь тасклет разрешен */
    Из очереди тасклетов, ожидающих на выполнение, тасклет может быть удален с помощью функции t a s k l e t _ k i l l (). Эта функция получает указатель на соответ-
    Обработка нижних половин и отложенные действия 145
    ствующуго структуру t a s k l e t _ s t r u c t в качестве единственного аргумента. Удаление запланированного на выполнение тасклета из очереди очень полезно в случае, когда используются тасклеты, которые сами себя планируют на выполнение. Эта функция сначала ожидает, пока тасклет не закончит выполнение, а потом удаляет его из оче- реди. Однако это, конечно, не может предотвратить возможности, что другой код запланирует этот же тасклет на выполнение. Так как данная функция может перехо- дить в состояние ожидания, то ее нельзя вызывать из контекста прерывания.
    Демон ksoftirqd
    Обработка отложенных прерываний (softirq) и, соответственно, тасклетов может осуществляться с помощью набора потоков пространства ядра (по одному потоку на каждый процессор). Потоки пространства ядра помогают обрабатывать отложенные прерывания, когда система перегружена большим количеством отложенных преры- ваний.
    Как уже упоминалось, ядро обрабатывает отложенные прерывания в нескольких местах, наиболее часто это происходит после возврата из обработчика прерывания.
    Отложенные прерывания могут генерироваться с очень большими частотами (как,
    например, в случае интенсивного сетевого трафика). Хуже того, функции-обработ- чики отложенных прерываний могут самостоятельно возобновлять свое выполнение
    (реактивизировать себя). Иными словами, во время выполнения функции-обработ- чики отложенных прерываний могут генерировать свое отложенное прерывание для того, чтобы выполниться снова (на самом деле, сетевая подсистема именно так и делает). Возможность больших частот генерации отложенных прерываний в соче- тании с их возможностью активизировать самих себя может привести к тому, что программы, работающие в пространстве пользователя, будут страдать от недостатка процессорного времени. В свою очередь, не своевременная обработка отложенных прерываний также не допустима. Возникает дилемма, которая требует решения, но ни одно из двух очевидных решений не является подходящим. Давайте рассмотрим оба этих очевидных решения.
    Первое решение — это немедленная обработка всех отложенных прерываний, как только они приходят, а также обработка всех ожидающих отложенных прерываний перед возвратом из обработчика. Это решение гарантирует, что все отложенные пре- рывания будут обрабатываться немедленно и в то же время, что более важно, что все вновь активизированные отложенные прерывания также будут немедленно об- работаны. Проблема возникает в системах, которые работают при большой загрузке и в которых возникает большое количество отложенных прерываний, которые по- стоянно сами себя активизируют. Ядро может постоянно обслуживать отложенные прерывания без возможности выполнять что-либо еще. Заданиями пространства пользователя пренебрегают, а выполняются только лишь обработчики прерываний и отложенные прерывания, в результате пользователи системы начинают нервни- чать. Подобный подход может хорошо работать, если система не находится под очень большой нагрузкой. Если же система испытывает хотя бы умеренную нагрузку,
    вызванную обработкой прерываний, то такое решение не применимо. Пространство пользователя не должно продолжительно страдать из-за нехватки процессорного времени.
    Второе решение — это вообще не обрабатывать реактивизированные отложенные прерывания. После возврата из очередного обработчика прерывания ядро просто
    146 Глава 7
    просматривает список всех ожидающих на выполнение отложенных прерываний и выполняет их как обычно. Если какое-то отложенное прерывание реактивизирует себя, то оно не будет выполняться до того времени, пока ядро в следующий раз сно- ва не приступит к обработке отложенных прерываний. Однако такое, скорее всего,
    произойдет, только когда поступит следующее аппаратное прерывание, что может быть равносильно ожиданию в течение длительного промежутка времени, пока но- вое (или вновь активизированное) отложенное прерывание будет выполнено. В та- ком решении плохо то, что на не загруженной системе выгодно обрабатывать от- ложенные прерывания сразу же. К сожалению, описанный подход не учитывает то,
    какие процессы могут выполняться, а какие нет. Следовательно, данный метод хотя и предотвращает нехватку процессорного времени для задач пространства пользова- теля, но создает нехватку ресурсов для отложенных прерываний, и к тому же такой подход не выгоден для систем, работающих при малых нагрузках.
    Необходим какой-нибудь компромисс. Решение, которое реализовано в ядре, — не
    обрабатывать немедленно вновь активизированные отложенные прерывания. Бместо этого, если сильно возрастает количество отложенных прерываний, ядро возвраща- ет к выполнению (wake up) семейство потоков пространства ядра, чтобы они спра- вились с нагрузкой. Данные потоки ядра работают с самым минимально возможным приоритетом (значение параметра nice равно 19). Это гарантирует, что они не будут выполняться вместо чего-то более важного. Но они в конце концов тоже когда-ни- будь обязательно выполняются. Это предотвращает ситуацию нехватки процессор- ных ресурсов для пользовательских программ. С другой стороны, это также гаран- тирует, что даже в случае большого количества отложенных прерываний они все в конце концов будут выполнены. И наконец, такое решение гарантирует, что в случае незагруженной системы отложенные прерывания также обрабатываются достаточно быстро (потому что соответствующие потоки пространства ядра будут запланирова- ны на выполнение немедленно).
    Для каждого процессора существует свой поток. Каждый поток имеет имя в виде k s o f t i r q d / n , где п — номер процессора. Так в двухпроцессорной системе будут за- пущены два потока с именами ksoftiqd/0 и k s o f t i r q d / 1 . To, что на каждом про- цессоре выполняется свой поток, гарантирует, что если в системе есть свободный процессор, то он всегда будет в состоянии выполнять отложенные прерывания.
    После того как потоки запущены, они выполняют замкнутый цикл, похожий на сле- дующий.
    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);
    Обработка нижних половин и отложенные действия 147
    }

    Если есть отложенные прерывания, ожидающие на обработку (что определяет вызов функции s o f t i r q _ p e n d i n g ()), то поток ядра k s o f t i r q d вызывает функ- цию d o _ s o f t i r q () , которая эти прерывания обрабатывает. Заметим, что это де- лается периодически, чтобы обработать также вновь активизированные отложен- ные прерывания. После каждой итерации при необходимости вызывается функция schedule (), чтобы дать возможность выполняться более важным процессам. После того как вся обработка выполнена, поток ядра устанавливает свое состояние в значе- ние TASK_INTERRUPTIBLE и активизирует планировцик для выбора нового готового к выполнению процесса.
    Поток обработки отложенных прерываний вновь возвращается в состояние го- товности к выполнению, когда функция d o _ s o f t i r q () определяет, что отложенное прерывание реактивизировало себя.
    Старый механизм ВН
    Хотя старый интерфейс ВН, к счастью, уже отсутствует в ядрах серии 2.6, тем не менее им пользовались очень долгое время — с первых версий ядра. Учитывая, что этому интерфейсу удалось продержаться очень долго, он, конечно, представляет со- бой историческую ценность и заслуживает большего, чем просто беглого рассмотре- ния. Этот раздел никаким образом не касается ядер серии 2.6, но значение истории переоценить трудно.
    Интерфейс ВН очень древний, и это заметно. Каждый обработчик ВН должен быть определен статически, и количество этих обработчиков ограничено максималь- ным значением 32. Так как все обработчики ВН должны быть определены на этапе компиляции, загружаемые модули ядра не могли напрямую использовать интерфейс
    ВН. Тем не менее можно было встраивать функции в уже существующие обработчи- ки ВН. Со временем необходимость статического объявления и максимальное коли- чество обработчиков нижних половин, равное 32, стали надоедать.
    Все обработчики ВН выполнялись строго последовательно — никакие два обра- ботчика ВН, даже разных типов, не могли выполняться параллельно. Это позволяло обеспечить простую синхронизацию, но все же для получения высокой производи- тельности при многопроцессорной обработке это было не очень хорошо. Драйверы,
    которые использовали интерфейс ВН, очень плохо масштабировались на несколько процессоров. Например, страдала сетевая подсистема.
    В остальном, за исключением указанных ограничений, механизм ВН был похож на механизм тасклетов. На самом деле, в ядрах серии 2.4 механизм ВН был реали- зован па основе тасклетов. Максимальное количество обработчиков нижних поло- вин, равное 32, обеспечивалось значениями констант, определенных в заголовочном файле < l i n u x / i n t e r r u p t . h > . Для того чтобы отметить обработчик ВН как ожида- ющий на выполнение, необходимо было вызвать функцию mark_bh () с передачей номера обработчика ВН в качестве параметра. В ядрах серии 2.4 при этом плани- ровался на выполнение тасклет ВН, который выполнялся с помощью обработчика b h _ a c t i o n (). До серии ядер 2.4 механизм ВН существовал самостоятельно, как сей- час механизм отложенных прерываний.
    В связи с недостатками этого типа обработчиков нижних половин, разработчики ядра предложили механизм очередей заданий (task queue), чтобы заменить механизм нижних половин. Очереди заданий так и не смогли справиться с этой задачей, хотя и завоевали расположение большого количества пользователей. При разработке серии
    148 Глава 7
    ядер 2.3 были предложены механизмы отложенных прерываний (softirq) и механизм тасклетов (lasklet), для того чтобы положить конец механизму ВН. Механизм ВН
    при этом был реализован на основе механизма тасклетов. К сожалению, достаточно сложно переносить обработчики нижних половин с использования интерфейса ВН
    на использование механизм тасклетов или отложенных прерываний, в связи с тем что у новых интерфейсов нет свойства строгой последовательности выполнения
    7
    Однако при разработке ядер серии 2.5 необходимую конвертацию все же сдела- ли, когда таймеры ядра и подсистему SCSI (единственные оставшиеся системы, ко- торые использовали механизм ВН) наконец-то перевели на использование отложен- ных прерываний. И в завершение, разработчики ядра совсем убрали интерфейс ВН.
    Скатертью дорога тебе, интерфейс ВН!
    1   ...   15   16   17   18   19   20   21   22   ...   53


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