Главная » Статьи » Ядро | [ Добавить статью ] |
Методы синхронизации необходимы тогда, когда существует параллелизм. Параллелизм – это ситуация, когда одновременно выполняется два или более процесса, которые могут потенциально взаимодействовать друг с другом (например, использовать один и тот же набор ресурсов). Параллелизм может встречаться на однопроцессорных рабочих станциях, где несколько потоков используют один и тот же процессор, вытесняя друг друга и создавая ситуацию гонки. Вытеснением называется прозрачное совместное использование процессора путем временной приостановки одного потока для обеспечения возможности выполнения другого. Ситуация гонки - это ситуация, когда два или более потока управляют одними и теми же данными и результат зависит от порядка выполнения. Параллелизм также существует на многопроцессорных машинах, где одновременно выполняемые потоки разных процессоров используют одни и те же данные. Обратите внимание, что в случае многопроцессорных систем имеет место истинный параллелизм, поскольку потоки выполняются одновременно. В случае однопроцессорной системы параллелизм создаётся механизмом вытеснения. В обоих режимах параллелизма имеются свои сложности. Ядро Linux поддерживает параллелизм в обоих вариантах. Ядро само по себе динамично, и ситуации гонки могут возникать в различных случаях. Ядро Linux также поддерживает многопроцессорный режим, известный как симметричная многопроцессорность (SMP). Подробнее узнать об SMP можно из литературы, ссылки на которую имеются в разделе Ресурсы . Для решения проблемы ситуаций гонки было разработана концепция критической секции. Критическая секция - это часть кода, защищенная от одновременного доступа. Эта часть кода может управлять данными и службами общего пользования (например, периферийным оборудованием). Критические секции работают по принципу взаимного исключения (когда поток находится в критической секции, вход любых других потоков не допускается). Однако с критическими секциями связана проблема тупиковой взаимной блокировки. Рассмотрим две отдельные критические секции, каждая из которых защищает свой ресурс. Каждый ресурс имеет свою блокировку; назовем их A и B. Рассмотрим два потока, которым необходим доступ к нашим ресурсам. Поток X захватывает блокировку A, а поток Y - блокировку B. Пока эти блокировки удерживаются, каждый из потоков пытается захватить блокировку, удерживаемую другим потоком (поток X пытается захватить блокировку B, а поток Y – блокировку A). Теперь потоки находятся в тупиковой ситуации, поскольку каждый из них блокирует нужный другому ресурс. Простое решение состоит в том, чтобы всегда захватывать блокировки в одном и том же порядке, что позволяет завершить работу потока. Другое решение состоит в обнаружении таких ситуаций. В таблице 1 определены наиболее важные обсуждаемые здесь понятия из области параллелизма. Таблица 1. Важные понятия параллелизма
Теперь, когда мы немного ознакомились с теорией и поняли, какую проблему предстоит решать, давайте рассмотрим различные способы реализации параллельного исполнения и взаимных исключений в Linux. Раньше обработка взаимных исключений выполнялась путем отключения прерываний, но такая блокировка неэффективна (хотя её следы еще можно найти в ядре). Кроме того, этот метод не очень хорошо масштабируется и не гарантирует взаимное исключение на других процессорах. В приведенном ниже обзоре механизмов блокировки мы сначала рассмотрим атомарные операции, которые обеспечивают защиту простых переменных (счетчиков и битовых масок). После этого рассматриваются простые взаимные блокировки (спинлоки) и взаимные блокировки чтения и записи в качестве эффективного механизма активного ожидания блокировок для архитектур SMP. И, наконец, мы рассмотрим взаимные исключения (мьютексы) ядра, которые построены на атомарном API. Простейшим средством синхронизации ядра Linux являются атомарные операции. Атомарность
означает, что критическая секция содержится внутри функции API.
Необходимость в блокировке отсутствует, поскольку она подразумевается в
вызове. Учитывая, что язык C не может гарантировать атомарности
операций, Linux использует для этого архитектуру более низкого уровня.
Поскольку архитектуры могут различаться в значительной степени,
существуют различные реализации атомарных функций. Некоторые из них
выполнены почти полностью на ассемблере, тогда как другие прибегают к
помощи C и отключению прерываний с помощью
Атомарные операторы идеальны для ситуаций, в которых защищаемые данные просты, например, как счетчик. При всей своей простоте атомарный API предоставляет целый ряд операторов для различных ситуаций. Рассмотрим пример использования этого API. Чтобы объявить атомарную переменную, мы просто объявляем переменную типа Листинг 1. Создание и инициализация атомарных переменных
Атомарный
API поддерживает множество функций, охватывающих много вариантов
применения. Мы можем прочесть содержимое атомарной переменной с помощью
Листинг 2. Простые арифметические атомарные функции
API-интерфейс
также поддерживает ряд других распространенных сценариев использования,
в том числе функции выполнения и проверки. Они позволяют управлять
атомарной переменной и после этого проверять её значение (всё это
выполняется в рамках одной атомарной операции). Специальная функция Хотя многие из этих функций не имеют возвращаемых значений, две из них выполняют операцию и возвращают получившееся значение ( Листинг 3. Атомарные функции выполнения и проверки
Если архитектура поддерживает 64-разрядные типы long ( Атомарный API также поддерживает операции битовых масок. Помимо арифметических операций (которые обсуждались выше) имеются операции установки (set) и очистки (clear). Атомарные операции используются многими драйверами, в частности, драйверами SCSI. Использование атомарных операций битовых масок слегка отличается от арифметических операций, поскольку здесь доступны только две операции (установить или очистить маску). Входными параметрами являются числовое значение и битовая маска, с которой будет выполняться операция, как показано в листинге 4. Листинг 4. Атомарные функции битовых масок
Взаимные блокировки (спинлоки) предоставляют особый способ обеспечения взаимных исключений посредством циклов активного ожидания блокировок. Если блокировка доступна, она захватывается, взаимно исключающее действие выполняется, и блокировка снимается. Если блокировка недоступна, поток переводится в состояние активного ожидания блокировки, пока она не освободится. Хотя активное ожидание может показаться неэффективным решением, на деле оно может быть быстрее, чем перевод потока в ждущий режим с последующей его активацией при доступности блокировки. На самом деле взаимные блокировки полезны только в системах с SMP, но поскольку ваш код однажды будет запускаться и на SMP-системах, разумно вводить их и в однопроцессорные системы. Взаимные блокировки бывают двух видов: полные блокировки и блокировки на запись и чтение. Рассмотрим сначала полные блокировки. Для
начала создадим новую взаимную блокировку посредством простого
объявления. Ее можно инициализировать сразу же или с помощью вызова Листинг 5. Создание и инициализация взаимных блокировок
Теперь, когда мы определили взаимную блокировку, нам доступен целый ряд вариантов блокировки. Каждый из них полезен в своем контексте. Первый вариант, Листинг 6. Функции установки и снятия взаимной блокировки
Следующая пара, Листинг 7. Вариант взаимной блокировки с отключенными локальными прерываниями
Менее безопасный вариант И, наконец, если поток ядра использует общие данные с нижней половиной, вы можете использовать другой вариант взаимной блокировки. Нижняя половина - это способ переноса работы из обработчиков прерываний в драйверы устройств. Этот вариант взаимной блокировки отключает программные прерывания локального процессора. Это предотвращает выполнение программных прерываний, тасклетов и нижних половин на локальном процессоре. Этот вариант показан в листинге 8. Листинг 8. Функции взаимной блокировки для взаимодействий с нижней половиной
В большинстве случаев доступ к данным характеризуется большим числом читающих процессов и меньшим числом пишущих (доступ к данным для чтения более распространен, чем доступ для записи). Для поддержки такой модели были созданы взаимные блокировки чтения/записи. В этой модели интересно то, что одновременно разрешается доступ к данным нескольким операциям считывания и только одной операции записи. Если блокировка установлена пишущим процессом, чтение в критической секции не допускается. Если блокировка установлена читающим процессом, в критической секции допускается несколько операций чтения. Эта модель показана в листинге 9. Листинг 9. Функции взаимной блокировки чтения и записи
Для различных ситуаций, в которых может потребоваться блокировка, существуют варианты взаимных блокировок чтения и записи для сохранения запросов прерываний (IRQ) и нижних половин. Очевидно, если необходимая вам блокировка по своей природе связана с чтением и записью, следует использовать такую взаимную блокировку вместо стандартной, которая не делает различия между операциями чтения и записи. Для осуществления работы семафоров в ядре предусмотрены взаимные исключения (мьютексы). Мьютексы ядра реализованы поверх атомарного API, хотя это и не видно пользователю ядра. Мьютексы устроены просто, но надо помнить ряд правил. В каждый момент времени удерживать мьютекс может только одна задача, и только эта задача может освободить его. Не допускается рекурсивная установка и снятие блокировки мьютексов, а также использование мьютексов в контексте прерываний. Однако мьютексы быстрее и компактнее текущей реализации семафоров ядра, поэтому, если они вам подходят, лучше использовать именно их. Мьютексы создаются и инициализируются в одной операции с помощью макроса
В
API-интерфейсе мьютексов реализовано пять функций: три используются для
блокировки, одна для снятия блокировки и ещё одна для проверки
мьютекса. Рассмотрим сначала функции блокировки. Первая функция, Листинг 10. Попытка получить мьютекс с помощью mutex_trylock
Если же вы готовы ждать освобождения мьютекса, вы можете вызвать Листинг 11. Блокировка взаимного исключения с возможностью перевода в режим ожидания
После того, как мьютекс заблокирован, его необходимо разблокировать. Это делается функцией Листинг 12. Проверка мьютекса с помощью mutex_is_locked
Хотя у API мьютексов есть свои ограничения, поскольку он построен на базе атомарного API, он эффективен, и если он подходит к вашим потребностям, его применение целесообразно. И,
наконец, остаётся большая блокировка ядра (BKL). Её применение в ядре
сокращается, но оставшиеся случаи применения устранить сложнее всего.
BKL сделала возможной многопроцессорность Linux, но со временем BKL
заменили более детальные блокировки. BKL выполняется с помощью функций В том, что касается возможностей выбора, Linux напоминает швейцарский армейский нож, и методы блокировки здесь не являются исключением. Атомарные блокировки предоставляют не только механизм блокировки, но и арифметические и битовые операции. Взаимные блокировки реализуют механизм блокировки, ориентированный большей частью на SMP-системы; существуют также взаимные блокировки чтения и записи, которые допускают получение блокировки несколькими операциями чтения и только одной операцией записи. И, наконец, мьютексы - это относительно новый механизм блокировки, который предоставляет простой API, построенный на базе атомарных операций. В Linux найдется схема блокировки для защиты ваших данных на любой случай.
Источник: http://www.ibm.com/developerworks/ru/library/l-linux-synchronization/index.html | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Просмотров: 2051 | |
Всего комментариев: 0 | |
Операционные Системы
[61]
ОС Open Source
|
Мобильный Linux [26] |
Сравнение ОС [7] |
Статьи о Linux [16] |
Свободное ПО [10] |
Програмирование [6] |
Не для нубов [5] |
Ядро [13] |
Хранилище данных [9] |
Устройства [1] |
Установка/конфигурирование/планиров [16] |
Файловые системы [3] |
Управление, основанное на политиках [1] |
Управление инфраструктурой [0] |
Серверы [5] |
Биографии [6] |
Прочее [25] |