Всем привет!
Пишу этот пост, чтобы поделиться своим опытом и получить критику или советы от людей с большим опытом.
Мне 22 года, я из Латвии. По образованию я судовой механик, но уже около 4 лет увлекаюсь программированием. Долгое время это оставалось хобби: пробовал сайты, простые игры — но они не приносили настоящего удовольствия. Я считал, что в программировании нужно было разбираться ещё со школы, и долго не верил, что могу найти себя в этой сфере.
Переломный момент наступил, когда я заинтересовался системным программированием. Каждый раз, когда узнаю, как работает низкоуровневая часть ОС, у меня будто открывается новый мир. Особенно зацепила тема процессорного планирования: все говорят «железо, CPU, видеокарта», но на практике даже реализация планировщика процессов может заметно влиять на производительность.
Учёба и идея проекта
Год назад я поступил в университет на программу Computer Systems, чтобы увеличить свои шансы на работу в ИТ. Но, к сожалению, программа оказалась сильно смещена в сторону веба и сетей. Поэтому я сделал ставку на самообучение и параллельно развивался в системном направлении, а университет оставил для диплома.
На одном из экзаменов по ООП нужно было реализовать программу с применением объектно-ориентированного подхода. Тогда я решил совместить приятное с полезным — сделать первый серьезный pet-проект который:
можно защитить на экзамене;
покажет, что я "умею" проектировать и писать код;
и будет реально интересен мне самому.
Так родилась идея synd3 — сообственного процесс-менеджера для Linux.
От идеи до реализации
Когда я впервые познакомился с Linux через WSL (Windows Subsystem for Linux), мне понадобилось отслеживать нагрузку на систему. Я запустил htop — и сразу заметил, что отображение загрузки CPU идет по ядрам, а не по суммарной мощности процессора.
Мне захотелось сделать инструмент, который, по моему мнению, будет отображать статистику более "человечно" и интуитивно.
Мы работали над проектом вдвоем и решили разделить обязанности:
Логика на С,
Интерфейс (TUI) на С++ с использованием ncurses и ООП.
Сборка проекта осуществляется через простой Makefile, который компилирует оба модуля и связывает их.
Как synd3 работает внутри
Подсчет загрузки CPU
Основные данные synd3 получает из файлов /proc/stat и proc/[PID]/stat.
Для каждого процесса мы вычисляем разницу между его пользовательским (utime) и системным (stime) временем между двумя циклами опроса.
Затем результат нормализируется относительно общего CPU времени, чтобы получить процент загрузки.
Если "упростить", то мы измеряем, сколько тиков CPU досталось процессу за последний интервал, и сравниваем это с общим временем работы всех ядер за тот же промежуток.
Пример логики в упрощенном виде:
unsigned long long prev_total = total_ticks();
unsigned long long prev_proc = process_ticks(pid);
sleep(1);
unsigned long long total = total_ticks();
unsigned long long proc = process_ticks(pid);
double usage = 100.0 * (proc - prev_proc) / (total - prev_total);
Подсчет использование памяти
В synd3 для вычисления использования памяти реализована функция get_process_mem_usage(pid_t pid, float *mem_usage).
Она не просто считывает VmRSS а учитывает весь контекст использования памяти — в том числе кэш, буферы, и reclaimable-области, чтобы получить более точное значение реальной нагрузки.
Алгоритм выглядит примерно так:
Считываются метрики /proc/meminfo: MemTotal, MemFree, Buffers, Cached, SReclaimable.
Подсчитывается используемая память:
used_mem = MemTotal - MemFree - Buffers - Cached - SReclaimable;Для каждого процесса извлекается VmRSS из /proc/[PID]/statm.
Расчитывается процент использования памяти:
*mem_usage = (VmRSS / used_mem) * 100.0;
При этом в коде учитываются ситуации, когда /proc[PID]/statm может временно отсутствовать (например, при завершении процесса), а так же ошибки при чтении.
Такой подход дает более реалистичную картину загрузки памяти, чем простое чтение RSS — ведь часть памяти может быть кэширована или освобождена системой, но еще не возвращена пользователю.

Проблемы и неожиданные открытия
Когда мы впервые тестировали synd3, первое, что нас удивило — разное поведение программы под WSL и полноценным Linux. Тестируя программу на полноценном Linux, мы столкнулись с тем, что в synd3, отображались процессы без имени. У них был PID, однако имя отсутствовало. Нас это насторожило, так как тестируя на WSL, мы такой проблемы не наблюдали.
Оказалось, что в WSL процессы без имени не отображаются в /proc обычными средствами, из-за чего список в synd3 выглядел “аккуратнее”. Проще говоря, эти процессы “фильтруются“ и не видны.
На реальной системе же таких процессов много, и часть из них просто не имеет ничего в строке в /proc/[PID]/cmdline.
Проблема решилась добавлением одной проверки: если при чтении /proc/[PID]/cmdline функция fgets() возвращает NULL (что озночает, что строка пуста и у процесса нет имени), то такой процесс мы просто пропускаем.

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

Да, интерфейс обновляется чуть реже, зато стабильно, без артефактов и цветных глюков.
Этот опыт, пожалуй, стал самым ценным: теперь мы четко понимаем, что TUI-библиотеки (вроде ncurses) требуют однопоточного вывода и аккуратного обращения с буферами.
Планы на будущее
Переписать проект полностью на С, отказаться от ООП и снизить уровень абстракции.
Сделать архитектуру ближе к POSIX.
Реализовать мониторинг потоков.
Разработать клиент-хост взаимодействие, чтобы можно было мониторить процессы на удаленной машине.
Улучшить интерфейс: добавить дерево процессов и адаптивную перерисовку при изменении размера окна.
Репозиторий проекта
Проект лежит на GitHub: oxonomiAK/synd3.
Зачем пишу
Я не призываю никого пользоваться нашей программой — просто хочу понять взгляд со стороны.
Любая критика, советы или даже просто впечатления будут очень ценны.
Мне также интересно, почему кто-то выбирает htop, а кто-то — btop++ или даже glances?
Что именно для пользователя важно в системных мониторах: функциональность, визуальная часть, минимализм, или, может быть, производительность?
Я хочу понять, какие решения делают подобные инструменты удобными и живыми, а не просто «ещё одним монитором процессов».
Всё это поможет точнее направлять развитие как synd3, так и других будущих проектов — чтобы они были не просто учебными, а реально полезными
Спасибо, что дочитали!
Комментарии (11)

sharptop
20.10.2025 07:02Вставлю свои 5 копеек, поскольку тоже реализовывал в свое время свою утилиту по процессам ), правда не аналог top, а скорее аналог ps, ну и не на С, а на go.
Итак, что меня не устраивало в текущих утилитах типа ps/top и что мне часто нужно было видеть по работе - возможно вам это тоже будет полезно:
1) Нужно было видеть, сколько и какой процесс потребляет свопа, потому что часто с ним возникала проблема.
2) Нужно было понимание, процесс запущен на хосте или же в контейнере - во всех утилитах для этого приходится делать дополнительные действия, чтобы выяснить это.
3) Нужна фильтрация - показать только процессы, запущенные в контейнерах, показать процессы у которых потребление какого-то одного или нескольких ресурсов (например cpu и памяти или cpu и swap) больше определенных значений
4) желательно чтобы вывод команды можно было настраивать и чтобы этот вывод потом можно было бы использовать в пайпах для других программ
oxonomii Автор
20.10.2025 07:02Отличные идеи, спасибо большое!
Swap мы думали добавить в ближайшее время, так как это неотъемлемая часть нормального процесс менеджера, однако о контейнерах мы не задумывались — действительно полезный кейс.
Я подумаю, как можно реализовать определение процессов внутри контейнеров.
И фильтры — да, это одна из вещей, которую хочется добавить в TUI.

domix32
20.10.2025 07:02что нас удивило
мы хотели,
А кого вас-то? Где я пропустил момент, когда статья внезапно стала от множественного лица?
VmRSS из /proc/[PID]/statm.
Я так понимаю про виртуальную память пока ещё ничего не выводилось. Про подумать о фичах
Ещё не вижу чтобы выводилось хоть какое-то состояние процесса - жив, повис, зомби и прочие варианты.
ncurses не является потокобезопасной библиотекой.
вероятнее всего что оно не предназначенно для immediate mode и поэтому попытки обращаться к вашим неподготовленным данным приводят к дичи.
и сделали обновление экрана синхронным: сначала сбор данных, потом отрисовка.
Собственно с подгтовкой данных для рендеринга можно теперь унести рендеринг в отдельный поток. Глядишь ещё и двойную буфферизацию освоите.
Но ещё интереснее - попробовать самостоятельно навелосипедить свой ncurses, благо там нчиего сложного нет. Если конечно вас не интересует поддержка легаси кодировок и интернационализация.
По коду - используйте больше статических выделений памяти. А то у вас, например, есть куча бесполезных перевыделений памяти - вектор при вставке триггерит переаллокации, когда вполне мог бы выделить необходимые буфферы единожды перед отрисовкой кадра - размеры кадра вам на этот момент по идее должны быть известны.
Ну, и крайне советую включить хотя бы 17 стандарт и использовать
std::string_viewвместо голых char*. Заодно можно включить-Wall -Wextra,чтобы понимать, где могут быть косяки.Вот так делать не стоит - разделяйте обработку данных и их отрисовку. В обще понятно откуда у вас могут быть проблемы с многопотоком. В идеале должен быть единственный вызов этой функции и общий флоу должен выглядеть как-то так
AppState state; while(!state.should_close) { handle_input(state); if (state.should_close) { goodbye(state); break; } prepare_data(state); render(state); } // ну или ООП AppState state; while (!state.should_close()) { state.handle_input(); if (state.should_close()) { state.goodbye(); break; } state.prepare_data(); state.render(); }Штуки типа таких стоит проворачивать через std::chrono, чтобы избегать проблем с какими-нибудь високосными секундами. Это, конечно, если вы не хотите дауншифтнуться до C.
Ну, а для такой магии изобрели enum.

oxonomii Автор
20.10.2025 07:02Огромное спасибо за такой развёрнутый фидбек!
Да, нас двое — я и однокурсник, поэтому в тексте местами "мы".
Про ncurses согласен — это была одна из самых болезненных тем, особенно при попытке параллелить сбор данных и рендер.
Идея "навелосипедить свой ncurses" звучит интересно, вполне возможно, что в будущем мы это попробуем.
Так же, идею с подготовкой данных отдельно от отрисовки обязательно попробуем внедрить — звучит логично.
Отдельное спасибо за советы по std::string_view и статическим буферам, учтём при переходе на чистый С, когда "окончательно дауншифтнемся".

iamkisly
20.10.2025 07:02В htop заглядывали ?
Несмотря на то что тут задача придумывалась под технологию, а не наоборот.. вставлю что для менеджера будет более уместен Data DD (какой-нибудь ECS), а не ООП

oxonomii Автор
20.10.2025 07:02Конечно, htop — один из вдохновителей.
На самом деле использование ООП это было техническое задание университета, нужно было продемонстрировать использование наследования, композиции, инкапсуляции и т.д. По этой причине наше решение смотрится не логично, но без этого было никак.

inv2004
20.10.2025 07:02Попробуйте https://github.com/inv2004/ttop/
+ там сохраняются исторические снепшоты

Tuxman
20.10.2025 07:02Чтобы самому не изобретать TUI-велосипеды, и не связываться с ncurses, а писать на современном C++, предлагаю взять готовую библиотеку https://github.com/ArthurSonzogni/FTXUI
Einherjar
Хороший интерфейс у вас получился - наглядный и лаконичный, и глазу приятно
oxonomii Автор
Спасибо! Рад, что интерфейс зашел — мы старались сделать его минималистичным, но информативным.
Сейчас как раз думаем, как аккуратно добавить фильтры и сортировку, чтобы не потерять лаконичность.