Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра Linux
Назад Внутренние механизмы ядра Вперед

Множественное блокирование

В системах с большим количеством блокировок (ядро именно такая система), необходимость проведения более чем одной блокировки за раз не является необычной для кода. Если какие-то операции должны быть выполнены с использованием двух различных ресурсов, каждый из которых имеет свою собственную блокировку, часто нет альтернативы, кроме получения обоих блокировок. Однако, получение множества блокировок может быть крайне опасным:

	DEFINE_SPINLOCK( lock1, lock2 );
	...
	spin_lock ( &lock1 ); /* 1-й фрагмент кода */
	spin_lock ( &lock2 );
	...
	spin_lock ( &lock2 ); /* где-то в совсем другом месте кода... */
	spin_lock ( &lock1 );

- такой образец кода, в конечном итоге, когда-то обречён на бесконечное блокирование (dead lock).

Если есть необходимость захвата нескольких блокировок, то единственной возможностью есть а). один тот же порядок захвата, б). и освобождения блокировок, в). порядок освобождения обратный порядку захвата, и г). так это должно выглядеть в каждом из фрагментов кода. В этом смысле предыдущий пример может быть переписан так:

	spin_lock ( &lock1 ); /* так должно быть везде, где использованы lock1 и lock2 */
	spin_lock ( &lock2 );
	   /* ... здесь выполняется действие */
	spin_unlock ( &lock2 );
	spin_unlock ( &lock1 );

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

Предписания порядка выполнения

Механизмы, предписывающие порядок выполнения кода, к синхронизирующим механизмам относятся весьма условно, они не являются непосредственно синхронизирующими механизмами, но рассматриваются всегда вместе с ними (по принципу: надо же их где-то рассматривать?).

Одним из таких механизмов являются определённые в <linux/compiler.h> макросы likely() и unlikely(), например:

	if( unlikely() ) {
	   /* сделать нечто редкостное */
	};

Или:

	if( likely() ) {
	   /* обычное прохождение вычислений */
	}
	else {
	   /* что-то нетрадиционное */
	};

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

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

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

	a = 1;
	b = 2;

- порядок выполнения операция, вообще то говоря, непредсказуем, причём последовательность (во времени) выполнения операций может изменить а). компилятор из соображений оптимизации, б). процессор (периода выполнения) из соображений аппаратной оптимизации работы с шиной памяти. В этом случае это совершенно нормально, более того, даже запись операторов:

	a = 1;
	b = a + 1;

- будет гарантировать отсутствие перестановок в процессе оптимизации, так как компилятор «видит» операции в едином контексте (фрагменте кода). Но в других случаях, когда операции производятся из различных мест кода нужно гарантировать, что они не будут перенесены через определённые барьеры. Операции (макросы) с барьерами объявлены в </asm-generic/system.h>, на сегодня все они (rmb(), wmb(), mb(), ...) определены одинаково:

	#define   mb()  asm volatile ("": : :"memory")

Все они препятствуют выполнению операций с памятью после такого вызова до завершения всех операций, записанных до вызова.

Ещё один макрос объявлен в <linux/compiler.h>, он препятствует компилятору при оптимизации переставлять операторы до вызова и после вызова :

	void barrier( void );

Предыдущий раздел: Оглавление Следующий раздел:
Блокировки   Обработка прерываний