вторник, 23 декабря 2008 г.

uleak

Создал новый проект на googlecode, который назвал uleak.

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

Почему такое странное название? С названиями у меня всегда проблема. С одной стороны длинное название труднее запоминать. А с другой стороны длинные названия обычно больше говорят о себе. Но всетаки предпочитаю краткость. uleak - это типа как umount... :)

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

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

Да и не все мысли еще воплотились. Например, под linux наверняка можно использовать libUnwind, которая почему-то не работает под FreeBSD. Но под linux uleak пока не заработает. В линуксе его наверное можно будет использовать к уже скомпиленным проектам через LD_PRELOAD, но я никогда с этим не работал.

Если у кого-то есть в сопровождении проблемные проекты, можно попробовать погонять их с uleak. Это поможет сделать его более универсальным и полезным.

PS: Кроме того на сайте uleak я доработал классификацию проблем, с удовольствием выслушаю критику.

PPS: самый короткий пост?

среда, 17 декабря 2008 г.

Классификация проблем с памятью

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

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

Кто-то скажет - переписать... Нет, это путь тупиковый. Всмысле переписать - это непроизводительная деятельность, Надо учиться наводить порядок. :) Но, ближе к теме.

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

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

Нарушение границы блока мне попалось не далее как сегодня. Выделялась память для локального буфера (фу), 26 байт (два раза фу), потом она заполнялась данными и прогонялась через RC2_cbc_encode, который выравнивает данные в большую сторону на 8 байт. Итого 6 байт за границей блока. Но в данном случае это скорее всего не вызывало проблем. malloc вероятно до 16 байт выравнивает и сам, и реально в блоке 6 дополнительных байт точно было. Так никто бы и не узнал о проблеме.

Эта ошибка является типичной при работе с си-строками.

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

Использование блока после освобождения. Самая опасная форма - модификация блоков, последствия таких ошибок очень сложно предсказать. Но эту форму теоретически можно отслеживать. Если же доступ к блоку осуществляется на чтение, отследить это невозможно. Можно попытаться подобрать данные для заполнения блока при освобождении так, чтобы приложение на них сломалось.

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

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

Менеджер памяти в ликтрейсере устроен так, что дефрагментация памяти и повторное использование блоков, особенно на ранней стадии работы, сведены к минимуму. Что позволяет надеяться что при повторном освобождении этот блок окажется нетронутым, и мы сможем констатировать этот факт. Увеличение размера хипа увеличивает эти шансы.

Использование неинициализированной памяти. Не знаю как можно извратиться так, чтобы работать с неинициализированным блоком. Но если есть подозрения - можно заполнять блоки при выделении специальным знчением, чтобы сломать приложение.

Выделение блока нулевой длины. В стандарте си по этому поводу написано, что поведение может определяться реализацией. If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

Однако далеко не всем приложения может понравиться NULL в ответ на malloc. Хотя помоему это наиболее правильный вариант. Допустимым также можно считать указатель куда нибудь на ридонли память, чтобы приложение не пыталось туда что-нибудь писать. Кстати вот не знаю как должен вести себя в этом случае operator new?

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

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

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

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

PS: Еще немного поковыряюсь с ликтрейсером и наверное выложу его куда нибудь. Может быть будет полезен.

понедельник, 8 декабря 2008 г.

Ловушка для меморилика (проблемы)

Основная проблема с перехватыванием функций, работающих с памятью - это бесконечная рекурсия. Десятки функций стандартной библиотек хотят память. Но и нам хочется не просто заменить malloc на свой, но и довести до программиста какую-то полезную информацию. И тогда приходять они... проблемы...

Первая проблема возникает тогда, когда мы пытаемся воспользоваться функцией printf. И заключается она примерно в следующем: первый раз malloc вызывается задолго до вызова функции main, и видимо задолго до инициализации подсиситемы ввода-вывода. В результате попытка дернуть printf из malloc приводит к тому, что printf пытается выделить буфер, и опять таки вызывает наш malloc. Замена оператора printf чем нибудь (sprintf, puts, и тд) ничего путного не принесла. Но наша библиотека в любом случае требует инициализации. Мы просто проинициализируем буфер stdout, можно в _IONBF, и эта проблема себя исчерпывает.

setvbuf(stdout, NULL, _IONBF, 0);

Другая проблема проявляет себя во всей красе, когда мы пытаемся использовать библиотеку в многопоточном приложении. Как ни крути, а медленный менеджер хипа надо защищать блокировками. Но pthread_mutex_lock, опять таки, вызывает malloc для инициализации tls, локов и тд. Надо сказать что libpthread во FreeBSD весьма закрытая вещ. Все структуры у них сугубо приватны, всмысле в инклюды не включены, и выделить и проинициализоровать лок самостоятельно не выходит.

Долго бился над этой проблемой. Пытался прикрутить свои блокировки на ассемблере, не помогает. Еще что-то пытался - сейчас уже не вспомню. Но приложение упорно уходило в кору по исчерпанию стека. В результате поступил следующим образом: если гора не идет к Магомету... в смысле, если pthread_mutex_lock вызывает malloc, тогда мы не будем из malloc вызывать pthread_mutex_lock. Но и совсем от него мы тоже не может отказаться. Мы не будем вызывать блокировку только в том случае, если malloc вызывается из недр libpthread... хотя наверное можно было бы просто сделать проверку на рекурсию, флажек какой нибудь, это наверное тоже сработало бы. Хотя я вроде даже что-то такое пробовал, не помню. Сделал по хитрому. я проверяю адрес возврата на попадание в области критичных к рекурсии функций, и в этом случае не произвожу блокировку. Извращенный метод конечно, но показало себя вполне надежным.

Но это была еще и не последняя проблема. Последняя проблема с трудом поддается моему пониманию. Заключается она в том, что при переключении потоков приложение падало в функции _thr_setcontext, на операции FXRSTOR. Поковырявшись немного в ядре вычитал, что блок данных для хранения контекста FPU должен быть выровнен на 16 байт. Исправилось выравниванием всех блоков в менеджере хипа на 16 байт. Но так и не могу понять каким образом сохранение контекста FPU производится в буфер, выделенный пользовательским malloc, в пользовательском же пространстве? Возможно, там используется какой-то хитрый коллбек из ядра... странно это. Я полагал, что этим должно заниматься ядро.

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

среда, 3 декабря 2008 г.

Ловушка для меморилика (слайды)

Струя воды толщиной в спичку
дает утечку двести литров в сутки...
(c) Афоня



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

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

Но обо всем попорядку. Как же это работает?

Сразу отмечу, что данный код работает только под FreeBSD 32bit, возможно он сможет работать и под Linux - не проверял, но точно не заработает на 64-х битной платформе. Кроме того весь код приводить тоже не буду, может быть выложу куда нибудь.

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

extern "C" void *malloc (size_t size);
extern "C" void *calloc (size_t number, size_t size);
extern "C" void *realloc (void *ptr, size_t size);
extern "C" void *reallocf (void *ptr, size_t size);
extern "C" void free (void *ptr);
extern "C" char *strdup(const char *str);
void *operator new(size_t size);
void operator delete (void *ptr) throw();
void *operator new[] (unsigned int size);
void operator delete[] (void *ptr) throw();

Каждая из этих функций представляет из себя обертку к медленному и простому хип менеджеру.
Например:

extern "C"
void *malloc (size_t size)
{
return salloc (size);
}

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

static
uint32_t getCallPoint(const void *stack)
{
const uint32_t *sptr = reinterpret_cast(stack);
return sptr[-1];

}

extern "C"
void *malloc (size_t size)
{
const uint32_t cp = getCallPoint(&size);
return salloc (size, cp);
}

Вот так будет достаточно. Что же касается менеджера памяти, то переопределив malloc во FreeBSD мы потеряли всякую возможность воспользоваться стандартным, и вынуждены писать свой. Но так даже лучше, в четвертой статье расскажу почему.

Релизация у него будет предельно проста. Заводится статический буффер большого размера. Определяется структура заголовка блока, которая состоит из двух полей: cp - точка выделения блока, и size - размер блока. Если cp имеет нулевое значение - это обозначает, что блок пуст.

И для учета блоков по точкам вызова мы заводим массив структур:

struct callpoint {
uint32_t cp;
uint32_t current_blocks;
uint32_t max_blocks;
uint32_t size;
};

static struct callpoint scps[cnum];

Размеры хипа и таблицы точек вызова я выбрал вроде бы с запасом, 16 мегабайт и 4096 точек. При выделении каждого блока я нахожу соответствующую точке запись и увеличиваю счетчик блоков. Если current_blocks превышает max_blocks, это может быть утечка. Для понижения уровня шума max_blocks можно проинициализировать некоторым значением - у меня 100. То есть до 100 блоков на точку - ругани не будет, дальше будет. И при каждом повышении количества блоков ругань будет продолжаться.

При освобождении блока я извлекаю точку вызова из информации блока, ищу эту точку в таблице и понижаю счетчик current_blocks.

В таком виде это будет легко работать на однопоточных приложениях. Про многопоточные позже.

среда, 26 ноября 2008 г.

Ловушка для меморилика

Не знаю как люди ловаят мемориликов. Пробовал google-perftools, что-то ничего не говорит даже на примитивный пример с единственным malloc. valgrind можно было бы попробовать прикрутить, но это связано с определенными трудностями, поскольку меморилик затаился в сервере c замкнутой программной средой на базе FreeBSD.

К ловле меморилика надо подходить творчески.

Заменить все вызовы malloc на debug_malloc, а потом вглядываться в исписанный экран в поисках утечки - это не наш метод. Хотя при должном уровне автоматизации он тоже должен дать плоды, но такой метод обычно требует модификации кода, и поэтому не является предпочтительным.

Что касается должного уровня автоматизации, я считаю что мало вывести на экран информацию. Гораздо лучше, если эту информацию предварительно обработать. Что же мы можем сделать в случае выделения/освобождения памяти?

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

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

Некоторая проблема есть в том, что работать придется практически на системном уровне. Для реализации мы пишем свои версии malloc, free, realloc, strdup, ::operator new и других, оперирующие с памятью функций. в начале каждого обработчика определяем точку вызова. Но возможности вызвать функции из стандартных библиотек у нас уже нету.

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

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

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

четверг, 20 ноября 2008 г.

Отгадки времени

Забавная проблема возникла на работе. Разрабатываем мы Континент, в нем есть компонент - сервер доступа. Он позволяет пользователям подключаться к VPN извне. Авторизация осуществляется на сертификатах, технология открытых ключей, как там ее.

Появился баг - Невозможно создать сертификат с датами начала/окончания срока действия меньше/больше соответственно 1950/2049гг. Причем интерфейс позволяет ввести в качестве года значение от 1753 до 4111. И откуда взялись эти странные цифры?

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

Понятно уже, что сервером обрабатывался только первый вариант. Там правда были прикручены какие-то костыли на тему трех и четырехзначного значения года, но никаких попыток обработать поля типа generalizedTime не производилось.

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

Но текйщий момент времени мы можем получить только в time_t, диапазон которого ограничен датами от 1970 до 2037 года.

Но и generalizedTime сконвертировать в time_t сразу не получится. Можно воспользоваться структурой tm, диапазон которой снизу ограничен 1900 годом.

Сплошные ограничения. Могли бы создать проблемы, если бы текущее время системы можно было задвинуть за пределы 1970 года. Думаю что ни одна система этого сейчас не потерпит, а нам нужно только убедиться, что сертификат действует сейчас. Поэтому время сертификата можно смело обрубить до диапазона time_t, и сравнивать с текущим.

Хотя сам сертификат может иметь даты в значительно более широких пределах. Вопрос в том, кому это надо? По хорошему, выписывать сетификаты задним числом вообще не правильно.

Ссылки по теме: RFC2459, RFC3161.

вторник, 18 ноября 2008 г.

Заниматься делом

У нас внедряется SCRUM. Сейчас идет третья двухнедельная итерация с момента внедрения. Можно сказать, что только начали ощущать вкус этого дела. До реальной пользы от гибких технологий нам еще далеко.

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

Но статья не про это.

Традиционный SCRUM, ежели таковой встречается в природе, подразумевает однородность комманд. То есть вся команда должна состоять из специалистов одного профиля. Например из одних программистов. Наши команды не такие. На треть они состоят из тестировщиков. Что вносит смуту в организацию работ. Наш таскборд состоит из 5 колонок: todo-coding-implemented-testing-done. Я бы конечно оставил три, но это вызывает проблемы с идентификацией работ по тестированию. Может быть выделять их цветом?

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

Ничто так не поднимает дух команды, как стремительно понижающийся график сгорания работ (burndown chart). :) Для этого очень важно соблюдать правило - одна бумажка - одно дело - один человек. Задачи перемещаются только в сторону завершения. Это в любой методологии написано, но у нас, почему-то, приживается с трудом. Упорно пытаемся одну бумажку делить на разработчика, тестировщика, еще кого нибудь, поэтому до завершения они добираются достаточно медленно, к концу итерации дай бог. :)

Видимо это происходит из привычной всем схемы работы над ошибками - ошибка может десятки раз ходить от тестировщика к разработчику, до тех пор, пока ее не исправят и не закроют. А это происходит из за того, что кто-то не ставит перед собой реальных задач. Гадание на кофейных гущах какое-то. Исправлять ошибку можно долго, можно заниматься этим годами. Но в условиях SCRUM надо научиться отвечать на вопрос - чем я собираюсь заниматься сегодня. Не так, что сегодня я мусолю ошибку номер xxxx, хрен знает что там такое. А стараться конкретизировать работу. Например: Чтобы исправить эту ошибку я прикручу такие-то возможности, и это позволит понять в чем дело.

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

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

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

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

вторник, 11 ноября 2008 г.

Компьютер для ребенка

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

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

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

В качестве системы я естественно выбрал свою любимую Gentoo.

Не знал, что cdrom приводы Pioneer такие проблематичные, linux категорически отказывается воспринимать его как cdrom, без магического hdc=noprobe hdc=cdrom. Я несколько дней потратил в поисках нормально загружающегося и содержащего все необходимые инструменты linux'а, в конце концов обнаружил магическую комбинацию и вернулся к тому, с чего начал - с Gentoo install-x86-minimal-2008-r1.iso.

Забыл сказать, что детский компьютер у меня Pentium-MMX (молодой еще, всего 10 лет ему :D ). Видео они конечно смотреть на нем не смогут (если я видеокарту не поставлю какую нибудь более мощную), но аудио слушать смогут вполне. Да и десктоп должен вполне нормально крутится. При случае сделаю апгрейд.

Итак, чтобы накатить Gentoo на такой допотопный компьютер надо много времени... Но есть способ проще - Воспользуемся distcc. Сами portage делаем доступными с моего компьютера по NFS. Но есть еще проблема с компилятором. у меня i686-pc-linux-gnu, а там i486-pc-linux-gnu. Переводить его на i586 не вижу большого смысла, Я вообще не вижу особой разницы между i486, i586, i686, если кто знает о такой разнице - сообщите мне пожалуйста. Но для полноценной работы distcc необходимо, чтобы на моем родительском хосте стоял, кроме всего прочего, еще и i486-pc-linux-gnu.

Для этого устанавливаем crossdev, portage overlays должны быть настроены, и выполняем

# crossdev -S i486-pc-linux-gnu

Через некоторое время все необходимое устанавливается, но не совсем. Не знаю по каким причинам все установленные пакеты не включаются в world-файл и будут снесены при emerge --depclean. Для того чтобы этого избежать я добавил их в /var/lib/portage/world руками.

crossdev-i486-pc-linux-gnu/binutils
crossdev-i486-pc-linux-gnu/linux-headers
crossdev-i486-pc-linux-gnu/glibc
crossdev-i486-pc-linux-gnu/gcc

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

Например нету никакой необходимости интернационализировать консоль. как следствие keymaps и consolefonts тоже можно отключить.

# rc-update del keymaps
# rc-update del consolefonts

Также нет никакой необходимости в info, man и doc.

make.conf: FEATURES="noman nodoc noinfo"

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

Ссылки:
HOWTO: Portage через NFS (на данный момент недоступно, не знаю вернется ли вновь)
Описание distcc в Gentoo
DistCC Cross-compiling Guide

PS: Надо сказать что смотреть длинные видео через youtube не особо удобно. Смотрел тут про концепты по ссылке Юрия Волкова, на 20 минуте (смотрел я с долгими паузами) все заглючило и перестало воспроизводиться. Что сподвигло меня на поиск даунлоадеров, помню был какой-то консольный, найти не могу, зато нашел kde-misc/youtube-servicemenu, теперь можно сохранять ролики из контекстного меню konqueror.

среда, 8 октября 2008 г.

Управление сертификатами?

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

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

Не знаю почему, но видимо в linux бытует такое мнение, что PGP работает на ключах. Хотя я считаю что инфраструктура шифрования базируется на сертификатах. Это конечно мелочи, но управление сертификатами в линуксе реализовано из рук вон плохо.

Для начала импорт сертификата. Сертификат с закрытым ключем хранится в контейнере pkcs12, gpgsm (утилита для манипулирования сертификатами) в этом формате воспринимает только ключи. Начинаем колдовать.

Сперва обеспечим автоматический запуск gpg-agent. Честно говоря, не знаю как правильно сделать это в kde. Хотя судя по всему здесь описан правильный рецепт (надо дописать себе скриптик для выгрузки). Но есть одна проблема. Иногда gpg-agent может ругаться со словами:

gpg-agent[17182]: can't connect to `/home/gor/.gnupg/S.gpg-agent': В соединении отказано
gpg-agent: нет gpg-agent доступого для данной сессии

Это решается путем добавления флага --use-standard-socket при вызове агента или добавлением опции use-standard-socket в файле конфигурации ~/.gnupg/gpg-agent.conf.

Импортируем закрытый ключ:

$ openssl pkcs12 -in keycert.p12 -out keycert.pem -nodes
$ openssl pkcs12 -in keycert.pem -export -out key.p12 -nocerts -nodes
$ gpgsm --call-protect-tool --p12-import --store key.p12

Непонятно почему, но извлечь ключ сразу из .p12 не получается. Зато получается извлеч сертификат.

$ openssl pkcs12 -in keycert.p12 -out certs.pem -nokeys
$ gpgsm --import certs.pem

Чтобы это все заработало необходимо иметь доверие к корневому сертификату. Списки доверенных сертификатов хранятся в ~/.gpg/trustlist.txt. Там должен быть указан fingerptint корневого сертификата и опциональный флаг. Можно поручить это дело gpg-agent'у, указав ему при запуске опцию --allow-mark-trusted, или allow-mark-trusted в файле ~/.gnupg/gpg-agent.conf.

Но это еще далеко не все. Мы можем попытаться указать сертификат S/MIME в kmail, но на попытку подписи получим сообщение - Ошибка шифрования. Чтобы узнать подробности необходимо включать протокол работы gpgsm. Это можно сделать через настройку kmail, правда я не совсем понимаю как работает socket в качестве файла протокола, я указал просто имя файла. Что касается debug-level, то можно использовать следующие значения: none, basic, advanced, expert, guru. И после включения протокола можно узнать, что для полного счастья gpgsm не хватает dirmngr, это демон хранения CRL (почему он не в зависимостях?).

# emerge dirmngr

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

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

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

Полезные ссылки:
Mew's S/MIME Support
HOWTO KMail gpg-agent kde
запоминалка gnupg-пароля, используем gpg-agent

суббота, 4 октября 2008 г.

Автоматизация тестирования

За что я люблю свою работу - это за то, что не скучно. Я бы наверное умер бы от скуки лет пять назад, если бы трудился все свое время над одним единственным проектом. Может быть поэтому и бытует мнение, что раз в три года надо менять работу? Мне не скучно. Редкий проект затягивается больше чем на год. Причем разнообразие в моей работе весьм радикально. Позапозавчера я писал под Линукс, позавчера под линукс для ARM, вчера под FreeBSD на Perl, сегодня я занимаюсь автоматизацией тестирования. :) Под Windows я тоже иногда пишу. Между АРМ и Perl почти месяц потратил на поддержку АРМ ЭЦП. И еще жду не дождусь, когда меня допустят до CSP, ух, я его перекопаю. Но это если меня на сервер под FreeBSD не посадят. Что-то мне FreeBSD не нравится, сил нету, хотя мне же под нею и не жить. Жить я предпочитаю в Linux, даже Windows запускаю в виртуали, благо компьютер мощный.

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

Собственно сервер представляет из себя замкнутую среду на базе FreeBSD, ни о каких vmware-tools речи там быть не может. Но надо ведь его как-то проинсталлировать без вмешательства человека. А инсталляция происходит в интерактивном режиме, вопрос/ответ.

Три дня я ломал голову над тем как можно постать в гостевую систему в vmware нажатия клавиш. Я посмотрел VI Perl (не работает ни с VMWare Server ни с Workstation), VIX - работает, но список его функций без mware-tools весьма ограничен. Там даже нельзя подключить носитель!

Я сел за Visual Studio и долго изголялся с SendInput, но VMWare Ctrl+G получает благополучно, а после блокировки клавиатуры - ничего не ловит, видимо перехватывает ввод где-то еще глубже, хотя уж куда глубже? SendInput прокачивает на ввод сканкоды.

Нагуглил, что есть filter driver. И даже видел фрагменты кода, как с помощью этого симулировать нажатия на клавиши, но фрагмент кода был весьма невелик, что-то не осилил я эту премудрость. Да и не факт что это сработало бы с vmware.

Причем в процессе разбирательства с SendInput я вспомнил про то, что QEMU позволяет вводить символы в гостевой ОС без блокирования клавиатуры. Проверил свои догадки - точно работает, всмысле вводит все как положено в гостевой системе. Но использование QEMU не входило в наши основные планы, в которые входило создание тима в vmware workstation и тестирование серверов в тиме.

И только под конец рабочего дня, когда я уже отчаялся послать что-то в vmware, мне пришла в голову замечательная мысль. А почему бы мне не автоматизировать инсталляцию с помощью QEMU, при этом вовсе не обязательно в том же QEMU производить и тестирование. Тем более, что QEMU прекрасно поддерживает vmdk.

И вот в пятницу с утреца я довел до ума программку, которая посылает строки в виртуальную машину. После чего засел за Visual Build, который первым делом вызывал QEMU для инсталляции, выжидал определенное время до первого вопроса инсталлятора (Хотите ли вы установить Континент?), и начинал посылать туда 'y', всякие необходимые цифры. После окончания инсталляции дожидается перезагрузки, убивает QEMU, запускает ее снова, уже для загрузки с винта, опять куча вопросов, и наконец последняя перезагрузка, QEMU по halt выключается сам, после чего запускается машина vmware, с уже настроенной рабочей конфигурацией, и сконфигурированный сервер стартует как ни в чем не бывало.

Только потом я уже сообразил, что наверное можно было бы обойтись и без отдельной программки для передачи символов. Visual Build поддерживает различные скрипты, которые наверное вполне могут позволить сделать FindWindow, SetForegroundWindow и SendInput. Но в скриптах я не силен, мне проще отдельную программульку написать.

среда, 1 октября 2008 г.

Windows как второй монитор...

Сейчас стало модно работать за двумя мониторами, или даже за тремя. Причем технология работы различается.

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

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

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

Для начала идем с винды на сайт, и жмем кнопку 'Install Cygwin/X now'. В появившемся окне инсталлятора cygwin выбираем xorg-x11-base из категории X11. После окончания установки запускаем cygwin
C:\cygwin\Cygwin.bat
и в появившемся окне запускаем xorg server
$ startx

Теперь можно подключаться удаленно. Для этого надо переустановить переменную DISPLAY и запустить необходимое приложение.
$ DISPLAY=192.168.1.3:0.0 kate &
после чего мы сможем увидеть на windows машине, нет, не kate, а сообщение об ошибке:
... X: client 6 rejected from ip 192.168.1.2
Чтобы windows машина нас пустила, необходимо на ней выполнить команду:
xhost +192.168.1.2.
Теперь снова повторяем вызов kate (к примеру) и видим нашу Катю на рабочем столе Windows.

К сожалению информация о хсте не сохранится до следующего запуска. Чтобы она сохранилась надо редактировать файл /etc/X*.hosts, как написано в man xhost, но что-то у меня пока не сработало...

Только не стоит забывать что само то приложение реально работает на Linux машине, Можно легко дезориентироваться, ибо изображение находится на мониторе виндовой тачки, и управлять им надо виндовой мышкой.

PS: Хотя с другой стороны наверное логично то, что kde desktop не может использовать удаленные рабочие столы. Забросить туда приложение наверное не сложно, а после этого оно становится недосягаемо для текущей мышки - проблема.

вторник, 30 сентября 2008 г.

Революции нужны?

Я сторонник эволюционного подхода. То есть я верю что любая программа может планомерно мутировать куда нужно. При условии, что совместимость со всем прошлым не стоит как задача. То есть я не отрицаю частичную совместимость. Например KDE 4.0, 4.1, 4.2 должны быть совместимы между собой. Но нету никакой необходимости поддеживать запуск приложений 10-ти летней давности. Это нужно только гнусным проприетарщикам. :)

В то же время совместимость - понятие разноплановое. Совместимость бывает бинарная, или сборочная, или скриптовая.

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

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

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

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

Сейчас в gentoo явная проблема например с KDE. Часть команды KDE по политическим разноглаcиям покинула проект, и стабильные пользователи gentoo еще не скоро увидят KDE4 (Кстати не понимаю, почему она не в слоте).

Такая ситуация, видимо, давно уже кому-то надоела и возник проект Exerbo, где переделана сама структура портеджей. А кроме того новые сценарии инициализаци системы и некоторые другие вкусности. Но одна беда - система еще не достаточно наполнена пакетами. Но это быстро проходит. буквально два месяца назад там еще не было даже иксов, но сейчас там уже есть kde4. Пожалуй как нибудь на досуге я посмотрю на не поближе (в плане поставить), Потому что это самый вероятный кандидат на замену моей любимой gentoo.

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

PS: А что творится в недрах Windows - я даже представить боюсь, хотя они тоже иногда забивают на совместимость, но это пока исключение. Совместимость - их хлеб. А Windows еще не умер от груза совместимости? есть ли жизнь после XP? о чем это я...

пятница, 26 сентября 2008 г.

deprecated: gcc vs vs vs vs...

vs Visual Studio... Опять наверное прописные истины, но хочу рассказать про аттрибут deprecated.

Аттрубут deprecated - это не только ценный инструмент для командной разработки (чтобы довести до других членов команды необходимость изменения API), но так же он незаменим при рефакторинге.

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

Все, про рефакторинг больше ничего не скажу.

Хочется сравнить возможности атрибутов на gcc и visual с++. Поскольку атрибуты не входят в стандарт, то в каждом компиляторе это делается как попало.

Начнем с gcc. Для объявления чего-то устаревшим в нем используется конструкция __attribute__((deprtecated)). Её можно применять для переменных и типов:
int a __attribute__((deprtecated));
struct a_struct {} __attribute__((deprtecated));
struct { int a_field __attribute__((deprtecated)); };
void func(int a_arg __attribute__((deprtecated))) {}

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

Для функций:
void func() __attribute__((deprtecated)); // в прототипе
void __attribute__((deprtecated)) func() {} // в декларации

Причем если поставить атрибут не на место, то gcc будет ругаться грязно и непонятно насчет точек с запятыми, еще какой-то фигни.

В visual c++ дело обстоит так: указание аттрибута осуществляется с помощью __declspec(deprecated) и используется практически единообразно.


__declspec(deprecated) int a;
struct __declspec(deprecated) a_struct {};
// почему после struct то?
struct { __declspec(deprecated) int a_field; };
void func(__declspec(deprecated) int a_arg) {}
// игнорируется
__declspec(deprecated) void func();

В принципе схоже, свои заморочки, но их все перекрывает возможность указать сообщение.
__declspec(deprecated("use foo instead")) void func();
В gcc такая возможность только обсуждаются.

Правда как всегда в Microsoft - без ложки дегтя не обходится. Указал сперва сообщение по русски, но в Output увидел только квадратики... только два квадратика разного размера. Хотя в сообщении было явно больше двух букв...

среда, 24 сентября 2008 г.

Разбор командной строки в boost

Долго ломал голову как сделать обработку командной строки в boost по хитрому. А по хитрому - это так: сперва указываются глобальные опции, потом некоторая команда, потом локальные опции, свойственные конкретной команде. Но program_option из boost, упрямо засасывает все опции сразу, и чтобы придумать способ - пришлось покопаться в исходниках.

И как всегда, после того как разобрался, решение оказывается простым и понятным, но где оно было раньше - не понятно. Всмысле это могло бы быть написано в докумментации.

Начнем с устройства program_options::command_line_parser. Устроен он следующим образом:

Имеется некоторый набор парсеров стиля (style_parser), которые по некоторым критериям выбирают из строки опции. набор стайл парсеров формируется на основе стиля командной строки, который может варьироваться в некоторых пределах - поддержка длинных имен, значения у опций, опции формата DOS и тд.

Каждый парсер возвращает список опций (vector<program_options::option>), обработанных им на данном этапе. Это может быть пустой список, одна или много опций, хотя парсить много опций за раз нет необходимости, там цикл и остатки командной строки обязательно снова пройдут через каждый парсер. Обработка заканчивается после того, как командная строка опустевает.

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

Это было введение. :)

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

Но как и в любом деле здесь главное вовремя остановиться, то есть хотелось бы чтобы boost не анализировал строку до конца в поисках опций. И тут нам на помощь приходит extra_style_parser. Дополниельный парсер стиля - это отдельная функция. Её задачей будет при обнаружении первого неопционального слова, предположительно команды, оставшуюся часть строки без разбора загнать в результат. Cлайды:

vector<program_options::option> stop_on_command (vector<string> &args)
{
vector<program_options::option> result;
const string &tok = args[0];
if (tok[0] != '-') {
BOOST_FOREACH (string &arg, args) {
program_options::option opt;
opt.value.push_back(arg);
result.push_back(opt);
}
args.clear();
}
return result;
}

Используем это при разборе строки:

program_options::command_line_parser parser (argc, argv);
parser.extra_style_parser (stop_on_command);

...

program_options::store(parser.run(), vm);

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

Вот собственно и все. Может пригодиться кому.

понедельник, 15 сентября 2008 г.

Компиляцию варнингом не испорть...

Но не все варнинги одинаково полезны. Включение варнингов предполагает, что при компиляции ты вообще не будешь их видеть. То есть, на предупреждения надо реагировать и исправлять их. Но если предупреждения вываливаются из внешнего кода (из инклюдов), то вывод засоряется, свои предупреждения теряются и и приходится мучаться. Лишние предупреждения надо убирать, но как?

Я очень люблю предупреждения компилятора. :) В своих проектах я всегда выставляю -Wall -Wextra -Weffc++. Последний тоже весьма полезен, поскольку позволяет писать на C++ в соответствии с рекоммендациями Скота Майерса, что позволяет избежать лишних побочных эффектов.

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

Вот в MSVC испокон веков существует #pragma warning, которая легко и непринужденно позволяет делать все это. Но в gcc все обстоит не столь радужно.

Вероятно, в gcc-2.96 появилась #pragma GCC system_header. Которая должна отключить генерацию последующего кода, при условии, что используется она в теле файла инклюда. Но мне почему-то не удалось заставить ее работать. Может быть ей требуется исключительно #include <>, В то время как локальные инклюды #include "" не прокатывают? не знаю.

Вычитал, что есть #pragma GCC diagnostic, правда появилась эта прагма только в gcc-4.2. Она позволяет выбирать реакцию на предупреждения от игнорирования до ошибки.

#pragma GCC diagnostic ignored "-Weffc++"
#include <boost/...>
#pragma GCC diagnostic warning "-Weffc++"

Это дело работает, но основной gcc в моей Gentoo на данный момент 4.1.2, Хотелось бы заставить компилиться без ошибок и на нем. Как же юзается этот system_header???

воскресенье, 7 сентября 2008 г.

О макросах бедных замолвите слово...

Макросы конечно опасны. Так же, как опасны и многие другие, весьма повседневные вещи. И всем понятно, что нет никакой необходимости применять макросы препроцессора в C++. Но здесь речь пойдет не о C++, хотя во второй части, посвященной рефакторингу, я немного коснусь и его. Совсем чуть-чуть. Мог бы даже и не говорить об этом во вступлении.

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

Начнем с тех моментов, когда нет никакой необходимости применять макросы. Это происходит тогда, когда типы параметров и результата заранее известны. То есть ситуация предельно конкретная. Нет необходомости использовать макрос. Вполне можно описать функцию. Даже в инклюде, если она используется из множества файлов, описав ее как static inline. За счет контроля типов этот способ гораздо предпочтительнее в плане надежности.

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

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

За исключением одного момента. Есть в препроцессоре две переменных - __FILE__ и __LINE__. Использование их в инлайн функции сводит смысл их использования практически к нулю. Поэтому я позволяю себе использовать макроопределение, которое помимо параметров использует эти две переменных, но привязывает их к месту вызова макроса. Хотя с другой стороны это трудно назвать побочным эффектом, поскольку состояние выполнения никак не меняется. Но если попытаться заменить макрос на функцию, то соответствующая информация просто не будет доступна.

Вот совсем недавно ковырялся тут со своим проектам. У меня там широко используются утверждения (asserton). Я конечно стараюсь чтобы выражения в утверждениях были понятны, но в отрыве от контекста они обычно ни о чем не говорят. Поэтому я дополнил утверждения текстовой строкой, которая выводится в случае нарушения. После этого я заметил один момент.

Выражение assert(x < y) содержит в себе условие допустимости выполнения. И это вполне адекватно воспринимается. Но если попытаться дополнить его текстовой строкой, которая должна отображаться в случае нарушения условия - assert(x < y, "слишком большой x"), То все становится с ног на голову. Почему же x большой, если он меньше y? Долго думал во что мне переименовать assert, чтобы можно было использовать обратную логику утверждений, но потом бросил это дело, ибо assert - это просто утверждение.

Но логику выражений я твердо решил перевернуть (мои утверждения носят название STUB_ASSERT, но это почти ничего не меняет). А поскольку недвано начитался Фаулера, то делать это начал по всем правилам. :)

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

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

После чего я, долго и нудно, менял все функции на новый вариант, паралельно переворачивая логику утверждений. Поскольку функция использовалась широко - это была долгая работа, но поскольку старая функция существовала независимо от новой - я мог производить эту модификацию поэтапно. После того, как старый вариант исчез из обращения - я просто взял с помощью sed переименовал все новые функции на старые: sed -i -e 's/REAL_ASSERT/STUB_ASSERT/g'. И получил старое имя макроса и новую логику его использования. Все встало на свои места и стало логичным. Теперь главное не начать использовать новую логику по старому. :)

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

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

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

пятница, 5 сентября 2008 г.

Последняя статья...

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

И в этой статье вы услышите про плохое поведение ntlmaps, про применимость макросов препроцессора и еще может быть какую нибудь другую ерунду. :) Но коротенькие статьи я не воспринимаю. Должна быть в статье какая нибудь глобальная мысль, размазанная по нескольким страницам текста. А то как у некоторых - "У меня сегодня хорошее настроение, поэтому я пишу эту статью, чтобы поделиться этим хорошим настроением со всеми остальными. Все, пока." - Не мой формат. :)

Итак. Долго не мог понять почему у меня firefox застревает на открытии страниц. Долго ломал голову. Пока сегодня случайно не вывел на панель Kima загрузку всех 4-х моих CPU (об этом чуть позже). И обнаружил что мой proxy-proxy клиент - ntlmaps, загрузил одно из CPU на 100%, в то время как я тщетно пытаюсь дождаться от firefox вменяемого ответа на свой запрос. ntlmaps - мне очень нужен, потому, что proxy сервер у нас конечно от MS, и авторизацию не принимает никак иначе кроме как через NTLM. Собственно для чего и был создан ntlmaps. Он берет на себя авторизацию на сервере, а все подключения пускает уже без авторизации, что позволяет ходить в интернет даже самыми отсталыми инструментами.

Но firefox вроде бы не является отсталым, что он успешно доказал - авторизовавшись на проксе самостоятельно. И вот я здесь. Теперь надо будет разобраться что же такое не нравилось ntlmaps в запросах?, что он так напряженно об этом думал...

Про практическую пользу дефайнов я наверное расскажу потом. А сейчас немного о том счастье, которое мне привалило на работе. Собственно попал под плановый апгрейд и получил в свое девелоперское распоряжение Intel Core 2 Quad с 4 гигами памяти. Я до сих пор никогда особо с EM64T дела не имел, да и с 4 гигами тоже. Я всегда хотел узнать насколько же 64 бита быстрее 32-х, но все это собственно ерунда, я даже исследования соответствующие проводить вряд ли буду. Потому, что 64-х битная система в отличии от 32-х битной видит все 4 гига памяти, в то время как 32-х битная видит только 3,2. 800мегабайт дополнительной памяти достаточно весомый аргумент в пользу 64-х бит.

Своп я отключил сразу... Не знаю как в линуксе, но теоретически ядро должно сильно упрощаться, если не заставлять его парится на тему вытеснения страниц. :) Хотя вот Vista на 4 гигах без свопа часто не справляется со своими функциями. Но моя 32-х битная система на данный момент юзает 900М с учетом всех кешей.

64-х битная система, которая сейчас неспешно устанавливается, вероятно будет юзать побольше. Где то читал, что примерно на 150 мег. Но 3 гигабайта всеравно в резерве.

На этом все. Надеюсь что теперь буду вылезать в блог почаще, завтра в любом случае надо отметить начало нового блогогода. :)

пятница, 15 августа 2008 г.

Поворчу...

Я часто плююсь на Windows. Да и на Linux иногда тоже поплевываюсь. Не то, чтобы придираюсь, просто стараюсь быть объективным. И ведь не так уж плохо все делается, но почему-то все как-то через задницу.

Не далее как сегодня наткнулся на ерунду... Ради теста стоит у меня центр сертификации, и у него истек корневой сертификат, который ради теста имел минимальный срок службы - 2 дня. И вот я открываю 'Администрировани/Центр сертификации' и получаю жизнерадостную ошибку что дескать 'Не найден сетевой путь, ошибка 0x89423423'. Полчаса ушло на то, чтобы понять что причина находится в просроченном корневом сертификате, и еще час ушел на то, чтобы научиться выпускать новый корневой сертификат. Делается это, как оказалось, тривиально: certutils -renewCert, но только с командной строки, специальной утилитой. Почему нельзя было сделать этого когда я открываю администрирование ЦС??? Ну или хотя бы сообщение об ошибке повменяемее? Я же не многого хочу! :) Хотя насчет ошибок - это вечная неиссякаемая тема.

И еще одна очень странная ерунда. Наверное я что-то не понимаю в инфраструктуре PKI. Но помоему, если у центра сертификации новый сертификат с новым ключем, то он на старом сертификате/ключе уже не должен ничего выпускать. Ан нет, ЦС от Microsoft преспокойненько выпускает новые CRL как на новом сертификате, так и на старом. Хотя может быть в этом и есть смысл?

И еще немного про Linux... mencoder у меня так и не научился кодировать видео в два прохода. Зато научился ffmpeg. Судя по всему разработчики задвинули mencoder в пользу нового и красивого ffmpeg. Хотя ИМХО всем этим программам не хватает одной очень полезной опции -quality, Ну почему я должен сам выбирать все эти усреднители и чередователи? Почему нельзя просто сказать - покачественнее или побыстрее???

entern int i;

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

Особенно это актуально при использовании разных форм printf. Самый правильный путь - это указывать строку непосредственно в тексте. При этом в случае с printf мы получаем возможность визуально установить соответствие аргументов и собственно строки без отрыва от данного участка кода. В других случаях это тоже может быть актуально, так как строка обычно содержит в себе какой-то смысл, который вряд ли может быть полноценно передан через какой либо идентификатор.

Но тут есть одна проблема - интернационализация. Очень хочется все строки программы легким движением заменить на соответствующие фразы другого языка. И всеравно наиболее правильным мне кажется указание строки основного языка непосредственно в тексте, пусть в специальных скобках типа QObject::tr(). В этом случае строка на основном языке видна явно, а соответствие интернациональным версиям - отдельная самостоятельная проблема, но самостоятельные проблемы решать проще, чем взаимосвязанные.

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

LoadResourceString(IDS_STRING136,strTemp);
wsprintfA(szTm, strTemp.c_str(), from.wDay, from.wMonth, from.wYear, to.wDay, to.wMonth, to.wYear);


Мало того, что содержимое строки не видно. Но даже если мы перейдем к описанию IDS_STRING136, то мы обнаружим всего лишь...

#define IDS_STRING136 136

Даже если бы у этого идентификатора было бы осмысленное имя - это всеравно ничего не дает, потому что строки нету. Да, можно полезть в ресурсы и выискивать эту строку из сотен других. Не знаю сильно ли это облегчит задачу. Это почти то же самое что описать int i в отдельном файле, может даже хуже. Могу себе представить какие сложности испытывают программисты многоязычных приложений.

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

Похожую проблему видел в одной прошивке, но там это более менее можно понять. Всетаки экономия памяти, всякое разное. Но и там теоретически можно было сделать основной язык в тексте, а дополнительные языки - подставлять при компиляции к примеру. На *nix обеспечить такую подстановку - как два байта подменить. написал скриптик в пять строк и все дела. А проект, к сожалению, был под DOS, чтобы так легко все заменить пришлось бы писать специальную программу. Там сделано просто - файл с 'ресурсами' представлял из себя большой массив, в котором производилось сопоставление идентификатора и строки. Файл для нужного языка выбирался на этапе компиляции статически. Идентификаторы, само собой были отдельно, через enum.

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

четверг, 31 июля 2008 г.

Время менять корневые разделы...

Переодически, обычно в связи с выходом нового релиза, я переставляю свою систему (моя система - это Gentoo). С одной стороны это полезно для того, чтобы не забыть как это делается. А с другой стороны постоянные обновления всетаки замусоривают систему постепенно. А с третьей стороны - установка системы с нуля, это повод попробовать что нибудь новое.

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

Разбиение дисков у меня особенное. Я честно говоря никогда не видел необходимости отделять boot, usr и var от рута. Их содержимое во многом предсказуемо и на десктопе все они прекрасно размещаются на одном разделе. Эксплуатируя gentoo я вывел для себя следующую схему:

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

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

Сравнительно небольшую партицию - 4-6 гиг я выделяю для /usr/portage. Это позволяет ему не раздуваться, а заодно позволяет использовать его из разных gentoo-систем.

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

Диск у меня небольшой. В этом плане я придерживаюсь такого мнения, что любой объем всеравно когда нибудь кончится, и его придется разгребать. И при выборе диска руководствуюсь больше единовременными потребностями. Мой диск имеет размер 80гиг.

Итак, во второй корневой раздел я залил новую систему. И решил побаловаться с программами мониторинга под KDE. До сих пор я пользовался ksensors, но он помоему древний как KDE1. Я не надеялся что найду что-то идеальное, но к идеалу надо хотя бы стремиться. Программ мониторинга под KDE не много. Помимо указанного выше ksensors можно упомянуть ksim, ksysguard (правда меня мучают сомнения, что ksysguard умеет мониторить датчики lm_sensors) и kima.

Kima - это апплет для панели задач KDE. Умеет мониторить датчики lm_sensors, умеет получать информацию от демона hddtemp и еще среди фич есть мониторинг Thermal Zone CPU. Надо порыть что это за зверь и где собственно стоит. :) Инфрмацию kima отображает непосредственно в панели, во всплывающем окне информации может быть больше.

Хотя конечно на полноценный мониторинг это не тянет, просто информирование пользователя, но выглядит поприятнее чем ksensors. Мониторинг ИМХО должен еще как-то по времени все это отслеживать, тенденции всякие. И вообще, компьютер должен работать.

четверг, 24 июля 2008 г.

Многоблирование информации...

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

Где-то у меня была прикольная закладочка.. а, вот она - Copy/Paste detector. Но я хотел написать немного о другом.

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

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

Когда-то файлы библиотек создавались с таким расчетом, чтобы в одном объектном модуле была одна функция. При связывании выполняемого модуля из библиотеки извлекается исключительно то, что необходимо. Но ведь никому не охота для каждой мизерной функции писать отдельный си-файл. Да и не удобно это. Поэтому библиотеки стали набиваться гигазами объектников, неоптимально это. Я тут вынашиваю мысли на тему делить большие сишники на маленькие, компилировать и пихать в библиотеку. Естественно все это должно производиться в автоматическом режиме в процессе сборки. И тестировать модули индивидуально удобнее. Глобальное тестирование неизбежно натыкается на ограничения областей видимости и тд. Но я хотел написать не об этом...

Ковыряюсь тут с одним старым проектом, ошибки исправляю. Хотя весь этот проект представляет из себя одну большую ошибку, которая собирается полтора часа, а функциональности в ней и на 5 минут не наберется. Хотя то, о чем я хочу написать свойственно многим проектам MSVC. Одна из ошибок очень явно это высветила - в одном месте указан старый телефон компании. Начал выяснять где... Для этого пришлось поставить InstallShield, ибо данная информация хранилась в инсталляторе. Телефон больше нигде не хранился, а вот название компании, версия продукта помимо инсталлятора хранится еще и в ресурсах каждого модуля (как я не люблю ресурсы), в диалогах в виде статического текста, иногда в самом тексте программ. Не удивительно что версии везде указаны разные, а название компании в разных формах.

Любую информацию необходимо указывать единожды. Правда я с трудом представляю как это можно сделать в условиях MSVC.

Вот собственно и все, о чем хотел написать.

вторник, 22 июля 2008 г.

Абсолютно бесшумный компьютер...

Ну вот и свершилось, на день рождения выпросил в подарок FSP ZEN-400. И последний вентилятор вместе с блоком питания отправился в историю...

Комплект производит очень хорошее впечатление, ну он и стоит 150 баксов :)... Разъемы питания снабжены отжимниками, в комплекте идут липучки для связывание проводов, красота...

Только вот с установкой пришлось повозиться, в корпусе что-то тесно. Чтобы отключить все разъемы мне необходимо выкрутить БП и двигать его туда-сюда. Но старый БП и так необходимо снимать. Хуже оказалось то, что отключиться то он отключился, но вот вытаскиваться абсолютно не захотел, радиатор мешает. Пришлось открутить материнскую плату и двигать туда-сюда ее. Не снимать же радиатор. Пусть даже я поставил его кверхногами и теперь надпись Scythe в нормальном положении системного блока перевернута, о ужас!

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

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

А всетаки ksensors - отстойная программа. Оказывается у меня в системе перепутались два температурных датчика, и до сих пор я предполагал что первый датчик показывает температуру CPU, но сопоставив показания ksensors с показаниями sensors из комплекта lm_sensors обнаружил странное расхождение, которое вынудило меня зарыться в /etc/sensors.conf и выяснить что CPU temp - это второй датчик. Но раньше то я не обращал на него особого внимания, и предполагаю что он показывал нечто, болшее 40 градусов... Сейчас он показывает порядка 55-56 под нагрузкой.

И вот еще подумал, существуют ли отдельные комплекты мониторинга состояния компьютера? а то мне явно не хватает датчиков. Мне нужен датчик на БП и на винт желательно тоже... Но пока продолжаю наблюдать за поведением системы. Погода располагает для крэштестов, температура в квартире поднимается до 30 градусов...

суббота, 5 июля 2008 г.

Операции над указателями...

Долгая дискуссия заставила призадуматься...

С одной стороны в исходниках gcc я явно обнаружил обмен индекса и указателя, в случае операций с массивами (gcc/gcc/c-types.c):
 if (TREE_CODE (TREE_TYPE (array)) != ARRAY_TYPE
&& TREE_CODE (TREE_TYPE (array)) != POINTER_TYPE)
{
tree temp;
if (TREE_CODE (TREE_TYPE (index)) != ARRAY_TYPE
&& TREE_CODE (TREE_TYPE (index)) != POINTER_TYPE)
{
error ("subscripted value is neither array nor pointer");
return error_mark_node;
}
temp = array;
array = index;
index = temp;
swapped = true;
}
Но загадочности на этом не закончились. На самом деле компилятору действительно пофиг что с чем складывать. указатель с индексом или индекс с указателем, результатом всеравно будет указатель на элемент.
char s[] = "hello"; 
int idx = 3;
assert (*(s + idx) == 'l' && *(idx + s) == 'l');
Так вот второе выражение смущает меня больше всего.

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

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

Вообще компилятор во многих случаях молчит. Я вот до сих пор считал, что удаление недостижимого кода - это оптимизация. И при компиляции без оптимизации компилятор должен тупо генерить все что написано. Но это оказалось не так.
int i = 10;
if (1) i += 20;
if (0) i -= 30;
$ gcc -O0 -S ...
        movl    $10, -8(%ebp)
addl $20, -8(%ebp)
add есть, а вот sub не наблюдаю...

Эта ерунда всплыла у меня когда я тшетно пытался посмотреть код, генерируемый выражениями argc + argv - 1, argv + argc - 1 с помощью BOOST_ASSERT(argc + argv - 1 == argv + argc - 1);. Выражение оказывалось истинным, и BOOST_ASSERT вообще не генерировал никакого кода, не смотря на -O0. Дык он и свертку выполнил безо всякого разрешения.

Может быть у gcc есть какой нибудь волшебный ключик, который позволяет разрешить генерацию недостижимого кода? не знаю точно...

четверг, 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: Кстати вот тоже неоднозначность, после последнего элемента запятая не обязательна...