Научная статья на тему 'Эффективные методы синхронизации в выгружаемых операционных системах'

Эффективные методы синхронизации в выгружаемых операционных системах Текст научной статьи по специальности «Компьютерные и информационные науки»

CC BY
126
66
i Надоели баннеры? Вы всегда можете отключить рекламу.

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Гусев Игорь Сергеевич

Предлагается подход к реализации эффективной синхронизации, основанный на организации взаимосвязи между базовыми объектами ядра операционной системы в виде дерева. Для синхронизации между модулями предлагается активный интерфейс.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

Effective synchronization methods for operating of unloading systems

One suggests approach to realization of effective synchronization based on organization links between basic kernel objects in tree form. One suggests new active interface for synchronization between modules.

Текст научной работы на тему «Эффективные методы синхронизации в выгружаемых операционных системах»

И.С. Гусев

ЭФФЕКТИВНЫЕ МЕТОДЫ СИНХРОНИЗАЦИИ В ВЫГРУЖАЕМЫХ ОПЕРАЦИОННЫХ СИСТЕМАХ

Предлагается подход к реализации эффективной синхронизации, основанный на организации взаимосвязи между базовыми объектами ядра операционной системы в виде дерева. Для синхронизации между модулями предлагается активный интерфейс.

С момента появления операционные системы занимают важное место в программном обеспечении современных компьютеров. Именно операционная система определяет базовую функциональность вычислительной системы, и именно от эффективности ее функционирования зависит производительность всей вычислительной системы. Операционные системы изначально проектируются для определенных целей и для определенного круга задач. Одной из таких задач является работа операционной системы в режиме реального времени, что накладывает дополнительные сложности на разработку соответствующей системы. Как правило, производители операционных систем проводят разделение на системы реального и распределенного времени. Однако редко требуются специализированные системы. В большинстве случаев важно было бы совместить использование операционной системы как для задач мягкого реального времени, так и для задач разделения времени, т.е. в системе разделения времени дать возможность работать некоторым задачам мягкого реального времени. В статье рассматривается подход к организации взаимодействия основных объектов ядра операционной системы для обеспечения условия мягкого реального времени, а также организации гибкого взаимодействия между объектами в разных модулях операционной системы.

Базовые объекты и их взаимодействие

В современных многозадачных операционных системах поддерживают одновременную работу нескольких пользователей; существует, по крайней мере, три типа базовых объектов: пользователи, процессы и потоки. Все объекты этих типов некоторым образом связаны между собой. Следует заметить, что в отличие от остальных объектов с объектом типа поток связан поток команд процессора или, собственно говоря, поток исполнения, а соответствующий объект описывает этот поток исполнения и содержит все необходимые данные и поля синхронизации. Далее по тексту если не очевидно, что именно имеется в виду, то для описания объекта типа поток будет использоваться термин «объект-поток», а для описания потока инструкций будет использоваться термин «поток управления» или просто поток.

Для эффективного управления объектами предлагается установить взаимосвязи в виде дерева. На верхнем уровне находится контейнерный объект, содержащий ссылки на пользователей. Пользователи ссылаются на свои процессы. Процессы содержат ссылки на потоки соответствующих процессов.

Естественно, что есть и ссылки наверх, от потоков к процессам, от процессов к пользователям (рис.1).

Использование такого дерева позволяет эффективно производить поиск объектов, а также группировать объекты для тех или иных действий, например выделения процессорного времени с учетом справедливой стратегии для пользователей. Однако основная цель этой структуры - сделать эффективный доступ к объектам в многопроцессорной системе для выгружаемых операционных систем.

Рис. 1. Взаимодействие базовых объектов

Определим действия для введенной структуры объектов:

1. Создание объекта (выделение памяти, инициализация свойств и вставка созданного объекта в дерево). Например, при запуске приложений происходит создание объекта-процесса и, по крайней мере, одного объекта-потока.

2. Удаление объекта (удаление объекта из дерева и освобождение ресурсов). При завершении выполнения потока или процесса все ранее выделенные для них объекты должны быть уничтожены для освобождения ресурсов.

3. Доступ к свойствам объекта для чтения. Например, просмотр процессов пользователя или получение имени процесса и его характеристик. Доступ на чтение выделяется отдельно, потому что одновременно несколько потоков управления могут работать с одним и тем же объектом.

4. Доступ к свойствам объекта для модификации. Например, изменение свойств объектов или изменение ссылок на объекты-потомки. В данном случае с объектом может работать только один поток управления.

При многопотоковом доступе к объектам возникает проблема синхронизации. В простейшем случае можно использовать один семафор на всю структуру, гарантируя, что в каждый момент времени с деревом объектов работает только один поток. Следует заметить, что этот вариант естественным образом получается в невыгружаемых системах. Однако такой подход неэффективен по нескольким причинам:

1. Происходит увеличение задержки ожидания ресурса, так как блокируется доступ ко всей структуре в целом, а для большинства алгоритмов выполняется принцип локальности, т.е. они работают только с некоторым обособленным подмножеством объектов.

2. При работе на низком уровне происходит, как правило, активное ожидание освобождения ресурсов. Соответственно увеличивается суммарное время на-

хождения потоков управления в критических участках. Например, если два потока работают с объектами последовательно, а не параллельно, то третий поток будет ожидать освобождения ресурса значительно дольше, даже если ему необходимо работать с другими объектами. В случае если используется пассивное ожидание на семафоре, значительно увеличивается количество переключений контекстов задач, что тоже приводит к потере эффективности.

Повышение эффективности требует блокировать каждый объект отдельно, для чего используются семафоры для каждого объекта. Однако при этом возникают две проблемы [1-3]: некорректные указатели во временных переменных (ссылки на несуществующие объекты, которые были удалены другим потоком на соседнем процессоре) и тупики (взаимное блокирование при захвате ресурсов). Для защиты от некорректных указателей вводятся следующие ограничения:

1. Объект не может быть удален, если у него есть хотя бы одна ссылка на потомка, что в некоторых случаях позволяет переходить по ссылке к по-токам-предкам.

2. Объект-поток может быть удален только своим потоком команд. При выполнении любого потока команд гарантируется, что объект-поток, описывающий этот поток команд, никогда не будет удален, если только он сам не удалит его. Процессы и пользователи могут быть удалены только потоками команд объектов-потоков, которые являются их потомками.

Чтобы избавиться от тупиков, введем следующие ограничения:

1. Объект не может быть заблокирован, если заблокирован хотя бы один из его потомков. Это означает, что объекты всегда блокируются сверху вниз.

2. Всегда можно подняться от собственного объекта-потока на любой необходимый уровень вверх по дереву и начать блокирование с него.

Общая процедура блокировки объекта для доступа выглядит так.

1. Выбираем объект для начала блокирования. Если объект является предком, то находим его, иначе выбираем самый верхний в дереве объект. Делаем выбранный объект текущим.

2. Блокируем текущий объект. На чтение или на модификацию - зависит от конкретного алгоритма.

3. Блокируем потомка.

4. Делаем потомка текущим объектом.

5. Блокируем текущий объект.

6. Разблокируем предка.

7. Если текущий объект не тот, который нам нужен, то переходим на пункт 3.

8. Выполняем необходимые действия. В каждом конкретном алгоритме они свои.

9. Разблокируем текущий объект.

Добавление объекта сводится к модификации

ссылок объектов и не приводит к появлению недопустимых указателей, в то время как удаление может привести к появлению ссылок на уже удаленные объекты. Для избежания этого алгоритм должен удовлетворять ранее введенным ограничениям. Следует заметить, что объекты пользователя и процесса

не могут быть удалены без объекта-потока. Поэтому их удаление происходит попутно с удалением потока.

Процедура удаления объекта:

1. Текущим объектом делаем объект, который надо удалить.

2. Блокируем предка на модификацию.

3. Удаляем ссылку.

4. Блокируем текущий объект и удаляем его.

5. Разблокируем предка.

6. Если у предка нет больше ссылок, делаем предка текущим объектом и переходим на пункт 2.

Данные алгоритмы могут использоваться для дерева объектов любой высоты, однако на практике уровень составляет три. В связи с этим в алгоритмах используются не циклы, а простой линейный алгоритм с тремя, двумя или одной блокировкой. На практике замечены следующие свойства.

1. Чем больше необходимо делать блокировок разных уровней в алгоритме, тем реже такие алгоритмы работают в системе. Например, удаление и добавление процессов происходит достаточно редко, не говоря уже об удалении и добавлении пользователей.

2. Чем ближе в дереве блокировки к корню, тем меньше они длятся. Корень блокируется исключительно только для добавления или удаления ссылок на пользователей, что происходит очень быстро, вероятность, что хотя бы два потока будут удалять или добавлять ссылки одновременно, очень низка. Основные и самые длительные блокировки происходят для объектов-потоков, при этом они блокируются своими же потоками инструкций, что приводит к независимости таких блокировок, так как для блокирования своего объекта-потока нет необходимости блокировать еще какие-нибудь объекты.

Предложенные алгоритмы дают неплохие результаты в системах реального времени и на многопроцессорных системах, обеспечивая минимальное время блокирования ресурсов и максимально возможную параллельную работу потоков, что особенно ценно в вычислительных системах с несколькими процессорами.

Активный интерфейс

Для модульных операционных систем хорошо известны понятия интерфейсов. Каждый загружаемый модуль опирается на интерфейсы других модулей, от которых он зависит, и блокирует соответствующие модули. Для того чтобы выгрузить определенный модуль из системы (например, с целью замены на другой), необходимо выгрузить все остальные модули, которые от него зависят. Выгрузка сразу нескольких модулей требует значительного времени и значительных издержек. Например, для замены видеодрайвера необходимо выгрузить всю видеоподсистему и практически все приложения. Многие операционные системы для этого требуют перезагрузки компьютера.

Предлагаемый активный интерфейс позволяет разорвать жесткую зависимость одних модулей от других, выполняя все необходимые действия по синхронизации. Активный интерфейс есть не что иное, как именованный объект ядра. Он состоит из структур

синхронизации и набора интерфейсов функций, которые находятся в некоторых модулях (рис. 2). Благодаря уникальности своего имени в рамках системы он может быть легко запрошен и перехвачен или расширен новыми подгружаемыми модулями.

Рис. 2. Структура активного интерфейса

Изначально интерфейс может быть создан вообще без реализации, т.е. просто декларирован. Модули ядра могут добавить к нему свой набор функций, однако все вызовы через интерфейс строго определены, это означает, что поведение функций строго предопределено, а меняться может только реализация.

Несмотря на кажущуюся простоту, активный интерфейс довольно сложен в реализации, особенно для выгружаемых систем. Для интерфейса необходимо соблюдать все необходимые блокировки, от-

слеживать его использование, а также использование функций модулей для этого интерфейса.

Для интерфейса определяются следующие пять функций:

1. Создать интерфейс и/или добавить набор функций. Создание интерфейса как раз и есть декларация его в системе. Если же интерфейс уже существует, то происходит просто добавление набора функций в очередь в соответствии с приоритетом.

2. Удалить интерфейс и/или убрать набор функций. Изначально происходит удаление набора функций из списка и ожидание, когда из функций этого интерфейса выйдут все потоки, которые успели туда попасть до удаления. Далее, если у интерфейса не осталось наборов функций, то он просто удаляется, если, конечно, его никто не захватил для работы. В случае если интерфейс захвачен для работы, то он удаляется после освобождения.

3. Захватить интерфейс для работы. По этой функции происходят поиск интерфейса по имени и увеличение счетчика использования. Эта функция необходима для гарантии, что интерфейс не будет удален во время дальнейшей работы и не появятся неактуальные указатели на интерфейс.

4. Освободить интерфейс. Эта функция уменьшает счетчик использования интерфейса и тем самым сообщает, что работа с интерфейсом закончена.

5. Вызвать функцию интерфейса. По данной функции происходит захват набора функций, вызов соответствующей функции и освобождение набора функций. Потенциально возможен вызов предыдущих функций, естественно той же самой функции, только из другого набора функций.

Благодаря этому интерфейсу нет необходимости выгрузки зависимых модулей, поскольку явной зависимости нет.

ЛИТЕРАТУРА

1. Кейлингерт П. Элементы операционных систем: Введение для пользователей: Пер. с англ. М.: Мир, 1985. 295 с.

2. Корнеев В.В. Параллельные вычислительные системы. М.: Нолидж, 1999. 320 с.

3. Цикритзис Д., Бернстайн Ф. Операционные системы. М.: Мир, 1977. 333 с.

Статья представлена кафедрой теоретических основ информатики факультета информатики Томского государственного университета, поступила в научную редакцию номера 3 декабря 2001 г.

i Надоели баннеры? Вы всегда можете отключить рекламу.