четверг, 19 июня 2008 г.

Видеть больше...

Количество разума на планете - величина постоянная. А население растет... Хотя нет, я же хотел рассказать что-то другое...

Все мы конечно выдающиеся мыслители, но проблема в том, что человеческий мозг использует свой потенциал лишь на 5%. И имеющегося в нашем распоряжении потенциала катастрофически не хватает на все. Наша краткосрочная память может запоминать одновременно только семь объектов. Это означает лишь одно - чтобы в голову приходили хорошие мысли нужно стараться не расходовать наш мыслительный потенциал попусту. То есть, не надо думать про то, про что думать не требуется.

В былые времена оперативной памяти постоянно не хватало. Что только не придумывали люди ради этого. Помню были утилиты, позволяющие использовать видеопамять в качестве оперативки - 700 килобайт основной памяти... это было круто. Но это только цветочки.

Программисты напрягались границами сегментов и моделями памяти. Но гораздо сложнее было было программистам, программирующим ресурсоемкие приложения. Существовала когда-то технология оверлеев. Это когда толстый бинарник загружался в память по частям. Причем эта возможность поддерживалась компиляторами. Я к сожалению не знал толком как этим пользоваться, не доводилось. А сейчас это уже практически никому не интересно.

И вот пришла эра плоской памяти. И случилось чудо, думать о использовании памяти стало не нужно. По большому счету именно тогда, наверное, отпала необходимость оптимизировать. Не скажу точно, чем заполнился оставшийся свободным потенциал... много воды утекло с тех пор. Хотя нынешние архиваторы жмут не в пример лучше тех.

Но потенциала то больше не стало. Программируя мы невольно думаем о том, что ограничивает нас. И это ограничивает нас еще больше, потому что мы невольно делаем все основываясь на ограничениях. Ограничения нами управляют, они есть и не думать о них не очень то получается. Сам не понял что сказал, но поедем дальше. Наверное здесь стоило бы ввернуть пару поучительных примеров, но думаю что многим мои примеры покажутся надуманными и неинтересными, как и эта статья. Хотя пожалуй вот...

Я долгое время не хотел писать ядро на C++, во первых для этого требуются хорошие знания C++, которых пять лет назад откровенно не хватало. Во вторых я считал, что на ассемблере я лучше представляю себе взаимодействие с железом... С C++ это было правильно, потому что борьба с языком слишком сильно отвлекала меня от мыслей о логике ядра. Может быть именно это и отвращало меня от C++ тогда.

Прошло время, знаний C++ прибавилось, кроме того прибавилось понимания того, что имеющееся железо - это лишь средство функционирования системы. Оно не должно руководить. После чего ядро разделилось на две части. Высокоуровневая часть вообще ничего не знает о железе, а лишь формирует логику взаимодействия объектов ядра. А низкоуровневая часть могла бы по прежнему остаться на ассемблере, но она стала на си, что в принципе несущественно, все равно не переносимо.

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

Прозрения в принципе случаются в разных областях. Например вот раньше интернет файлохранилища неизбежно обзаводились зеркалами. Взять к примеру Sourceforge.Net, помню времена когда зеркал у него не было (я пользовался им с 2002 года). Но с ростом популярности опенсорс, нагрузка на сервер возросла, и назрела необходимость ее распределять, и появились зеркала. В принципе логично. Но не слишком гибко.

Но вот с некоторых пор я перешел на Google code, мне наскучил SF с его избыточным интерфейсом, люблю лаконичность, а GC необычайно лаконичен. И вот что я думаю. При всей популярности GC, при всей нагрузке на их сервера им вряд ли когда нибудь понадобятся зеркала в традиционном понимании этого слова, у них и так достаточно распределенная структура. И уж конечно они не будут каждый раз спрашивать у пользователей с какого зеркала они предпочитают скачивать (они сами все лучше нас знают :D SkyNet блин).

И вот думаю, в юниксе традиционно приложения имеют стандартный ввод, и два стандартных вывода (по большому счету один). И хоть при дальнейшей работе они могут использовать произвольное количество вводов-выводов, но это все равно накладывает ограничения на их использование. Какие перспективы открылись бы перед приложениями если бы количество потоков было бы неограниченным? Может быть именно из за этого и придумали контейнеры, типа avi? Как в свое время придумали оверлеи ради преодоления ограничений памяти? Хотя надо было просто придумать линейную память. :)

Где-то я уже слышал (в NTFS кажется) про файловые потоки. Файловые метаданные тоже что-то никак не найдут себе достойного применения. Идеи витают в воздухе... где же прорыв?

Или вот еще про приложения думаю. В юниксе традиционная мелкозадачность. То есть для каждой конкретной задачи свое приложение, скрипты кишмя кишат в наших системах и постоянно десятки приложений запускаются и останавливаются. На что мы тратим свои гигагерцы? Альтернативой могло бы стать сервисное взаимодействие, то есть не библиотеки и не приложения, а некие службы. Гибкости в этом варианте гораздо больше но почему-то опять таки пробивается плохо. COM еще жив? Может быть нафиг объекты, да здравствует HTTP-like? как там все просто и гибко. Может быть именно это стремление обуславливает нынешнее развитие SAAS?

Собственно к чему я это все. Ограничения управляют нашим мышлением. Мы мыслим в рамках. В некоторых случаях это может быть оправданно, но не замечая рамок и ограничений можно увидеть и понять то, что раньше казалось невозможным. Ложки нет...

среда, 11 июня 2008 г.

Инстанцирование шаблонов смещениями полей.

Надо сказать, что с шаблонами я как-то не особо дружу, но надо когда-то начинать.

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

Но вот решил наконец таки разобраться и сделать по уму. Кто-то спросит - а почему не STL? - потому, что это ядро. нету там STL и не будет. К тому же я понимаю так, что почти любой контейнер STL выделяет память для организации элементов в контейнере, что мне в данном случае не особо нравится. В ядре все должно быть более предсказуемо.

Главная суть идеи заключается в том, что список должен знать, по какому полю ему надлежит связывать элементы. То есть надо каким-то образом хранить смещение поля в объекте. Я о такой стандартной возможности раньше не слышал, к тому же в интренете много советов на тему reinterpret_cast, но это не наш путь.

Шаблоны можно инстанцировать именами типов, или константами. Причем тип у константы может быть любой, главное чтобы ее значение было заранее известно и неизменно. Именно так и происходит с предварительно описанным классом, которым мы инстанцируем шаблон. Поскольку типы объектов заранее не известны, то и List и Link - шаблонные. Здесь я приведу упрощенную модель.
template <typename I>
class Link {
...
I *next;
...
};

template<typename I, Link<I> I::* L>
class List {
...
I *m_first;
...
};

Здесь самое интересное - это второй параметр. Фактически это смещение поля, которое надо использовать с помощью операторов ->* или .* .
class List {
...
void Insert (I *i)
{
(i->*L).next = m_first;
m_first = i;
}
...
}

Инстанциирование осуществляется следующим образом:

class Item {
Link<Item> m_link;
Link<Item> m_link2;
};

List<Item, &Item::m_link> list1;
List<Item, &Item::m_link2> list2;

И получается что пользователь по ошибке конечно может попытаться запихнуть элемент два раза в list1, вместо list2, но такая ситуация достаточно легко отслеживается с помощью утвеждений (asserts), элементы другого типа вообще не смогут попасть в этот список, а размещение дочерних элементов на первый взгляд вполне безопасно.

Но зато уже никакие операции не требуют указывать поле по которому производится связывание. Сам список прекрасно это знает и без чужих подсказок.

PS: В принципе тип Link<I> I::* достаточно интересен. ostream принимает его наверное за bool, и на любые, даже нулевые смещения выводит 1. А привести его к чему-то у меня не получилось вообще. Но старый, добрый printf с помощью %p отобразил реальное значение этого типа, которое как и ожидалось является смещением в классе.

PPS: Полем я как-то по старинке называю переменные-члены.

понедельник, 9 июня 2008 г.

Блочные устройства linux, шифрование и не только...

Как и обещал, начинаю делиться опытом, приобретенном на последнем проекте. В плане нового опыта проект был весьма богатым, всех подробностей раскрыть я не могу, но общелинуксовые вещи расскажу.

И начну с шифрования блочных устройств. Обнаруженная информация по этой теме характеризуется очень большой неактуальностью и неполнотой. В этой статье вы не услышите от меня о том, как сделать шифрованный корневой раздел, потому что сам не пробовал и не вижу в этом необходимости. Кроме того я не пробовал баловаться с программными RAID разделами, мой проект этого не требовал. Но может быть та информация, которую я изложу всетаки будет полезна кому нибудь.

Начать стоит с того, что cryptoloop устарел. Наверное 50% ссылок по данной теме ведут на описание cryptoloop. Причем устарел он уже несколько лет назад.

Актуальной технологией на сегодняшний день является device-mapper. Но 40% из оставшихся ссылок указывают на описание cryptsetup, который вполне позволит настроить шифрование, но наверное не более того, хотя утвеждать не буду. Для типичного использования она вполне подходит. Кроме того расширение luks позволяет хранить информацию о используемом шифровании раздела. По умолчанию никакия информация нигде не хранится, что чревато ошибками конфигурирования. стоит ошибиться в алгоритме шифрования или ввести некорректный ключ (да, информация о корректности ключа тоже нигде не хранится) и данные будут безвозвратно уничтожены.

Вот только что подумал, что вероятно для того, чтобы найти информацию не только про шифрование но и про другие возможности device-mapper, ее стоило искать задавая другие вопросы. Например про создание программных RAID разделов... но как говорится хорошая мысля приходит опосля. Эта ссылка в конце концов помогла осознать как это работает.

Cryptsetup - это обертка над базовым механизмом, о котором и пойдет речь дальше. А базовый механизм выглядит следующим образом. В ядре существует подсистема "Device Mapper", она находится в "Device Drivers" ---> "Multiple devices driver support (RAID and LVM)". Для управления носителями сущствует пакет device-mapper, в который входит в частности утилита dmsetup. Смысл существования этой подсистемы состоит в том, что она создает блочные устройства, которые располагаются в /dev/mapper, на основании информации о целях. Причем одно устройство маппера может состоять из нескольких целевых устройств. Собственно так это мапперу и указывается. Дескать создаем линейную цель, которая займет в создаваемом девайсе сектора с 0 по 999, но сама располагается на /dev/sda со смещением в 300 секторов от начала диска.

Примерно аналогично задается и шифрованная цель. Или правильнее говорить источник? Но в терминологии DM это называется target. Только дополнительно задается алгоритм шифрования и устанавливаеся ключ. Поддерживаемые ядром алгоритмы шифрования можно посмотреть в /proc/crypto. Помимо собственных алгоритмов ядром поддерживаются некоторые режимы работы блочного шифра. ecb, cbc, pcbc. Название блочного шифра комбинируется из двух этих понятий (например aes-cbc). Кроме того для шифрованного раздела необходимо задать метод формирования вектора инициализации сектора. Инфрмацию об этих методах можно найти в исходниках (drivers/md/dm-crypt.c), вот они: plain, essiv, benbi, null. перечислять подробности не буду.

Теперь начну возмущаться, как программист, который пытался внести то, что нужно в очень узкое ложе device mapper. Хотя какой смысл возмущаться, любая открытая разработка следует каким-то своим целям, которые далеко не всегда соответствуют желаемому.

Начать стоит с того, что шифрованные цели не предлагают никаких средств для контролирования целостности секторов, хотя во всяческой информзащите это распространенная практика, странно, что этого нету. Половину dm-crypt пришлось перепахать ради этого. Но это совсем другая история.

Другим отрицательным моментом является отсутствие явной возможности формировать ключ сектора а не вектор инициализации. Но мне требовалось и то и другое, поэтому вектор инициализации у меня устанавливается обычным порядком, а ключ шифрования через обходные пути. Но это трудно назвать большим упущением, потому что методы формирования вектора инициализации - это сильно внуренняя логика dm-crypt. Хотя с другой стороны как можно назвать внутренним понятием то, что каждый пользователь должен указывать при инициализации?

Еще одной большой проблемой стала именно криптосхема моего проекта, которая подразумевает сложную процедуру формирования ключа из большого образующего ключа. dm-crypt пытается сразу же, в процесее инициализации, установить ключ в алгоритм шифрования. Но проблема в том, что мой образующий ключ имеет размер более килобайта. Пришлось и тут править, чтобы ключ сразу не устанавливался, а использовался в методах формирования вектора инициализации, для выработки ключа сектора.

Конечно это все проектные проблемы. Для рядовых пользователей, желающих включить шифрование дисков - это все несущественно. Чувствую что тему не раскрыл, исправлюсь если что.

воскресенье, 8 июня 2008 г.

5 программ, с которыми я работаю ежедневно

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

С другой стороны, вот вроде бы есть хорошая программа, которую постоянно используешь. Но когда начинаешь подсчитывать, выясняется, что пользуешься ею далеко не каждый день. Проблема... :)

  1. Первой программой я пожалуй назову свою любимую систему - Gentoo. Это удивительно конфигурируемая и контролируемая система. С такой конфигурируемостью может поспорить разве что LFS. А более контролируемой системы я вообще не знаю, здесь просто каждый файл учтен, и я в любой момент могу сказать из какого пакета он установлен.

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

  2. На второе место я пожалуй поставлю свой любимый текстовый редактор - Kate. Ведь любой программист это практически писатель, и основной инструмент - редактор. К IDE я достаточно равнодушно отношусь. Kdevelop мне как-то совершенно не нравится. Visual Studio конечно великая вещ, но под линуксом к сожалению не работает. Иногда конечно не достает возможности навигации по исходникам, но с другой стороны отсутствие всяческой автоматизации может быть стимулирует написание более понятного кода?

  3. Теперь плавно переходим к другим инструментам... Очень удобной штукой оказалась Yakuake. Впервые услышал про нее я кажется от Михаила Конника, и с тех пор не расстаюсь с ней ни на день :). Про мощность линуксовой командной строки говорить нет необходимости, я почти все свое время провожу в редакторе и в командной строке, остальное - баловство всякое.

  4. На четвертое место я поставлю Konqueror. Firefox под линуксом как-то не очень хорошо работает, хотя он тоже у меня есть. К сожалению Konqueror работает не везде. Например вот эту статью я вынужден писать из под Firefox, потому что Konqueror испытывает проблемы с данной формой (джаваскрипты наверное не нравятся. что-то все криво). Но в остальном он меня польностью устраивает, тесно интегрирован с KDE, и в 99% случаев он отображает содержимое может быть даже корректнее некоторых.

  5. А на пятое место я поставлю LaTeX. хоть и пользуюсь им не каждый день (в виде исключения поставлю). Меня так радуют возможности этой программы. Я могу ставить нормальные ударения над любой буквой :) и возможности самовыражения на этом далеко не исчерпываются.


Вот такой вот вполне предсказуемый список получился... linux + KDE, мое все. Попробую передать эстафету своим друзъям, если они сюда заглядывают.

Роман Химов
Владимир Садовников
Mikae

PS: Наконец то подходит к концу проект. Проект непростой и без определенного будующего, но у меня появилось много нового опыта, будет о чем писать. Времени во всяком случае ближайшие пару месяцев будет побольше.

пятница, 6 июня 2008 г.

Слияние строк.

В продолжение предыдущей темы немного углублюсь в подробности...

Испокон веков в си, а следовательно и в c++ два рядом расположенных строковых литерала без разделителей конкатенировались, или как это по русски то? :) сливаются.
printf ("Hello " "world!\n");

Выдает на экране естественно 'Hello world!'. Но это еще не все...

Это так же работает с использованием макросов препроцессора, или правильнее сказать определений, ведь это просто строковая константа.
printf ("Hello " __FILE__ "!\n");

Выдает на экран 'Hello test.c!', чего собственно и добивались.

До некоторых пор аналогично работали и определения __FUNCTION__ и __PRETTY_FUNCTION__, но все меняется когда приходят они - новые стандарты. :) Всвязи с приходом стандарта c99 введена новая константа, содержащая имя функции - __func__. Причем, стоит отметить, что это именно строковая константа, а не определение препроцессора, то есть фокусы со слиянием строк уже не пройдут. А вышеуказанные __FUNCTION__ и __PRETTY_FUNCTION__ отныне просто синонимы __func__. За исключением C++, в котором __PRETTY_FUNCTION__ содержит полное имя функции с типом и аргументами.

В info gcc отмечено, что начиная с gcc-3.4 эти имена не конкатенируются с другими строками, и вообще не стандартны в отличии от __func__. А еще отмечено что __FUNCTION__ и __PRETTY_FUNCTION__ в C++ всегда были переменными. Я этого не знал, как всетаки иногда полезно почитать маны...

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

четверг, 5 июня 2008 г.

Трудноуловимые ошибки...

Нашел сегодня одну ошибку в своем коде. Ну очень интересная на мой взгляд.
const char *exception[] = {
"Division Error",
"Debug",
"NMI Interrupt",
"Breakpoint"
"Overflow",
"BOUND Range Exceeded",
"Invalid/Undefined Opcode",
"Device Not Available (No Math Coprocessor)",
"Double Fault"
"Coprocessor Segment Overrun",
"Invalid TSS",
"Segment Not Present",
"Stack-Segment Fault",
"General Protection",
"Page Fault",
"Intel Reserved",
"x87 FPU Floating Point Error (Math Fault)",
"Alignment Check",
"Machine Check",
"SIMD Floating Point"
};
Поскольку кусок кода не очень большой, опытный читатель сразу раскусит в чем дело. А я пока продолжу...

Этот массивчик помогает мне наглядно выводить информацию о произошедшем нарушении. По моему чем больше информации на экране - тем меньше надо ее искать для выяснения обстоятельств. Но злоупотреблять в этом плане тоже не стоит - информация должна быть концентрированная. Но речь не о том.

Отлаживаю я значит свои задачи, и тут у меня происходит исключение с дескрипшином "Intel Reserved". Я слишком давно вожусь с IA32, чтобы понимать, что такое исключение не может возникнуть. Тем более что номер у него почему-то 13...

Считать строки было лень, поэтому я сразу переписал код более конкретно:
const char *exception[] = {
[0] = "Division Error",
[1] = "Debug",
[2] = "NMI Interrupt",
[3] = "Breakpoint"
[4] = "Overflow",
...
И тут же получил две ошибки. Ошибки в принципе просты, в двух местах забыл поставить запятые. Только приводит это к тому что в тех местах, где пропущена запятая, строки сливаются.

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

Хорошо, что в gcc есть такие замечательные возможности как явная инициализация. Насколько я помню msvc не имеет таких возможностей, поправьте меня если я не прав. Новыми полезными фичами надо пользоваться.

PS: Кто нибудь сталкивался с чем нибудь подобным, или я один забываю ставить запятые?

PPS: Кстати вот тоже неоднозначность, после последнего элемента запятая не обязательна...