Как стать автором
Обновить
51.65

Системное программирование *

Обеспечение работы прикладного ПО

Сначала показывать
Порог рейтинга

На днях в офисе я столкнулась с коллегами из направления системного программирования в «Криптоните». Они пишут на С++ — а ошибки на этом языке у нас ещё не было!

Поэтому я попросила их придумать код с ошибкой специально для Хабра — и вот что получилось!

Итак, есть ли в этом коде проблема кроме narrowing conversion? Ждём ваши варианты в комментариях.

#include <cstdint>
#include <vector>

struct Type
{
    Type(uint16_t, uint32_t = {}) 
    {}
};

int main()
{
    std::vector<Type> vector;
    std::uint32_t object_id{};

    // Есть предупреждение о narrowing conversion
    vector.insert(vector.begin(), {0, object_id}); 

    // Нет narrowing conversion
    vector.push_back({0, object_id}); 

    // Нет narrowing conversion
    vector.insert(vector.begin(), Type{0, object_id}); 
}

АККУРАТНО, ДАЛЬШЕ СПОЙЛЕР!

Проблема в том, что в строке

 vector.insert(vector.begin(), {0, object_id});

в вектор вставляется 2 элемента типа Type, а не один, как ожидает программист.

Причина в том, что метод insert у вектора имеет перегрузку (номер 5), которая вторым параметром принимает std::initializer_list. А компилятор, видя фигурные скобки в коде, в первую очередь пытается создать объект такого типа.

И тут ему это удается, потому что у конструктора Type второй параметр имеет значение по умолчанию, следовательно, Type умеет создаваться, если в конструктор передали только один аргумент. В итоге компилятор успешно создает std::initializer_list с двумя элементами типа Type.

Так как создание std::initializer_list выполняется с использованием uniform initialization, то компилятор следит за корректностью преобразований. В примере объект типа std::uint32_t передается в конструктор Type, который принимает первым параметром std::uint16_t. То есть, возникает риск потери точности (32 бита не могут поместиться в 16 бит).

Об этом компилятор и предупреждает. Вот упрощенный пример, где видим то же самое предупреждение.

Может возникнуть вопрос, почему создание Type от 0 не вызывает предупреждение, ведь 0 - это int, а он, скорее всего 32 бита и тоже не помещается в 16 бит. Но тут литерал. Компилятор видит, что 0 вмещается в 16 бит и не предупреждает. Но если поместить int в переменную, то также возникает предупреждение.

Теги:
+3
Комментарии0

Устройство компилятора (кратко) на LLVM
Компилятор - инструмент конвертации исходного кода, написанного на высокоуровневом языке программирования в машинный код, который может исполнять компьютер.

Компилятор делится на 3 этапа:

  • FRONTEND - анализирует текст исходного кода и преобразует его в IR.

  • MIDDLE - анализирует и оптимизирует этот сгенерированный код IR.

  • BACKEND - преобразует IR в машинный код.

Сам компилятор, и собственно язык программирования состоит из нескольких частей.

Lexer - лексер

Лексер сканирует и превращает сырой текст в токены. То есть сам исходный код разбиваеты на набор токенов (такие как литералы, идентификаторы, ключевые слова, операторы, разделители)
Лексер читает исходный код символ за символом и идентифицирует последовательности символов, соответствующие определённым правилам языка.

Парсинг

Парсинг немного сложнее чем лексический анализ. Существует множество паресров и парсеров-генераторов.

Парсеры в компиляторах обычно принимают входные данные в форме токенов и строят определенное дерево - AST или дерево парсинга.

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

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

AST

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

IR

Эта часть занимается созданием [[1.2 IR]]. Через примитивы LLVM мы можем сгенерировать промежуточное представление. Каждому типу в AST дается метод, называемый codegen, который всегда возвращает объект значение LLVM, используемый для представления одного регистра присваивания (single assignment register), который является переменной для компилятора, которая может быть назначена только один раз. Интересно, что в этих примитивах IR то, что в отличии от ассемблера, они не зависят от какой-либо конкретной архитектуры машины, и это значительно упрощает работу для разработчиков языков, которым больше не нужно сопоставлять вывод в набор инструкций процессора. Теперь, когда фронтенд может генерировать IR, инструмент LLVM Optimizer используется для анализа и оптимизации сгенерированного кода. Он выполняет несколько проходов по IR и выполняет такие действия как устранение мертвого кода и скалярная замена агрегатов, и, наконец, это приводит нас к бекенду, где мы пишем модуль, который принимает IR в качестве входных данных, который выдает объектный код, который может работать на любой архитектуре.

Теги:
+3
Комментарии0

LLVM IR: что это такое?

Главной особенностью LLVM является промежуточное представление кода (англ. Intermediate Representation, IR), форма, которую использует LLVM для представления кода в компиляторе. LLVM IR был разработан для выполнения функций промежуточного анализа и преобразований внутри оптимизатора компилятора. Ее создание имело целью решение множества специализированных задач, включая поддержку легковесных оптимизаций среды выполнения, кроссфункциональные и межпроцедурные оптимизации, полный анализ программы и агрессивные реструктурирующие преобразования. Промежуточное представление кода определено как язык первого порядка с четкой семантикой.

IR (Intermediate Representation) в контексте LLVM — это промежуточное представление кода. Это низкоуровневое, независимое от платформы и типобезопасное представление программного кода, которое используется в качестве промежуточного языка между интерфейсной частью и серверной частью компилятора.

define i32 @add1(i32 %a, i32 %b) {
entry:
  %tmp1 = add i32 %a, %b
  ret i32 %tmp1
}

define i32 @add2(i32 %a, i32 %b) {
entry:
  %tmp1 = icmp eq i32 %a, 0
  br i1 %tmp1, label %done, label %recurse

recurse:
  %tmp2 = sub i32 %a, 1
  %tmp3 = add i32 %b, 1
  %tmp4 = call i32 @add2(i32 %tmp2, i32 %tmp3)
  ret i32 %tmp4

done:
  ret i32 %b
}

Этот код LLVM IR соответствует следующему коду на языке C, обеспечивающему возможность сложения целых чисел двумя разными способами:

unsigned add1(unsigned a, unsigned b) {
  return a+b;

}
// возможно не самый лучший способ сложения двух чисел
unsigned add2(unsigned a, unsigned b) {
  if (a == 0) return b;
  return add2(a-1, b+1);
}

Как видно из этого примера, LLVM IR — низкоуровневый RISC-подобный набор виртуальных инструкций. Как и настоящий набор инструкций RISC, он поддерживает линейные последовательности простых инструкций (сложение, вычитание, сравнение и ветвление). Эти инструкции имеют трехадресную форму. Это значит, что они берут некоторое количество входных данных и вычисляют результат в другом регистре. LLVM IR поддерживает метки и в целом выглядит как необычная форма языка ассемблера.

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

Теги:
+3
Комментарии0

Рэймонд Чен — ветеран компьютерной индустрии, который работает в Microsoft c 1992 года. Рэймонд участвовал в разработке OS/2, Windows 95, DirectX и оболочки Windows, а последние десятилетия отвечает за сохранение обратной совместимости системы. В своём блоге Old New Thing Чен регулярно делится забавными историями из разработки софта, но также показывает действительно полезные примеры.

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

// В целях наглядности вся проверка ошибок опущена
#include <windows.h>

void SetClipboardText(HWND hwnd, PCWSTR text)
{
    OpenClipboard(hwnd);
    EmptyClipboard();
    auto size = sizeof(wchar_t) * (1 + wcslen(text));
    auto clipData = GlobalAlloc(GMEM_MOVEABLE, size);
    auto buffer = (LPWSTR)GlobalLock(clipData);
    strcpy_s(buffer, size, text);
    GlobalUnlock(clipData);
    SetClipboardData(CF_UNICODETEXT, clipData);
    CloseClipboard();
}

// Чтобы они были под рукой, разместим эти строки в истории буфера обмена
static constexpr PCWSTR messages[] = {
    L"314159", // номер бага, который мы хотим исправить
    L"e83c5163316f89bfbde7d9ab23ca2e25604af290", // коммит, к которому привязываем ошибку
    L"Widget polarity was set incorrectly.", // комментарий, который нужно добавить
};

int wmain([[maybe_unused]] int argc,
          [[maybe_unused]] wchar_t* argv[])
{
    auto tempWindow = CreateWindowExW(0, L"static", nullptr, WS_POPUPWINDOW,
            0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);

    for (auto message : messages)
    {
        SetClipboardText(tempWindow, message);
    }
    DestroyWindow(tempWindow);
    return 0;
}

Код записывает в буфер обмена последовательно три строковые переменные. Однако при запуске утилиты в истории буфера обмена оказывалась лишь одна — последняя. Куда делись две остальные?

Дело в том, что служба истории буфера обмена работает асинхронно через механизм Clipboard Format Listener, существующий с эпохи Windows Vista. В этом механизме через функцию Add­Clipboard­Format­Listener приложение добавляет себя в качестве листенера. После этого никаких дополнительных опросов буфера обмена проводить не нужно — система сама оповестит приложение, если буфер изменился.

При получении уведомления служба истории буфера обновляет собственно историю буфера обмена. Но из-за асинхронности событие может происходить с задержкой. Как объясняет Чен, из-за асинхронной природы обновлений при получении WM_CLIPBOARD­UPDATE от Clipboard Format Listener буфер может успеть обновиться ещё раз.

Как считает Рэймонд, это даже не баг, а фича. Так получается избегать приложений, которые быстро спамили бы в буфер обмена множество изменений. Если даже пользователь не успевает воспользоваться содержимым буфера, то сохранять это для истории смысла нет, указывает Чен.

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

Рэймонд обещает в следующий раз показать, как исправить код выше.

Теги:
Всего голосов 5: ↑4 и ↓1+6
Комментарии0

Читаемость Си-кода: грустный ликбез, чтобы жить стало веселее

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

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

Последние два очень легко спутать, если читать код невнимательно. Вместо них иногда рекомендуют использовать

#if defined // вместо #ifdef
#if !defined // вместо #ifndef

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

Чего далеко ходить - все open source проекты пестрят именно сокращенными вариантами этих выражений и с этим уже ничего не поделаешь.

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

Конечно же выражения препроцессора - это очень гибкий и полезный инструмент, позволяющий делать всё:

  • условную компиляцию;

  • выравнивание структур;

  • предотвращать повторные включения файла;

  • работать со строками;

  • грамотно оборачивать повторяющийся код и т.д. и т.п.

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

struct {
  const char *name;
  const char *value;
#define _SPECIAL(x) { .name = #x, .value = b->x, }
} specials[] = {
  { .name = "object", .value = b->object_string, },
  _SPECIAL(host),
  _SPECIAL(endpoint),
#undef _SPECIAL
};

*ну все, можно начинать грустить*

Чтобы разобраться, давайте очистим код от макросов, но сохраним суть:

struct {
  const char *name;
  const char *value;
} specials[] = {
  { .name = "object", .value = b->object_string, },
  { .name = "host", .value = host, },
  { .name = "endpoint", .value = endpoint, },
};

Что понятно из очищенного варианта:

  • инициализируется массив specials[];

  • типом данных этого массива является структура с полями *name и *value;

  • поля элементов массива задаются вручную.

Но как можно этот процесс немного автоматизировать и не прописывать вручную одинаковые строки?

Правильно, с помощью макроса:

#define _SPECIAL(x) { .name = #x, .value = b->x, }

который определяется после объявления полей структуры.

Как обрабатывается аргумент x:

  • имя аргумента преобразуется в строку с помощью макроса "#x" и присваивается полю *name;

  • полю *value присваивается значение поля структуры b с названием аргумента x (тут нужно убедиться, что поле с именем x действительно существует в структуре b).

То есть чтобы при заполнении массива не писать каждый раз одинаковые строчки:

{ .name = "host", .value = host, },
{ .name = "endpoint", .value = endpoint, }

можно вызвать выражение _SPECIALS:

_SPECIAL(host),
_SPECIAL(endpoint),

Ну и в самом конце вызываем удаление созданного макроса, чтобы оно не было использовано в коде в дальнейшем:

#undef _SPECIAL

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

А какие выражения и макросы в Си видели вы?

Теги:
Всего голосов 7: ↑7 и ↓0+10
Комментарии2

Что такое быть Unix-программистом? Быть наполовину сисадмином (и вот почему)

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

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

Итак, навыки, правила, они же житейские мудрости, они же грабли, на которые наступал.

1. Сначала править конфиги и только потом - код. Это база

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

Сначала проверь конфигурацию и все её возможные варианты! Если все эти варианты исчерпаны, то только тогда, в последнюю очередь смотри в код!

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

2. Владеть инструментами командной строки

Логично, но не очевидно на первый взгляд. В отличие от обычного программиста (в сферическом вакууме), когда ты можешь ограничиться пошаговой отладкой или логами, Unix-программист должен уметь работать с командной строкой. Хоть и не обязательно знать все команды и их опции "на зубок", но нужно понимать, для каких случаев какие утилиты полезны.

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

Например:

- при невозможности пошаговой отладки использовать perf, чтобы проанализировать стек вызовов

- уметь пользоваться grep'ом для поиска и выделения нужной информации из конкретных файлов

- использовать sed для формирования файлов без лишней информации (например в логах убирать строки с наличием отметок времени)

3. Уметь работать с виртуальными машинами, докером, анализаторами трафика

Например, не плодить виртуалки, а делать снэпшоты (вы скажете: "Спасибо, капитан-очевидность!", но я видел на своей практике тех, кто, не зная про снэпшоты, плодил виртуалки). Держать мастер-копии виртуалок с предварительно настроенной конфигурацией для быстрого развертывания новых машин. Понимать, для каких целей проще использовать докер-контейнер и т.д.

4. Работать с огромным количеством открытых одновременно утилит

Как ни странно, я встречал разработчиков, которые задавали вопрос: "Зачем так много всего?"

Ответ прост: когда непонятно поведение программы, нужно принимать во внимание всё.

Резюме: с таким набором навыков в какой-то момент начинаешь себя чувствовать, как оператор из Матрицы.

А какие особенности в Unix-разработке (и не только) подметили вы?

Теги:
Всего голосов 4: ↑4 и ↓0+4
Комментарии4

Я тут опубликовал библиотеку для программирования в парадигме потоков данных (dataflow) на микроконтроллерах: https://github.com/Zubax/ramen

Она чрезвычайно проста (один заголовочный файл) и работает на любой платформе без портирования.

Простейший пример --- сумматор:

               ┌────────┐
       (float) │ Summer │ (float)
 in_a ◄────────┤        │◄──────── out_sum
               │        │
       (float) │        │
 in_b ◄────────┤        │
               └────────┘
struct Summer
{
    ramen::Puller<float> in_a;
    ramen::Puller<float> in_b;
    ramen::Pullable<float> out_sum = [this](float& out) { out = *in_a + *in_b; };
};

Больше примеров, а также объяснение зачем это нужно при наличии альтернатив, по ссылке. Всем спасибо.

Теги:
Всего голосов 5: ↑4 и ↓1+3
Комментарии0

Готовим прошивки для BIOS. Нужно всего лишь...

В YADRO есть команда, которая разрабатывает и отлаживает UEFI для самых разных продуктов компании. Подробнее о ней — в видеоролике → .

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

Системы, для которых разрабатывают прошивки в YADRO, делятся на два типа: с BMC и без него.

Больше о работе команды и, в целом, об истории BIOS/UEFI читайте в статье → 

А если тоже хотите стать «поваром» UEFI, присмотритесь к одной из вакансий:

→ Старший/ ведущий разработчик на C++ (Linux/OpenBMC)

→ Старший разработчик на С (BIOS/UEFI)

→ Тимлид разработки в OpenBMC

Теги:
Всего голосов 4: ↑3 и ↓1+4
Комментарии0

SystemVerilog отжил свое? На пятки наступает Scala/Chisel?

DARPA, управление перспективных исследовательских проектов Минобороны США, описывает Chisel как технологию, позволяющую маленьким командам создавать большие цифровые проекты. И я вполне могу с этим согласиться, но есть нюансы.

Chisel — это, по сути, библиотека Scala, а точнее, Domain Specific Language. Языку Scala уже больше 20 лет, он постоянно развивается, сочетает функциональное и императивное программирование. При написании кода на Scala вам доступны все библиотеки Java. 

Scala — это масштабируемый язык, который позволяет добавлять свои языковые конструкции. На основе Scala можно создать язык под свои задачи. Так 12 лет назад и поступили инженеры в Беркли: выкинули из Verilog 90%, оставив только нужное, и обернули все это в Scala. Получился Chisel. 

Chisel используют прежде всего для создания RTL-описаний. Также он позволяет проводить симуляцию несложных модулей. Это удобно для создания юнит-тестов и моделирования работы различных алгоритмов. В плане симуляции не стоит возлагать на Chisel такие же надежды, как на System C или что-то подобное. Симулировать вы сможете лишь очень маленькие схемки, а генерировать — хоть целые кластеры из тысяч процессоров, вообще все, что захотите.

На основе Chisel/Scala можно написать свой HLS-инструмент (High Level Synthesis), где одним росчерком пера вы будете создавать очень большие схемы, что с использованием одного Verilog невозможно.

В блоге YADRO Денис Муратов подробно сравнил Chisel/Scala с SystemVerilog в создании RTL-описаниях, раскрыл основные преимущества и недостатки альтернативы, а также ее дополнительные возможности — функциональное программирование и переиспользование модулей.

Теги:
Всего голосов 4: ↑4 и ↓0+5
Комментарии0

Новый релиз языка Zig 0.14.0

Github Release

Описания изменений версии

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

Важное изменение, которое всё же я упомяну - появился свой backend для компилятора. То есть отказ от LLVM состоялся, но на данный момент только для Linux x86_64.

Теги:
Всего голосов 4: ↑3 и ↓1+4
Комментарии0

Как и зачем дублировать Intel NTB Gen3 в QEMU

Системным программистам в YADRO нужно было «обмануть» драйвер в Linux: он не должен «знать», что работает в эмуляции. 

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

PCIe NTB не позволяет увидеть адресное пространство, которое принадлежит к подключенному по NTB соседнему устройству. Вкратце он работает так:

  • перенаправляет трафик PCIe между шинами как мост,

  • CPU рассматривает мост как конечное устройство,

  • CPU не «видит» все устройства на «другой» стороне, как правило, другая сторона — это другой компьютер.

Упрощенное представление PCIe NTB
Упрощенное представление PCIe NTB

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

В работе с PCI BAR необходимо обеспечить прозрачное использование Linux-драйвера Intel NTB для оптимального взаимодействия с оборудованием, которое мы эмулируем. Еще одна задача — разработать новые транспорты, которые работают поверх эмуляции: RPMSG, Virtio/Vhost, NTRDMA и другие. Также одна модель помогла найти ошибки в инициализации драйвера.

Никита подробно описывает тернистый путь создания виртуального двойника Intel NTB Gen3 в статье →

Теги:
Всего голосов 5: ↑5 и ↓0+7
Комментарии0

«Пушим байты» в сугробы: новогодняя демосцена 2025

Признанная классика демосцены — PICO-8, в арсенале которой сотни игр разной сложности. Мы же пойдем более оригинальным путем и напишем новогоднюю демку для BytePusher. Эта приставка включает 8-битный процессор, предлагает разрешение 256x256 и 8-битный цвет. Но самое интересное в ней — это OISC-архитектура ByteByeJump (BBJ).

OISC, One-Instruction Set Computer, известна гораздо меньше, чем RISC или CISC. Ее простота, очевидная из названия, привлекает немного энтузиастов, судя по странице BytePusher. Тем интересней будет сделать что-нибудь для нее с нуля.

Этим и занялся в своей статье Пётр Советов, специалист в области разработки DSL-компиляторов и старший научный сотрудник лаборатории специализированных вычислительных систем РТУ МИРЭА. Написал ассемблер на Python, разобрался с вычислениями без АЛУ и «отрисовал» классическое демо с падающим снегом. Еще и со «звездочкой» в виде сугробов и статичных цифр.

Читать полное руководство →

Теги:
Всего голосов 3: ↑3 и ↓0+3
Комментарии0

Три проверенных метода организовать обмен прерываниями между машинами QEMU c KVM и без

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

Быстрая работа такой связки приятна при разработке/отладке и очень важна при массовом прогоне автотестов в CI. Как оптимизировать обмен прерываниями и какой подход к организации IQI вам подойдет — узнаете из статьи. А еще разберемся c:

  • устройством QEMU под капотом,

  • реализацией модели и драйвера,

  • добавлением прерываний MSI-X,

  • результатами замеров.

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

Теги:
Всего голосов 6: ↑5 и ↓1+5
Комментарии0

Ближайшие события

Битовые маски #17: О гипервизорах, реализации разных языков и будущем системного программирования

Смотреть или слушать →

В гостях у подкаста «Битовые маски» — Николай Иготти, разработчик, участвовавший в создании многих известных проектов международных корпораций. Николай успел поработать над HotSpot в Sun Microsystems, над гипервизором VirtualBox, а также в разных проектах Google и EMC. Руководил разработкой Kotlin/Native компилятора и Compose Multiplatform в JetBrains, а сейчас трудится в Huawei. В выпуске затронули много разных тем — от гипервизоров до дизайна современных языков программирования:

  • Чем виртуальные машины отличаются друг от друга и от процессоров.

  • В чем сложности создания гипервизоров.

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

  • В чем особенности и отличия разработки системного ПО от прикладного.

  • Как связана разработка современных UI-фреймворков с системным программированием.

  • Какие задачи в системном программирование самые интересные.

  • Как учить будущих специалистов в этой сфере.

Бонус: разбор того, что не давало запустить VirtualBox c MacOS на архитектуре x86.

Николай Иготти в студии «Битовых масок»
Николай Иготти в студии «Битовых масок»

Теги:
Всего голосов 7: ↑7 и ↓0+10
Комментарии0

Книга по C и не только

Добрый день. Пару месяцев назад я закончил написание книги и выставляю её в открытый доступ: GitHub

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

Посмотреть .html файл книги можно на GitHub-е. Но он не рендерит MathJax, поэтому лучше скачать файл c-book.html локально. Можно как угодно (в том числе в issues) сообщать мне о нерабочем коде в примерах, опечатках, неправильных утверждениях с моей стороны. Буду также рад увидеть конструктивную критику. Возможно, в ответ на неё, я буду добавлять новые главы в книгу.

(Эта книга абсолютно точно не является рекламой Zig-а.)

Теги:
Всего голосов 22: ↑19 и ↓3+21
Комментарии12

Почему умирает OpenHarmony

Если вы откроете проект на gitee.com вы увидите что проект включает в себя больше 700 (семисот!) репозиториев. Секрет в том что ни один из этих проектов в отдельном репозитории не компилируется независимо! Один знакомый разработчик, который работает с этим богатством, рассказал мне, что чтобы скомпилировать какой либо из проектов составляющих OpenHarmony вы должны скачать и скомпилировать минимум 450 репозиториев! Дело в том что даже отдельные приложения такие как mailBox, storage с СМС-ками, с контактами, видео плеер, ... которые, казалось бы, должны быть отдельными приложениями таковыми не являются. Они все компилируются как части одной монолитной прошивки смартфона (как части операционной системы) и вы не можете скомпилировать их без компиляции всей системы, такая опция просто не предусмотрена.

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

Каждый новый добавленный в OpenHarmony репозиторий приближает видимый крах проекта.

Интересно как обстоят дела у Андроида в этом смысле.

Теги:
Всего голосов 4: ↑4 и ↓0+6
Комментарии1

Первая вакансия на Zig в России

Собственно вот: https://career.habr.com/vacancies/1000147586

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

Теги:
Всего голосов 6: ↑5 и ↓1+6
Комментарии5

Всем привет. Возможно, вы помните меня по статьям об изменениях в C++ и о фреймворке 🐙 userver, поэтому сразу к делу. 27 июля я выступаю на конференции C++ Zero Cost Conf, которая пройдёт в Москве и Ереване. Там я поделюсь новостями со встречи Международного комитета по стандартизации языка в Сент-Луисе и расскажу о наших планах на C++26 и C++29. А ещё отвечу на ваши вопросы. Приходите в гости!

Помимо меня, вас также будут ждать: 

  • Константин Владимиров, руководитель отдела компиляторов и средств разработки Syntacore. Покажет развитие архитектуры сложного LLVM-based-C++-проекта и конкретные задачи, возникающие при его развитии.

  • Андрей Аксёнов, руководитель разработки инфраструктуры поиска Авито/Sphinx. Расскажет историю из продакшена с One Billion Row Challenge, парсингом гигабайтов TSV’шек, десятью странными оптимизациями и боттлнеками вообще везде.

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

  • Константин Облаков, старший разработчик браузера Яндекс Поиска и Рекламных технологий. Покажет на практике, как GDB позволяет добиться результатов, недоступных другими методами.

Полный список спикеров и темы докладов смотрите на сайте

Если не получится прийти лично, подключайтесь дистанционно.

Теги:
Всего голосов 5: ↑5 и ↓0+9
Комментарии0
Теги:
Всего голосов 4: ↑4 и ↓0+5
Комментарии1

25.03.2024 года «Лаборатория программно‑аппаратных комплексов с искусственным интеллектом» при финансовой поддержке ООО «НИЦ ЦТ» организовала первый семинар, посвящённый системному программированию.

Докладчики: сотрудники МЦСТ Александр Ермолицкий, Мурад Нейман‑заде. Предварительная тема: «Обзор оптимизаций компилятора LCC».

Запись мероприятия доступна по этой ссылке.

Теги:
Всего голосов 7: ↑6 и ↓1+5
Комментарии0

Откуда берётся мусор?

Рассмотрим следующую программу:

#include <stdio.h>
int main () {
  int a [100];
  int i;
  for (i=0; i<100; i++)
    printf ("%d ", a[i]);
  return 0;
}

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

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

Объяснение в стиле “потому что виртуальные адреса мапируются на реальные рандомно” я и сам могу дать, хочется более глубокого понимания. Компьютер – вещь детерминированная, в нём случайность – это не познанная закономерность.

Upd: ответ дан уважаемым @alexvangog в закреплённом комментарии.

Теги:
Всего голосов 4: ↑3 и ↓1+2
Комментарии13

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

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

#include <dlfcn.h>
#include <stdio.h>

struct leaksaver {
        struct leaksaver *next;
        void *pointer;
} *bigbucket;

void *
malloc(size_t len)
{
        static void *(*nextmalloc)(size_t);
        nextmalloc = dlsym(RTLD_NEXT, "malloc");
        void *ptr = nextmalloc(len);
        if (ptr) {
                struct leaksaver *saver = nextmalloc(sizeof(*saver));
                saver->pointer = ptr;
                saver->next = bigbucket;
                bigbucket = saver;
        }
        return ptr;

Пояснение автора кода в оригинале:

Every allocated pointer is saved in the big bucket, where it remains accessible. Even if no other references to the pointer exist in the program, the pointer has not leaked.

It is now entirely optional to call free. If you don’t call free, memory usage will increase over time, but technically, it’s not a leak. As an optimization, you may choose to call free to reduce memory, but again, strictly optional.

Problem sovled!

Теги:
Всего голосов 6: ↑3 и ↓30
Комментарии7
1