|
|
Алексей Мандрыкин

| Дек. 6, 2007 01:12 am Время В микроядре L4 время используется для определения периода ожидания фазы посылки и фазы передачи сообщений. Спецификация «L4 eXperimental Kernel Reference Manual» использует два понятия времени: временной интервал и время события. Рассмотрим их более подробно.
Временной интервал - это относительное время, т.е. время, которое отчитывается от какого либо события. В нашем случае начало события, это точка входа в фазу передачи или приёма сообщения. Таким образом, задавая временной интервал, мы определяем максимальное время, в течение которого сообщение должно быть получено или передано. Временной интервал - интегральная величина. Это означает, что при увеличении интервала уменьшается его точность. Но для наших целей это подходит как нельзя лучше.
Другой важной особенностью временного интервала являются его граничные значения. Минимальный временной интервал равен нулю. Это означает, что вне зависимости от того, готова ли другая сторона к приёму сообщения, передающая сторона не попадает в фазу ожидания, если временной интервал передачи равен 0. Аналогично себя ведёт принимающая сторона, в случае, если временной интервал приёма равен 0. Другим граничным значением временного интервала является бесконечность. Использование такого временного интервала блокирует нить (программный поток) до получения/передачи сообщения или обрыва передачи привилегированной задачей. Временной интервал упакован в 16 битах и может быть описан следующим образом:

Время события описывает абсолютное время. Абсолютное время более точное, но не может описывать события, чей интервал продолжительнее 67 секунд. Более подробную информацию о времени в системе L4 ты можешь найти в разделе 3.3 L4 Version X.2 Reference Manual 3 комментария - Оставить комментарий | |

| Ноя. 29, 2007 01:11 am Прощай, ICQ Сегодня обнаружил, что ICQ № 17794441 мне больше не принадлежит. Там ещё болтается моя фотография и устаревшая информация.
Сначала было жалко - я никогда и никого не вычерёркивал из контакт листа. Этот номер получил при помощи micq из Linux консоли первого Новошахтинского интернет сервера. Было это, дай Бог памяти, в 1998 году.
А ведь я даже не помню, на какой почтовый адрес она была зарегистрирована.
Что-ж, прощай прошлая жизнь. Я верю, что впереди будет лучшая! 5 комментариев - Оставить комментарий | |

| Ноя. 22, 2007 01:56 am Что необходимо знать о микроядре L4 Прежде всего необходимо знать историю микроядра L4.
Также необходимо иметь представление о L4 IPC. IPC - это аббревиатура от словосочетания Inter Process Communication. В переводе на русский это означает "Взаимодействие между процессами". Каким же образом осуществляется такое взаимодействие в системах, построенных на базе L4? Это взаимодействие основано на обмене синхронными сообщениями между процессами и программными потоками (нитями).
Для начала рассмотрим что есть программный поток (нить). Это некий контекст исполнения программного кода процессором. Т.е. когда процессор выполняет какую либо задачу, выбирая команды из оперативной памяти и исполняя их, такую работу можно назвать нитью. Нитей может быть несколько. В простейшем случае нити могут быть распределены между физическими ядрами современных процессоров. Но есть и другой, классический способ, когда процессор быстро переключается между несколькими нитями, в результате чего, создаётся видимость одновременного исполнения нескольких задач. Современные операционные системы способны одновременно исполнять множество нитей на нескольких ядрах или физических процессорах.
Понятие процесса опирается на понятие нить. Процесс представляет собой одну или несколько нитей, которые исполняются в выделенном адресном пространстве. По сути, процесс L4 весьма схож с процессом системы Unix или процессом системы Windows. Иными словами, исполняемая программа представляет собой процесс.
Не буду загружать тебя объяснением, что есть адресное пространство, поскольку это знание не обязательно для прикладного программирования, а вернусь к описанию IPC микроядра L4.
Вся красота и мощь IPC микроядра L4 заключается в их синхронности. Что означает термин синхронные IPC? Это означает, что для того чтобы сообщение было передано от одной нити к другой, передающая нить должна находиться в фазе передачи сообщения, а принимающая нить, соответственно, в фазе приёма сообщения. Если не выполняется любое из этих двух условий, то происходит следующее: 1. Если в момент передачи сообщений, приёмник сообщения не находится в фазе приёма, то передающая нить блокируется до готовности приёмника, но не дольше чем на время t. 2. Аналогично, приёмник сообщения блокируется до получения сообщения, но не дольше чем на время t. В случае таймаута при передаче сообщения, инициатору IPC возвращается соответствующий код ошибки.
Время t, значение которого задаётся программно, варьируется в диапазоне от нуля до бесконечности. При указании нулевого таймаута, IPC не блокируется, но передача сообщения происходит только в случае готовности обеих сторон, т.е. приёмника и передача. В случае же, когда времен равно бесконечности, исполнение нити блокируется до успешной передачи сообщения или до тех пор, пока IPC не будет прервана внешним процессом, имеющем соответствующие права доступа. Следует обратить внимание, что таймаут - экспоненциальная величина, и её точность падает при увеличении таймаута. Впрочем, на при малых значениях таймаута точность находится в пределах 1 микросекунды. Для более подробной информации смотри раздел 3.3 Справочного руководства L4 X.2.
У нас ещё осталось не освещено понятие блокировок. Что есть блокировка? Блокировка, это состояние, при котором блокируемая нить переходит в состоянии ожидания, при котором кванты времени этой нити отдаются другим нитям в пределах физического процессора. В случае, когда физический процессор не имеет других нитей или они также находятся в состоянии блокировки, процессор переходит в состояние низкого энергопотребления.
И вот, наконец, мы подошли к самому главному. L4 IPC может содержать две фазы - фазу передачи и/или фазу приёма сообщений. Фактически, один системный вызов может использоваться для запроса сообщения и получения ответа. При этом операция передачи сообщения будет атомарной. Вот как выглядит описание системного вызова IPC микроядра L4 на языке С:
MsgTag Ipc (ThreadId to, FromSpecifier, Word Timeouts, ThreadId& from)
Как видно, системный вызов использует четыре аргумента: Первый аргумент - идентификатор нити, которой посылается сообщение. Второй аргумент - идентификатор нити, от которой ожидается сообщение. Третий параметр содержит упакованное значение двух временных интервалов - таймаут передачи сообщения и таймаут приёма сообщения. Наконец, последний аргумент, это указатель на переменную, которая пример значение идентификатора нити источника сообщения.
Последний параметр имеет смысл при условии, что в качестве источника ожидаемого сообщения задана константа L4_AnyThread. В этом случае нить может принимать сообщения от любой нити в системе.
Если взаимодействие между процессами не подразумевает фазу приёма сообщения, то в качестве источника следует указать константу L4_nilthread. В этом случае, миную фазу приёма, управление будет передано вызывающей нити сразу после передачи сообщения или ошибки передачи.
Если взаимодействие между процессами не подразумевает фазу передачи сообщения, то в качестве приёмника сообщения необходимо указать константу L4_nilthread. В этом случае, минуя фазу передачи, IPC сразу войдут в фазу приёма/ожидания сообщения.
Системный вызов IPC возвращает тег сообщения, описывающий сообщение и результат его передачи. Рассмотрим его более внимательно:
При передаче сообщения:
 При приёме сообщения:

Поле Label идентифицирует сообщение. На 32-х разрядных архитектурах это поле занимает 16 бит, на 64-х разрядных архитектурах это поле занимает 48 бит.
Бит p используется для перенаправления сообщения. Более подробную информация смотрите в разделе 5.6 Справочного руководства L4 X.2.
Важными параметрами, описывающими сообщения, являются поля t и u. Поле t задаёт количество типизированных виртуальных регистров, которые содержит сообщение. Соотвественно, поле u содержит количество нетипизированных виртуальных регистров.
Вот мы и подошли к описанию формата сообщений, которыми обмениваются нити. Каждая нить имеет 64 виртуальных регистра, чья разрядность соответствует разрядности физического процессора. Эти регистры используются при обмене сообщений. В простейшем случае, мы может заполнить их данными и передать эти данные от одной нити к другой. При этом, параметр u определяет количество таких регистров, которые задействованы в сообщении. Обрати внимание, регистр с номером 0 используется для хранения тега сообщения.
Вероятно, у тебя возник вопрос - как передать больший объём информации, нежели может поместиться в 63 регистрах. В простейшем случае, мы можем в одном из регистров передать указатель на область памяти. Но этот подход не будет работать, если сообщениями обмениваются нити в разных адресных пространствах.
Для передачи больших объёмов данных между различными адресными пространствами, микроядро L4 предоставляет следующие возможности: Строки, Составные Строки, Элементы Отображения Адресного Пространства. При помощи этих примитивов реализуется обмен данными между нитями, выполняемыми в различных адресных пространствах. Замечу, что ничто не мешает использовать эти примитивы для обмена сообщениями между нитями в пределах одного адресного пространства. 9 комментариев - Оставить комментарий | |

| Ноя. 14, 2007 05:22 am Методы синхронизации и блокировки в системе Хамелеон Привет! Рад видеть тебя в своём журнале. Так случилось, что в очередной раз "зацепился языком" в теме про Методы синхронизации и блокировки в Linux ядре. После дискуссии, а она, как видно, ещё не закончилась, осталось двоякое впечатление. Первое чувство - "Ну вот, наконец-то удалось донести хоть часть идей до широкой общественности", второе чувство - "Господи, там так много ошибок, опечаток и стилистических промахов, что народ не воспримет всерьёз". Посему, попробую оправдаться у себя в журнале и немного упорядочить тот поток сознания, который я вылил в тему о синхронизациях и блокировках Линукса.
Итак, каркас сервиса:
///////////////////////////////////////////////////////////////////////////////
// Пример каркаса для Хамелеон сервиса
// Автор: Алексей Мандрыкин aka alman (c) 2007
// Рассмотрим случай, когда сервис оформлен в виде обычного исполняемого файла
//
#include <l4/ipc.h> // Стандартный заголовочный файл из 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;
}
Большое спасибо всем участникам дискуссии, благодаря которой появился этот пост.Музыка: тишина
7 комментариев - Оставить комментарий | |

| Ноя. 13, 2007 12:58 am Writer's Block: Current Favorites My favorite book is "Letters From The Earth" Mark Twain 3 комментария - Оставить комментарий | |

| Ноя. 11, 2007 08:00 am Придуманная история В один прекрасный вечер шеф вызывает меня на ковёр и повествует о том, что проект уже почти готов, что ему надо общаться с инвесторами, а показывать - нечего. При этом убедительно доказывая, что программный код никто смотреть не будет, а подавай аналитикам и инвесторам хорошо написанную, структурированную и оформленную документацию. В общем, обычная офисная история. И весь трагизм ситуации в том, что шеф в этот раз оказался прав....
В общем, с унылым видом я вышел из его кабинета, рассуждая о том, сколько времени и нервов займёт сей труд, и насколько теперь кодирование отстанет от графика. На памяти был прошлый опыт написания документации для предыдущего проекта. В тот раз мы решили использовать Microsoft Word для оформления документации и по полной огребли проблемы с конвертированием в распространённые форматы, поддержанием структуры документа и оформлением внешнего вида. Наученная горьким опытом, наша команда решила начать написание документации с её проектирования.
Прежде всего, мы решили отделить информацию от её представления. Несмотря на то, что в прошлый раз мы использовали стили, программисту Николаю таки пришлось задержаться после работы, чтобы отредактировать 150 страниц вордовского текста, после того как по какой-то, одному Богу известной причине, у документа "слетели" стили.
На внеочередной планёрке мы решили использовать базу данных для хранения информации и какой-либо генератор отчётов, для построения документации на основе этой базы данных. После непродолжительной дискуссии большинство проголосовало за базу данных на основе MicrosoftAccess. Что касается выбоа генератора отчётов, то мы решили использовать FastReport, лицензия на который у нас осталась с предыдущего, кстати сказать - успешного, проекта, который был связан с генерацией финансовой отчётности.
Итак, выбор был сделан, и нам предстояло представить структуру документации на бумаге. Прежде всего, нам предстояло описать протоколы, которые поддерживает наша система. Каждый протокол поддерживает некий набор функций. В свою очередь, каждая функия имеет некоторый набор параметров. Необходимо было описать все протоколы, функции и параметры. Как говорится - глаза боятся, а руки делают. Была создана база данных, а в ней три таблицы - таблица протоколов, таблица функций и таблица аргументов.
Первым делом мы заполнили таблицу протоколов, благо их было не много. Первым порывом было увидеть это на бумаге. Мы не стали отказывать себе в удовольствии и построили простейший отчёт при помощи встроенного в FastReport мастера отчётов. Возможно, здесь сыграл свою роль дизайнерский талант автора FastReport, но этот простейший отчёт нам весьма понравился.
Поскольку хранить описание всех методов всех протколоов мы решили хранить в одной таблице, то следующим шагом мы привязали таблицу описания методов к таблице описания протоколов по её ключу и заполнили таблицу всеми методами одного протокола. Благо, Microsoft Access оказал в этом помощь, предоставляя удобный интерфейс редактирования связанных данных. На это раз ситуация повторилась – мы хотели убедиться что пошли по правильному пути и снова взяли в руки FastReport. На этот раз мастер создания отчётов нам не смог помочь, поэтому пришлось вооружаться мышкой и описанием FastReport. Вскоре, нам удалось во встроенном дизайнере добавить новую таблицу в отчёт, используя ADO Query объект. Немногим больше времени заняло создания бэнда детализации, который мы привязали к основному бэнду. То есть мы построили отчёт Master-Detail. Построение отчёта заняло немногим более секунды и нашим глазам предстал документ, в котором уже был виден будущий прототип новой спецификации. Не буду подробно останавливаться на том, как мы вносили данные, скажу лишь, что таблица с описанием аргументов была аналогичным образом привязана к таблице с опсанием методов. Microsoft Access легко и непринуждённо справился с тремя уровнями вложенности, за что мы были несказанно благодарны разработчикам Майкрософт. Не вызвало проблем и добавление в шаблон отчёта бэнда Subdetail с описанием аргументов. Наш отчёт уже явно принял форму профессионального документа, как по форме, так и по содержанию.
Наш коллектив поручил мне отнести распечатку полученного документа шефу для одобрения. Каково же было моё удивление, когда шеф после минутного ознакомления вернул распечатку с приказом добавить титульный лист и схему документа. С титульным листом проблем не возникло, он был добавлен и без чтения документации. Что касается схемы документа... По прошлому опыту мы знали, что Microsoft Word повзоляет строить схему документа автоматически, на основе стилей. Непродолжительное изучение документации FastReport открыло нам секрет богатых возможностей по автоматической генерации схемы отчёта. Проблема была решена в течение нескольких минут и меня снова отправилии на ковер к шефу. В этот раз изучение документа было долгим и пристальным и я в нерешительности переминался с ноги на ногу, пока шеф листал страницы. На этот раз его лицо выражало благодушность и он сообщил что «В общем, документ ему понравился, но не хватает нескольких вещей». На этот раз он поведал, что сухие перечисления протоколов, функций и аргументов могут оказать положительное воздействие на аналитиков только в случае, если они подкреплены соответствующими схемами и диаграммами. При этом он прочитал получасовую лекцию об особенностях человеческого восприятия и преимуществе графического представления информации. В общем, он сел на своего любимого конька и был самим собой. Результатом его монолога было задание добавить несколько схем и диаграмму (кстати, это не для печати, но данные для диаграммы он придумал сам).
В очередной раз я понуро вышел из его кабинета. Работать не хотелось и меня всего переполняла злость на шефа. Попутно я запустил дизайнер отчётов и начал рисовать стрелочки и квадратики, попутно раскрашивая их в разные цвета. Эта «игра» постепенно начала успокаивать нервы а на экране начало вырисовываться нечто похожее на схему. Поиграясь часик с дизайнером, я получил желаемую схему, при этом даже не заметив как это произошло. Следующие схемы были созданы по образу и подобию первой. При этом я полюбил инструмент копирования формата.
Для построения диаграммы, пришлось «скормить» данные, придуманные шефом, в отдельную таблицу. Как и со всем остальным, разбираться долго не пришлось – помог встроенный в FastReport модуль построения диаграмм. Немного повозившись, были получены (а местами и подогнаны) графики.
Человек такое существо, что быстро ко всему привыкает. Следующий поход к шефу был, хоть и не весьма приятный, но уже не было страха перед его новыми требованиями. На этот раз лицо шефа уже было довольным. Бегло просматривая документ и разговаривая по телефону, он заметил. «Так-так, весьма неплохо. Кстати, сколько раз вы редактирвали документ?», а в его глазах светилась усмешка. Не дав мне опомниться, он вдруг сказал: «А где история редактирования документа?!! Продукт ещё не готов, некоторые протоколы могут быть расширены или дополнены, как мне ориенторваться в этом?» При этом он показывал пальцем на кипу бумаги, на которой были распечатаны предыдущие версии документов.
В общем, в большой спешке были добалена новая таблица в базу данных, содержащая историю редактирования документа и в шаблон отчёта добавлен новый лист, на котором помещался отчёт Document Revision History.
Чтобы не предстать в невыгодном свете, заметим, что некоторые вещи в документ были добавлены не по приказу шефа, а по инициативе нашей команды. Уже после вышеприведённых событий, мы отредактировали базу данных, связав её с ещё одной таблицей, в которой мы поместили описание типов данных аргументов.
История будет не полной, если не рассказать что при построении отчёта мы всё-таки нашли некоторые «особенности» FastReport. К чести разработчиков сказать, наши пожелания были учтены довольно быстро – служба поддержки отвечала быстро и по существу. Но это, говорят, как повезёт – зависит от времени года, времени суток и дрейфа айсбергов в Северном Ледовитом океане.
Но это ещё не конец истории. Документация всё-таки попала на стол аналитиков и первым вопросом было: «А чем в ваших протоколах отличаются входные параметры от выходных?». Говорят, когда шеф услышал этот вопрос, то он густо покраснел и сделал один звонок. Звонок был Вашему покорному слуге. Не буду передавать содержание этого монолога, скажу лишь одно - спасибо тебе, Господи, за то что ты дал людям электронную почту. В срочном порядке в таблицу было добавлено новое поле, определяющее принадлежность каждого аргумента ко входному или выходному параметру. В отчёт был вставлен бэнд группировки по новому полю и отчёт принял вид, который и используется нашей компанией и нашими клиентами по настоящее время.
Резюмируя, хочу сказать, что в следующий раз наша команда не будет размышлять над тем, какое решение выбрать для описания форматов протоколов. Полученный опыт однозначно показал преимущества разделения информации от её представления. Как в плане трудозатрат для получения результата, так и в плане качества. Помимо этого нам удалось сэкономить на дополнительных инструментах по конвертирования документации из формата MS Word, в широко распространённые форматы представления данных, а это немалые суммы - наша компания не использует нелицензионное программное обеспечение.
P.S. И все-таки прав был шеф насчёт «Истории исправления документа», уже не раз наc выручала эта возможность. А продукт наш успешно развивается вместе с документацией. Теперь вот у шефа новый бзик – когда узнал, что фастрепорты выпустили сервер, захотел чтобы для наших партнёров в сети всегда лежала самая свежие версия доки. Хочет нашу спецификацию под FR сервер перетащить, как думаете, имеет смысл? 6 комментариев - Оставить комментарий | |

| Ноя. 7, 2007 04:55 am Виртуальные файловые системы На протяжении нескольких лет не вступаю в священные holywars. А вот последнее время чувствую, что если не вступать в споры, то тебя никто и не заметит. Получается, с одной стороны, споры отнимают много времени, а с другой стороны - без них никак не обойтись. Главное - знать с кем и о чём спорить.
Недавно нарочно ввязался в спор C vs C++, чтобы маленько попиарить Хамелеон. На примере сравнения виртуальных файловых систем. Эта ссылка ведёт на информацию об устройстве виртуальной файловой системы Linux
В Хамелеоне это реализовано несколько иначе:
class ISuperblock
{
public:
virtual ~ISuperblock();
virtual int ConnectToDevice(class IBlockDevicePartition * Device) = 0;
// Mount service
virtual class IBlockDevicePartition * GetDevice(void) = 0;
virtual class Inode * GetRootInode(void) = 0;
virtual class ISuperblock * GetParentSuperblock(void) = 0;
virtual const ino_t GetParenInode(void) = 0;
virtual void SetParent(class Inode * inode) = 0;
// Controls how many objects use Superblock
virtual void AddReference() = 0;
virtual void DelReference() = 0;
virtual const int GetReferenceCount(void) = 0;
// Inode service
virtual class Inode * GetInode(int nInodeNumber, int nParenNumber ) = 0;
virtual class Inode * MakeInode (unsigned short uid, unsigned short gid, unsigned short mode) = 0;
virtual int RemoveInode(class Inode * inode) const = 0;
virtual int ReleaseInode(class Inode * inode) = 0;
// Directory service
virtual typeDirentry * ReadDir(class DirectoryHandler * dir) = 0;
virtual int InsertDirectoryEntry (class Inode * where, class Inode * which, const char *szName) = 0;
virtual int DeleteDirectoryEntry (class Inode * where, const char *szName) = 0;
virtual bool IsDirectoryEmpty ( class ISuperblock * sb, class Inode * inode_dir) = 0;
// Symbolic link service
virtual int MakeSymbolicLink(class Inode * inode, const char * szLinkTo) = 0;
virtual class Inode * GetSymlink (class IProcess * process, class Inode * node) = 0;
// File system name and status service
virtual int StatFS( statfs_t * stat) = 0;
virtual char * GetFileSystemName(void) = 0;
// Prepare Inode to read/write operations
virtual int OpenNode(class Inode * inode) = 0;
virtual int CloseNode(class Inode * inode) = 0;
// Disk blocks service
virtual block_t AllocateFreeBlocks(int nCount) = 0;
virtual int ReleaseBlock(block_t nBlock) = 0;
virtual block_t GetBlockNumberByIndex(class Inode * node, unsigned int nIndex) = 0;
virtual int SetBlockNumberByIndex(class Inode * node, unsigned int nIndex, block_t nBlockNumber) = 0;
virtual int TruncateInode(class Inode * inode, unsigned int nSize = 0) = 0;
// Flushing service
virtual int FlushInode( class Inode * inode, FlushMode_t flush_mode ) = 0;
virtual int FlushDiskCaches(void) = 0;
// File path service
virtual int GetCurrentDirectoryName (class IProcess * process, class Inode * inode, char * dirname) = 0;
};
Т.е. "движок" любой файловой системы должен "всего-лишь" поддерживать этот интерфейс для предоставления POSIX совместимого сервиса. Естественно, напрашивается желание завернуть всё это в некое подобие COM технологии, наследовать от IUnknown или, тем паче, от ISubject... Но это в перспективе, а пока от этого класса наследованы MinixSuperblock, Ext2Superblock, IsoSuperblock, FatSuperblock и DevSuperblock. Последний - это суперблок для devfs, файловой системы, где регистрируются сервисы (в т.ч. драйвера устройств).
Как это ни странно, наиболее сложной оказалась поддержка FAT. Дело в том, что в файловая система FAT не использует Inode. Это было бы полбеды, поскольку в момент чтения FAT директории, для каждого файла в оперативной памяти автоматически создаются inode. Такой же подход используется и в реализации IsoSuperblock и этот метод работает. Проблемы начинаются когда в FAT директории находится файл нулевой длины. Соответственно, у этого файла не имеется записи в таблице FAT и поэтому у пустого файла невозможно однозначно индексировать inode. Как ты уже понял, для ненулевого файла в качестве индекса inode брался номер первого элемента в таблице FAT. О, какая тафтология получается, но я верю, ты понял о чём речь. Пока я не придумал ничего лучше, чем использовать счётчик отрицательных чисел, для индексации inode, принадлежащих пустым файлам на FAT. И при записи первого байта в такую "виртуальную" inode, она автоматически переиндексируется номером первого элемента в таблице FAT. Ох, чувствую, я тебя утомил излишними подробностями. Но если ты был внимателен, то у тебя возник закономерный вопрос: "А где же методы чтения и записи файла?". Методы Read и Write реализованы в классе Inode. Каким образом удалось не задействовать класс Superblock для чтения и записи файлов? Таки он задействован. Дело в том, что ISuperblock предоставляет целых три метода, которые используются при чтении/записи файлов. Вот они:
block_t AllocateFreeBlocks(int nCount); block_t GetBlockNumberByIndex(class Inode * node, unsigned int nIndex); int SetBlockNumberByIndex(class Inode * node, unsigned int nIndex, block_t nBlockNumber);
Например, при чтении файла с некоторой позиции, мы сначала находим индекс блока для чтения по формуле: index = position / block_size, а затем получаем логический номер блока посредством метода GetBlockNumberByIndex. И уже полученный номер мы передаём в качестве аргумента методу ReadBlock интерфейса IBlockDevicePartition. Запись происходит по аналогии, только номер блока для записи предварительно возвращается методом AllocateFreeBlocks. Разумеется, методы выделения свободных блоков и индексации уникальны для каждой файловой системы. Помимо этого, не каждая файловая система обязана поддерживать все методы интерфейса ISuperblock. К примеру, методы AllocateFreeBlocks и SetBlockNumberByIndex не имеют смысла для файловой системы ISO и должны возвращать код ошибки EROFS (файловая система только для чтения). Другой пример - методы MakeSymbolicLink и GetSymlink не имеют смысла для файловой системы FAT, поскольку она не поддерживает символические ссылки. Соответсвенно, заглушки этих методов должны возвращать код ошибки ENOSYS. В заключение хотелось бы сказать, что вышеприведённый интерфейс окончательно не устоялся. За время написания этого поста я обнаружил несколько мест, которые возможно и необходимо оптимизировать. Другой вопрос, я пока не уверен, что в эту схему красиво ложатся файловые системы, которые умеют хранить несколько файлов в одном логическом блоке. Хотелось бы услышать мнение экспертов по этому поводу.11 комментариев - Оставить комментарий | |

| Ноя. 5, 2007 03:15 am Под отладкой Намедни решил заглянуть, сколько же отладочной информации в системе и насколько она может быть понятна человеку непосвященному. Но для начала немного теории. L4Ka::Pistachio имеет очень интересный макрос - L4_KDB_Enter. При внимательном рассмотрении можно обнаружить что он генерит следующий код (для наглядности перевожу этот макрос в fasm):
macro L4_KDB_Enter msg { local jump int3 jmp jump mov eax, msg jump: }
Т.е. когда процессор встречает в общем-то обычную инструкцию int3, то передает управление обработчику отладочного прерывания, который является частью микроядра Pistachio. В свою очередь, обработчик проверяет команды, идущие после инструкции прерывания и в случае, если они соответствуют вышеприведённому формату, выводит сообщение перед переходом во встроенный отладчик. Это позволяет идентифицировать что и в каком месте вызвало переход в отладчик.
Здесь следует отметить, что поставляемый вместе с Pistachio отладчик является самым низкоуровневым средством, которое предоставляет весьма скромные, но необходимые возможности для отладки - пошаговое исполнение, дамп памяти, дизассемблирование, показ регистров и т.д. К сожалению, этот консольный отладчик не предоставляет возможности StepOverFunctionCall, но эту возможность можно эмулировать, поставив точку останова. Другой особенностью встроенного в Pistachio отладчика является его режим работы с клавиатурой. Он не использует прерывания, а в цикле опрашивает порт клавиатуры. При этом загрузка процессора занимает 100%. Естественно, это не значит что Хамелеон работает по такому же принципу, Хамелеон честно использует прерывания.
Для человека, избалованного отладчиком TurboDebugger, а впоследствии плотно подсевшего на MS VisualStudio, такая отладка покажется адской пыткой. Впрочем, по сравнению со встроенным отладчиком Pistachio, даже GDB будет казаться верхом удобства и "юзер френдли". Надеюсь, теперь ты имеешь представление о встроенном отладчике микроядра Pistachio. Но что делать, в подавляющем большинстве случаев этот отладчик является единственным средством, дающим понять что происходит в каком либо программном потоке какого либо процесса.
Итак, не далее как 10 минут назад я набрал следующую команду:
grep -r -e L4_KDB_Enter * > dbg_msg.txt
и публикую здесь результат. Не суди строго за мой "руглиш":
display_driver/vga/VideoVGA.cc: L4_KDB_Enter("unable map video memory"); display_driver/ProtocolStartup.cc: L4_KDB_Enter("vty.drv: Error: Unable register console driver"); display_driver/ProtocolStartup.cc:// L4_KDB_Enter("Before wait"); display_driver/ProtocolStartup.cc:// L4_KDB_Enter("vty.drv: Error: No response from driver"); display_driver/ProtocolStartup.cc:// L4_KDB_Enter("vty.drv: Checkpoint"); display_driver/display/display.cc:// L4_KDB_Enter("Console sync error"); display_driver/display/display.cc: L4_KDB_Enter("Screen index error"); display_driver/main.cc: L4_KDB_Enter("Unable get keyboard service ID. Application will lock"); display_driver/main.cc: L4_KDB_Enter("vty.drv: Log reply IPC failed"); display_driver/main.cc: L4_KDB_Enter("vty.drv: Unknown command code"); display_driver/main.cc: L4_KDB_Enter("vty.drv: Logger reply IPC failed"); display_driver/Console.cc: L4_KDB_Enter("vty.drv: Close IPC failed"); display_driver/Console.cc: L4_KDB_Enter("vty.drv: Close IPC failed"); display_driver/Console.cc: L4_KDB_Enter("vty.drv: Closing non-opened console"); display_driver/Console.cc: L4_KDB_Enter("vty: Read confirmation failed"); display_driver/Console.cc: L4_KDB_Enter("vty.drv: Confirm write IPC failed"); display_driver/Console.cc: L4_KDB_Enter( "Unparsed ioctl"); display_driver/Console.cc: L4_KDB_Enter("vty.drv: Confirm IOCTL IPC failed"); ethernet_driver/ethernet_driver.cc: L4_KDB_Enter( "Ethernet registration IPC failed" ); ethernet_driver/ethernet_driver.cc: L4_KDB_Enter( "Got bad status of ethernet registration" ); ethernet_driver/ip_stack.cc: L4_KDB_Enter("IP packet lenght error"); filesystem_driver/RpcTalker.cc:// L4_KDB_Enter("Whoa"); filesystem_driver/BlockDeviceEnumerator.cc: L4_KDB_Enter("Read block"); filesystem_driver/executor/elf/Elf.cc:// L4_KDB_Enter("Check it!"); filesystem_driver/executor/elf/Elf.cc: //L4_KDB_Enter("?"); filesystem_driver/ProtocolStartup.cc: L4_KDB_Enter("Error"); filesystem_driver/Junction.cc: L4_KDB_Enter("CJunction: IPC failed" ); filesystem_driver/Junction.cc: L4_KDB_Enter("Asynchronus reponse sync error"); filesystem_driver/Junction.cc: L4_KDB_Enter("Asynchronous reponse sync error"); filesystem_driver/Junction.cc: L4_KDB_Enter("No free context"); filesystem_driver/Junction.cc: L4_KDB_Enter("CJunction: Input buffers exhausted"); filesystem_driver/Junction.cc: L4_KDB_Enter("CJunction: Context exhausted"); filesystem_driver/Junction.cc:// L4_KDB_Enter("Context switched, what next?"); filesystem_driver/Junction.cc: L4_KDB_Enter("Got unwanted response. May be algorithm error."); filesystem_driver/Junction.cc: L4_KDB_Enter( "Unable reply to upper layer requestor" ); filesystem_driver/Junction.cc:// L4_KDB_Enter("Before SP modification"); filesystem_driver/Junction.cc: L4_KDB_Enter("Junctions: Put buffers full"); filesystem_driver/Junction.cc: L4_KDB_Enter("Junction: PUT: Waiting error\n"); filesystem_driver/Junction.cc:// L4_KDB_Enter("Junction: GET: Waiting error\n"); filesystem_driver/Junction.cc:// L4_KDB_Enter("Memory initiialized"); filesystem_driver/Junction.cc:// L4_KDB_Enter("FileSystem initiialized"); filesystem_driver/Junction.cc:// L4_KDB_Enter("Crossroad initiialized"); filesystem_driver/FileSystemServer.cc: L4_KDB_Enter("Unable send filesystem log to device"); filesystem_driver/FileSystemServer.cc: L4_KDB_Enter("No log device attached"); filesystem_driver/FileSystemServer.cc: L4_KDB_Enter("handler IOCTL: Wtf?"); filesystem_driver/FileSystemServer.cc: L4_KDB_Enter("IOCTL: listen to message failed"); filesystem_driver/FileSystemServer.cc: L4_KDB_Enter("Sad news!!!"); filesystem_driver/FileSystemServer.cc: L4_KDB_Enter("FS Error"); filesystem_driver/FileSystemServer.cc:// L4_KDB_Enter("Filesystem started!!!!!"); filesystem_driver/source/FileSystem.cc: L4_KDB_Enter("/dev/tty: listen to message failed"); filesystem_driver/source/FileDescriptor.cc:# define L4_KDB_Enter(s) { puts(s); getc(stdin); } filesystem_driver/source/FileDescriptor.cc: L4_KDB_Enter("FileDescriptor_t found open file on Clear()"); filesystem_driver/source/FileDescriptor.cc: L4_KDB_Enter("Found inode without superblock"); filesystem_driver/source/MountList.cc:# define L4_KDB_Enter(x) filesystem_driver/source/MountList.cc: L4_KDB_Enter("Ttf!"); filesystem_driver/source/Inode.cc://L4_KDB_Enter("Before read block"); filesystem_driver/source/Inode.cc://L4_KDB_Enter("After read block"); filesystem_driver/source/Inode.cc:// L4_KDB_Enter("Single buffer allocated"); filesystem_driver/source/Inode.cc: L4_KDB_Enter("Single buffer error"); filesystem_driver/source/Inode.cc: L4_KDB_Enter("Double buffer error"); filesystem_driver/source/Inode.cc: L4_KDB_Enter("Triple buffer error"); filesystem_driver/source/dev_fs/DevSuperblock.cc: L4_KDB_Enter("DevSuperblock: IPC failed"); floppy_driver/floppy_driver.cc: L4_KDB_Enter("Puh"); floppy_driver/floppy_driver.cc: L4_KDB_Enter( "Floppy registration IPC failed" ); ide_driver/ide_server.cc: L4_KDB_Enter( "ATA/ATAPI registration IPC failed" ); init/init.cc:// L4_KDB_Enter("Constructor called"); init/init.cc:// L4_KDB_Enter("Destructor called"); init/init.cc: L4_KDB_Enter("Mount root failed"); init/init.cc: L4_KDB_Enter("Mount devfs failed"); init/init.cc:// L4_KDB_Enter("Ups!"); libc/xam_vsnprintf.cc:// L4_KDB_Enter( "Problem detected" ); libc/xam_signal.cc: L4_KDB_Enter("libc: Leave sigproxy signal terminated in send phase"); libc/xam_signal.cc: L4_KDB_Enter("libc: __sig_leave: YOU MUST NEVER REACH THIS POINT"); libc/xam_signal.cc: L4_KDB_Enter("sigprocmask() called"); libc/xam_string.cc: L4_KDB_Enter("Will fall"); libc/xam_string.cc: L4_KDB_Enter("Check me"); libc/xam_stub.cc: L4_KDB_Enter("Attempt to use a virtual function before object has been constructed\n"); libc/xam_unistd.cc: L4_KDB_Enter("sys_exit: Unable terminate process"); libc/call/xam_fs_call.cc: L4_KDB_Enter("FileSystem called prior initialization"); libc/call/xam_fs_call.cc: L4_KDB_Enter("Interrupted fs call"); libc/stdio/xam_stdio.cc: L4_KDB_Enter("fopen 'a' mode detected"); libc/stdio/xam_vsscanf.cc: L4_KDB_Enter("vsscanf called"); libc/unistd/xam_seek.cc:// L4_KDB_Enter("lseek completed"); libc/xam_console.cc: L4_KDB_Enter("Cannot found console driver"); libc/xam_console.cc: L4_KDB_Enter("Fix console status"); libc/xam_console.cc: L4_KDB_Enter("xam_gethar() IPC fault"); libc/xam_console.cc: L4_KDB_Enter("Console device locked?"); libc/xam_console.cc: L4_KDB_Enter("xam_printf(): Console did not initialized yet"); libc/xam_console.cc: L4_KDB_Enter("xam open interrupted"); libcc/CriticalSection.cc: L4_KDB_Enter("Destroing a not released critical section"); libcc/CriticalSection.cc: L4_KDB_Enter("Stop process by critical section"); libcc/CriticalSection.cc: L4_KDB_Enter("Continue process by critical section"); libcc/Memory.cc:// L4_KDB_Enter("Check msg"); libcc/LineDiscipline.cc: if( nInputBytesCount < nBytesRead ) L4_KDB_Enter("Line discipline problem"); libcc/LineDiscipline.cc: L4_KDB_Enter("Something strange around here"); libcc/LineDiscipline.cc: L4_KDB_Enter("Got VKILL character"); libcc/LineDiscipline.cc:// L4_KDB_Enter("vty:LineDisciplineSendData"); libcc/DeviceDriver.cc: L4_KDB_Enter("Cannot found init process"); ramdisk_driver/ramdisk_driver.cc: L4_KDB_Enter( "RAMDISK registration IPC failed" ); roottask/xam_pager.cc: L4_KDB_Enter("Console device locked?"); roottask/xam_pager.cc: L4_KDB_Enter("PAGER IPC failed "); roottask/xam_pager.cc: L4_KDB_Enter("Unrecoverable Page fault"); roottask/xam_pager.cc: L4_KDB_Enter("MAP IPC failed"); roottask/xam_pager.cc: L4_KDB_Enter("Wow!"); roottask/xam_pager.cc: L4_KDB_Enter("Startup IPC failed"); roottask/TaskManager/process.cc: L4_KDB_Enter("Address space creation error"); roottask/TaskManager/process.cc: L4_KDB_Enter("Space control error"); roottask/TaskManager/process.cc:// L4_KDB_Enter("Check KIP, please"); roottask/TaskManager/process.cc: L4_KDB_Enter("Activate thread error"); roottask/TaskManager/process.cc: L4_KDB_Enter("Allocate_Thread: Too many threads"); roottask/TaskManager/process.cc: L4_KDB_Enter("Allocate_Thread: Local thread map full"); roottask/TaskManager/process.cc:// L4_KDB_Enter("Write page"); roottask/TaskManager/process.cc: L4_KDB_Enter( "Unable set BSS segment due to synchronozation error" ); roottask/TaskManager/process.cc: L4_KDB_Enter( "Unable set owner to BSS segment" ); roottask/TaskManager/process.cc: L4_KDB_Enter("Delivery signal"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Release tid: TID was not allocated"); roottask/TaskManager/task_manager.cc:// L4_KDB_Enter("Allocate thread"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Release tid: TID was not allocated"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Allocate_Thread: Unable get PLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Allocate_Thread: unable allocate TID"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Allocate_Thread: Unable allocate TID"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Thread_Create: Unable get TLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Thread_Create: Unable get PLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Thread_Create: Unable allocate TID"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Thread start"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Thread_Destroy: Unable get TLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Thread_Destroy: Unable get PLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Unable allocate Thread ID\n"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Unable allocate process local strorage\n"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Unable allocate UTCB\n"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Unable allocate thread local strorage\n"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("AllocateProcess() cannot create an address space\n"); roottask/TaskManager/task_manager.cc:// L4_KDB_Enter("Checkpoint"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Supervisor: Process_Exec: Unable get TLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Supervisor: Process_Exec: Unable get PLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Exec: Unable allocate process local strorage\n"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Exec: Unable allocate UTCB\n"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Fork: Unable get TLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Fork: Unable get PLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Destroy: Unable get TLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Destroy: Unable get PLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Wait: Unable get parent PLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter("Process_Wait: Unable get parent TLS"); roottask/TaskManager/task_manager.cc: L4_KDB_Enter( "Fault instruction pointer" ); roottask/TaskManager/thread.cc:// L4_KDB_Enter("PREPARE STACK"); roottask/TaskManager/thread.cc:// L4_KDB_Enter("PutReturnAddressOnStack"); roottask/MemoryManager/object_stream.cc: L4_KDB_Enter("Release did not implemented yet"); roottask/MemoryManager/object_stream.cc:// L4_KDB_Enter("Woo a la"); roottask/MemoryManager/MemoryManager.cc:// L4_KDB_Enter("Requesting first page"); roottask/MemoryManager/MemoryManager.cc: L4_KDB_Enter("Release of a that size complex page is not implemented"); roottask/MemoryManager/MemoryManager.cc: L4_KDB_Enter("Physical pages that size complex page is not implemented"); roottask/MemoryManager/MemoryManager.cc: L4_KDB_Enter("Use step for debug"); roottask/MemoryManager/MemoryManager.cc: L4_KDB_Enter("sigma0 failed"); roottask/MemoryManager/segment.cc:// L4_KDB_Enter("Page size not match"); roottask/MemoryManager/segment.cc: L4_KDB_Enter(); roottask/MemoryManager/segment.cc: L4_KDB_Enter("Broken segment's allocation type"); roottask/MemoryManager/segment.cc: L4_KDB_Enter("Somebody release direct segment"); roottask/MemoryManager/segment.cc: //L4_KDB_Enter("Do we need modify refence counter on parent object?"); roottask/MemoryManager/segment.cc: L4_KDB_Enter("Does anybody could imagine where a pointer map useful?"); roottask/MemoryManager/segment.cc:// L4_KDB_Enter("checkpoint"); roottask/MemoryManager/segment.cc: L4_KDB_Enter("Import pages"); roottask/MemoryManager/segment.cc: L4_KDB_Enter("Unaligend address on MemorySegment::Get_Word"); roottask/MemoryManager/page_stream.cc:// L4_KDB_Enter("Requesting page of pages"); roottask/MemoryManager/page_stream.cc: L4_KDB_Enter("Unable allocate page of pages"); roottask/MemoryManager/page_stream.cc: L4_KDB_Enter("Unable allocate page of pages"); roottask/MemoryManager/CompoundSegment.cc: L4_KDB_Enter("Unable map pysical pages"); roottask/MemoryManager/CompoundSegment.cc: L4_KDB_Enter( "Compound segment synchronization error" ); roottask/MemoryManager/CompoundSegment.cc:// L4_KDB_Enter("Got compound page"); roottask/roottask.cc:// L4_KDB_Enter("Unable start the init process. Press g to continue"); roottask/roottask.cc: L4_KDB_Enter("Unable start pager"); roottask/roottask.cc: L4_KDB_Enter("Unable start timer"); roottask/roottask.cc: L4_KDB_Enter("Unable start Hardware abstraction layer"); roottask/roottask.cc: L4_KDB_Enter("Unable start control thread"); roottask/roottask.cc: L4_KDB_Enter("Server terminated"); roottask/xam_dbgconsole.cc: L4_KDB_Enter("Superisor:Unable initialize console"); roottask/xam_dbgconsole.cc: L4_KDB_Enter("Superisor:Unable open debug console"); roottask/xam_dbgconsole.cc: L4_KDB_Enter("Superisor:Unable open debug console"); roottask/xam_dbgconsole.cc: L4_KDB_Enter("--- enter kernel debugger"); roottask/xam_modules.cc: L4_KDB_Enter("unable to map multi-boot-info"); roottask/xam_modules.cc: L4_KDB_Enter("unable to map multi-boot-info"); roottask/xam_modules.cc:// L4_KDB_Enter("Before run modile"); roottask/xam_modules.cc: L4_KDB_Enter( "Press g to continue" ); roottask/xam_modules.cc: L4_KDB_Enter( "Press g to continue" ); roottask/xam_modules.cc: L4_KDB_Enter("Module execution completed"); roottask/xam_modules.cc: L4_KDB_Enter( "Unable allocate module pages" ); roottask/xam_timer.cc:// L4_KDB_Enter("Timer service message"); roottask/xam_timer.cc: L4_KDB_Enter("Timer IPC failed "); roottask/xam_dispatcher.cc: L4_KDB_Enter("System exception catched"); roottask/xam_dispatcher.cc: L4_KDB_Enter("Program exception catched"); roottask/xam_dispatcher.cc: L4_KDB_Enter("ProcessCreate syscall removed from specification"); roottask/xam_dispatcher.cc:// L4_KDB_Enter("SUPERVISOR: before process destroy\n"); roottask/xam_dispatcher.cc:// L4_KDB_Enter("SUPERVISOR: Within process destroy\n"); roottask/xam_dispatcher.cc:// L4_KDB_Enter("SUPERVISOR: Message sent to an event listener\n"); roottask/xam_dispatcher.cc: L4_KDB_Enter("Change heap size"); roottask/xam_dispatcher.cc: L4_KDB_Enter("Puh"); roottask/drivers/xam_blkdevice.cc: L4_KDB_Enter("Bad format detected"); roottask/drivers/i8042.cc:// if( ch == 1 ) L4_KDB_Enter("Inside keyboard handler"); roottask/drivers/i8042.cc: L4_KDB_Enter("console driver busy?!"); roottask/drivers/i8042.cc:// L4_KDB_Enter("Nobody lisen for interrupt"); rs232/rs232.cc: L4_KDB_Enter("RS232: Unknown IOCTL code"); rs232/rs232.cc: L4_KDB_Enter("RS232: Confirmation IPC failed"); rs232/rs232.cc: L4_KDB_Enter("RS232: Confirm write IPC failed"); rs232/rs232.cc:// L4_KDB_Enter("Checkpoint"); rs232/rs232.cc: L4_KDB_Enter("Serial device %d does not exist"); rs232/rs232.cc: L4_KDB_Enter("rs232.drv: Logger reply IPC failed"); rs232/rs232.cc: L4_KDB_Enter("RS232: Unknown request"); rs232/rs232.cc: L4_KDB_Enter("rs232.drv: IPC failed"); rs232/rs232.cc:// L4_KDB_Enter("Attempt to use a virtual function before object has been constructed\n"); rs232/rs232.cc: L4_KDB_Enter( "RS232 registration IPC failed" );
2 комментария - Оставить комментарий | |

| Окт. 31, 2007 07:07 pm +++ Прошёл месяц с момента последней новости о системе Xameleon и ещё больше с момента публичной сборки. Тебе интересно, на что сейчас похожа система?
Внешне не изменилось почти ничего, разве что поубавилось ошибок. Загрузка стала более стабильна и предсказуема.
От системного вызова CreateProcess пришлось отказаться и вот почему - в третьей сборке этот вызов использовался исключительно для старта драйверов в выделенном адресном пространстве. Т.е. идея безопасных драйверов была основана на этом вызове. Например, первая версия инструментария разработчика включает в себя примеры драйверов, которые могут быть собраны как для работы в адресном пространстве супервизора, так и собственном адресном пространстве. В следующей сборке вызова CreateProcess не будет. Однако, это не значит, что в следующей сборке не будет безопасных драйверов. Читай дальше, и ты поймёшь почему. :)
Для начала расскажу как стартовали драйвера в Хамелеоне до недавнего момента. При старте системы стартовал отдельный поток, который брал информацию о загруженных драйверах, любезно предоставленную загрузчиком kickstart.
Этот поток входил в экспортируемую драйвером функцию _start, которая, в свою очередь, вызывала метод register_driver. Далее, метод register_driver отсылал запрос супервизору, в котором содержались параметры и информация о драйвере. Далее супервизор, в зависимости от параметров, стартовал драйвер при помощи CreateThread (в адресном пространстве супекрвизора) или CreateProcess() в собственном адресном пространстве). Сложно? Но это работает в третьей сборке.
Что же произойдёт в четвёртой? Кроме отмены вызова CreateProcess, изменилась семантика вызова Exec. В случае однопоточного приложения, которых большинство в мире Unix, Exec работает в соответствии со стандартом POSIX. (во всяком случае я стремлюсь к этому), но всё меняется, когда системный вызов Exec инициирует программный поток, который стоит не в голове списка потоков процесса. В этом случае образ процесса, переданный системному вызову Exec, не затирает образ вызывающего процесса, а запускается во вновь созданном адресном пространстве. Вызывающий поток мигрирует в новое адресное пространство и становится лидером процесса.
При этом системный вызов CreateProcess становится ненужным, поскольку предоставляет более высокий уровень абстракции.
Другим нововведением в четвёртой сборке будет поддержка Sticky бита. Эта фича уже реализована и более/менее протестирована. Мы ещё посоревнуемся с Linux, кто быстрее скрипты исполняет.
Хотелось бы прояснить ситуацию с технологией CopyOnWrite. К сожалению в ближайшее время подержки CopyOnWrite не предвидится. Безусловно, технология позволяет получить значительный выигрыш в скорости, когда делится жирный процесс, с большим сегментом .data и разросшимися сегментами .bss и стека. Ну что-ж, некоторые сетевые сервисы и современные shell, которые активно используют fork() - да, они будут проигрывать в скорости. Я это чётко осознаю. Но с другой стороны, это открывает нишу для аналогичных программ, использующих потоки вместо fork(). Ну а те, кто использует fork() по религиозным причинам, хорошо знают приёмы минимизации накладных расходов.
А в это время в пользовательском пространстве идет активная работа над портированием программ. И на этом поле не всё хорошо. Делать загрузочный компакт диск пока рано, хотя для диска уже многое подготовлено.В то же время, уже явно не хватает размера виртуального диска для расширения.демонстрационных возможностей и портирования новых программ...
Скрепя сердце я пошёл на нарушение GPL - прилинковал несколько программ к утилите xambox. На настоящий момент утилиа xambox включает в себя статически связанные утилиты fdisk, mkfs.minix, fsck.minix, gzip и stty. Я понимаю, что формально нарушаю лицензию, но надеюсь что пока Хамелеон не слишком известен, меня избежит обвинение в нарушении лицензии. Ну а когда созреет загрузочный компакт диск, то отвязать эти приложения от xambox не составит труда. Впрочем, не исключен вариант публикации xambox под лицензией GPL. В общем, время расставит всё по местам.
До Ext2 руки пока не дошли. Проверял тестовый образ - монтируется. В файловой системе Minix уничтожен неприятный баг, когда размер читаемого/записывемого файла попадал на Double Indirect блоки. Если не ошибаюсь, данный фикс автоматически распространяется и на файловую систему ext2.
Помимо этого, в четвёртой сборке ожидается более сотни небольших фиксов, большую часть из которых уже и не вспомнить. Много ошибок исправлено в libc.
Чуть не забыл. На демонстрационном диске в четвёртой публичной сборке появится нечто интересное, что будет приятным сюрпризом для части разработчиков. Такие дела. Музыка: тишина
3 комментария - Оставить комментарий | |

| Сент. 27, 2007 12:36 am Ethernet для Xameleon Сегодня день рождения первого ethernet драйвера для системы Xameleon.
Эмулятор Microsoft Virtual PC поддерживает сетевую карту DEC21140. Соответственно, выбирать было не из чего. Спецификация на чип 21140 нашлась довольно быстро. Приблизительно через день, Хамелеон уже умел показывать на экране заголовки принятых широковещательных Ethernеt пакетов. Но как принять ethernet пакеты, посланные на определённый МАС адрес? И какой МАС адрес у виртуальной сетевой карты? В качестве временного решения виделась установка битов "Receive All" и "Promiscuous Mode" для приёма любых Ethernet пакетов, что гуляют по локальной сети. Что неприятно удивило, ожидаемого эффекта это не дало. По прежнему принимались исключительно широковещательные Ethernet пакеты. Уж не знаю кого в этом винить, спецификацию на сетевую карту или "MS Virtual PC". Отчаявшись, я решил заглянуть в код драйвера Tulip, котрый идёт в составе загрузчика grub (с патчем os). Код GNU-того драйвера меня немного ошарашил - оказывается 21140 соместимых чипов великое множество и каждый производитель считал своим долгом что-либо изменить.
Оказывается, МАС адрес необходимо вычитывать из ППЗУ карты и для этого используется интерфейс I2C. Покопавшись в интернете на предмет спецификации на I2C, обнаружил что написание универсального сервиса для этого интерфейса может занять несколько дней. Тогда было решено придумать "левый" MAC и указать его в качестве адреса виртуальной карты. Поскольку этот адрес будет выводится в лог наряду с адресами других карт, то он должен "бросаться в глаза". Для этого был выбран адрес 00-03-44-44-44-44.
В спецификации сказано, что установка МАС адреса аналогична посылке пакета в сеть. Единственное отличие в том, что для конфигурационного пакета необходимо установить бит SetupPacket в дескрипторе передающих буферов. Похоже, это было самое время, чтобы попробовать послать пакет. К счастью, алгоритм посылки пакета во много похож на процесс приёма. На исход второго дня появилась первая версия драйвера, которая умела принимать и передавать Ethernet пакеты. Но как его тестировать?
Несколько лет назад я разбирался с тем, как работает TCP/IP стек. Все началось с класса, написанного моим другом для работы с COM портом. В то время мы использовали MS Visual Studio 6. В то время мы много работали с PPP поверх порта RS232. У меня возникла идея сделать разбор PPP протокола для будущего TCP/IP стека. Я понимал, что достучаться к портам сетевой карты из Windows NT невозможно, а браться за низкоуровневый перехват пакетов средствами SDK - не хотелось. Во первых, это нетривиальная задача, а во вторых, впоследствии всё равно пришлось бы переделывать это код для будущей системы. Используя PPP, можно было дописывать протоколы в соответствии с моделью OSI.
Результатом стало поднятие Windows RAS сервера и соединение портов COM1 и COM2 кабелем. Если ты пытался соединить Windows NT и Linux посредством RS232, то должен вспомнить ту ненормативную лексику, которая прозвучала из твоих уст, когда ты узнал что Windows RAS протокол имеет некоторое расширение. А именно - когда Windows используется в качестве RAS сервера, то перед началом обмена HDLC пакетами (HDLC пакет это пакет, в который обёрнуты PPP фреймы), RAS сервер должен получить строку CLIENT, на которую он отвечает CLIENTSERVER. Соответственно, не получив CLIENT, RAS сервер ничего не посылает в ответ, а по истечении таймаута "бросает трубу". Тем не менее, разобравшись с этой "фичей", мне удалось синхронизировать HDLC фреймы и получить PPP пакет. Дальше был разбор PPP пакетов и вложенных протоколов. Затем слой оперирующий с IP протоколом, затем парсер TCP и конечный автомат для сокетов. В результате получился исполняемый файл, который умел коннектиться к Windows RAS серверу, при этом получал IP адрес. Также эта программа умела отвечать на ping и, самое главное, умела коннектится к WEB серверу, посылать запрос GET и выводит полученную информацию в стандартный поток вывода.
Впрочем, я отвлёкся - доморощенный TCP/IP стек дело далёких дней. Но его исходный код бережно хранился на флешке. Глупо было бы упустить возможность с помощью этого стека потестировать драйвер сетевой карты. Тут дело начало развиваться быстрее.
Поскольку источников Ethernet фреймов может быть много (несколько сетевых карты или другое оборудование), то имеет смысл выделить отдельный программный поток (thread) для приёма любых Ethernet фреймов. При этом, TCP/IP стек и драйвер сетевой карты могут находиться как одном адресном пространстве, так и в разных. С небольшими правками, старые классы переписывались под Xameleon. IP стек также был реализован в виде программного потока. Попутно обнаружилось, что в случае использования Ethernet транспорта, IP протокол опирается на ещё один протокол - ARP. В Сети нередко проскакивает информация о методе взлома компьютерных сетей, который называется ARP Poisoning. Поскольку мне предстояло реализовать ARP протокол, то показалось интересным заранее предусмотреть такую атаку и постараться защититься от ней. Насколько это удалось, покажет время.
На момент написания этого поста, сетевая подсистема Хамелеона пока даже не дошла до уровня программы, которая умела коннектиться к RAS серверу. Это печально, но не смертельно. Эх... Недельки бы две отпуска и у Хамелеона мог бы появиться свой TCP/IP стек.
В текущем состоянии драйвер DEC21140 представляет из себя модуль для Хамелеона, включающий в себя собственно драйвер и фрагменты оригинального TCP/IP стека. Это модуль умеет парсить и показывать на второй консоли некоторые протоколы из семейства TCP/IP. Помимо этого он умеет отвечать на пинг на адрес 192.168.1.7 и умеет рутить все пакеты, не попадающие в сеть 192.168.1.0 на адрес 192.168.1.1. Что приятно удивило, так это то, что модуль выдержал линуксовый ping c ключом -f имея минимальную потерю пакетов и одновременно с этим стабильно отвечал на ping из Windows.
Но есть и плохие новости. Microsoft Virtual PC не так широко распространён, как WMWare. Я раздобыл спецификацию на чип Am79C970A, который эмулируется WMWare, но понял, что у меня пропал интерес к написанию драйверов сетевых карт. Помимо этого, виртуальная сетевая карта, которая используется в WMWare, содержит больше регистров и сложнее в программировании. Конечно, можно было бы посвятить неделю жизни, на разработку ещё одного драйвера, но у меня нет WMWare. Музыка: тишина
8 комментариев - Оставить комментарий | |

| Авг. 28, 2007 06:19 am Выход из тени. Как скоротечна жизнь и как она коротка. Такие мысли приходят в голову, когда обернувшись назад, смотришь на то, что сделано и размышляешь о том, что предстоит сделать. А сделано было не мало. В начале предистория.
В юности нас, троих друзей программистов (Александр, Александр и Алексей) объединяла общая тяга к компьютерам. Мы были "всеядны" и с удовольствием проглатывали любую информацию, касающуюся программирования и компьютерного железа. Всеми правдами и неправдами обеспечивали себе доступ компьютерам. Кроме этого, в нашем стремлении узнать больше нас подгоняло соперничество, и сразу, узнав новое, мы спешили поделиться этим знанием друг с другом. С тех пор прошло много лет и каждый пошёл своим путём, но этот путь у каждого из нас связан с программированием.
Я расскажу тебе свой путь. Всё началось с юношеского максимализма. Мне повезло, я застал время, когда компьютеры были уже маленькими, а программы ещё маленькими. В умных книжках, в которых мне не отказывали родители, в то время писалось как работать в командной строке MS-DOS и что MS-Windows 1.0 - бесполезная надстройка, впустую пожирающая ресурсы компьютера. Ты помнишь это время? В это же время широким массам уже пошатнувшегося СССР стали доступны, так называемые, домашние компьютеры. Что из себя представлял домашний компьютер? Обычно это был маломощный персональный компьютер, использующий телевизор в качестве монитора, кассетный магнитофон в качестве внешнего накопителя и, что характерно, собранный из отечественных элементов. Да ты точно должен помнить это время.
Стоит ли говорить, что использование магнитофона в качестве внешнего накопителя, мягко говоря, неудобно? Конечно, загрузить игрушку с кассеты - это полбеды. Но использование магнитофона для хранения разрабатываемых программ, это есть мука невыносимая. Когда я вспоминаю об этом, то который раз преклоняюсь перед поколением, хранившем свои программы на перфокартах. Время шло, и хоть доступ MS-DOS совместимым компьютерам оставался нашей заветной мечтой, появилась возможность использовать пятидюймовый дисковод вместе с домашним компьютером. Это был прорыв. Представляешь, ОПЕРАЦИОННАЯ СИСТЕМА! На домашнем компьютере! И не беда, что пятидюймовые диски читались через раз, не беда что CP/M не поддерживал и десятой части того, что имели пользователи MS-DOS. У нас была операционная система на домашнем компьютере!
Ты просто обязан помнить это время. А что же Юникс? О..., Юникс! Юникс в то время считался уделом гуру. Юникс использовался на серверах Релкома. Ты помнишь Релком? Ну да ладно...
Всё началось с Бейсика и Ассемблера. Ещё во времена домашних компьютеров, мы поняли, что некотрые вещи нарисовать на Бейсике невозможно. Некоторые задачи могут быть решены только на Ассемблере или с использованием компилятора. Компиляторов под рукой не было, зато ассемблера было предостаточно. :) Затем был Pascal на учительском ДВК. Затем было стояние в лаборатории кафедры за спиной старшекурсника, в ожидании когда он сможет и захочет поделиться машинным временем. Затем лабораторная на ассемблере и дипломная работа с многозначительным названием "Многозадачный монитор САПР". Именно в то время мне попалась книжка с описанием защищённого режима процессора I386. Это была эра ассемблера, имея поверхностные знания о дизайне операционных систем, были потрачены сотни часов на разработку программы, которая умела переходить в защиденный режим и переключать задачи. Туда же были портированы несколько ни с чем несовместимых драйверов и оконная система, рисующая окна в псевдографическом режиме. Всё это работало параллельно от переключаемого таймером диспетчера задач. Возможно, немаловажной деталью будет то, что в это время повсеместно использовался MS-DOS, который и был загрузчиком многозадачного монитора. В настоящее время такие трюки невозможны, поскольку MS Windows держит все ресуры "в своих руках". Поэтому чаша написания начального загрузчика меня обошла. Точнее не обошла, я как и все забросил проект, написав начальный загрузчик. Но этому времени у меня уже был работающий код, крутящий несколько потоков в расширенном режиме процессора I386 и стойкая неприязнь к ассемблеру. Признаюсь честно, в то время я не был знаком с такими вещами как многопоточность, критические секции, событийно ориентированное программирование и т.д.
Немного позже один из Александров, о которых я рассказал тебе выше, дал мне несколько классических книг по операционной системе Unix и диск с операционными системами, на котором был какой-то юникс и Slackware Linux древней, даже по тем временам, версии. Так начались мою знакомство с Юниксом и моё следующее увлечение. Как ты догадался, это увлечение звали Linux. По сравнению с MS-DOS - Linux был чем-то революционным. Это была реальная вещь! А то, что в комплекте шёл бесплатный 32-х разрядный компилятор, так это вообще было немыслимо. Возможно именно тогда, читая manpages, понял что "я знаю, что ничего не знаю". После знакомства с Линуксом, проект "многозадачный монитор САПР" успешно почил в бозе.
В этот период я начал увлекаться программированием с использованием XLib. Возможно на старой квартире ещё где-то валяются дискеты, с попыткой написать нечто объектно-ориентированное поверх xlib. Эта работа продолжалась некоторое время, пока я не увидел GNOME и QT, причём, я понял что я хотел написать QT, имея поверхностные знания о событиях и объектно ориентированному программированию. К счастью, именно чтение спецификации X протокола и дало мне понятие о событийно ориентированном программировании. Моя библиотека нещадно тормозила при перетаскивании окон, узнать о синхронизации потоков мне ещё предстояло впереди.
Затем меня судьба столкнула с ещё одним другом из нашей троицы. Александр улетел работать в Корею и через некоторое время вытянул туда и меня, убедив своего шефа в моей полезности. Мы программировали проктолы для Bluetooth устройств, используя соответствующие спецификации. В это время мне удалось пощупать и понять как работают программные потоки и как их синхронизировать. В общем, об этом можно говорить долго, но у меня нет желания пересказывать всю биографию. В это время совершенно случайно мне попадается ссылка на микроядро L4. Ознакомившись со спецификацией, а благо опыт работы с такими документами уже имелся, я поразился красоте и стройности концепции L4.

О том, что было дальше, ты можешь прочитать в разделе "Вопросы и ответы" на сайте http://www.l4os.ru, а я расскажу что предстоит сделать...
Мне предстоит заработать на этом деньги и я не собираюсь останавливаться. Как? Не думаю, что это просто. Нееет, это не быстро и не просто. Но:
- Я вложил немало времени и труда в изучение микроядра, технологий и приёмов работы с ним.
- На написание кода, на сегодняшний день, было потрачено более четырех лет.
- Много времени и сил ушло на наполнение сайта информацией.
- Поддержание сайта - процесс требующий некоторых финансовых затрат.
- На этом фоне неспешно ведется раскрутка проекта.
Почему неспешно и почему о нём не кричат на каждом углу? На это есть две причины:
- Основная причина в том, что разработка сложной системы в одиночку - трудоёмкий и длительный процес. На ранних стадиях, а проект едва дошёл до середины, юные "осеписатели" и дилетанты своими коментариями могут загубить всё дело. Чтобы понять, что из себя представляет система Хамелеон, необходимо пройти приблизительно такой же путь как и я, или активно участвовать в разработке какого-либо ядра, например Linux. И даже в этом случае, взглянув на неработающую или падающую систему, когда не оправдываются ожидания, специалист поставит своё клеймо на проект и не станет следить за новыми версиями. Это один из ответов на вопрос, как заработать на системе деньги - заинтересовать специалиста, чтобы он ждал новой версии.
- Вторая причина - любой успешный проект должен приносить деньги. Ты можешь согласиться или оспорить это утверждение, но жизнь показывает, что это так и есть. Например, если поручить написание системы какой-либо административной и неповоротливой структуре, то в результате мы получим неэффективно израсходованные средства и, скорее всего, какой-либо плагиат. Ой, а не сболтнул ли я лишнее?!! Впрочем, вернусь к ответу на вопрос, почему неспешно. Ты дочитал досюда? Этот пост и есть раскрутка на целевую аудиторию, а на каждом углу пусть кричат те, кто семечками торгует. :)

С уважением, Алексей МандрыкинCurrent Location: On air Музыка: silent
2 комментария - Оставить комментарий | |

| Июл. 3, 2007 06:11 am Ну вот. Не поверишь, я до сих пор не развёлся. Живу тихонько с семьёй в Новошахтинске. Днями сплю, ночами "топчу клавиши ". В основном занимаюсь программированием и саппортом. По Средам мотаюсь в Ростов в офис на планёрки.
Иногда удаётся выкроить время для своего проекта - операционной системы на базе микроядра L4. С высоты прошедших лет скажу - если бы я знал, сколько труда и времени займёт это проект, никогда бы его не начинал, а как и мои друзья старался бы поиметь зарплату побольше. Но теперь поздно, особенно после того как система, пусть пока и не стабильно, заработала.
Да, я могу сказать - я написал операционную систему. Я действительно написал операционную систему. Много ли людей могут похвастаться, что написали операционную систему? Думаю, что нет.
Спросишь, почему же никто не знает об этой системе? Ну почему же, кому надо, тот знает. А вот показывать её широкому кругу специалистов пока рано - слишком много отладки впереди и любой сбой в системе автоматически означает, что человек не будет скачивать и смотреть следующую сборку. Вот так-то.
Тебе наверное интересно, на что же похожа эта система? Естественно на Unix. Почему естественно? Потому что стандарт POSIX ещё никто не отменял, хотя, между нами говоря, OpenGroup - на самом деле закрытое сообщество зажравшихся менеджеров. Только об этом никому. Это между нами. Кстати, русских в OpenGroup нет. И не предвидится. Так что будем использовать их стандарты без зазрения совести.
Как, ты до сих пор не понял почему Unix? Да потому что под Unix существует миллионы программ, которые распространяются в исходном коде и которые есть возможность откомпилировать с минимальными правками, а то и без правок вовсе. Надеюсь, тебе известно, что Linux - это тоже Unix, хотя GNU это усиленно отрицает. Но их можно понять, признай они что Linux является Unix'ом, его тут же задавят исками с лицензионными отчислениями и исками по использованию торговой марки. Потому Лиунксоиды во всеуслышанье заявляют, что Linux - это не юникс. Ну мы-то с тобой знаем истину.
Кстати, а ты знаешь почему умер GNU Hurd? Потому что его пишут дилетанты. Уж поверь мне, я за ним давно наблюдаю. А сколько слухов и домыслов о микроядрах породил GNU Hurd - просто не счесть. А на самом деле, часть из этих домыслов и утверждений просто не имеет под собой основания. На основе кое-как спроектированной и ужасно реализованной системы, делаются выводы о всех микроядрах. А немцы молодцы, да немцы молодцы. Упокой Господь душу Йохана Лиедке, его концепция микроядер второго поколения - впереди планеты всей. Жаль что он умер. Великий человек был. Думаю, через десяток лет его поминать будут, как основоположника целого направления компьютерной науки.
Ну вот и всё на сегодня. Может через полгода ещё чего-нить тут отпишу. Журнал, он ведь кушать не просит.... 2 комментария - Оставить комментарий | |

| Апр. 10, 2006 09:16 pm Да уж. Ужасно "оборванный" рассказ из жизни. Чушь какая-то.
Интересно, а как удалить этот журнал? Ну или сделать так, чтобы его никто не прочитал... Настроение: lazy
| |

|
|