523MB VSZ, Samba з коробки і живий JSON

що я знайшов всередині embedded Linux

Частина 2: Docker, крос-компіляція і живий JSON API

Дашборд працює, SSH підключений, Python сервер крутиться, але метрики захардкоджені, цифри в браузері статичні і не мають нічого спільного з реальним станом системи. У попередній частині “Це не Raspberry Pi. І саме тому це цікаво” ми розібрались з підключенням, налаштували ip і попередньо ознайомились з встановленою в Luckfox Pico Pro платі на чіпі Rockchip RV1106 системою.

Сьогодні налаштовую Docker середовище для крос-компіляції, пишу Go HTTP сервер, компілюю під armv7, запускаю на платі і показую, що вийшло, по плану до кінця дня маю отримати живий JSON API з реальними системними метриками. Звісно ж буде кілька сюрпризів які знайдуться всередині системи поки буду розбиратись.

1. Чому Go, а не Python або Node.js?

Python вже є на платі і для прототипу — ідеальний. Але для сервера який буде крутитись постійно хочеться щось легше і швидше. Node.js це перший кандидат для мене, бо це моє улюблене середовище в якому перебував не один рік. Але, на превеликий жаль на embedded це одразу ні, тому що:

  • Node.js runtime важить ~50MB — при доступних 33MB RAM це проблема ще до старту
  • Крос-компіляція Node для armv7 — окремий квест на кілька днів
  • Buildroot не має Node в дефолтній конфігурації — треба компілювати ядро заново

А от Go вирішує всі ці проблеми одразу. Головна суперсила Go для embedded це statically linked бінарник. Компілятор пакує все і runtime, і стандартну бібліотеку, залежності в один файл. Копіюєш один бінарник на плату і він просто працює. Без apt install, без .so бібліотек, без Node_modules. Окрім того, в перше познайомився з go ще років 7 тому коли працював з блокчейном і дізнався про існування InterPlanetary File System , враховуючи те, що останній рік писав усілякі цікаві штуки на go, то буде весело і може щось підійде з раніше написаного.

Але повернемось до Go, отже найголовніше для мене в цьому проекті це вбудований крос-компілятор. Одна змінна оточення і Go сам збере бінарник для будь-якої архітектури: GOOS=linux GOARCH=arm GOARM=7 go build -o server main.go

2. Docker — ізольоване середовище для крос-компіляції

Можна встановити Go прямо на комп’ютер, років 7 тому так би і зробив, але ж є Docker який дає чисте ізольоване середовище, легко відтворити на іншому комп’ютері, легко оновити версію Go.

Dockerfile — мінімальний і безпечний:

FROM golang:1.23-alpine

 

RUN apk add –no-cache \

git curl vim bash \

openssh-client file

 

WORKDIR /go-dev

CMD [“tail”, “-f”, “/dev/null”]

Alpine замість Debian Bookworm — менше пакетів, менше вразливостей (Завдяки мінімалістичності Alpine має значно меншу «поверхню атаки». Він містить менше встановлених пакетів, що автоматично зменшує кількість знайдених вразливостей (CVE) порівняно з повним Debian). Вихідний Dockerfile Bookworm з одного з старих проектів мав 3 критичних і 18 високих CVE. Alpine образ суттєво чистіший.

Збираємо і запускаємо:

docker build -f Dockerfile.go-luckfox -t go-arm-dev .

docker run -it –rm -v $(pwd):/go-dev go-arm-dev bash

3. Hello World на платі — перевірка крос-компіляції

Класичний перший крок — переконатись що бінарник взагалі запускається на цільовій платформі. Всередині контейнера:

mkdir -p /go-dev/luckfox && cd /go-dev/luckfox

# пишемо main.go…

GOOS=linux GOARCH=arm GOARM=7 go build -o hello-luckfox main.go

Перевіряємо що отримали командою file і ось де Alpine образ дає перевагу над попереднім (у Debian Bookworm file не було):

file hello-luckfox

ELF 32-bit LSB executable, ARM, EABI5, statically linked ✅

Три ключових слова: ARM це наша архітектура, 32-bit наш armv7, statically linked — без залежностей.

Копіюємо на плату і запускаємо:

scp -o PubkeyAuthentication=no ./hello-luckfox root@192.168.1.102:/root/

# на платі:

chmod +x /root/hello-luckfox && /root/hello-luckfox

Hello from Go on Luckfox! ✅

Отже, як результат бінарник скомпільований на x86_64 Linux запускається на ARM Linux. Крос-компіляція працює.

4. HTTP сервер з JSON API

Ну що ж, Hello World це класика з сивих часів, та реальна мета це сервер який читає системні метрики і віддає JSON. Наш дашборд лише робить fetch() і показує живі дані, на не зовсім, зараз там заглушки хардкоду.

Три endpoint:

  • GET / – список доступних endpoint
  • GET /api/stats – системні метрики у JSON
  • GET /metrics – сирі дані з /proc

Поки просто зчитую /proc напряму – без зовнішніх бібліотек:

func parseMeminfo() (total, free, avail int) {

for _, line := range strings.Split(readFile(“/proc/meminfo”), “\n”) {

fields := strings.Fields(line)

val, _ := strconv.Atoi(fields[1])

switch fields[0] {

case “MemAvailable:”: avail = val

// …

}

Запускаю у фоні щоб консоль залишалась вільною:

/root/http-server &

Listening on :8080

# & запускає процес у фоні консоль залишається

# викликати назад в консоль можна командою fg

# або вбити процес kill $(pgrep http-server)

Відкриваю в браузері http://192.168.1.102:8080/api/stats:

{

“hostname”: “luckfox”,

“arch”: “arm”,

“uptime_seconds”: 4500.48,

“load_avg”: 6.17,

“mem_total_kb”: 55852,

“mem_free_kb”: 14112,

“mem_available_kb”: 37504

}

 

5. Що цікавого знайшов всередині системи від Luckfox

Поки розбирався з сервером, то помітив кілька цікавих речей в системі.

loadavg завжди ~6.x при порожній системі

Перший погляд на top де loadavg 6.37 при одному ядрі, явно виглядає як катастрофа. Але %CPU у всіх процесів 0%. Моє дослідження через dmesg показало:

RKNPU: rknpu iommu device-tree entry not found!, non-iommu mode

RKNPU: no regulator (rknpu) found: -19

NPU драйвер ініціалізувався з помилками і тримає kernel threads у стані очікування. Плюс RISC-V MCU ядро (процес [vmcu]) постійно щось чекає від hardware. Система реально вільна — просто ядро рахує hardware wait як навантаження, можливо це собливість цієї прошивки.

На платі з коробки крутиться Samba

В списку процесів є smbd і nmbd. Luckfox з коробки шарить файли по мережі як Windows сервер. Навіщо це на embedded камері поки загадка.

killall smbd nmbd # звільняє ~3MB RAM

Після killall — MemAvailable виріс з 33.9MB до 37.3MB. Невелика але відчутна різниця на системі з 54MB RAM.

http-server з’їв 523MB VSZ

top показав для нашого сервера VSZ 523MB при RAM всього 54MB. Це не баг бо Go runtime резервує великий віртуальний адресний простір наперед. Реальна резидентна пам’ять (RSS) нормальна. Але виглядає страшно, якщо не знати цю особливість Go.

6. Що далі

API готовий — тепер підключаємо дашборд до реальних даних:

  • Замінити хардкод в дашборді на fetch(‘/api/stats’)
  • Autostart сервера через init.d щоб піднімався автоматично
  • Камера MIS5001 5MP вже їде підключення і перший живий стрім

📦 Камера їде. Наступна частина — MJPEG стрім з embedded Linux.

Підбиття підсумків:

  • Go, можливо найзручніша мова для embedded HTTP сервісів: один статичний бінарник, вбудована крос-компіляція
  • Alpine Docker образ замість Debian з 3C/18H CVE до мінімуму вразливостей
  • file бінарник, завжди перевіряй архітектуру перед копіюванням на плату
  • loadavg ~6.x на Luckfox це норма, не баг: NPU/MCU kernel threads тримають чергу
  • Samba з коробки, просто killall smbd nmbd звільняє 3MB RAM
  • VSZ 523MB у Go процесі не страшно, бо це віртуальний адресний простір, а не реальна RAM

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *