StudyDocs.ru Logo

report3.docx


Санкт-Петербургский политехнический университет Петра Великого Институт Информационных Технологий и УправленияКафедра компьютерных систем и программных технологий















Отчёт по практической работе № 3по предмету «Проектирование ОС и компонентов»
Изучение работы системных вызовов
















Работу выполнил студент гр. 63501/3 Мартынов С. А. Работу принял преподаватель Душутина Е. В.Санкт-Петербург 2016

Содержание



В рамках данной работы необходимо ознакомиться с некоторыми системными вызовами
sched_setparamsched_getparamsched_rr_get_interval
В процессе работы требуется изучить принцип работы и дать описание механизма реализа- ции системных вызовов в исходных кодах ядра Linux версий:Linux kernel 2.6.32Linux kernel 4.1Исходные коды получить из официального источника
.

В практической части привести демонстрацию использования изучаемых системных вызо- вов в программе. Результаты исследований оформить в виде отчёта с примерами кода и ссылками на источники.


В языке C для осуществления операций ввода-вывода или работы с памятью используются механизмы стандартной библиотеки языка, объявленные в заголовочном файле stdio.h. Все эти механизмы являются надстройками над низкоуровневыми механизмами ввода-вывода ядра операционной системы. Пользовательские программы взаимодействуют с ядром операционной системы посредством специальных механизмов, называемых системными вызовами (system calls, syscalls). Внешне системные вызовы реализованы в виде обычных функций языка C, однако каждое обращение к таким функциям приводит к передаче управления непосредственно ядру операционной системы. Список всех системных вызовов Linux можно найти в файле /usr/include/asm/unistd.h[1].Основным предназначением ядра всякой операционной системы, является обслуживание системных вызовов из выполняющихся в системе процессов (операционная система тратит на это порядка 99% своего времени)[2]. Возникает системный вызов когда пользовательский процесс требует некоторой службы, реализуемой ядром (такой как открытие файла), и вызывает специальную функцию (например, open). В этот момент пользовательский процесс переводится в режим ожидания. Ядро анализирует запрос, пытается его выполнить и передает результаты пользовательскому процессу, который затем возобновляет свою работу.Системные вызовы в общем случае защищают доступ к ресурсам, которыми управляет ядро, при этом самые большие категории системных вызовов имеют дело с вводом/выводом (open, close, read, write, poll и многие другие), процессами (fork, execve, kill и т.д.), временем (time, settimeofday и т.п.) и памятью (mmap, brk и пр.) Под это категории подпадают практически все системные вызовы[3].При изучении реализации системного вызова может оказаться, что ожидания пользователя отличаются от имеющегося кода. Во-первых, библиотека С в Linux реализует некоторые системные вызовы полностью в терминах других системных вызовов. Например, реализа- ция waitpid сводится к простому вызову wait4, однако в документации на обе функции ссылаются как на системные вызовы. Другие, более традиционные системные вызовы, наподобие sigmask и ftime, реализованы почти полностью в библиотеке С, а не в ядре Linux.Системный вызов должен возвращать значение типа int и только int. В соответствие с принятыми соглашениями, возвращаемое значение равно 0 или любому положительному числу в случае успеха и любому отрицательному числу в случае неудачи. Это вполне согласуется с теми подходами к разработке, которые приняты в сообществе C-разработчиков– в случае, когда какая-либо функция из стандартной библиотеки С завершается неудачей, она устанавливает глобальную целочисленную переменную errno для отражения природы

возникшей ошибки; те же соглашения актуальны и для системных вызовов. Однако, способы, в соответствие с которыми это происходит в случае с системными вызовами, невозможно предугадать, изучая лишь исходный код ядра. В случае сбоя системные вызовы возвращают отрицательные значения кодов ошибок, а за оставшуюся обработку отвечает стандартная библиотека C. В нормальных ситуациях системные функции ядра не вызываются непосредственно из пользовательского кода, а через тонкий слой кода в рамках стандартной библиотеки С, который в точности ответственен за подобного рода трансляцию[3].Исторически сложилось, что с некоторого момента отрицательные значения возврата из системных вызовов больше не указывают на наличие ошибки. Несколько системных вызовов (подобных lseek) реализованы таким образом, что они даже в случае успеха воз- вращают большие отрицательные значения; в настоящий момент возвращаемые значения, соответствующие ошибке, лежат в пределах от -1 до -4095. Теперь стандартная библиотека С более избыточна в интерпретации значений возврата из системных вызовов (ядро при получении отрицательных значений возврата не предпринимает никаких особых действий).В данной работе мы изучим назначение некоторых системных вызовов, связанных с работой планировщика и проследим их вызов из пользовательского кода.

Системные вызовы процессов реального времени
Для изучения системных вызовов sched_setparam, sched_getparam и sched_rr_get_interval целесообразно рассмотреть всю группу системных вызовов, позволяющих процессу менять его дисциплину планирования и, в частности, становиться процессом реального времени. Процесс должен иметь способность CAP_SYS_NICE (т.е. способностью изменять при- оритет чужих процессов), чтобы модифицировать значения полей rt_priority и policy у дескриптора любого процесса, включая собственный.Системный вызов sched_getscheduler запрашивает политику планирования, действую- щую в отношении процесса, идентифицируемого параметром pid. Если значение pid во время вызова равен 0, считывается политика вызвавшего процесса. В случае успеха систем- ный вызов возвращает политику sched_fifo, sched_rr или sched_normal (последняя также называется sched_other). Соответствующая служебная процедура sys_sched_getscheduier вызывает функцию find_process_by_pid, которая находит дескриптор процесса по пере- данному значению pid и возвращает значение его поля policy.Системный вызов sched_setscheduier устанавливает как политику планирования, так и соответствующие параметры для процесса, идентифицируемого параметром pid. Как и в случае с sched_getscheduler, если pid равен 0, то устанавливаются параметры плани- ровщика, применяемые к вызвавшему процессу. Соответствующая служебная процедура sys_sched_setscheduler вызывает функцию do_sched_setscheduler. Эта функция прове- ряет допустимость политики планирования, определяемой параметром policy, и нового приоритета, определяемого параметром param->sched_priority. Она также проверяет, есть ли у процесса способность CAP_SYS_NICE, или наличие прав суперпользователя у его владельца. Если все в порядке, она удаляет процесс из очереди на выполнение (если он выполняемый), обновляет статический и динамический приоритеты и приоритет реального времени у процесса, возвращает процесс в очередь на выполнение и, если необходимо, вызывает функцию resched_task для вытеснения текущего процесса, принадлежащего данной очереди.Системный вызов sched_getparam читает параметры процесса, идентифицируемого параметром pid. Если pid равен 0, считываются параметры текущего процесса. Соответ- ствующая служебная процедура sys_sched_getparam, как и следует ожидать, находит указатель на дескриптор процесса по параметру pid, сохраняет поле rt_priority в локальной переменной типа sched_param и вызывает функцию copy_to_user, чтобы скопировать это значение в адресное пространство процесса, по адресу, заданному параметром param.Системный вызов sched_setparam аналогичен вызову sched_setscheduler, различие состо- ит в том, что sched_setparam не позволяет вызвавшему процессу задавать значение поля

policy. Соответствующая служебная процедура sys_sched_setparam вызывает функцию do_sched_setscheduler практически с теми же параметрами, что и служебная процедура sys_sched_setscheduler.Системный вызов sched_yieido позволяет процессу добровольно освободить процессор без приостановки своего выполнения. Процесс остается в состоянии TASK_RUNNING, а планировщик заносит его либо в набор процессов с истекшими квантами времени (если это обычный процесс), либо в конец списка в очереди на выполнение (если это процесс реального времени). Затем вызывается функция schedule. В результате у других процессов с тем же динамическим приоритетом появляется возможность поработать. Данный вы- зов используется, в основном, процессами реального времени, принадлежащими классу SCHED_FIFO.Системные вызовы sched_get_priority_min и sched_get_priority_max возвращают, соответственно, минимальный и максимальный статический приоритет реального времени, который может быть использован при проведении политики планирования, идентифициру- емой параметром policy. Служебная процедура sys_sched_get_priority_min возвращает 1, если текущий процесс является процессом реального времени, и 0 в противном случае. Служебная процедура sys_sched_get_priority_max возвращает 99 (наивысший приоритет), если текущий процесс является процессом реального времени, и 0 в противном случае.Системный вызов sched_rr_get_interval записывает в структуру, хранящуюся в ад- ресном пространстве режима пользователя, квант времени, соответствующий круговому принципу работы, для процесса реального времени, идентифицируемого параметром pid. Если pid равен 0, системный вызов записывает квант времени текущего процесса. Как и в предыдущих примерах, соответствующая служебная процедура sys_sched_rr_get_interval вызывает функцию find_process_by_pid, для получения дескриптора процесса по значе- нию pid. Затем она преобразует базовый квант времени выбранного процесса в секунды и наносекунды, и копирует эти числа в структуру пользовательского режима. В соответствии с соглашением, временной квант процесса реального времени, принадлежащего классу "первым вошел первым вышел равен нулю.Рассмотренные системные вызовы позволяют реализовать различные расширения реально- го времени POSIX.1b начиная с ядра Linux версии 2.6[4]. Для определения задачи реального времени в Linux есть три основных параметра:Класс планированияПриоритет процессаИнтервал времени

Таблица 1: Диапазоны приоритетов различных политик планирования

Класс планированияДиапазон приоритетов
SCHED_OTHER0
SCHED_FIFO1 - 99
SCHED_RR1 - 99

Планировщик Linux предлагает три класса планирования, два для приложений реального времени и один для приложений не реального времени. Этими тремя классами являются:SCHED_FIFO: политика планирования реального времени первый вошёл, первый вы- шел (First-In First-Out). Алгоритм планирования не использует никаких интервалов времени. Процесс SCHED_FIFO выполняется до завершения, если он не заблоки- рован запросом ввода/вывода, вытеснен высокоприоритетным процессом, или он добровольно отказывается от процессора. Следует обратить внимание на следующие моменты:Процесс SCHED_FIFO, который был вытеснен другим процессом более вы- сокого приоритета, остаётся во главе списка с его приоритетом и возобновит выполнение, как только все процессы с более высоким приоритетом будут вновь заблокированы.Когда процесс SCHED_FIFO готов к работе (например, после пробуждения от операции блокировки), он будет вставлен в конец списка с его приоритетом.Вызов sched_setscheduler или sched_setparam поставит процесс SCHED_FIFO в начало списка. Как следствие, это может вытеснить работающий в данный момент процесс, если его приоритет такой же, как и у работающего процесса.SCHED_RR: циклическая (Round-Robin) политика планирования реального време- ни. Она похожа на SCHED_FIFO с той лишь разницей, что процессу SCHED_RR разрешено работать как максимум время кванта. Если процесс SCHED_RR ис- черпывает свой квант времени, он помещается в конец списка с его приоритетом. Процесс SCHED_RR, который был вытеснен процессом с более высоким приоритетом, завершит оставшуюся часть своего кванта времени после возобновления выполнения.SCHED_OTHER: стандартный планировщик Linux с разделением времени для про- цессов, работающих не в реальном времени.Диапазоны приоритетов для различных политик планирования показаны в Таблице 1. Ядро позволяет значению nice быть установленным как для процесса SCHED_RR или

SCHED_FIFO, но это не будет иметь никакого влияния на планирование, пока задача выполняется с классом SCHED_OTHER.Точка зрения ядра на приоритеты процессов отличается от точки зрения процессов. Соот- ветствие между приоритетами пользовательского пространства и пространства ядра для задач реального времени в ядре показывает Рисунок 1.

Рис. 1: Отображение приоритетов пользовательского уровня на пространство ядра
Для ядра низкое значение означает высокий приоритет. Приоритеты реального времени в ядро находятся в диапазоне от 0 до 98. Таким образом, пользовательский приоритет 1 связывается с приоритетом ядра 98, приоритет 2 с 97, и так далее.Интервал времени действителен только для процессов SCHED_RR. Процессы SCHED_FIFO можно рассматривать как имеющие бесконечный интервал времени. Так что это обсуждение касается только процессов SCHED_RR.Linux устанавливает минимальный интервал времени для процесса как 10 мс, интервал времени по умолчанию как 100 мс, а максимальный интервал времени как 200 мс. Интер- валы времени заполняются вновь после их окончания[4]. В версии 2.6 интервал времени процесса рассчитывается так:


Можно заметить, что static_prio содержит значение nice для процесса. Ядро преобразует диапазон nice c -20 до +19 во внутренний диапазон nice в ядре от 100 до 139. Поле nice процесса конвертируется в такой масштаб и сохраняется в static_prio. Таким образом, значение nice -20 соответствует static_prio 100, а +19 для nice, static_prio 139. Наконец, интервал времени процесса возвращает функция task_timeslice.
Отметим, что static_prio является единственной переменной в расчёте интервала времени. Таким образом, можно сделать некоторые важные выводы:Все процессы SCHED_RR выполняются по умолчанию с интервалом времени в 100 мс, поскольку они обычно имеют значение nice, равное 0.При значении nice -20 процесс SCHED_RR получит интервал времени 200 мс, а при nice +19 процесс SCHED_RR получит интервал времени 10 мс. Таким образом, значение nice может быть использовано для управления выделением интервала времени для процессов SCHED_RR.Чем меньше значение nice (то есть, приоритет более высокий), тем больше интервал времени.

Реализация системных вызовов в различных версиях ядраПроцессы реального времени Linux добавляют новый уровень к схеме приоритетов. При- оритет реального времени хранится в члене rt_priority структуры struct task_struct и является целым числом в диапазоне от 0 до 99. (Значение, равное 0, означает, что процесс не является процессом реального времени, и в этом случае его членом policy должен быть SCHED_OTHER.)Задачи реального времени используют тот же член counter, что и их аналоги не реального времени, и поэтому их динамические приоритеты обрабатываются таким же образом. Задачи реального времени даже используют член priority для той же цели, что и задачи не реального времени – в качестве значения, посредством которого они пополняют зна- чение counter, когда оно полностью использовано. Член priority используется только для ранжирования процессов реального относительно друг друга, в остальных случаях они обрабатываются идентично процессам не реального времени.Поле rt_priority процесса устанавливается в качестве части определения его политики планирования с помощью стандартизованных POSIX.1b функций sched_setscheduler и sched_setparam (которые, обычно, имеет право вызывать только привилегированный пользователь, как будет показано при рассмотрении возможностей). Это означает, что политика планирования процесса может изменяться во время его существования, если, конечно, процесс имеет разрешение выполнять изменение.Интерфейс изучаемых системных вызовов оказался идентичным точностью до смещения строк) для рассматриваемых ядер, поэтому остановимся подробнее на одном из них (2.6). Листинг файла unistd достаточно объёмный и по этой причине не приведён в отчёте.Системные вызовы, реализующие функции POSIX sched_setscheduler (строка 27688) и sched_setparam (строка 27694), делегируют всю реальную работу функции setscheduler (строка 27618).Исследуем эту функцию подробнее.
27618: Тремя аргументами этой функции являются целевой процесс pid (значение 0 означает текущий процесс), новая политика планирования policy и param, структура, содержащая дополнительную информацию – новое значение rt_priority.27630: Выполняя некоторые профилактические проверки, функция setscheduler копиру- ет переданную структуру struct sched_param из области пользователя. Эта структура, определенная в строке 16204, имеет только один член sched_priority, который является

затребованным вызывающей функцией значением rt_priority для целевого процесса.
27639: Находит целевой процесс, используя функцию find_process_by_pid (строка 27608), которая возвращает либо указатель на текущую задачу (если pid равен 0), либо указатель на процесс с заданным PID (если таковой существует), либо NULL (если не существует ни одного процесса с этим PID).27645: Если аргумент policy был отрицательным, текущая политика планирования сохра- няется. В противном случае она принимается временно, если ее значение допустимо.27657: Убеждается, что приоритет находится в допустимом диапазоне. Это достигается несколько сложным путем. Данная строка — всего лишь первый шаг, подтверждающий, что переданное значение не слишком выходит за рамки диапазона.27659: Теперь известно, что приоритет реального времени лежит в диапазоне между 0 и 99, включая крайние значения. Если значением policy является SCHED_OTHER, но новый приоритет реального времени не равен 0, этот тест не пройдет. Тест не пройдет, также, если policy определяет один из планировщиков реального времени, но новый приоритет реального времени равен 0 (если он не равен 0, значит, он имеет значение от 1 до 99, как и должно быть). В противном случае тест будет успешным. Эта конструкция не очень понятна в представленном виде, но её можно привести к более читабельному виду (и наверняка это не на много более медленно):
27663: He каждому процессу должно быть разрешено устанавливать собственную политику планирования или политику планирования другого процесса. Если бы было можно, любой процесс мог бы узурпировать центральный процессор, по существу блокируя систему, просто устанавливая свою политику планирования в значение SCHED_FIFO и входя в бесконечный цикл. Естественно, это нельзя допустить. Поэтому функция setscheduler позволяет процессу устанавливать собственную политику планирования, только если он имеет возможность сделать это.

27666: Нежелательно, чтобы кто угодно мог изменять политику планирования процессов любых других пользователей. Как правило, пользователю должно разрешаться изменять политику планирования только собственных процессов. Поэтому setscheduler убеждается, что пользователь либо устанавливает планировщик собственного процесса, либо имеет возможность устанавливать политику планирования любых пользователей.27672: Именно здесь функция setscheduler наконец берется за дело, устанавливая поля policy и priority в структуре struct task_struct целевого процесса. И, если процесс находится в текущей очереди (что проверяется путем проверки того, что значение его члена next_run не является NULL), он перемещается в ее начало – это несколько странно; возможно, это было сделано, чтобы помочь процессу SCHED_FIFO получить доступ к процессору. Процесс помечается для повторного планирования, а функция setscheduler осуществляет уборку и выход.

Использование системных вызовов из пользователь- ского кодаДля отслеживания обращений к системным вызовам используется код из листинга 1. Результаты его работы представлены на рисунке 2.Листинг 1: демонстрация использования системных вызовов (src/syscalls/sched.c)1 #i nc l ude <sched . h>2 #i nc l ude <s td i o . h>
3
4 i n t main ( ) {5 s t r u c t sched_param param , new_param ;
6
7 /*8 * Проце с с запускае тся с политикой по умолчанию SCHED_OTHER,9 * е сли не порожд¨e н проце с с ом SCHED_RR или SCHED_FIFO.10 */
11
12 p r i n t f ( " s t a r t p o l i c y = %d\n" , sched_ getscheduler ( 0 ) ) ;13 /*14 * выводит > s t a r t p o l i c y = 0 .15 * (Для политик SCHED_FIFO или SCHED_RR, sched_ getscheduler16 * во звращае т 1 и 2 с оотве тс твенно )17 */
18
19 /*20 * Со зда ¨eм проце с с SCHED_FIFO, работающий с о средним приорите том21 */22 param . s c he d_ pri o r i ty = ( sched_get_priority_min (SCHED_FIFO) +23 sched_get_priority_max (SCHED_FIFO) ) / 2 ;24 p r i n t f ( "max p r i o r i t y = %d , min p r i o r i t y = %d , my p r i o r i t y = %d\n " ,25 sched_get_priority_max (SCHED_FIFO) ,26 sched_get_priority_min (SCHED_FIFO) ,27 param . s c he d_ pri o r i ty

) ;

= 1 ,






) != 0 ) {














отоку/проце с су вызов sched_ yieldе го приорите том. э тот вызов





мя работы */x (SCHED_FIFO) ;









e d_ pri o r i ty ) ;



Рис. 2: Результат запуска программы sched
Для отслеживания системных вызовов будем использовать strace. Эта утилита отслеживает системные вызовы и представляют собой механизм трансляции, обеспечивающий интерфейс между процессом и операционной системой (ядром). Эти вызовы могут быть перехвачены и прочитаны, что позволяет лучше понять, что процесс пытается сделать в заданное время. Перехватывая эти вызовы, можно добиться лучшего понимания поведения процессов, особенно в процессе отладки. Функциональность операционной системы, позволяющая отслеживать системные вызовы, называется ptrace. Strace вызывает ptrace и читает данные о поведении процесса, возвращая отчет.Отчёт по работе программы sched представлен в листинге 2.
Листинг 2: Протокол системных вызовов

1execve ( " . / sched " , [ " . / sched " ] , [ / * 27vars * /] ) = 0
2brk ( 0 )= 0 xd 3 f 000
3a c c e s s ("/ e tc / ld . so . nohwcap " , F_OK)= −1 ENOENT (No such f i l e or
d i r e c to r y )
4mmap(NULL, 8192 , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,−1, 0 ) = 0 x 7 fe 49 bdb4000
5a c c e s s ("/ e tc / ld . so . preload " , R_OK) = −1 ENOENT (No such f i l eor
d i r e c to r y )
6open ("/ e tc / ld . so . cache " , O_RDONLY|O_CLOEXEC) = 3
7f s t a t ( 3 , { st_mode=S_IFREG| 0 6 4 4 , s t_ s i z e =144114 , . . . } ) = 0
8mmap(NULL, 144114 , PROT_READ, MAP_PRIVATE, 3 , 0 ) = 0 x 7 fe 49 bd 90000
9c l o s e ( 3 ) = 0
10a c c e s s ("/ e tc / ld . so . nohwcap " , F_OK) = −1 ENOENT (No such f i l eor
d i r e c to r y )

NLY|O_CLOEXEC) = 3

20 \37 \ 2 \ 0 \ 0 \ 0 \ 0 \ 0 "... ,

28 , . . . } ) = 0PRIVATE|MAP_DENYWRITE,

= 0RITE, MAP_PRIVATE|7 fe 49 bb 89000RITE, MAP_PRIVATE|b 8 f 000

RIVATE|MAP_ANONYMOUS,

RIVATE|MAP_ANONYMOUS,


0




SCHED_OTHER)ev ( 136 , 10 ) , . . . } ) = 0 RIVATE|MAP_ANONYMOUS,
= 0







. , 54max p r i o r i t y = 99 ,


39
40
41
42

43
44
45

Как можно видеть в листинге 2, помимо изучаемых программа делает ещё множество сто- ронних вызовов (к примеру, подгружает системные библиотеки). Но в последних строчках происходят ожидаемые системные вызовы.


В данной работе были рассмотрены некоторые системные вызовы, используемые для управления планировщиком при работе с процессами реального времени (POSIX.1b).В теоретической части было дано описание работы системных вызовов и работы планиров- щика; по умолчанию все процессы выполняются с интервалом времени равным 100 мс и приоритетом (nice) 0, который влияет на интервал, позволяя изменять его в диапазоне от 10 мс до 200 мс.В практической части приведён пример кода, вызывающего изучаемые системные вызовы. Перехват этих вызовов осуществлялся при помощи системной утилиты strace.





[1] Иванов Н. Программирование в Linux– M.: 2006
[2] Цилюрик О.И. Модули ядра Linux. Архитектура и окружение. – M.: 2008 [3] Максвелл C. Ядро Linux в комментариях – M.: 2000 – 488 стр.[4] Raghavan P., Lad A., Neelakandan S. Embedded Linux System Design and Development. – CRC Press: 2005 – 432 pgs.