Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
STM32H755 ecmx — HTTP/S server po nginx-ovim pravilima, na mikrokontroleru
#1
ecmx — HTTP/S server po nginx-ovim pravilima, na mikrokontroleru

Svaki embedded web server koji sam do sada koristio ima isti problem: rute su C funkcije zakucane u firmver. Hoćeš da dodaš redirect, promeniš timeout, zaštitiš /admin klijentskim sertifikatom? Novi build, novo flešovanje, i moli se da usput nisi slomio nešto drugo. A taj problem je na serverskoj strani rešen pre dvadeset godina — rešenje se zove nginx.conf.

ecmx je HTTP/S server za mikrokontrolere koji čita nginx-ovu konfiguraciju: isti format fajla, isti location blokovi (prefiks, =, ^~, ~, ~*), iste direktive — podskup, ne sve, o tome pošteno niže. I što je važnije od sintakse: pušta nginx-ove sopstvene testove, pa se njegovo ponašanje ne dokazuje mojom rečju nego test suite-om koji svako može da pokrene.

Uz njega, na drugom jezgru istog čipa, radi ecm-forth — real-time Forth interpreter u kome živi aplikaciona logika. Rezultat: ponašanje uređaja se menja izmenom tekst fajla i reload-om, ne flešovanjem.

Meta: NUCLEO-H755ZI-Q — Cortex-M7 na 480 MHz (server) plus Cortex-M4 na 240 MHz (Forth, sa tvrdim rokom od 1 ms).


Zašto ne neki od običnih embedded servera?

Legitimno pitanje — za prosto "serviraj mi index.html sa senzorskim očitavanjima" ecmx je overkill. Razlika se vidi kad uređaj treba da živi godinama kod klijenta:
  • Konfiguracija umesto rekompajliranja. Rute, TLS, keepalive, pristup — tekst fajl, ne #ifdef šuma. Ko je ikad pisao nginx.conf, već zna da podesi ovaj server.
  • Ponašanje koje je neko već dokumentovao. Ne izmišljam semantiku pa je opisujem u README-u — preuzimam nginx-ovu, koja je opisana, poznata adminima i pokrivena tuđim testovima. Kad se ecmx i nginx ne slažu, to je po definiciji bug u ecmx-u.
  • Deterministička memorija. Nula malloc-a — nigde, uključujući TLS. Nema fragmentacije hipa, nema "radio je tri nedelje pa stao". Sve što će ikad biti zauzeto zna se u trenutku linkovanja.
  • Logika bez flešovanja. Forth skripta se kompajlira jednom pri boot-u, pa se VM zapečati. Izmena logike = nova skripta + reload. Firmver se ne dira.

Ne znam ni za jedan drugi server u ovoj klasi koji se meri nginx-ovim testovima — ako neko zna, neka javi, ozbiljno me zanima.


Kako je građen

Jezgro servera je sans-I/O: transport mu doda bajtove, ono vrati nameru (WANT_READ, WANT_WRITE, CLOSE, IDLE). Zato isto jezgro radi i preko POSIX soketa i na golom metalu bez sloja za kompatibilnost — i zato može da se testira punom brzinom na radnoj stanici.

Dve tvrde odluke, za ceo sistem: nula malloc-a (uključujući TLS i Forth VM) i nula statičkog RAM-a — 0 data i 0 bss u svih 19 modula servera.


Memorija

Svaki broj je marginalna cena iz dva prava linkovanja — sagradi sa modulom, sagradi bez njega, oduzmi. Zbir veličina objektnih fajlova se ne koristi, jer se tako deljene pomoćne funkcije i niske naplate dvaput.

Code:
komponenta                    fleš (B)  RAM fiksni  RAM po vezi -------------------------------------------------------------------- jezgro (petlja, parser,        16 196          664        2 651         odgovor, baferi, tajmeri) TLS (mbedTLS, bez hipa)        101 744      20 792        23 552 regex mašina                    6 052            0            0 telnet konzola                  5 384            0            0 promenljive                      3 684            0            0 ciklus konfiguracije            2 620            0            0 datum / vreme                    2 336            0            0 merni brojači                    1 892          480            0 parser konfiguracije            1 724            0            0 statički fajlovi                1 116            0            0 auth kuka                          316            0            0 -------------------------------------------------------------------- sve osim TLS-a                  41 320        1 144        2 651

Zauzeće čipa: fleš 317 376 od 1 048 576 B (30,3 %), RAM 482 016 od 524 288 B (91,9 % — server, stekovi, mrežni baferi, 16 TLS veza).

TLS pojede sve. Na 23,5 KB po vezi, 16 istovremenih TLS sesija traži oko 400 KB od 512 KB koliko ovaj čip ima. Pravi budžet nije broj funkcija nego broj veza — podizanje sa 16 na 64 košta 127 KB, više nego svi opcioni moduli zajedno.


Brzina

Četiri izmene, svaka merena posebno na hardveru. Merenja na radnoj stanici su svaki put pogrešno predvidela veličinu dobitka, pa ih ovde i ne navodim.

Code:
                                      zaht/s    promena ------------------------------------------------------------ 1. polazno                              3 567 2. -O2                                  4 155      +16,5 % 3. izbačen snprintf                    4 305      +3,6 % 4. međujezgarni prekid (HSEM)          4 149      -3,6 %

Korak 2: radna stanica je predvidela 1,58×, hardver dao 1,17×. Korak 3: snprintf zamenjen formaterom celih brojeva od 709 bajtova, proverenim protiv samog snprintf-a na 3,5 miliona slučajeva.

Korak 4 je zanimljiv. Na ruti koja stvarno prelazi na drugo jezgro, propusnost je otišla sa 631 na 3 853 zaht/s — 6,1×, a p99 kašnjenje sa 32 ms na 8,2 ms. Na svim ostalim rutama je oduzeo 3,6 %. Da sam naveo samo ono 6,1× ispalo bi lepše i bilo bi manje korisno.

Usko grlo nikad nije bilo sanduče između jezgara nego milisekundna granulacija tajmera: drugo jezgro odgovori za 51 µs, dvadeset puta brže nego što petlja ume da probudi vezu. Prekid je omogućio da se red tajmera potpuno preskoči. Sama granulacija je i dalje tu.


ecm-forth — logika na drugom jezgru, sa rokom

Server servira; logiku uređaja piše korisnik — u Forth-u. ecm-forth je threaded-code VM po Chuck Moore modelu: reči se jednom kompajliraju u tokene, runtime vidi samo brojeve, imena posle boot-a više ne postoje. Ćelija je mašinska reč (32-bit na STM32, 64-bit na serveru), dispatch je computed-goto, ceo VM je ~10 KB .text na Cortex-M7 (-Os).

Tri odluke koje ga čine upotrebljivim u produkciji, ne samo na demo tabli:
  • Budžet koraka — nijedna reč ne može da vrti večno. "begin 0 until" vrati grešku "limit izvršavanja"; čip ne trepne, ništa se ne obesi.
  • Pečat — posle boot-a VM odbija SVAKO novo definisanje. Izvršava se samo ono što je već kompajlirano. Nema "eval user inputa" rupe, po dizajnu.
  • Nula alokacija — VM se digne nad statičkim baferom, ista priča kao server.

Na H755 Forth radi na Cortex-M4 sa tvrdim rokom od 1 ms po otkucaju. Sa ploče, u trenutku pisanja ovog posta:

Code:
najgori slučaj ikada          21 µs  (od 1 000 µs budžeta) propušteni rokovi od reseta      0 greške Forth koda                0

Onaj međujezgarni prekid iz koraka 4 gore dodao je tačno 2 µs na najgori slučaj — zato je i smeo da uđe.

I VM je testiran kako treba: 303 provere × 5 konfiguracija (host 64-bit computed-goto, host 64-bit switch, host 32-bit, MCU profil, ASan+UBSan) plus prevod za M0/M4/M7 — na svakoj gradnji. U kompajler su ugrađene tri tehnike iz PicForth-a (tail-call na ";", constant folding, fuzija poređenja i grane), svaka izmerena istim bench harnessom: konstantna petlja 2,7×, flag petlja 1,7×, a repna rekurzija od 100k nivoa pretvorena iz "ruši stek" u "radi".


Naspram nginx-a — 25 imena direktiva prema 459

Referenca je nginx 1.24.0 sa podrazumevanim modulima plus http_ssl: 459 jedinstvenih direktiva u 53 modula. Pošteno sortirano, tri korpe:

Code:
radi          statički fajlovi  uslovni zahtevi, opsezi bajtova, ETag,                                   gzip_static; 77 od 78 diferencijalnih                                   provera se poklapa sa pravim nginx-om               location          prefiks, =, ^~, ~, ~*               TLS                mbedTLS bez alokacije, klijentski certi,                                   ECDSA P-256/P-384 delimično      keepalive          4 direktive; granulacija tajminga 1 ms nema          error_page        svaki 404/403/301 ide sa Content-Length: 0                                   (nginx šalje 153-169 B HTML-a)               rewrite/if/set    19 od 34 testa pada; regex mašina POSTOJI,                                   modul ne               allow/deny        mali moduli, ali vidi upozorenje dole               auth_basic neće ni biti  gzip u radu        zlib deflate traži ~262 KB po vezi na                                   nginx-ovim podrazumevanim vrednostima;                                   gzip_static pokriva isti posao               proxy/FastCGI      221 direktiva; traže klijentsku stranu,               upstream          upstream skup, keš na disku i deljenu                                   memoriju između procesa — ovde je jedan                                   proces i jedna nit               HTTP/2, HTTP/3    van opsega za ovu klasu uređaja               stream, mail

Najoštrija ivica, otvoreno: nepoznata direktiva se trenutno ćutke ignoriše. nginx odbija da se digne na njoj. To znači da konfiguracija koja sadrži deny all ili auth_basic ovde uredno prođe i ne sprovodi ništa — bezbednosna zamka, ne neudobnost. To je sledeća stvar koja se popravlja.


Kako se proverava

Tvrdnje o ugrađenom serveru su jeftine. Ovih pet provera je teško lažirati i puštaju se na svakoj gradnji.

1. Nula alokacija, dokazano linkerom. Test binarni fajl se linkuje sa -Wl,--wrap=malloc,--wrap=calloc,--wrap=realloc. Svaka alokacija bilo gde — uključujući unutar mbedTLS-a — uveća brojač. Pun mTLS handshake i raskid prijavljuju nulu. Ovo nije tvrdnja iz pregleda koda; build pada ako ikad prestane da važi.

2. nginx-ovi sopstveni testovi, sa imeniocem koji se ne pumpa. 279 od 313. Dve ograde, obe čine broj gorim: 476 od 495 test fajlova se preskoči jer traže proxy, stream ili HTTP/2 — i konfiguracija koju server prijavljuje namerno ne tvrdi --without-http_rewrite_module, iako rewrite-a nema. Da tvrdi, rezultat bi bio 279/294, tako što bi padove sakrio umesto popravio.

3. Diferencijalno protiv pravog nginx-a. Isti zahtevi se puste na ecmx i na pravi nginx 1.24.0 nad istim fajlom, pa se odgovori porede polje po polje. Odvojeno, regex mašina se poredi sa PCRE2 10.42 na 367 584 parova obrazac/ulaz, uključujući nasumično generisanje obrazaca. Oba trenutno prijavljuju nula neslaganja.

4. Greška koju je ovo našlo prošle nedelje. POSIX klase znakova — [[:digit:]], [[:alnum:]] — kompajlirale su se bez greške i čitale kao običan skup znakova:

Code:
[[:digit:]]+  nad "123"  ->  ne hvata      <- trebalo bi da hvata [[:digit:]]+  nad ":]"  ->  HVATA        <- ne bi smelo [0-9]+        nad "123"  ->  HVATA        (kontrola, ispravno)

Znači location ~ ^/admin/[[:alnum:]]+$ ne bi hvatao ništa. Preživelo je 354 415 nasumičnih poređenja zato što generator obrazaca nikad ne emituje [[:. Uhvatio ga je harness sa fiksnim obrascima. To je argument za to da se puštaju oba, i razlog zašto se "nula neslaganja" uvek mora čitati zajedno sa "nad kojim ulazima". Sad je popravljeno: svih 14 POSIX klasa, negacija, i PCRE-ova tačna pravila za neispravne slučajeve.

5. Ploča opisuje samu sebe. Sve gore je i "datasheet" stranica koju servira sam H755 — sa živim brojačima (uptime, opsluženi zahtevi, najgori slučaj RT jezgra, propušteni rokovi) koje stranica vuče sa /api/sve svake tri sekunde. Server koji se opisuje je server koji ti odgovara.


Otvoreno

Code:
nepoznate direktive se ćutke ignorišu    bezbednost  konfiguracija koja izgleda                                                       zaštitno to nije istek sertifikata se ne proverava        bezbednost  istekli klijentski certi                                                       se prihvataju listen [::]:port ubija server blok      ispravnost  proces radi bez slušača granulacija tajmera je 1 ms              brzina      spori rukovaoci plaćaju                                                       >=1 ms po krugu nema rewrite / if / set                  funkcije    19 padova; mašina postoji prazna tela odgovora na greške          kozmetika    odstupa od nginx-a na                                                       svakom statusu greške


Sitno

Merenja: NUCLEO-H755ZI-Q, GCC -Os / -O2, mbedTLS bez hipa. Propusnost preko wrk -t2 -c8 -d20s na 100 Mbit Ethernetu, isti parametri u svakom prolazu. Referenca: nginx 1.24.0, PCRE2 10.42. Forth: 303 provere × 5 konfiguracija, bench harness identičan pre i posle optimizacija.

Pitanja i rupe u rezonovanju su dobrodošli — pola gornjeg spiska "otvoreno" je nastalo tako što je neko pitao "a jesi proverio...".
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)