Реферат - Взаимоисключение параллельных процессов и потоков в Linux. Синхронизация в ядре - файл n1.doc
Реферат - Взаимоисключение параллельных процессов и потоков в Linux. Синхронизация в ядрескачать (182.1 kb.)
Доступные файлы (5):
n1.doc
Взаимоисключение параллельных процессов и потоков
В предыдущей главе обсуждались источники и решения проблем, связанных с конкуренцией за ресурсы. К счастью, в ядре Linux реализовано большое семейство средств синхронизации. В этой главе обсуждаются эти средства, интерфейсы к ним, а также особенности их работы и использования. Эти решения позволяют разработчикам писать код, в котором отсутствуют состояния конкуренции за ресурсы.
Атомарные операции
Атомарные операции (atomic operations) предоставляют инструкции, которые выполняются атомарно, — т.е. не прерываясь. Так же как и атом вначале считался неделимой частицей, атомарные операции являются неделимыми инструкциями. Например, как было показано в предыдущей главе, операция атомарного инкремента позволяет считывать из памяти и увеличивать на единицу значение переменной за один неделимый и непрерывный шаг. В отличие от состояния конкуренции за ресурс, которая обсуждалась в предыдущей главе, результат выполнения такой операции всегда один и тот же, например, как показано в следующем примере (допустим, что значение переменной i вначале равно 7).
Поток 1 инкремент i (7->8) — | Поток 2 — инкремент i (8->9) |
Результирующее значение 9 — правильное. Параллельное выполнение двух атомарных операций с одной и той же переменной невозможно никогда. Таким образом, для такой операции инкремента состояние конкуренции за ресурс возникнуть не может.
Ядро предоставляет два набора интерфейсов для выполнения атомарных операций: один — для работы с целыми числами, а другой — для работы с отдельными битами. Эти интерфейсы реализованы для всех аппаратных платформ, которые поддерживаются операционной системой Linux. Большинство аппаратных платформ поддерживают атомарные операции или непосредственно, или путем блокировки шины доступа к памяти при выполнении одной операции (что в свою очередь гарантирует, что другая операция не может выполниться параллельно). Это как-то позволяет справиться с проблемой в случае аппаратных платформ, таких как SPARC, которые не поддерживают базовых машинных инструкций для выполнения атомарных операций.
Целочисленные атомарные операции
Средства выполнения атомарных операций с целыми числами работают с типом данных atomic_t. Вместо того, чтобы использовать функции, которые работают непосредственно с типом данных int языка С, по ряду причин используется специальный тип данных. Во-первых, функции, которые выполняют атомарные операции, принимают только аргументы типа atomic_t, это гарантирует, что атомарные операции выполняются только с данными этого специального типа. В то же время это также гарантирует, что данные этого типа не смогут передаваться в другие функции, которые не выполняют атомарных операций. Действительно, ничего хорошего не будет от таких атомарных операций, которые иногда атомарные, а иногда — нет. Следующий момент— использование типа atomic_t позволяет гарантировать, что компилятор (по ошибке, но для повышения эффективности) не будет оптимизировать операции обращения к атомарным переменным. Важно, чтобы атомарные операции получали правильное значение адреса переменной в памяти, а не адреса временных копий. И наконец, за типом atomic_t скрываются различия между реализациями для различных аппаратных платформ.
Кроме того, что тип atomic_t — это 32-разрядное целое число на всех машинах, которые поддерживаются операционной системой Linux, при разработке кода необходимо учитывать, что максимальный диапазон значений переменной этого типа не может быть больше 24 бит. Это связано с аппаратной платформой SPARC, для которой используется несколько странная реализация атомарных операций: в младшие 8 бит 32-разрядного целого числа типа int встроена блокировка, как показано на рис. 9.1.
3

2-разрядный тип atomic_t
24-битовое знаковое целое | Блокировка |
|
(Бит) 31 | 7 | 0 |
Рис. 9.1. Структура 32-битового типа atomic_t для,
аппаратной платформы SPARC в старой реализации
Блокировка используется для предотвращения параллельного доступа к переменной атомарного типа, так как для аппаратной платформы SPARC отсутствует соответствующая поддержка на уровне машинных инструкций. Следовательно, на машинах SPARC могут быть использованы только 24 бит. Хотя код, который рассчитан на использование полного 32-битового диапазона значений, будет работать и на машинах других типов, он может приводить к странным и коварным ошибкам на машинах типа SPARC, и так делать не нужно. В последнее время умные хакеры додумались, как для аппаратной платформы SPARC обеспечить тип atomic_t, который позволяет хранить полноценное 32-разрядное целое число, и указанного ограничения больше не существует. Тем не менее старая 24-битовая реализация все еще используется в старом коде для аппаратной платформы SPARC, и этот код все еще имеется в файле
для этой аппаратной платформы.
Объявления всего, что необходимо для использования целочисленных атомарных операций, находятся в заголовочном файле . Для некоторых аппаратных платформ существуют дополнительные средства, которые уникальны только для этой платформы, но для всех аппаратных платформ существует минимальный набор операций, которые используются в ядре повсюду. При написании кода ядра необходимо гарантировать, что соответствующие операции доступны и правильно реализованы для всех аппаратных платформ.
Объявление переменных типа atomic_t производится обычным образом. При необходимости можно установить заданное значение этой переменной.
atomic_t u; /* определение переменной и */
atomic_t v = ATOMIC_INIT (0); /* определение переменной v и
инициализация ее в значение нуль */
Выполнять операции так же просто.
atomic_set(&v, 4); /* v = 4 (атомарно) */
atoraic_add(2, &v); /* v = v + 2 = 6 (атомарно) */
atomic_inc(&v); /*v = v + l = 7 (атомарно) */
Если необходимо конвертировать тип atomic_t в тип int. то нужно использовать функцию atomic_read().
printk("%d\n", atomic_read (sv) ) ; /* будет напечатано "7" */
Наиболее частое использование атомарных целочисленных операций — это инкремент счетчиков. Защищать один счетчик с помощью сложной системы блокировок — это глупо, поэтому разработчики используют вызовы atcmic_int () и atomic_dec (), которые значительно быстрее. Еще одно использование атомарных целочисленных операций — это атомарное выполнение операции с проверкой результата. Наиболее распространенный пример — это атомарные декремент и проверка результата, с помощью функции
int atomic_dec_and_test (atoraic_t *v) .
Эта функция уменьшает на единицу значение заданной переменной атомарного типа. Если результат выполнения операции равен нулю, то возвращается значение true, иначе возвращается false. Полный список всех атомарных операций с целыми числами (т.е. тех, которые доступны для всех аппаратных платформ) приведен в табл. 9.1. Все операции, которые реализованы для определенной аппаратной платформы, приведены в файле .
Обычно атомарные операции реализованы как функции с подстановкой тела и встраиваемыми инструкциями на языке ассемблера (разработчики ядра любят inline). В случае если какая-либо из функций обладает внутренней атомарностью, то обычно она выполняется в виде макроса. Например, для большинства нормальных аппаратных платформ считывание одного машинного слова данных — это атомарная операция. Операция считывания всегда возвращает машинное слово в непротиворечивом состоянии или перед операцией записи, или после нее, но во время операции записи чтение не может быть выполнено никогда. Следовательно, функция atomic_read () обычно реализуется как макрос, который возвращает целочисленное значение переменной типа atomic_t.
Таблица 9.1, Полный список всех атомарных операций с целыми числами
Атомарная целочисленная операция | Описание |
ATOMIC_INIT(int i) int atomic read(atomic_t *v) void atomic set (atomic t *v, int i) void atomic add(int i, atomic_t *v) void atomic_sub(int i, atomic_t *v) void atomic_inc(atomic_t *v) void atomic_dec(atomic_t *v) int atomic_sub_and_test (int i, atomic_t *v) int atomic_add_negative(int i, atoraic_t *v) int atomic dec and test(atomic t *v) int atomic inc and test(atomic t *v)
| Объявление и инициализация в значение i переменной типа atomic_t Атомарное считывание значения целочисленной переменной v Атомарно установить переменную v в значение i Атомарно прибавить значение i к переменной v Атомарно вычесть значение i из переменной v Атомарно прибавить единицу к переменной v Атомарно вычесть единицу из переменной v Атомарно вычесть значение i из переменной v и возвратить true, если результат равен нулю, и false в противном случае Атомарно прибавить значение i к переменной v и возвратить true, если результат операции меньше нуля, иначе возвратить false Атомарно вычесть единицу из переменной v и возвратить true, если результат операции равен нулю, иначе возвратить false Атомарно прибавить единицу к переменной v и возвратить true, если результат операции равен нулю, иначе возвратить false |
Взаимоисключение параллельных процессов и потоков