1. Объекты ядра ОС Windows.
2. Совместное использование объектов ядра ОС Windows.
Время от времени возникает необходимость в разделении объектов ядра между потоками, исполняемыми в разных процессах. Причин тому может быть несколько:
• объекты "проекции файлов" позволяют двум процессам, исполняемым на одной машине, совместно использовать одни и те же блоки данных;
• почтовые ящики и именованные каналы дают возможность программам обмениваться данными с процессами, исполняемыми на других машинах в сети;
• мьютексы, семафоры и события позволяют синхронизировать потоки, исполняемые в разных процессах, чтобы одно приложение могло уведомитьдругое об окончании той или иной операции.
Но поскольку описатели объектов ядра имеют смысл только в конкретном процессе, разделение объектов ядра между несколькими процессами — задача весьма непростая. У Microsoft было несколько веских причин сделать описатели процессно-зависимыми", и самая главная — устойчивость операционной системы к сбоям. Если бы описатели объектов ядра были общесистемными, то один процесс мог бы запросто получить описатель объекта, используемого другим процессом, и устроить в нем (этом процессе) настоящий хаос. Другая причина — защита. Объекты ядра защищены, и процесс, прежде чсм оперировать с ними, должен запрашивать разрешение на доступ к ним.
3. Управление потоками в ОС Windows.
В ОС Windows реализовано вытесняющее приоритетное планирование, когда каждому потоку присваивается определенное числовое значение – приоритет, в соответствии с которым ему выделяется процессор. Потоки с одинаковыми приоритетами планируются согласно алгоритму Round Robin (карусель). Важным достоинством системы является возможность вытеснения потоков, работающих в режиме ядра – код исполнительной системы полностью реентерабелен. Не вытесняются лишь потоки, удерживающие спин-блокировку Поэтому спин-блокировки используются с большой осторожностью и устанавливаются на минимальное время.
|
В системе предусмотрено 32 уровня приоритетов. Шестнадцать значений приоритетов (16-31) соответствуют группе приоритетов реального времени, пятнадцать значений (1-15) предназначены для обычных потоков, и значение 0 зарезервировано для системного потока обнуления страниц
Чтобы избавить пользователя от необходимости запоминать числовые значения приоритетов и иметь возможность модифицировать планировщик, разработчики ввели в систему слой абстрагирования приоритетов. Например, класс приоритета для всех потоков конкретного процесса можно задать с помощью набора констант-параметров функции SetPriorityClass, которые могут иметь следующие значения:
• реального времени (REALTIME_PRIORITY_CLASS),
• высокий (HIGH_PRIORITY_CLASS),
• выше нормы (ABOVE_NORMAL_PRIORITY_CLASS),
• нормальный (NORMAL_PRIORITY_CLASS),
• ниже нормы (BELOW_NORMAL_PRIORITY_CLASS)
• и неработающий (IDLE_PRIORITY_CLASS).
Относительный приоритет потока устанавливается аналогичными параметрами функции SetThreadPriority:
4. Синхронизация потоков в ОС Windows.
В Windows реализована вытесняющая многозадачность - это значит, что в любой момент система может прервать выполнение одной нити и передать управление другой. Ранее, в Windows 3.1, использовался способ организации, называемый кооперативной многозадачностью: система ждала, пока нить сама не передаст ей управление и именно поэтому в случае зависания одного приложения приходилось перезагружать компьютер.
|
Все нити, принадлежащие одному процессу, разделяют некоторые общие ресурсы - такие, как адресное пространство оперативной памяти или открытые файлы. Эти ресурсы принадлежат всему процессу, а значит, и каждой его нити. Следовательно, каждая нить может работать с этими ресурсами без каких-либо ограничений. Но... Если одна нить еще не закончила работать с каким-либо общим ресурсом, а система переключилась на другую нить, использующую этот же ресурс, то результат работы этих нитей может чрезвычайно сильно отличаться от задуманного. Такие конфликты могут возникнуть и между нитями, принадлежащими различным процессам. Всегда, когда две или более нитей используют какой-либо общий ресурс, возникает эта проблема.
Именно поэтому необходим механизм, позволяющий потокам согласовывать свою работу с общими ресурсами. Этот механизм получил название механизма синхронизации нитей (thread synchronization).
Этот механизм представляет собой набор объектов операционной системы, которые создаются и управляются программно, являются общими для всех нитей в системе (некоторые - для нитей, принадлежащих одному процессу) и используются для координирования доступа к ресурсам. В качестве ресурсов может выступать все, что может быть общим для двух и более нитей - файл на диске, порт, запись в базе данных, объект GDI, и даже глобальная переменная программы (которая может быть доступна из нитей, принадлежащих одному процессу).
|
Объектов синхронизации существует несколько, самые важные из них - это взаимоисключение (mutex), критическая секция (critical section), событие (event) и семафор (semaphore). Каждый из этих объектов реализует свой способ синхронизации. Также в качестве объектов синхронизации могут использоваться сами процессы и нити (когда одна нить ждет завершения другой нити или процесса); а также файлы, коммуникационные устройства, консольный ввод и уведомления об изменении.
Любой объект синхронизации может находиться в так называемом сигнальном состоянии. Для каждого типа объектов это состояние имеет различный смысл. Нити могут проверять текущее состояние объекта и/или ждать изменения этого состояния и таким образом согласовывать свои действия. При этом гарантируется, что когда нить работает с объектами синхронизации (создает их, изменяет состояние) система не прервет ее выполнения, пока она не завершит это действие. Таким образом, все конечные операции с объектами синхронизации являются атомарными (неделимыми.
5. Семейство Interlocked-функций.
Используются в случае, если разным потокам необходимо изменять одну и ту же переменную. Например, увеличивать счетчик числа операций.
Код вида var++; компилятором генерируется в что-то вроде
MOV EAX, [var]; 1
INC EAX; 2
MOV [var], EAX; 3
Соответственно, если будут два потока с таким кодом, то нельзя гарантировать, что выполняться строки 1,2,3 первого потока, а потом 1,2,3 второго, первая строчка второго потока может выполнится до того, как первый запишет новое значение в переменную, и тогда второй поток изменит старое значение и перетрет изменения первого. Использование Interlocked-функций гарантирует, что операция будет выполнена атомарно, и описанная выше проблема отпадает. При использование процессоров х86 они выдают по шине аппаратный сигнал, не давая другим процессорам процессорам обратится по тому же адресу памяти на время работы функции.
В примере с инкрементом желанного результат можно достичь двумя функциями – InterlockedExchangeAdd(&var, 1), которая возвратит начальное значение var, либо InterlockedIncrement(&var), которая возвращает уже инкрементированое значение переменной. Также существуют их 64-разрядные версии — InterlockedIncrement64 и InterlockedExchangeAdd64.
Существуют ещё функции, которые реализуют:
декремент: LONG InterlockedDecrement(LONG volatile *Addend), возвращает значение декрементированной переменной и 64-битная версия — InterlockedDecrement64
монопольную замены текущего значения: LONG InterlockedExchange(LONG volatile *Target, LONG Value), которая всегда оперирует 32-разрядными значениямии возвращает начальное значение параметра Target, и PVOID InterlockedExchangePointer с такой же сигнатурой, которая в случае 64-разрядной программе будет оперировать с 64-разрядными значениями, возвращает указатель на Target
логические операции: логическое «И» LONG InterlockedAnd(LONG volatile *Destination, LONG Value), логическое «ИЛИ» InterlockedOr и сумму по модулю два InterlockedXor. У них одинаковая сигнатура, все они результат выполнения записывают в первый параметр и возвращают исходное значение первого параметра
выполнение сравнения и присвоения как атомарной операции: LONG InterlockedCompareExchange(LONG volatile *Destination, LONG Exchange, LONG Comparand) сравнивает значение первых двух параметров, и если они совпадают, записывает в первый параметр третий. Возвращает исходное значение первого параметра и работает всегдда з 32-разрядными значениями. PVOID InterlockedCompareExchangePointer(PVOID volatile *Destination, PVOID Exchange, PVOID Comparand) делает тоже самое, отличие в том, что в 64-разрядном приложение оперирует с 64-разрядными значениями. Также существует InterlockedCompare64Exchange128, которая тоже работает так же, сравнивает два 64-битных значения, а третий параметр у неё имеет разрядность 128 бит
Interlocked-функции не переводят процесс в режим ожидания, работают в пользовательском режиме, и поэтому обладают довольно большим быстродействием, так что если необходима синхронизация для доступа к одной переменной, следует использовать именно их.
6. Расскажите про Критические Секции.
Критическая секция — участок исполняемого кода программы, в котором производится доступ к общему ресурсу (данным или устройству), который не должен быть одновременно использован более чем одним потоком исполнения. При нахождении в критической секции двух (или более) процессов возникает состояние «гонки» («состязания»). Для избежания данной ситуации необходимо выполнение четырех условий:
Два процесса не должны одновременно находиться в критических областях.
В программе не должно быть предположений о скорости или количестве процессоров.
Процесс, находящийся вне критической области, не может блокировать другие процессы.
Невозможна ситуация, в которой процесс вечно ждет попадания в критическую область.
Критическая секция (англ. critical section) — объект синхронизации потоков, позволяющий предотвратить одновременное выполнение некоторого набора операций (обычно связанных с доступом к данным) несколькими потоками. Критическая секция выполняет те же задачи, что и мьютекс.
Между мьютексом и критической секцией есть терминологические различия, так процедура, аналогичная захвату мьютекса, называется входом в критическую секцию (англ. enter), снятию блокировки мьютекса — выходом из критической секции (англ. leave).
Процедура входа и выхода из критических секций обычно занимает меньшее время, нежели аналогичные операции мьютекса, что связано с отсутствием необходимости обращаться к ядру ОС.
В операционных системах семейства Microsoft Windows разница между мьютексом и критической секцией в том, что мьютекс является объектом ядра и может быть использован несколькими процессами одновременно, критическая секция же принадлежит процессу и служит для синхронизации только его потоков.
Критические секции Windows имеют оптимизацию, заключающуюся в использовании атомарно изменяемой переменной наряду с объектом «событие синхронизации» ядра. Захват критической секции означает атомарное увеличение переменной на 1. Переход к ожиданию на событии ядра осуществляется только в случае, если значение переменной до захвата было уже больше 0, то есть происходит реальное «соревнование» двух или более потоков за ресурс.
Таким образом, при отсутствии соревнования захват/освобождение критической секции обходятся без обращений к ядру.
Кроме того, захват уже занятой критической секции до обращения к ядру какое-то малое время ждёт в цикле (кол-во итераций цикла (англ. spin count) задаётся функциями InitializeCriticalSectionAndSpinCount() или SetCriticalSectionSpinCount()) опроса переменной числа текущих пользователей, и, если эта переменная становится равной 0, то захват происходит без обращений к ядру.
Сходный объект в ядре Windows называется FAST_MUTEX (ExAcquire/ReleaseFastMutex). Он отличается от критической секции отсутствием поддержки рекурсивного повторного захвата тем же потоком.
7. Расскажите про События.
В объектно-ориентированном анализе для описания динамического поведения объектов принято использовать модель состояний.[2]
Событие — это переход объекта из одного состояния в другое. Взаимодействие объектов также осуществляется при помощи событий: изменение состояния одного объекта приводит к изменению состояния другого объекта, а событие оказывается средством связи между объектами.
Согласно[2], событие — это «абстракция инцидента или сигнала в реальном мире, который сообщает нам о перемещении чего- либо в новое состояние». Далее, выделяются четыре аспекта события:
метка — уникальный идентификатор события.
значение — текстовое сообщение о сути произошедшего.
предназначение — модель событий, которая принимает событие.
данные — данные, которые переносятся от одного объекта к другому.
8. Расскажите про Ожидаемые Таймеры.
Ожидаемые таймеры (waitable timers) – это объекты ядра, которые самостоятельно переходят в свободное состояние в определенный момент или через определенные интервалы. Ожидаемый таймер создается вызовом функции CreateWaitableTimer:
HANDLE CreateWaitableTimer(
LPSECURITY_ATTRIBUTES lpTimerAttributes, // SD
BOOL bManualReset, // reset type
LPCTSTR lpTimerName // object name
);
По аналогии с событиями, параметр bManualReset определяет тип таймера – с автоматическим или ручным сбросом. Дескриптор ожидаемого таймера также может быть получен с помощью функции OpenWaitableTimer.
После создания объект таймер находится в занятом состоянии. Чтобы установить момент времени, в который он должен перейти в активное состояние, нужно вызвать функцию SetWaitableTimer:
BOOL SetWaitableTimer(
HANDLE hTimer, // handle to timer
const LARGE_INTEGER* pDueTime, // timer due time
LONG lPeriod, // timer interval
PTIMERAPCROUTINE pfnCompletionRoutine, // completion routine
LPVOID lpArgToCompletionRoutine, // completion routine parameter
BOOL fResume // resume state
);
Параметр pDueTime задает момент первого срабатывания таймера. Значение этого параметра представляет собой указатель на 64-х битное значение, представляющее число 100-наносекундных интервалов с 1 января 1601 года по Гринвичу. Информацию по работе с такими значениями можно найти в описании структуры FILETIME Windows API.
Если нас не интересует абсолютное время срабатывания таймера, а нужно чтобы он сработал через определенный промежуток времени, то мы можем использовать относительное время, указав отрицательное значение DueTime.
Параметр lPeriod определяет периодичность срабатывания таймера. Если это значение равно 0, то таймер сработает только один раз, в момент определенный параметром pDueTime. Положительное значение lPeriod заставит таймер периодически срабатывать через заданное число миллисекунд.
Параметр fResume позволяет использовать таймер, даже если компьютер находится в режиме пониженного энергопотребления (если эта функция поддерживается компьютером). Значение TRUE заставит компьютер выйти из режима пониженного потребления и активировать таймер.
Оставшиеся параметры используются для асинхронного вызова процедуры в момент активации таймера.
Таймер с ручным сбросом перейдет в активное состояние в заданный момент, и будет оставаться активным до тех пор, пока функция SetWaitableTimer не будет вызвана еще раз. Таймер с автосбросом остается свободным только до тех пор, пока на нем не будет успешно выполнена одна из Wait-функций.
Можно в любой момент изменить режим работы таймера, вызвав функцию SetWaitableTimer с другими параметрами. При этом отсчет времени таймера начнется заново, и ожидающие его потоки не будут активироваться до наступления нового момента срабатывания.
«Отключить» таймер можно функцией CancelWaitableTimer:
BOOL CancelWaitableTimer(
HANDLE hTimer // handle to timer);Эта функция переводит таймер в неактивное состояние.
9. Расскажите про Семафоры.
Семафо́р (англ. semaphore) — объект, ограничивающий количество потоков, которые могут войти в заданный участок кода. Семафоры используются для синхронизации и защиты передачи данных через разделяемую память, а также для синхронизации работы процессов и потоков.
Семафор — это объект, над которым можно выполнить три операции.
Инициализация семафора (задать начальное значение счётчика)
Захват семафора (ждать пока счётчик станет больше 0, после этого уменьшить счётчик на единицу)
Освобождение семафора (увеличить счётчик на единицу)
Некоторые из проблем, которые могут решать семафоры:
- запрет одновременного выполнения заданных участков кода (критические секции);
- поочерёдный доступ к критическому ресурсу (важному ресурсу, для которого невозможен (или нежелателен) одновременный доступ);
- синхронизация процессов и потоков (например, можно инициировать обработку события отпусканием семафора).
10. Расскажите про Мьютексы.
Мью́текс (англ. mutex, от mutual exclusion — «взаимное исключение») — аналог одноместного семафора, служащий в программировании для синхронизации одновременно выполняющихся потоков.
Мьютекс отличается от семафора тем, что только владеющий им поток может его освободить, т.е. перевести в отмеченное состояние. Мьютексы — это один из вариантов семафорных механизмов для организации взаимного исключения. Они реализованы во многих ОС, их основное назначение — организация взаимного исключения для потоков из одного и того же или из разных процессов.
Мьютексы — это простейшие двоичные семафоры, которые могут находиться в одном из двух состояний — отмеченном или неотмеченном (открыт и закрыт соответственно). Когда какой-либо поток, принадлежащий любому процессу, становится владельцем объекта mutex, последний переводится в неотмеченное состояние. Если задача освобождает мьютекс, его состояние становится отмеченным.
Задача мьютекса — защита объекта от доступа к нему других потоков, отличных от того, который завладел мьютексом. В каждый конкретный момент только один поток может владеть объектом, защищённым мьютексом. Если другому потоку будет нужен доступ к переменной, защищённой мьютексом, то этот поток блокируется до тех пор, пока мьютекс не будет освобождён.
Цель использования мьютексов — защита данных от повреждения в результате асинхронных изменений (состояние гонки), однако могут порождаться другие проблемы — например взаимная блокировка (клинч).
11. Сравните Мьютексы и Критические Секции.
Мьютексы позволяют контролировать состояние некоторой переменной и разграничивать к ней доступ сразу нескольких потоков, давая доступ только одному потоку единовременно, критические секции позволяют разграничить часть кода, которая должна выполняться только одним потоком.
12. Какая система называется распределённой.
Распределённая система — система, для которой отношения местоположений элементов (или групп элементов) играют существенную роль с точки зрения функционирования системы, а, следовательно, и с точки зрения анализа и синтеза системы.
Для распределённых систем характерно распределение функций, ресурсов между множеством элементов (узлов) и отсутствие единого управляющего центра, поэтому выход из строя одного из узлов не приводит к полной остановке всей системы. Типичной распределённой системой является Интернет.
13. Проблемы и технологии масштабирования.
Поскольку проблемы масштабируемости в распределенных системах, такие как проблемы производительности, вызываются ограниченной мощностью серверов и сетей,
Существуют три основные технологии масштабирования:
сокрытие времени ожидания связи,
распределение
репликация.
8.1. Сокрытие времени ожидания связи применяется в случае географического масштабирования.
Основная идея – постараться избежать ожидания ответа на запрос от удаленного сервера.
Это означает разработку приложения-клиента с использованием асинхронной связи.
При получении ответа, приложение-клиент прервет свою работу и вызовет специальный обработчик для завершения отправленного ранее запроса. Асинхронная связь используется в системахпакетной обработки и параллельных приложениях, в которых во время ожидания одной задачей завершения связи предполагается выполнение других независимых задач.
Во многих задачах приложения не могут эффективно использовать асинхронную связь. Например, в задачах интерактивной обработки с использованием форм. В этих случаях для сокращения объема взаимодействия часть вычислений, которые обычно выполняются на стороне сервера, обычно перемещают на сторону клиента.
Особенно это касается проверки данных на форме, т.е. эффективнее переместить на сторону клиента проверку данных всех полей формы.
Перенос кода на сторону клиента в настоящее время поддерживается в Web с помощью включения кода программы на JavaScript. Асинхронное взаимодействие клиента и сервера в Web-приложениях реализуется в настоящее время технологией AJAX.
8 .2. Распределение.
Предполагает разбиение компонентов на более мелкие части с последующим их распределением в системе.
Пример – система доменных имен Интернета (DNS).
Пространство DNS организовано иерархически в виде дерева доменов (domains) разбитых на зоны (по странам и областям деятельности). Имена каждой зоны обрабатываются отдельным сервером имен.
Получается, что служба доменных имен распределена по нескольким серверам, что позволяет избежать обработки всех запросов одним сервером.
Второй пример – Web.
Каждый документ имеет уникальное имя URL. Физически среда Web разнесена по многим серверам. Имя сервера, содержащего конкретный документ, определяется по его URL-адресу. Это позволяет наращивать количество документов без потери производительности.
8.3. Репликация (дублирование).
Применение репликацииповышает доступность ресурсов, а также помогает выравнивать нагрузку компонентов, что ведет к улучшению производительности.
Кроме того, в географически распределенных системах репликация (в виде близко лежащей копии) помогает уменьшить проблему ожидания связи.
Особую форму репликации представляет собой кеширование (caching). Хотя отличия между ними незначительны.
Как и при репликации, результатом кеширования является создание копии ресурса, обычно в непосредственной близости от клиента.
В отличие от репликации, кеширование – это действие, предпринимаемое со стороны клиента, а не сервера.