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

  • Таблица 4.2. Функции для управления флагом

  • Вытеснение пространства пользователя

  • Вытеснение пространства ядра

  • Режим реального времени

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


    Скачать 3.09 Mb.
    НазваниеВторое издание
    Дата08.09.2019
    Размер3.09 Mb.
    Формат файлаpdf
    Имя файлаLav_Robert_Razrabotka_yadra_Linux_Litmir.net_264560_original_254.pdf
    ТипДокументы
    #86226
    страница11 из 53
    1   ...   7   8   9   10   11   12   13   14   ...   53
    Балансировка нагрузки
    Как уже рассказывалось ранее, планировщик операционной системы Linux ре- ализует отдельные очереди выполнения и блокировки для каждого процессора в симметричной многопроцессорной системе. Это означает, что каждый процессор поддерживает свой список процессов и выполняет алгоритм планирования только для заданий из этого списка. Система планирования, таким образом, является уни- кальной для каждого процессора. Тогда каким же образом планировщик обеспечива- ет какую-либо глобальную стратегию планирования для многопроцессорных систем?
    Что будет, если нарушится балансировка очередей выполнения, скажем, в очереди выполнения одного процессора будет находиться пять процессов, а в очереди дру- гого — всего один? Решение этой проблемы выполняется системой балансировки на- грузки, которая работает с целью гарантировать, что все очереди выполнения будут сбалансированными. Система балансировки нагрузки сравнивает очередь выполне- ния текущего процессора с другими очередями выполнения в системе.
    Планирование выполнения процессов 83

    Функция add_wait_que-je() помещает задачу в очередь ожидания,
    устанавливает состояние задачи TASK_INTERRUPTIBLE и вызывает функцию schedule(). Функция scheduled вызывает функцию deactivate_task(), которая удаляет задачу из очереди выполнения.
    Задача готова к выполнению
    Задача не готова к выполнению
    Получен сигнал,
    задача устанавливается в состояние
    TASK_RUNNING и выполняет обработчик сигнала
    Событие, на которое ожидала задача, произошло, и функция try_to_wake_up() устанавливает задачу в состояние TASK_RUNNING,
    вызывает функцию activate_task() и функцию schedule() .
    Функция remove_wait_quaue () удаляет задачу из очереди ожидания.
    Рис. 4,3. Переход в приостановленное состояние (sleeping) и возврат к выполнению (wake up)
    Если обнаруживается дисбаланс, то процессы из самой загруженной очереди вы- полнения выталкиваются в текущую очередь, В идеальном случае каждая очередь вы- полнения будет иметь одинаковое количество процессов. Такая ситуация, конечно,
    является высоким идеалом, к которому система балансировки может только прибли- зиться.
    Система балансировки нагрузки реализована в файле k e r n e l / s c h e d . с в виде функции load_balance (). Эта функция вызывается в двух случаях. Она вызывается функцией s c h e d u l e (), когда текущая очередь выполнения пуста. Она также вызы- вается по таймеру с периодом в 1 мс, когда система не загружена, и каждые 200 мс в другом случае. В однопроцессорной системе функция l o a d _ b a l a n c e () не вызыва- ется никогда, в действительности она даже не компилируется в исполняемый образ ядра, питому что в системе только одна очередь выполнения и никакой балансиров- ки не нужно.
    Функция балансировки нагрузки вызывается при заблокированной очереди вы- полнения текущего процессора, прерывания при этом также запрещены, чтобы за- щитить очередь выполнения от конкурирующего доступа. В том случае, когда функ- ция load b a l a n c e ( ) вызывается из функции s c h e d u l e ( ) , цель ее вызова вполне ясна, потому что текущая очередь выполнения пуста и нахождение процессов в других очередях с последующим их проталкиванием в текущую очередь позволяет получить преимущества. Когда система балансировки нагрузки активизируется по- средством таймера, то ее задача может быть не так очевидна. В данном случае это необходимо для устранения любого дисбаланса между очередями выполнения, что- бы поддерживать их в почти одинаковом состоянии, как показано на рис. 4.4.
    84
    Глава 4
    load_balancer()
    Проталкивание процесса из одной очереди в другую для уменьшения дисбаланса
    Процесс 1
    Процесс 2
    Процесс 3
    Процесс 4
    Процесс 5
    Процесс 6
    Процесс 15
    Очередь выполнения процессора 1,всего
    20 процессов
    Очередь выполнения процессора 2, всего
    15процессов
    Рис. 4.4. Система балансировки нагрузки
    Функция load_balance () и связанные с ней функции сравнительно большие и сложные, хотя шаги, которые они предпринимают, достаточно ясны.
    • Функция load_balance () вызывает функцию find_busiest_queue () для определения наиболее загруженной очереди выполнения. Другими словами —
    очередь с наибольшим количеством процессов в ней. Если нет очереди выпол- нения, количество процессов в которой на 25% больше, чем в дайной очереди,
    то функция f ind_busiest_queue () возвращает значение NULL и происходит возврат из функции load_balance (). В другом случае возвращается указатель на самую загруженную очередь.
    • Функция load_balance () принимает решение о том, из какого массива прио- ритетов самой загруженной очереди будут проталкиваться процессы. Истекший массив является более предпочтительным, так как содержащиеся в нем задачи не выполнялись достаточно долгое время и, скорее всего, не находятся в кэше процессора (т.е. не активны в кэше, not "cache hot"). Если истекший массив приоритетов пуст, то ничего не остается, как использовать активный массив.
    • Функция load_balance () находит непустой список заданий, соответствующий самому высокому приоритету (с самым маленьким номером), так как важно бо- лее равномерно распределять задания с высоким приоритетом, чем с низким.
    • Каждое задание с данным приоритетом анализируется для определения зада- ния, которое не выполняется, не запрещено для миграции из-за процессорной привязки и не активно в кэше.Если найдена задача, которая удовлетворяет этому критерию, то вызывается функция p u l l _ t a s k () для проталкивания этой задачи из наиболее загруженной очереди в данную очередь.
    • Пока очереди выполнения остаются разбалансированными, предыдущие два шага повторяются и необходимое количество заданий проталкивается из са- мой загруженной очереди выполнения в данную очередь выполнения. В конце концов, когда дисбаланс устранен, очередь выполнения разблокируется и про- исходит возврат из функции load_balance ().
    Планирование выполнения процессов
    85
    Процесс З
    Процесс 4
    Процесс 5
    Процесс 6
    Процесс 20
    Процесс 1
    Процесс 2

    Далее показана функция load_balance (), немного упрощенная, но содержащая все цажные детали.
    static int load_balance(int this_cpu, runqueue_t *this_rq,
    struct sched_doraain *sd, enum idle_type idle)
    {
    struct sched_group *group;
    runqueue_t *busiest;
    unsigned long imbalance;
    int nr_moved;
    spin_lock(&this_rq->lock);
    group = find_busiest_group(sd, this_cpu, &imbalance, idle);
    if (!group)
    goto out_balanced;
    busiest = find_busiest_queue(group) ;
    if (!busiest)
    goto out_balanced;
    nr_moved = 0;
    if (busiest->nr_running > 1) {
    double_lock_balance(this_rq, busiest);
    nr_moved = move_tasks(this_rq, this_cpu, busiest,
    imbalance, sd, idle);
    spin_unlock(&busiest->lock);
    }
    spin_unlock(&this rq->lock);
    if (!nr_moved) {
    sd->nr_balance_failed++;
    if (unlikely(sd->nr_balance_failed > sd->cache_nice_tries+2)) {
    int wake = 0;
    spin_lock(abusiest->lock);
    if (!busiest->active_balance) {
    busiest->active_balance = 1;
    busiest->push_cpu = this_cpu;
    wake = 1;
    }
    spin_unlock(&busiest->lock);
    if (wake)
    wake_up_process(busiest->migration_thread);
    sd->nr_balance_failed = sd->cache_nice_tries;
    )
    } else sd->nr_balance_failed = 0;
    sd->balance_interval = sd->min_interval;
    return nr_moved;
    86 . Глава 4
    out_balanced:
    spin_unlock (&this_rq->lock) ;
    if (sd->balance_interval < sd->max_interval)
    sd->balance_interval *= 2;
    return 0;
    }
    Вытеснение и переключение контекста
    Переключение контекста — это переключение от одной, готовой к выпол- нению задачи к другой. Это переключение производится с помощью функции context_switch(), определенной в файле kernel/sched.с. Данная функция вы- зывается функцией schedule (), когда новый процесс выбирается для выполнения.
    При этом выполняются следующие шаги.
    • Вызывается функция switch_mm (), которая определена в файле include/asm/
    mmu_context.h и предназначена для переключения от виртуальной памяти старого процесса к виртуальной памяти нового процесса.
    • Вызывается функция s w i t c h _ t o () , определенная в файле i n c l u d e /asm/
    system.h, для переключения от состояния процессора предыдущего процесса к состоянию процессора нового процесса. Эта процедура включает восстанов- ление информации стека ядра и регистров процессора.
    Ядро должно иметь информацию о том, когда вызывать функцию schedule ().
    Если эта функция будет вызываться только тогда, когда программный код вызывает ее явно, то пользовательские программы могут выполняться неопределенное время.
    Поэтому ядро поддерживает флаг need_resched для того, чтобы сигнализировать,
    необходимо ли вызывать функцию schedule () (табл. 4.2). Этот флаг устанавлива- ется функцией schediiler_tick (), когда процесс истрачивает свой квант времени,
    и функцией try_to_wake_up (), когда процесс с приоритетом более высоким, чем у текущего процесса, возвращается к выполнению. Ядро проверяет значение этого флага, и если он установлен, то вызывается функция schedule () для переключения на новый процесс. Этот флаг является сообщением ядру о том, что планировщик должен быть активизирован по возможности раньше, потому что другой процесс должен начать выполнение.
    Таблица 4.2. Функции для управления флагом n e e d _ r e s c h e d
    Функция Назначение
    set_tsk_need_resched (task)
    Установить флаг need_resched для данного процесса clear_tsk_need_resched (task)
    Очистить флаг need_resched для данного процесса need_resched()
    Проверить значение флага need_resched для данного процесса. Возвращается значение true, если этот флаг установлен, и false, если не установлен
    Планирование выполнения процессов 87

    Во время переключения в пространство пользователи или при возврате из пре- рывания, значение флага need_resched проверяется. Если он установлен, то ядро активизирует планировщик перед тем, как продолжить работу.
    Этот флаг не является глобальной переменной, так как обращение к дескрипто- ру процесса получается более быстрым, чем обращение к глобальным данным (из-за скорости обращения к переменной current и потому, что соответствующие данные могут находиться в кэше). Исторически, этот флаг был глобальным в ядрах до серии
    2.2. В ядрах серий 2.2 и 2.4 этот флаг принадлежал структуре task_struct и имел тип int. В серии ядер 2.6 этот флаг перемещен в один определенный бит специаль- ной переменной флагов структуры thread info. Легко видеть, что разработчики ядра никогда не могут быть всем довольны.
    Вытеснение пространства пользователя
    Вытеснение пространства пользователя (user preemption) происходит в тот мо- мент, когда ядро собирается возвратить управление режиму пользователя, при этом устанавливается флаг need_resched и, соответственно, активизируется планиров- щик. Когда ядро возвращает управление в пространство пользователя, то оно нахо- дится в безопасном и "спокойном" состоянии. Другими словами, если продолжение выполнения текущего задания является безопасным, то безопасным будет также и выбор нового задания для выполнения. Поэтому когда ядро готовится возвратить управление в режим пользователя или при возврате из прерывания или после си- стемного вызова, происходит проверка флага need_resched. Если этот флаг уста- новлен, то активизируется планировщик И выбирает новый, более подходящий процесс для исполнения. Как процедура возврата из прерывания, так и процедура возврата из системного вызова являются зависимыми от аппаратной платформы и обычно реализуются на языке ассемблера в файле entry.S (этот файл, кроме кода входа в режим ядра, также содержит и код выхода из режима ядра). Если коротко,
    то вытеснение пространства пользователя может произойти в следующих случаях.
    • При возврате в пространство пользователя из системного вызова.
    • При возврате в пространство пользователя из обработчика прерывания.
    Вытеснение пространства ядра
    Ядро операционной системы Linux, в отличие от ядер большинства вариантов
    ОС Unix, является полностью преемптивным (вытесняемым, preemptible). В непре- емптивных ядрах код ядра выполняется до завершения. Иными словами, планиров- щик не может осуществить планирование для выполнения другого задания, пока какое-либо задание выполняется в пространстве ядра — код ядра планируется на вы- полнение кооперативно, а не посредством вытеснения. Код ядра выполняется до тех пор, пока он не завершится (возвратит управление в пространство пользователя)
    или пока явно не заблокируется. С появлением серии ядер 2.6, ядро Linux стало пре- емптивным: теперь есть возможность вытеснить задание в любой момент, конечно,
    пока ядро находится в состоянии, когда безопасно производить перепланирование выполнения.
    В таком случае когда же безопасно производить перепланирование? Ядро способ- но вытеснить задание, работающее в пространстве ядра, когда это задание не удер-
    88 Глава 4
    живает блокировку. Иными словами, блокировки используются в качестве маркеров тех областей, в которые задание не может быть вытеснено. Ядро рассчитано на мно- гопроцессорность (SMP-safe), поэтому если блокировка не удерживается, то код ядра является реентерабельным и его вытеснять безопасно.
    Первое изменение, внесенное для поддержки вытеснения пространства ядра, —
    это введение счетчика преемптивности p r e e m p t _ c o u n t в структуру t h r e a d _ i n f о каждого процесса. Значение этого счетчика вначале равно нулю и увеличивается на единицу при каждом захвате блокировки, а также уменьшается на единицу при каж- дом освобождении блокировки. Когда значение счетчика равно нулю— ядро явля- ется вытесняемым. При возврате из обработчика прерывания, если возврат выпол- няется в пространство ядра, ядро проверяет значения переменных n e e d _ r e s c h e d и p r e e m p t _ c o u n t . Если флаг n e e d _ r e s c h e d установлен и значение счетчика preempt__count равно нулю, значит, более важное задание готово к выполнению и выполнять вытеснение безопасно. Далее активизируется планировщик. Если зна- чение счетчика p r e e m p t _ c o u n t не равно нулю, значит, удерживается захваченная блокировка и выполнять вытеснение не безопасно. В таком случае возврат из об- работчика прерывания происходит в текущее выполняющееся задание. Когда осво- бождаются все блокировки, удерживаемые текущим заданием, значение счетчика preempt_count становится равным нулю. При этом код, осуществляющий освобож- дение блокировки, проверяет, не установлен ли флаг need_resched. Если установ- лен, то активизируется планировщик. Иногда коду ядра необходимо иметь возмож- ность запрещать или разрешать вытеснение в режиме ядра, что будет рассмотрено в главе 9.
    Вытеснение пространства ядра также может произойти явно, когда задача бло- кируется в режиме ядра или явно вызывается функция s c h e d u l e () . Такая форма преемптивности ядра всегда поддерживалась, так как в этом случае нет необходимо- сти в дополнительной логике, которая бы давала возможность убедиться, что вытес- нение проводить безопасно. Предполагается, что если код явно вызывает функцию schedule (), то точно известно, что перепланирование производить безопасно.
    Вытеснение пространства ядра может произойти в следующих случаях.
    • При возврате из обработчика прерывания в пространство ядра.
    • Когда код ядра снова становится преемптивным.
    • Если задача, работающая в режиме ядра, явно вызывает функцию schedule ().
    • Если задача, работающая в режиме ядра, переходит в приостановленное состо- яние, т.е. блокируется (что приводит к вызову функции schedule ()).
    Режим реального времени
    Операционная система Linux обеспечивает две стратегии планирования в режи- ме реального времени (real-lime): SCHED_FIFO и SCHED_RR. Стратегия планирования
    SCHED_OTHER является обычной стратегией планирования, т.е. стратегий планирова- ния не в режиме реального времени. Стратегия SCHED_FIFO обеспечивает простой алгоритм планирования по идеологии "первым вошел — первым обслужен" (first-in first-out, FIFO) без квантов времени. Готовое к выполнению задание со стратегией планирования SCHED_FIFO всегда будет планироваться на выполнение перед всеми заданиями со стратегией планирования SCHED_OTHER. Когда задание со стратегией
    Планирование выполнения процессов 89

    SCHED_FIFO становится готовым к выполнению, то оно будет продолжать выпол- няться до тех пор, пока не заблокируется или пока явно не отдаст управление. Две или более задач с одинаковым приоритетом, имеющие стратегию планирования
    SCHED_FIFO, будут планироваться на выполнение по круговому алгоритму (round- robin). Если задание, имеющее стратегию планирования SCHED_FIFO, является гото- вым к выполнению, то все задачи с более низким приоритетом не могут выполнять- ся до тех пор, пока это задание не завершится.
    Стратегия SCHED_RR аналогична стратегии SCHED_FIFO, за исключением того,
    что процесс может выполняться только до тех пор, пока не израсходует предопре- деленный ему квант времени. Таким образом, стратегия SCHED_RR — это стратегия
    SCHED_FIFO с квантами времени, т.е. круговой алгоритм планирования (round-robin)
    реального времени. Когда истекает квант времени процесса со стратегией планиро- вания SCHED_RR, то другие процессы с таким же приоритетом планируются по кру- говому алгоритму. Квант времени используется только для того, чтобы переплани- ровать выполнение заданий с таким же приоритетом. Так же как в случае стратегии
    SCHED_FIFO, процесс с более высоким приоритетом сразу же вытесняет процессы с более низким приоритетом, а процесс с более низким приоритетом никогда не смо- жет вытеснить процесс со стратегией планирования SCHED_RR, даже если у послед- него истек квант времени.
    Обе стратегии планирования реального времени используют статические при- оритеты. Ядро не занимается расчетом значений динамических приоритетов для за- дач реального времени. Это означает, что процесс, работающий в режиме реального времени, всегда сможет вытеснить процесс с более низким значением приоритета.
    Стратегии планирования реального времени в операционной системе Linux обе- спечивают так называемый мягкий режим реального времени (soft real-time). Мягкий режим реального времени обозначает, что ядро пытается планировать выполнение пользовательских программ в границах допустимых временных сроков, но не всегда гарантирует выполнение этой задачи. В противоположность этому операционные системы с жестким режимом реального времени (hard real-time) всегда гарантируют выполнение всех требований по планированию выполнения процессов в заданных пределах. Операционная система Linux не может гарантировать возможности плани- рования задач реального времени. Тем не менее стратегия планирования ОС Linux гарантирует, что задачи реального времени будут выполняться всякий раз, когда они готовы к выполнению. Хотя в ОС Linux и отсутствуют средства, гарантирующие ра- боту в жестком режиме реального времени, тем не менее производительность пла- нировщика ОС Linux в режиме реального времени достаточно хорошая. Ядро серии
    2.6 в состоянии удовлетворить очень жестким временным требованиям.
    Приоритеты реального времени лежат в диапазоне от 1 до MAX_RT_PRIO минус 1,
    По умолчанию значение константы MAX_RT_PRIO равно 100, поэтому диапазон зна- чений приоритетов реального времени по умолчанию составляет от 1 до 99. Это пространство приоритетов объединяется с пространством значений параметра nice
    для стратегии планирования SCHED_OTHER, которое соответствует диапазону прио- ритетов от значения MAX_RT_PRIO до значения (MAX_RT_PRIO+40). По умолчанию это означает, что диапазон значений параметра nice от -20 до +19 взаимно однознач- но отображается в диапазон значений приоритетов от 100 до 139.
    90 Глава 4

    1   ...   7   8   9   10   11   12   13   14   ...   53


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