Так случилось, что в очередной раз "зацепился языком" в теме про Методы синхронизации и блокировки в 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 года