Второе издание
Скачать 3.09 Mb.
|
Системные вызовы для управления планировщиком Операционная система Linux предоставляет семейство системных вызовов для управления параметрами планировщика. Эти системные вызовы позволяют мани- пулировать приоритетом процесса, стратегией планирования и процессорной при- вязкой, а также предоставляют механизм, с помощью которого можно явно передать процессор (yield) в использование другим заданиям. Существуют различные книги, а также дружественные страницы системного руко- водства (man pages), которые предоставляют информацию об этих системных вызо- вах (реализованных в библиотеке С без особых интерфейсных оболочек, а прямым вызовом системной функции). В табл. 4.3 приведен список этих функций с кратким описанием. О том, как системные вызовы реализованы в ядре, рассказывается в гла- ве 5, "Системные вызовы". Таблица 4.3. Системные вызовы для управления планировщиком Системный вызов Описание nice () Установить значение параметра nice schedsetscheduler () Установить стратегию планирования sched_getscheduler () Получить стратегию планирования sched_setparam () Установить значение приоритета реального времени sched_getparam () Получить значение приоритета реального времени sched_get_priority_max () Получить максимальное значение приоритета реального времени Eched_get_priority_min () Получить минимальное значение приоритета реального времени sched_rr_get_interval () Получить продолжительность кванта времени s c h e d _ s e t a f f i n i t y ( ) Установить процессорную привязку sched_getaffinity () Получить процессорную привязку sched_yield () Временно передать процессор другим заданиям Системные вызовы, связанные с управлением стратегией и приоритетом Системные вызовы sched_setscheduler () и sched_getcheduler () позволяют соответственно установить и получить значение стратегии планирования и приори- тета реального времени для указанного процесса. Реализация этих функций, так же как и для большинства остальных системных вызовов, включает большое количество разнообразных проверок, инициализаций и очистку значений аргументов. Полезная работа включает в себя только чтение или запись полей p o l i c y и r t _ p r i o r i t y структуры t a s k _ s t r u c t указанного процесса. Системные вызовы sched_setparam () и sched_getparam () позволяют устано- вить и получить значение приоритета реального времени для указанного процесса. Последняя функция просто возвращает значение поля r t _ p r i o r i t y , инкапсулиро- ванное в специальную структуру sched_param. Вызовы sched_get_priority_max () Планирование выполнения процессов 91 и sched_get_priority__min () возвращают соответственно максимальное и мини- мальное значение приоритета реального времени для указанной стратегии планиро- вания. Максимальное значение приоритета для стратегий планирования реального времени равно (MAX_USER_RT_PRIO-1), а минимальное значение - 1. Для обычных задач функция nice () увеличивает значение статического приори- тета вызывающего процесса на указанную в аргументе величину. Только пользователь root может указывать отрицательные значения, т.е. уменьшать значение параметра nice и соответственно увеличивать приоритет. Функция nice () вызывает функцию ядра s e t _ u s e r _ n i c e (), которая устанавливает значение полей s t a t i c _ p r i a и prio структуры task_struct. Системные вызовы управления процессорной привязкой Планировщик ОС Linux может обеспечивать жесткую процессорную привязку (processor affinity). Хотя планировщик пытается обеспечивать мягкую или естествен- ную привязку путем удержания процессов на одном и том же процессоре, он также позволяет пользователям сказать: "Эти задания должны выполняться только на ука- занных процессорах независимо ни от чего". Значение жесткой привязки хранится в виде битовой маски в поле cpus_allowed структуры t a s k _ s t r u c t . Эта битовая маска содержит один бит для каждого возможного процессора в системе. По умол- чанию все биты установлены в значение 1, и поэтому процесс потенциально может выполняться на всех процессорах в системе. Пользователь с помощью функции sched_setaffinity () может указать другую битовую маску с любой комбинацией установленных битов. Аналогично функция sched_getaffinity () возвращает тку- щее значение битовой маски cpus_allowed. Ядро обеспечивает жесткую привязку очень простым способом. Во-первых, толь- ко что созданный процесс наследует маску привязки от родительского процесса. Поскольку родительский процесс выполняется на дозволенном процессоре, то и по- рожденный процесс также будет выполняться на дозволенном процессоре. Во-вто- рых, когда привязка процесса изменяется, ядро использует миграционные потоки (mig- ration threads) для проталкивания задания на дозволенный пессор. Следовательно, процесс всегда выполняется только на том процессоре, которому сроцоответствует уста- новленный бит в поле cpus_allowed дескриптора процесса. Передача процессорного времени Операционная система Linux предоставляет системный вызов sched_yield () как механизм, благодаря которому процесс может явно передать процессор под управление другим ожидающим процессам. Этот вызов работает путем удаления процесса из активного массива приоритетов (где он в данный момент находится, потому что процесс выполняется) с последующим помещением этого процесса в ис- текший массив. Получаемый аффект состоит не только в том, что процесс вытес- няется и становшея последним в списке заданий с соответствующим приоритетом, а также в том, что помещение процесса в истекший массив гарантирует, что этот процесс не будет выполняться некоторое время. Так как задачи реального времени никогда не могут быть помещены в истекший массив, они составляют специальный случай. Поэтому они только перемещаются в конец списка заданий с таким же зна- чением приоритета (и не помещаются в истекший массив). В более ранних версиях Глава 4 ОС. Linux семантика вызова s c h e d _ y i e l d () была несколько иной. В лучшем случае задание только лишь перемещалось в конец списка заданий с данным приоритетом. Сегодня для пользовательских программ и даже для потоков пространства ядра должна быть полная уверенность в том, что действительно необходимо отказаться от использования процессора, перед тем как ввязывать функцию s c h e d _ y i e l d (). В коде ядра, для удобства, можно вызывать функцию y i e l d () , которая прове- ряет, что состояние задачи равно TASK_RUNNING, а после этого вызывает функцию sched_yield (). Пользовательские программы должны использовать системный вы- зов s c h e d _ y i e l d (). В завершение о планировщике Планировщик выполнения процессов является важной частью ядра, так как вы- полнение процессов (по крайней мере, для большинства из нас) — это основное ис- пользование компьютера. Тем не менее, удовлетворение всем требованиям, которые предъявляются к планировщику — не тривиальная задача. Большое количество гото- вых к выполнению процессов, требования масштабируемости, компромисс между производительностью и временем реакции, а также требования для различных ти- пов загрузки системы приводят к тому, что тяжело найти алгоритм, который под- ходит для всех случаев. Несмотря на это, новый планировщик процессов ядра Linux приближается к тому, чтобы удовлетворить всем этим требованиям и обеспечить оптимальное решение для всех случаев, включая отличную масштабируемость и при- влекательную реализацию. Проблемы, которые остались, включают возможность точной настройки (или даже полную замену) алгоритма оценки степени интерактивности задания, который приносит много пользы, когда работает правильно, и приносит много неудобств, когда выполняет предсказания неверно. Работа над альтернативными реализациями продолжается. Когда-нибудь мы увидим новую реализацию в основном ядре. Улучшение поведения планировщика для NUMA систем (систем с неоднородным доступом к памяти) становится все более актуальной задачей, так как количество ма- шин на основе NUMA-платформ возрастает. Поддержка доменов планирования (schedu- ler domain) — абстракция, которая позволяет описать топологию процессов; она была включена в ядро 2.6 в одной из первых версий. Эта глава посвящена теории планирования процессов, а также алгоритмам и специфической реализации планировщика ядра Linux. В следующей главе будет рас- смотрен основной интерфейс, который предоставляется ядром для выполняющихся процессов, — системные вызовы. Планирование выполнения процессов 93 5 Системные вызовы Я дро операционной системы предоставляет набор интерфейсов, благодаря ко- торым процессы, работающие в пространстве пользователя, могут взаимодей- ствовать с системой. Эти интерфейсы предоставляют пользовательским програм- мам доступ к аппаратному обеспечению и другим ресурсам операционной системы. Интерфейсы работают как посыльные между прикладными программами и ядром, при этом пользовательские программы выдвигают различные запросы, а ядро вы- полняет их (или приказывает убираться подальше). Тот факт, что такие интерфейсы существуют, а также то, что прикладные программы не имеют права непосредствен- но делать все, что им заблагорассудится, является ключевым моментом для обеспече- ния стабильности системы, а также позволяет избежать крупных беспорядков. Системные вызовы являются прослойкой между аппаратурой и процессами, ра- ботающими в пространстве пользователя. Эта прослойка служит для трех главных целей. Во-первых, она обеспечивает абстрактный интерфейс между аппаратурой и пространством пользователя. Например, при записи или чтении данных из файла прикладным программам нет дела до типа жесткого диска, до среды, носителя ин- формации, и даже до типа файловой системы, на которой находится файл. Во-вто- рых, системные вызовы гарантируют безопасность и стабильность системы. Так как ядро работает посредником между ресурсами системы и пространством пользовате- ля, оно может принимать решение о предоставлении доступа в соответствии с пра- вами пользователей и другими критериями. Например, это позволяет предотвратить возможность неправильного использования аппаратных ресурсов программами, во- ровство каких-либо ресурсов у других программ, а также возможность нанесения вреда системе. И наконец, один общий слой между пространством пользователя и остальной системой позволяет осуществить виртуальное представление процессов, как обсуждается в главе 3, "Управление процессами". Если бы приложения имели свободный доступ ко всем ресурсам системы без по- мощи ядра, то было бы почти невозможно реализовать многозадачность и виртуаль- ную память. В операционной системе Linux системные вызовы являются единствен- ным средством, благодаря которому пользовательские программы могут связываться с ядром; они являются единственной законной точкой входа в ядро. Другие интер- фейсы ядра, такие как файлы устройств или файлы на файловой системе /ргос, в конечном счете сводятся к обращению через системные вызовы. Интересно, что в ОС Linux реализовано значительно меньше системных вызо- вов, чем во многих других операционных системах 1 В этой главе рассказывается о роли и реализации системных вызовов в операци- онной системе Linux. API, POSIX и библиотека С Обычно прикладные программы не разрабатываются с непосредственным ис- пользованием системных вызовов, при этом используются программные интерфей- сы приложений (Application Programing Interface, API). Это является важным, так как в таком случае нет необходимости в корреляции между интерфейсами, которые используют приложения, и интерфейсами, которые предоставляет ядро. Различные API определяют набор программных интерфейсов, которые используются приложе- ниями. Эти интерфейсы могут быть реализованы с помощью одного системного вы- зова, нескольких системных вызовов, а также вообще без использования системных вызовов. В действительности, может существовать один и тот же программный ин- терфейс приложений для различных операционных систем, в то время как реализа- ция этих API может для разных ОС существенно отличаться. Один из наиболее популярных программных интерфейсов приложений в мире Unix-подобных систем базируется на стандарте POSIX. Технически стандарт POSIX включает в себя набор стандартов IЕЕЕ 2 , целью которого является обеспечение пе- реносимого стандарта операционной системы, приблизительно базирующегося на ОС Unix. ОС Linux соответствует стандарту POSIX. Стандарт POSIX является хорошим примером соотношения между интерфейсом API и системными вызовами. Для большинства Unix-подобных операционных си- стем вызовы интерфейса API, определенные в стандарте POSIX, сильно коррелиру- ют с системными вызовами. Конечно, стандарт POSIX создавался для того, чтобы сделать те интерфейсы, которые предоставляли ранние версии ОС Unix, похожими между собой. С другой стороны, некоторые операционные системы, далекие от OS Unix, такие как Windows NT, предоставляют библиотеки, совместимые со стандар- том POSIX. Частично интерфейс к системным вызовам в операционной системе Linux, так же как и в большинстве Unix-систем, обеспечивается библиотекой функций на язы- ке С. Библиотека С реализует главный программный интерфейс приложений для Unix-систем, что включает стандартную библиотеку языка программирования С и интерфейс системных вызовов. Библиотека С используется всеми программами, на- писанными на языке программирования С, а также, в связи со свойствами языка С, может быть легко использована для программ, написанных на других языках про- граммирования. 1 Дли аппаратной платформы x86 существует около 250 системных вызовов (для каждой аппаратной платформы разрешается определять свои уникальные системные вызовы). Хотя не для всех опера- ционных систем опубликованы действительные системные вызовы, но по оценкам для некоторых операционных систем таких вызовов более тысячи. 2 IEEE, eye-trple-E (Институт инженеров по электротехнике и радиоэлектронике, Institute of Electrical and Electronics Engineers) является бесприбыльной профессиональной ассоциацией, дей- ствующей во многих технических областях и отвечающей за многие важные стандарты, такие как стандарт POSIX. Больше информации доступно по адресу: h t t p : / / w w n . i e e e . o r g . 96 Глава 5 вызов p r i n t f () -> p r i r t f ( ) в библиотеке С -> w r i t e ( ) в библиотеке С Системный вызов w r i t e ( ) Приложение > Библиотека С > Ядро Рис. 5.1. Взаимоотношения между приложением, библиотекой С и ядром на примере вызова функции p r i n t f ( ) Дополнительно библиотека функций С также представляет большую часть API- стандарта POSIX. С точки зрения прикладного программиста, системные вызовы не существенны: все, с чем работает программист, — это интерфейс API. С другой стороны, ядро име- ет отношение только к системным вызовам: все, что делают библиотечные вызовы и пользовательские программы с системными вызовами, — это для ядра не существен- но. Тем не менее с точки зрения ядра все-таки важно помнить о потенциальных воз- можностях использования системного вызова для того, чтобы по возможности под- держивать универсальность и гибкость системных вызовов. Общий девиз для интерфейсов ОС Unix — это "предоставлять механизм, а не стратегию". Другими словами, системные вызовы существуют для того, чтобы обе- спечить определенную функцию в наиболее абстрактном смысле. А то, каким обра- зом используется эта функция, ядра не касается. Вызовы syscall Системные вызовы (часто называемые syscall в ОС Linux) обычно реализуются в виде вызова функции. Для них могут быть определены один или более аргументов (inputs), которые могут приводить к тем или иным побочным эффектам 3 , например к записи данных в файл или к копированию некоторых данных в область памяти, на которую указывает переданный указатель. Системные вызовы также имеют возвра- щаемое значение типа long 4 , которое указывает на успешность выполнения опера- ции или на возникшие ошибки. Обычно, но не всегда, возвращение отрицательного значения указывает на то, что произошла ошибка. Возвращение нулевого значения обычно (но не всегда) указывает на успешность выполнения операции. Системные вызовы ОС Unix в случае ошибки записывают специальный код ошибки в глобаль- ную переменную e r r n o . Значение этой переменной может быть переведено в удобо- читаемую формy с помощью библиотечной функции p e r r o r (). Системные вызовы, конечно, имеют определенное поведение. Например, систем- ный вызов g e t p i d () определен для того, чтобы возвращать целочисленное значе- ние, равное значению идентификатора PID текущего процесса. Реализация этой функции в ядре очень проста. 3 Следует обратить внимание на слово "могут". Хотя почти вге вызовы создают различные побоч- ные эффекты (т.е. приводят к каким-либо иэменениям в состоянии системы), тем не менее неболь- шое количество вызовов, как, например, вызов getpid (), просто возвращают некоторые данные ядра. 4 Тип long используется для совместимости с 64-разрядными платформами. Системные вызовы 97 asmlinkage long sys_getpid(void) { return current->tgid; ) Следует заметить, что в определении ничего не говорится о способе реализации. Ядро должно обеспечить необходимую функциональность системного вызова, но ре- ализация может быть абсолютно свободной, главное, чтобы результат был правиль- ный. Конечно, рассматриваемый системный вызов в действительности является та- ким же простым, как и показано, и существует не так уж много различных вариантов для его реализации (на самом деле более простого метода не существует) 5 Даже из такого примера можно сделать пару наблюдений, которые касают- ся системных вызовов. Во-первых, следует обратить внимание на модификатор asmlinkage в объявлении функции. Это волшебное слово дает компилятору инфор- мацию о том, что обращение к аргументам этой функции должно производиться только через стек. Для всех системных вызовов использование этого модификатора является обязательным. Во-вторых, следует обратить внимание, что системный вы- зов g e t p i d () объявлен в ядре, как s y s _ g e t p i d (). Это соглашение о присваивании имен используется для всех системных вызовов операционной системы Linux: си- стемный вызов bar () должен быть реализован с помощью функции sys_bar (). |