netlib.narod.ru | < Назад | Оглавление | Далее > |
Каждый процесс Linux имеет определенный приоритет — целочисленное значение в диапазоне от 1 до 40, которое хранится в члене priority структуры struct task_struct. (Для процессов реального времени применяется также дополнительный член rt_priority структуры struct task_struct. Вскоре это будет рассмотрено подробнее.) Теоретически этот диапазон ограничивается значениями переменных PRIO_MIN (которое определяется оператором #define в строке 16094) и PRIO_MAX (определяется одной строкой ниже). К сожалению, функции, управляющие приоритетом — sys_setpriority и sys_nice — в действительности не обращают никакого внимания на эти объявленные константы, предпочитая им жестко закодированные значения. (Кроме того, они используют максимальное значение правильности (niceness), равное 19, а не 20.) Если уж на то пошло, константы PRIO_MIN и PRIO_MAX вообще нигде не используются. Что ж, это еще одна возможность привлечь читателей к улучшению кода.
Поскольку согласно документации функция sys_nice (строка 27562) вот-вот устареет — предположительно она должна быть реализована заново в стиле функции sys_setpriority — давайте пропустим первую функцию и исследуем вторую.
29213: Функция sys_setpriority принимает три аргумента — which, who и niceval. Аргументы which и who обеспечивают способ указания отдельного процесса, группы процессов или всех процессов, принадлежащих данному пользователю. В зависимости от значения аргумента which аргумент who интерпретируется по-разному; он будет считываться в качестве идентификатора процесса, идентификатора группы процессов или идентификатора пользователя.
29220: Это профилактическая проверка того, что значение which является допустимым. Полагаю, что она выполняется излишне сложно. Вместо
if (which > 2 || which < 0)
лучше было бы написать
if (which != PRIO_PROCESS && which != PRIO_PGRP && which != PRIO_USER)
или, по крайней мере
if (which > PRIO_USER || which < PRIO_PGRP)
Между прочим, это же справедливо по отношению к строке 29270.
29226: niceval указывается в интервале пользователя — т.е. в диапазоне от –20 до 19 (или, по крайней мере, так предполагается), а не в диапазоне от 1 до 40, который является предпочтительным для внутренней структуры ядра. Как следует из названия, это значение «требовательности», а не приоритета. Таким образом, функция sys_setpriority вынуждена пропустить несколько значений, чтобы выполнить соглашение, одновременно усекая значения аргумента niceval, выходящие за пределы допустимого диапазона. Признаю, что был сбит с толку сложностью этого кода. При действительно используемом значении переменной DEF_PRIORITY равном 20, следующий гораздо более простой код явно дал бы такой же результат:
if (niceval < -19) priority = 40; else if (niceval > 19) priority = 1; else priority = 20 - niceval;
Эта версия также могла бы учитывать значение DEF_PRIORITY, оставаясь более простой, чем код в функции sys_setpriority. Таким образом, либо я чего-то не понял, либо имеющийся код действительно излишне сложен.
29241: Выполняет цикл по всем задачам в списке задач системы, выполняя разрешенные изменения. proc_sel (строка 29190) сообщает, удовлетворяет ли данный процесс значениям аргументов which и who, которые используются для выбора процесса; эта функция вынесена за пределы функции sys_setpriority, поскольку она используется также и функцией sys_getpriority.
И sys_setpriority, и sys_getpriority (которая имеет аналогичный внутренний цикл, начинающийся в строке 29274) можно было бы несколько ускорить для общего случая получения или установки приоритета единственного процесса (по крайней мере, путем более раннего прерывания цикла for_each_task). Функция sys__setpriority вызывается не очень часто, но sys_getpriority может быть достаточно распространенной, чтобы оправдать приложенные усилия.
Функция влияет только на член priority процесса — т.е. на его статический приоритет. Вспомните, что процессы имеют также динамические приоритеты, представленные членом counter, который был рассмотрен при рассмотрении функций schedule и goodness. Мы уже видели, что функция schedule периодически пополняет динамический приоритет каждого процесса, исходя из его статического приоритета, когда планировщик видит, что значение counter равно 0. Но еще не было показано, где значение counter уменьшается. Каким образом оно достигает 0?
Для однопроцессорной системы ответ заключается в функции update_process_times (строка 27382). (Освещение этой функции отложено до главы 10.) Функция update_process_times вызывается как часть функции update_times (строка 27412), которая, в свою очередь, является частью прерывания таймера, освещенного в главе 6. В результате, эта функция вызывается достаточно часто — 100 раз за секунду. (Конечно, это часто только с человеческой точки зрения, а для процессора такая частота является чрезвычайно малой.) При каждом вызове эта функция уменьшает значение counter текущего процесса на количество «тиков» (сотые доли секунды — см. главу 6), произошедших с момента последнего вызова. Обычно, это только один тик, но ядро может пропускать тики таймера, если, например, он занят обслуживанием прерывания. Когда значение счетчика падает ниже 0, функция update_process_times устанавливает флаг need_resched, показывающий, что этот процесс нуждается в повторном планировании.
Теперь, поскольку приоритет процесса, установленный по умолчанию (в терминах приоритетов ядра, а не значений требовательности процесса (niceness) области пользователя) равен 20, процесс по умолчанию получает 21-тиковый временной квант. (Да, именно 21 тик, а не 20, поскольку процесс не помечается для повторного планирования до тех пор, пока его динамический приоритет не упадет ниже 0.) Один тик равен одной сотой секунды или 10 миллисекундам, и таким образом временной квант, устанавливаемый по умолчанию, равен 210 миллисекундам — около одной пятой секунды — как и задокументировано в строке 16466.
Меня этот результат удивил, поскольку я был уверен, что для отзывчивой системы должен был бы требоваться гораздо меньший временной квант — я на столько был уверен в этом, что вначале заподозрил наличие ошибки в документации. Однако теперь мне ясно, что удивляться не следовало. В конце концов, процессы часто не полностью используют выделенные им временные кванты, поскольку они должны часто блокироваться для выполнения ввода/вывода. А когда несколько процессов обращаются к процессору, слишком частое переключение между ними оказывается бессмысленным. (Особенно в такой архитектуре, как х86, где переключения контекстов обходятся сравнительно дорого.) В конечном счете я вынужден был признать, что никогда не замечал в своем компьютере, работающем под управлением Linux, никаких задержек ответа и поэтому пришел к выводу, что 210-миллисекундный временной квант является удачным выбором — несмотря на то, что вначале этот интервал казался удивительно длинным.
Если по какой-либо причине требуются временные кванты, превышающие по длительности даже текущий максимум (410 миллисекунд, когда значения приоритетов повышаются до 40), можно просто воспользоваться политикой планирования SCHED_FIFO и освободить процессор, когда будете готовы к этому (или переписать функции sys_setpriority и sys_nice).
netlib.narod.ru | < Назад | Оглавление | Далее > |