Методы синхронизации и блокировки в системе Хамелеон

Так случилось, что в очередной раз "зацепился языком" в теме про Методы синхронизации и блокировки в Linux ядре. После дискуссии, а она, как видно, ещё не закончилась, осталось двоякое впечатление. Первое чувство - "Ну вот, наконец-то удалось донести хоть часть идей до широкой общественности", второе чувство - "Господи, там так много ошибок, опечаток и стилистических промахов, что народ не воспримет всерьёз". Посему, попробую оправдаться у себя в журнале и немного упорядочить тот поток сознания, который я вылил в тему о синхронизациях и блокировках Линукса.

Итак, каркас сервиса:

///////////////////////////////////////////////////////////////////////////////
// Пример каркаса для Хамелеон сервиса
// Автор: Алексей Мандрыкин aka alman (c) 2007
// Рассмотрим случай, когда сервис оформлен в виде обычного исполняемого файла
//

#include  // Стандартный заголовочный файл из userspace микроядра L4Ka

int main(
	int					argc,		// Количество аргументов
	char				*	argv[],		// Аргументы
	char				*	envp[] )	// Переменные среды
{
	L4_ThreadId_t				tid;		// Идентификатор потока(нити) инициатора сообщения
	L4_MsgTag_t				tag;		// Тэг полученного сообщения
	L4_Msg_t				msg;		// Собственно, структура носитель сообщения
	L4_Word_t				tmout;		// Величина таймаута на приём сообщения.
	int					irq_number;	// Этой переменной присваивается номер прерывания устройства
	bool					do_confirm;	// Признак, отвечающий за необходимость ответа на сообщение

	// Следующая функция инициализирует физическое устройство и возврашает номер его прерывания
	// Детали реализации данной функции зависят от устройства и выходят за рамки данного примера
	initialize_physical_device( &irq_number );

	// Первый таймаут - бесконечность, поскольку в очередеях ещё нет данных и экспайриться нечему
	tmout = (L4_Word_t) -1;

	// Поскольку сервис оформлен в виде потока(нити), заворачиваем всю обработку в бесконечный цикл
	// Бесконечный цикл используется для наглядности и простоты понимания
	while ( true )
	{
		// Примитив L4_Wait - это надстройка над системным вызовом IPC микроядра L4.
		// В показанном примере, IPC поддерживает только фазу приёма сообщений
		// Обратите внимание, что до получения любого сообщения, поток(нить)
		// исключается из очереди планировщика задач и, 
		// соответственно, кванты времени сервиса отдаются другим потокам(нитям) в пределах
		// физического процессора

		tag = L4_Wait( L4_TimePeriod( tmout ), &tid );
		if( L4_IpcFailed(tag) )
		{
			// По какой-то причине, во время фазы приёма мы получили ошибку.
			// Ошибка может быть вызвана следующими причинами:
			// 1. Переполнение сообщение 
			// 2. Ожидание оборвано супервизором
			// 3. За время, определённое величиной tmout, не принято ни одного события
			// Смотрим, чем вызвана ошибка
			if( L4_ErrorCode() == 0x3 )
			{
				// За период tmout не получили ни одного сообщения.
				// Дальнейшая работа заключается в обработке таймаута, 
				// например, повторная передача пакета или удаление пакета из очереди
				// и/или передача статуса операции иниициатору запроса
				// После обработки таймаута не забываем назначить переменной tmout актуальное значение.
				// Например, "время жизни" пакета, имеющего минимальное "время жизни".
			}
			else
			{
				// Здесь можно узнать остальные причины обрыва сообщения
				// Пожалуй, любые случаи попадания в этот участок кода, 
				// за исключением обрыва IPC супервизором, означают ошибку в реализации сервиса
				// наиболее распространённая ошибка  - вы не использовали примитив L4_Accept()
			}
			continue;
		}
		
		// В соответствии со спецификацией L4, номер прерывания равен номеру потока(нити), 
		// источника сообщения. 
		if( L4_ThreadNo(tid) == irq_number )
		{
			// Получили прерывание. Проверяем статус прерывания устройства и 
			// в зависимости от статуса выполняем обработку прерывания
			// Обратите внимание, что сброс бита активного прерывания и 
			// сброс бита прерывания в устройстве - ответственность обработчика прерывания
			handle_interrupt();

			// Затем подтверждаем прерывание на уровне микроядра
			L4_LoadMR( 0, 0 );
			L4_Send( tid );
			// И снова уходим в ожидание события
			continue;
		}

		// Сюда попадаем, когда получаем запрос от внешнего сервиса/приложения
		// Переносим сообщение в локальный буффер
		L4_Store( tag, &msg );
		
		// Определяем идентификатор полученного сообщения
		switch( L4_Label(tag) )
		{
			case WriteDataToDevice:
				// тут всё понятно - кто-то желает записать данные в устройство
				do_confirm = write_data_to_device( &msg );
				break;

			case ReadyToReadDataFromDevice:
				// тут тоже всё понятно - кто-то сообщает о готовности принять данные из устройства
				do_confirm = read_from_device( &msg );
				break;

			default:
				// По-видимому, чужой запрос или неподдерживаемый протокол
				// Чтобы расширить протокол, достаточно добавить новый case и обработчик
				// Запрещаем подтверждение - пусть инициатор "битого" сообщения зависнет в фазе ожидания подтверждения
				do_confirm = false;
				break;
		}

		// Требуется ли подтверждение? Нет? Тогда ждём следующее сообщение
		if( ! do_confirm ) continue;

		// Переносим сообщение в системный буффер.
		// Обратите внимание, что данные системного буфера в этом примере подготавливаются
		// функциями write_data_to_device( &msg ) и read_from_device( &msg );
		L4_Load  ( &msg );

		// Посылаем ответ инициатору сообщения
		// Примитив L4_Reply, как и примитив L4_Wait, - является надстройкой над L4_IPC.
		// L4_Reply исключает фазу приёма и блокировку.
		L4_Reply ( tid );

	}  // И снова в бой! :)

	// Этот пример никогда не попадёт в эту точку. Возвращаем ноль, чтобы не ругался компилятор
	return 0; 
}

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

Алексей Мандрыкин
14 ноября 2007 года