netlib.narod.ru | < Назад | Оглавление | Далее > |
Основная функция планирования ядра удачно называется schedule и начинается в строке 26686. В действительности, это очень простая функция, более простая чем кажется, хотя ее значение слегка сбивает с толку, поскольку объединяет три алгоритма планирования. Кроме того, некоторым усложнением она обязана своей поддержке SMT, рассмотрение которой будет отложено до главы 10.
Используемый в конкретном случае алгоритм планирования зависит от процесса. Алгоритм планирования, используемый для данного процесса, называется его политикой планирования и отражается в члене policy структуры struct task_struct процесса. Обычно в члене policy установлен только один из разрядов SCHED_OTHER, SCHED_FIFO или SCHED_RR. Но кроме того может быть установлен и разряд SCHED_YIELD, если процесс решит освободить центральный процессор — например, вызвав системную функцию sched_yield (см. sys_sched_yield в строке 27757).
Константы SCHED_XXX определяются посредством оператора #define в строках с 16196 по 16202.
16196: SCHED_OTHER означает, что применяется традиционное планирование Unix — данный процесс не является процессом реального времени.
16197: SCHED_FIFO означает, что этот процесс является процессом реального времени и объектом для планировщика FIFO (first in, first out — первым зашел, первым вышел) POSIX.1b. Он должен продолжаться до тех пор, пока не заблокирует ввод/вывод, явным образом не освободит процессор, либо не будет вытеснен другим процессом реального времени с более высоким приоритетом rt_priority. В реализации Linux процессы SCHED_FIFO имеют выделенные для них временные кванты — просто они не обязаны освобождать процессор, когда их временной квант заканчивается. Следовательно, как диктуется POSIX.1b, такой процесс действует, как если бы он не имел временного кванта. То, что процесс в любом случае отслеживает временной квант, это просто вопрос удобства реализации, поэтому не обязательно засорять код конструкцией
if (!(current->policy & SCHED_FIFO)) { ... }
Кроме того, вероятно, так он работает быстрее — другие политики действительно вынуждены отслеживать временные кванты, а постоянная проверка необходимости такого отслеживания была бы медленней простого отслеживания.
16198: SCHED_RR означает, что этот процесс является процессом реального времени и объектом планировщика RR (round-robin) POSIX.lb. Это аналогично SCHED_FIFO, за исключением того, что временные кванты имеют значение. Когда временной квант процесса SCHED_RR истекает, он перемещается в конец списка процессов SCHED_FIFO и SCHED_RR с таким же приоритетом rt_priority.
SCHED_YIELD является не политикой планирования, а дополнительным разрядом, который пресекает политики планирования. Как уже было сказано ранее, этот разряд указывает планировщику освободить процессор, если тот требуется кому-либо другому. В частности, обратите внимание, что это приведет даже к тому, что процесс реального времени освободит процессор для процесса не реального времени.
26689: prev и next будут установлены для двух процессов, которые представляют для schedule наибольший интерес: для одного, который выполнялся во время вызова schedule (prev), и для второго, которому центральный процессор должен быть предоставлен следующим (next). Имейте в виду, что prev и next могут быть одинаковыми — schedule может повторно запланировать процесс, который уже имел доступ к процессору.
26706: Как уже упоминалось в главе 6, здесь выполняются «нижние половины» обработчиков прерываний.
26715: Реализует часть планировщика реального времени (SCHED_RR), перемещая «исчерпавшийся» процесс RR — уже полностью использовавший свой временной квант — в конец очереди, чтобы другие процессы RR с таким же приоритетом могли сделать свой ход. Одновременно это обновляет временной квант исчерпавшегося процесса. Важно, чтобы такое обновление не выполнялось для SCHED_FIFO, чтобы последующие процессы не были вынуждены освобождать процессор при истечении отведенных им временных квантов.
26720: schedule часто используется потому, что какой-то другой фрагмент программы пришел к заключению о необходимости перемещения процесса в или из состояния TASK_RUNNING — например, при выполнении аппаратного условия, которого дожидался процесс; поэтому данный переключатель при необходимости изменяет состояние процесса. Если процесс уже находится в состоянии TASK_RUNNING, он остается неизменным. Если он был прерываемым (дожидающимся сигнала) и сигнал пришел, процесс возвращается в состояние TASK_RUNNING. Во всех остальных случаях (например, если процесс переходит в состояние TASK_UNINTERRUPTIBLE) процесс долен быть удален из текущей очереди.
26735: Инициализирует значением, соответствующим первой задаче в текущей очереди; р выполнит циклический просмотр всех задач в очереди.
26736: с отслеживает наибольшую адекватность (goodness) всех процессов в текущей очереди — наиболее адекватный процесс — тот, который имеет наибольшие требования к процессору. (Функция goodness будет вскоре рассмотрена.) Чем выше адекватность, тем лучше; причем значение адекватности процесса никогда не бывает отрицательным, в отличие от непонятной ситуации, ставшей привычной для пользователей Unix, когда более высокий приоритет (обычно называемый более высоким уровнем правильности (niceness)) приводит к тому, что процессу будет выделен меньший интервал времени процессора. (По крайней мере, это имеет смысл внутри ядра.)
26757: Начинает просмотр списка задач, отслеживая процесс с наилучшим значением адекватности. Обратите внимание, что это изменяет представление о наилучшем процессе только когда текущая запись прервана, а не когда она просто привязана. Следовательно, связи разрываются в интересах первого процесса в очереди.
26758: В этом цикле рассматриваются только те процессы, которые могут быть запланированы. Реализация SMP-макроса can_schedule приведена в строке 26568; его определение заставляет ядро SMP рассматривать планирование задач в этом центральном процессоре только если задача еще не выполняется в нем. (В этом заключен большой смысл — было бы расточительным без необходимости перетасовывать задачи.) однопроцессорная версия приведена в строке 26573 и это значение всегда истинно — другими словами, в однопроцессорной системе все процессы в текущей очереди соревнуются за получение доступа к центральному процессору.
26767: Значение адекватности, равное 0, означает, что процесс полностью использовал свой лимит временного кванта, или что он был вынужден освободить процессор. Если все процессы в текущей очереди имеют значение адекватности, равное 0, в конце цикла с равно 0, В этом случае schedule повторно вычисляет счетчики процессов; новое значение счетчика равно половине старого значения плюс значение статического приоритета процесса — и поскольку старое значение равно 0, если только процесс не освободил процессор, обычно schedule всего лишь повторно инициализирует счетчики значениями их статического приоритета. (Встретившийся в этом цикле процесс, мог бы также быть добавлен обработчиком прерываний или подпрограммой fork другого процессора, пока функция schedule искала наивысшее значение адекватности; поэтому значение его счетчика могло быть ненулевым. Однако, такой случай встречается редко.) Тем не менее, планировщик не утруждает себя повторным вычислением того, какой процесс обладает наивысшей адекватностью в данный момент; он лишь планирует процесс, первым встретившийся ему в предыдущем цикле. На тот момент этот процесс был первым найденным им, который имел наилучшее на тот момент значение адекватности (0), поэтому функция schedule заключает, что пока все сделано, и что программа будет успешно работать в течение длительного времени. (Помните, что речь идет о мышлении в стиле «быстро и приблизительно».)
26801: Если schedule решила запланировать другой процесс из тех, что выполнялись ранее, она должна подавить старый процесс и позволить выполняться новому. Это выполняется с помощью функции switch_to, которая будет исследована следующей. Один важный результат работы функции switch_to может показаться очень странным разработчикам приложений: обращение к функции schedule не выполняет возврат. По крайней мере, не сразу; функция осуществляет возврат, когда система снова переключается на текущую задачу. Особым случаем вызова schedule является случай, когда она вызывается потому, что задача осуществляет выход; в этом случае вызов schedule никогда не возвращает значение — поскольку ядро никогда не переключается на задачу, которая осуществила выход. Еще один особый случай — когда функция schedule не запланировала другой процесс — т.е., если значения next и prev равны по окончании работы schedule — и, следовательно, переключение контекста не выполняется, а schedule действительно возвращает значение немедленно.
26809: В однопроцессорных системах функции __schedule_tail и reacquire_kernel_lock в конце функции schedule не представляют собой ничего особенного, поэтому мы закончим исследование ядра планировщика. При случае, чтобы убедиться в правильном понимании кода, попытайтесь доказать следующее свойство: если текущая очередь пуста, следующей будет запланирована простаивающая задача.
switch_to обрабатывает переключение с одного процесса на другой, что называется переключением контекста; это функция самого нижнего уровня, которая обрабатывается по разному в различных процессорах. Интересно отметить, что на платформе х86 разработчики ядра решили обрабатывать большую часть переключений контекста программно, отказавшись от некоторой части аппаратной поддержки. Причины этого изложены в заглавном комментарии над функцией __switch_to (строка 2638), которая вместе с макросом switch_to (строка 12939) обрабатывает переключение контекста.
Поскольку переключение контекста в значительной степени зависит от понимания того, как ядро работает с памятью, что подробно описано лишь начиная со следующей главы, в этой главе бегло освещается эта тема. При переключении контекста главное помнить, где мы находились, и что делали — т.е. текущий контекст нужно сохранить, и лишь затем переключаться на другой контекст, сохраненный ранее. Используя незначительную постороннюю помощь, макрос switch_to сохраняет два важных фрагмента контекста, описанные далее.
12945: Во-первых, макрос switch_to сохраняет регистр ESP, который указывает на текущий стек процесса. Стек подробно описан в следующей главе; а пока, достаточно знать, что стек содержит локальные переменные и информацию о вызовах функций. Макрос switch_to сохраняет также регистр EIP, который является указателем текущей инструкции процесса — адресом следующей инструкции, которую он должен был бы выполнить, если бы ему было разрешено продолжать выполняться.
12948: Выталкивает next->tss.eip — указатель сохраненной инструкции — в стек возврата, делая его адресом возврата, если немедленный последующий переход jmp к функции __switch_to осуществляет возврат. Побочным эффектом возврата из функции __switch_to является получение нового процесса.
12949: Вызывает функцию __switch_to (строка 2638), которая выполняет работу по сохранению и восстановлению сегментных регистров и таблиц страниц. Это станет более понятным после прочтения главы 8.
12955: tss означает task-state segment (сегмент состояния задачи). Этот термин был введен компанией Intel для обозначения функции процессора, которая поддерживает аппаратное переключение контекстов. Вместо этого в коде ядра переключение контекстов выполняется программным путем, но разработчики продолжают использовать TSS для отслеживания состояния процесса. Член tss структуры struct task_struct имеет тип struct thread_struct, описание которого было опущено для экономии места в книге. Члены этой структуры всего лишь соответствуют TSS системы х86 — существуют члены для регистров EIP и ESP, и т.д.
netlib.narod.ru | < Назад | Оглавление | Далее > |