Как стать автором
Поиск
Написать публикацию
Обновить
58.45

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

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

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

«Клей» для GPIO в QEMU

В прошлой статье мы пришли к выводу, что QMP — это лучше, чем ничего. Но хочется большего — библиотеку или программу (желательно, уже готовую), которая умеет читать/писать и узнавать об изменении состояния через poll() / pselect() / select() / epoll() / read(). 

В таком случае для каждой модели GPIO нужен «клей», похожий на тот, что используется с chardev — мы включаем его прямо в модифицированный QEMU. Очевидное название такого «клея» — gpiodev. Вот его основные функции, которые сейчас почти полностью соответствуют GPIO UAPI в Linux:

  • сообщать количество линий, конфигурацию, название и потребителя каждой линии,

  • читать и задавать состояние линии,

  • отслеживать изменения состояния и конфигурации линии (вход/выход, запрос/освобождение).

«Клей» состоит из двух групп, первая — это индивидуальные для каждого модуля GPIO функции, которые gpiodev использует, чтобы запросить специфическую информацию:

  • LineInfoHandler() — информация о линии: имя, флаги и потребитель,

  • LineGetValueHandler() — состояние линии: условный 0 или 1,

  • LineSetValueHandler() — задать состояние линии: 0 или 1.

По аналогии с GPIO UAPI напрашиваются также функции LineGetMultiValueHandler() и LineSetMultiValueHandler() для запроса и выставления линий, но я решил ограничиться минимальным набором.

Можно ли организовать прозрачное взаимодействие с устройствами внутри QEMU — использовать те же библиотеки и инструменты, как и для реальных устройств? Читайте во второй части трилогии о долгом пути до GPIO в QEMU.

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

Как облачные сервисы продвигают продукты на хакатонах

Хакатон — способ поднять узнаваемость бренда и расширить воронку найма, все хорошо это знают.

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

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

Все топовые облака: Amazon, Google Cloud, Azure, Cloud.ru, VK Cloud, Yandex Cloud провели хакатоны в последние 3 года.

Самый масшабный был с призовыми в $150 000.

К участию приглашались стартапы для решения социально значимых задач. Единственное требование — использовать сервис в своем решении.

В РФ сильнее всего отметились Selectel и Cloud.ru, тогда под брендом SberCloud.

На SberCloud Advanced Hacking собралось 70 команд из малого и среднего бизнеса. Они внедряли облачные технологии в свои продукты — самые оригинальные решения получили призы.

Всего мы проанализировали 10 хакатонов от облаков. Делимся выводами.

  1. Хакатоны облачных сервисов выделяются необычно взрослой аудиторией: как правило, ЦА — средний и малый бизнес. Это потенциальные клиенты.

  2. Задачи ставятся максимально широко. Ограничения бывают
    А) по тематике, например, «сделайте что-то социально значимое»,
    Б) по технологии, например, собираются решения в области AI, Web3, Blockchain, IoT.
    Это идеально для хакатона.

  3. Максимум по участникам в РФ — около 260 человек. Есть простор для яркого крупного ивента.

Вывод: облачные сервисы и вообще все, кто производит продукт для разработчиков, одним махом закрывают сразу 3 задачи с помощью хакатона:

  • продвинуть продукт

  • поднять узнаваемость бренда

  • расширить воронку найма

Если ваша компания относится к таким — донесите ценность до бизнеса, это поможет «продать» идею хакатона внутри и оставить всех довольными.

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

Что ты сделал для хип-хопа IT-инфраструктуры в свои годы? Писал UEFI и BMC для высоконагруженного оборудования 

В YADRO есть распределенная команда, которая разрабатывает и сопровождает собственные программные реализации UEFI (BIOS) и BMC. Для самого разного оборудования — от серверов до телеком- и клиентского оборудования. 

Что делают BIOS и BMC в продуктах
Что делают BIOS и BMC в продуктах

Познакомиться с командой → 

Какие задачи выполняют в команде BIOS/BMC: 

  • Реализуют программную поддержку новых аппаратных продуктов компании, определяют протоколы и методы взаимодействия между программными и аппаратными компонентами продуктов YADRO.

  • Проводят верификацию микрокода и выполняют проверку прошивок микросхем всех продуктов компании. Выстраивают стратегию тестирования.

  • Исследуют новые программные и аппаратные технологии для применения в продуктах. Рефакторят код для повышения производительности.

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

  • Добавляют новые фичи и меняют существующие — от WebUI до политик управления аппаратными компонентами.

Команде нужно больше инженеров — разработчиков на С/C++, тестировщиков, автоматизаторов и техлидов. Знакомься с вакансиями на сайте и вовлекайся в трушные инженерные задачи на современном технологическом стеке. 

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

Последовательность Фибоначчи может конвертировать мили в километры с небольшой погрешностью

5 миль ≈ 8 км (5 и 8 - числа Фибоначчи). Реальность: 5 миль = 8.04672 км.

Почему?
1 миля = 1.609344 километра (точное значение).
Золотое сечение (φ) ≈ 1.618034

Погрешность возникает потому что отношение Fₙ₊₁ / Fₙ стремится к φ ≈ 1.618034, а точное соотношение миля/км = 1.609344.

Относительная погрешность: (1.618034 - 1.609344) / 1.609344 * 100% ≈ 0.54%.

Решил по фану реализовать конвертор милей в километры на C. Ссылка тут.

Advanced distance converter: miles to kilometers
Usage: ./bin/fib_miles2km [OPTIONS] [distance]

Options:
  -h, --help                     Show help information
  -f, --fib=ARG                  Convert miles to km using basic Fibonacci
  -b, --basic=ARG                Convert miles to km using standard formula
  -i, --fib-interp=ARG           Convert using Fibonacci interpolation
  -c, --fib-cache=ARG            Convert using cached Fibonacci
  -g, --fib-golden=ARG           Convert using golden ratio

Не знаю зачем, но прикольно :)

Теги:
+18
Комментарии1

Как создать простейшую модель GPIO для QEMU

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

Апертура и адресное пространство

Как я упоминал в одной из своих статей, любое MMIO-устройство — это MemoryRegion с заданными шириной доступа и размером. Для того, чтобы он был виден CPU или другому устройству, такому как DMA, его нужно разместить в соответствующем адресном пространстве — например, пространстве, назначенном для cpu0:

      0x0                                    0xffffffffffffffff
      |------|------|------|------|------|------|------|------|
0:    [                    address-space: cpu-memory-0        ]
0:    [                    address-space: memory              ]
                    0x102000           0x1023ff
0:                  [             gpio        ]

В любое время можно посмотреть существующие адресные пространства и регионы памяти в мониторе QEMU:

(qemu) info mtree
[...]
address-space: cpu-memory-0
address-space: memory
  0000000000000000-ffffffffffffffff (prio 0, i/o): system
    0000000000102000-00000000001023ff (prio 0, i/o): gpio
[...]

Тогда в модели устройства нам нужно всего лишь создать такой регион и назначить ему соответствующие функции записи и чтения:

static const MemoryRegionOps mmio_mmio_ops = {
    .read = mmio_gpio_register_read_memory,
    .write = mmio_gpio_register_write_memory,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .valid = {
        .min_access_size = 4,
        .max_access_size = 4,
    },
};
 
[...]
memory_region_init_io(iomem, obj, &mmio_mmio_ops, s,
                      "gpio", APERTURE_SIZE);
[...]

Фактически это означает, что все семейство инструкций Load/Store будет вызывать mmio_gpio_register_read_memory()/mmio_gpio_register_write_memory() при совпадении адреса чтения/записи с адресом региона в адресном пространстве.

static uint64_t mmio_gpio_register_read_memory(void *opaque, hwaddr addr, unsigned size);
static void mmio_gpio_register_write_memory(void *opaque, hwaddr addr, uint64_t value, unsigned size);

Передаваемые аргументы и возвращаемое значения интуитивно понятны. Отмечу, что hwaddr addr — это адрес относительно начала нашего региона, а не абсолютный адрес.

Нам остается лишь создать устройство и добавить его регион в файле машины:

gpio = qdev_new(TYPE_MMIO_GPIO);
sysbus_mmio_map(SYS_BUS_DEVICE(gpio), 0, ADDRESS);

Почти десять лет назад Никита Шубин, ведущий инженер по разработке СнК в YADRO, сделал возможность чтения и записи GPIO для QEMU. Читайте первую часть трилогии о долгом пути до GPIO в QEMU.

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

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

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

Итак, есть ли в этом коде проблема кроме 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: ↑3 и ↓0+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 в качестве входных данных, который выдает объектный код, который может работать на любой архитектуре.

Теги:
Всего голосов 6: ↑4 и ↓2+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.

Теги:
Всего голосов 5: ↑3 и ↓2+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
1

Вклад авторов