2.4 Az ARM család áttekintése: profilok, verziók és a könyv két része

2.4.1 Miért nem elég annyit mondani, hogy „ARM-on fut”?

Egy C-ben megírt adatfeldolgozó függvényt Linux alatt szeretnél gyorsítani. Egy másik feladatban egy szenzor jelére kell kiszámítható időn belül reagálnod, operációs rendszer nélkül. Mindkettőhöz választhatsz ARM-alapú processzort, mégsem ugyanazt az assembly kódot, fordítóprogram-célt vagy indítási eljárást fogod használni.

Az „ARM” egy architektúracsalád megnevezése, nem egyetlen gépi nyelvé. A megfelelő cél kiválasztásához külön kell azonosítani:

Az előző alfejezet ISA és mikroarchitektúra közötti különbségére építve most ezeket helyezzük el. Az utasítások részletes működése helyett a cél annak felismerése, hogy melyik program milyen gépen és milyen környezetben értelmezhető.

2.4.2 Architektúraprofilok: A, R és M

Az architektúraprofil (architecture profile) az ARM architektúracsaládon belül egy alkalmazási területhez igazított követelmény- és szolgáltatáskészlet. Nem pusztán teljesítménykategória: meghatározza például a memóriakezelés és a kivételkezelés alapmodelljét.

Profil Jellemző feladat Jellemző memóriakezelés Jellemző szoftverkörnyezet
A-profile, alkalmazási profil Általános alkalmazások, szerverek, összetett felhasználói rendszerek Virtuális memória, MMU Linux és más összetett operációs rendszerek
R-profile, valós idejű profil Határidős vezérlés, tárolóvezérlők, ipari és biztonságkritikus rendszerek Architektúraverziótól függő; gyakran MPU, egyes újabb változatokban MMU is Valós idejű operációs rendszer vagy célzott firmware
M-profile, mikrokontroller-profil Beágyazott vezérlés, szenzorok, kis erőforrású perifériakezelés A könyv Cortex-M3 célján nincs MMU; MPU alkalmazható, ha megvalósított Operációs rendszer nélküli firmware vagy RTOS

A memóriakezelő egység (Memory Management Unit, MMU) címfordítást és hozzáférés-szabályozást tesz lehetővé. Linux alatt a folyamatok jellemzően saját virtuális címtérben működnek: a C-mutató értéke nem közvetlenül a fizikai memória címét jelenti.

A memóriavédelmi egység (Memory Protection Unit, MPU) ezzel szemben memóriatartományok hozzáférési tulajdonságait szabályozza, de nem biztosít MMU-szerű virtuális–fizikai címfordítást. Az MPU meglétét mindig a konkrét processzor megvalósításán kell ellenőrizni.

Az R-profile nevében a real-time nem azt jelenti, hogy „nagyon gyors”. A valós idejű rendszerben a helyes eredménynek meghatározott határidőn belül kell rendelkezésre állnia. Ehhez nem elegendő a megfelelő processzorprofil: a memória, az ütemezés, a megszakítások és a szoftver legrosszabb eseti viselkedése is számít.

A táblázat tehát tipikus felhasználást, nem kizárólagosságot mutat. A-profile rendszeren is lehet valós idejű feladatot végezni, és M-profile processzoron is futhat operációs rendszer. A könyv az R-profile részletes programozását nem tárgyalja.

2.4.3 Verzió, végrehajtási állapot és processzormag

Az elnevezések különböző kérdésekre válaszolnak:

Megnevezés Mit azonosít? Könyvbeli szerep
Armv8-A Architektúraverzió és A-profile Az I. rész architekturális alapprofilja
AArch64 64 bites végrehajtási állapot (execution state) Az I. rész programjainak végrehajtási állapota
A64 Az AArch64 állapot utasításkészlete Az I. rész assembly nyelve
Armv7-M Architektúraverzió és M-profile A II. rész architekturális alapja
Cortex-M3 Konkrét processzormag-típus A II. rész referenciaprocesszora
Thumb-2 16 és 32 bites Thumb-utasításkódolásokat tartalmazó technológia A II. rész Armv7-M utasításainak kódolási környezete

A Cortex-M3 tehát nem „az ARM harmadik verziója”: a név egy processzormag-típust jelöl, amely az Armv7-M architektúrát valósítja meg. Az Armv7-M és az Armv8-A verziószámából önmagában nem következik teljesítményarány; eltérő profilokról beszélünk.

A64 ISA, AArch64 környezetben az általános célú regiszterek 64 bites nézete Xn, 32 bites nézete Wn. Ettől függetlenül minden A64 utasítás kódolása 32 bites. A regiszterszélesség és az utasításhossz tehát külön fogalom.

Thumb-2/Armv7-M környezetben a Cortex-M3 általános célú regiszterei 32 bitesek, jelölésük Rn. Az utasításkódolások lehetnek 16 vagy 32 bitesek. Egy 32 bites Thumb-utasítás ettől nem válik A64 utasítássá, és egy 16 bites kódolás sem jelent szükségképpen 16 bites adatfeldolgozást.

A Cortex-M3 nem hajt végre A64 vagy A32 utasításokat. Az A-profile családban előforduló AArch32 állapot és annak A32/Thumb utasításai nem képezik a könyv I. részének célját.

Fontos további korlát: az AArch64 nem minden környezetben az A-profile szinonimája; az R-profile családnak is vannak AArch64 változatai. Ebben a könyvben az AArch64 cél mindig a kijelölt A-profile/Linux környezetet jelenti.

2.4.4 Miért Linux-userspace az I. rész?

A felhasználói tér (userspace) az operációs rendszer által korlátozott alkalmazásfuttatási környezet. Az I. részben a program betöltését, a folyamat kezdeti környezetét és a rendszererőforrások kezelését Linux biztosítja.

Ez pedagógiai előny: először a regiszterekkel, aritmetikával, memóriacímzéssel és függvényhívásokkal foglalkozhatunk. Nem kell rögtön a rendszerindítást vagy a soros port vezérlését is megírni. A kimenethez C-könyvtári függvényt vagy később rendszerhívást használhatunk.

Ez azonban nem közvetlen hardverhozzáférés. Egy Linux-alkalmazás nem írhat tetszőlegesen perifériaregisztereket, és nem módosíthatja szabadon a processzor privilegizált állapotát.

A labor elsődleges futtatója a qemu-aarch64, felhasználói módú emulációban (user-mode emulation). Ez AArch64 Linux-programokat futtat, és közvetíti a rendszerhívásaikat a gazdarendszer felé. Nem indít külön AArch64 Linux-kernelt, és nem emulál egy teljes Raspberry Pi alaplapot.

Az Ubuntu x86_64 hoston futó keresztfordító (cross-compiler) ettől még AArch64 gépi kódot állít elő. A fordító futtatási gépe és a létrehozott program célgépe két külön dolog.

2.4.5 Miért bare-metal a II. rész?

A II. részben a közvetlen hardveren futó, operációs rendszer nélküli környezetet (bare-metal) tanulmányozzuk. Itt nincs Linux, amely előkészítené a C-program futását vagy kiszolgálná a rendszerhívásait.

A firmware feladata lesz többek között:

Ezeket nem feltételezzük ismertnek: a minimal runtime fejezete építi fel őket.

A cél a Cortex-M3 referenciaprofil, QEMU-ban az mps2-an385 gépmodell. A futtató eszköz itt qemu-system-arm, rendszerszintű emulációban (system emulation): a processzor mellett a kiválasztott gép memóriáját és modellezett perifériáit is biztosítja.

Az M-profile önmagában nem követeli meg a bare-metal működést; ez a könyv tudatos választása. Egy valós idejű operációs rendszer használata előtt így megérthető az a mechanizmus, amelyre az később épül.

2.4.6 Közös alapok, eltérő szerződések

Mindkét részben tovább használjuk a bináris reprezentációt, a regiszterek fogalmát, a memóriahozzáféréseket, az elágazásokat és a gépi kód vizsgálatát. Nem vihetők át változtatás nélkül a regiszternevek, az utasításkódolások, a hívási konvenciók vagy a programindítás szabályai.

Az I. rész a skaláris műveletektől a C–assembly együttműködésen át a NEON/SIMD feldolgozásig jut. A záróprojekt I egy C-programba illeszthető, teljesítménykritikus AArch64-modul: helyességét tesztekkel, hasznát mérési tervvel, működését audittal kell igazolni.

A II. részben az indulástól a megszakításokon át a perifériavezérlésig haladunk. A záróprojekt II megszakításvezérelt firmware-modul lesz, reprodukálható QEMU-teszttel és ellenőrizhető állapotkezeléssel.

A Cortex-M3 célon nincs NEON. A közösen továbbvihető tudás ezért elsősorban a gondolkodásmód és az ellenőrzési módszer, nem a konkrét utasítássor.

2.4.7 Kidolgozott példa: egy célplatform azonosítása

Munkakönyvtár: ~/arm-labor/a64/a004/ — az alfejezet minden laborfájlja ide kerül, és minden parancs innen futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).

mkdir -p ~/arm-labor/a64/a004
cd ~/arm-labor/a64/a004

Szerepe a fejezetben: ez a példa célzott kiegészítés: egyetlen jelenséget mutat meg külön builddel, ezért első olvasáskor kihagyható. A fejezet végigvitt projektjének ide tartozó lépése — amely nem hagyható ki — a szakasz végén, a „Végigvitt projekt” részben található.

Feladat: készítsünk olyan Linux-programot, amely C-ből meghív egy A64 assembly függvényt, majd ellenőrizze a célját és eredményét.

Profil: I. rész, A64 ISA, Armv8-A, AArch64 Linux, little-endian, LP64 adatmodell. Utóbbiban a mutató 64 bites, az unsigned int 32 bites; ezt nem általában az „ARM” névből következtetjük.

Az eszközök telepítését a labor-alapkörnyezet részletezi. Itt már elérhetőnek tekintjük az AArch64 keresztfordítót, a célrendszer C-könyvtári fejlesztői állományait és a QEMU user-mode futtatót.

A lab-profile.c fájl:

/* I. rész: AArch64 Linux, LP64, little-endian laborprofil. */
#include <stdio.h>

#if !defined(__aarch64__) || !defined(__linux__)
#error "Ehhez a laborhoz AArch64 Linux celu fordito kell."
#endif

_Static_assert(sizeof(void *) == 8, "64 bites mutato szukseges");
_Static_assert(sizeof(unsigned int) == 4, "32 bites unsigned szukseges");

extern unsigned int lab_add(unsigned int a, unsigned int b);

int main(void)
{
    unsigned int result = lab_add(19u, 23u);

    printf("mutato=%zu; unsigned=%zu; osszeg=%u\n",
           sizeof(void *), sizeof(unsigned int), result);

    return result == 42u ? 0 : 1;
}

A lab-profile.S fájl:

// I. rész: A64 ISA, AArch64 Linux; GNU assembler-direktívák.
.text
.p2align 2
.global lab_add
.type lab_add, %function

lab_add:
    add w0, w0, w1
    ret

.size lab_add, .-lab_add
.section .note.GNU-stack,"",%progbits

Az A64 ADD itt regiszteroperandusokat használ, nincs kódolandó immediate konstansa. Az A64 W0 és W1 tartalmazza a két 32 bites, előjel nélküli argumentumot; az eredmény az A64 W0 regiszterben tér vissza. Ez az AArch64 hívási konvenció egyik egyszerű esete, amelyet később az AAPCS64 regiszterszerződése részletez. Az A64 RET a hívóhoz tér vissza.

A ponttal kezdődő sorok GNU assemblerdirektívák, nem processzorutasítások. Az itt bemutatott jelentésük az AArch64 ELF-célhoz tartozik; például a .p2align 2 22=42^2=4 bájtos igazítást kér.

Build és futtatás — target: qemu-user, AArch64 Linux:

aarch64-linux-gnu-gcc -std=c11 -O0 -Wall -Wextra \
    -march=armv8-a -mlittle-endian \
    -c lab-profile.c -o lab-profile-c.o

aarch64-linux-gnu-gcc -march=armv8-a -mlittle-endian \
    -c lab-profile.S -o lab-profile-asm.o

aarch64-linux-gnu-gcc -march=armv8-a -mlittle-endian -static \
    lab-profile-c.o lab-profile-asm.o -o lab-profile-a64

qemu-aarch64 ./lab-profile-a64
printf 'kilepes=%d\n' "$?"

Az első lépés a C-forrást fordítja, a második az assembly forrást szereli össze. A harmadik linkeli a programot a szükséges C-futtatókörnyezettel. A statikus linkelés ennél a példánál elkerüli a külön AArch64 dinamikus betöltő megadását; a program ettől továbbra is Linux-szolgáltatásokat igényel.

Várt, nem ténylegesen mért eredmény:

Ellenőrzés Várt érték
A program kiírása mutato=8; unsigned=4; osszeg=42
A shell kiírása kilepes=0
Visszatérési érték az A64 RET előtt A64 W0 = 42, előjel nélküli értelmezésben

A bináris célját külön is ellenőrizzük.

Binárisvizsgálat — target: ugyanaz az AArch64 Linux-program:

aarch64-linux-gnu-readelf -h lab-profile-a64
aarch64-linux-gnu-objdump -d --disassemble=lab_add lab-profile-a64

Az ELF-fejlécben ELF64, little-endian adatábrázolás és AArch64 géptípus várható. A függvény visszafejtett kódjában az A64 ADD és RET utasításnak kell megjelennie.

Ez az ellenőrzés az ISA-t és a bináris formátumot azonosítja, nem egy konkrét processzormagot. A sikeres futásból sem a fizikai gép típusára, sem hardveres ciklusszámra nem következtethetünk.

2.4.7.1 Végigvitt projekt: a lab-hidden célplatformja

Ugyanezt az azonosítást a fejezet végigvitt projektjén is elvégezzük:

# Hostoldali vizsgálat; a fájl célja AArch64 Linux, qemu-user.
cp ~/arm-labor/a64/a001/lab-hidden .
aarch64-linux-gnu-readelf -h lab-hidden | grep -E 'Class|Data|Type|Machine'

Levezetett, várt eredmény:

ELF-mező Várt érték Mit azonosít
Class ELF64 64 bites fájlformátum
Data 2's complement, little endian little-endian adatábrázolás
Type EXEC statikusan linkelt, nem PIE futtatható fájl
Machine AArch64 A-profile architektúra AArch64 végrehajtási állapota, A64 ISA

A lab-hidden tehát az I. rész célplatformjára készült: A-profile, AArch64, Linux-userspace. Egy Cortex-M firmware a II. részben ugyanitt ELF32 osztályt és ARM géptípust mutat majd. Ahogy a fenti példánál, a fejléc itt sem árulja el a konkrét processzormagot.

2.4.8 Tipikus hibák és ellenpróbák

Tünet Hipotézis Adatgyűjtés és egyetlen célzott változtatás
A C-fordításnál a beépített ellenőrzés hibát jelez A host gcc fordítóját használtuk Ellenőrizzük a parancsot; csak a fordítót cseréljük aarch64-linux-gnu-gcc-re
Statikus linkeléskor hiányzó könyvtár jelenik meg Hiányos a célrendszer C-fejlesztői környezete Vizsgáljuk meg a linkerüzenetet; a hiányzó célcsomagot rendezzük
A program fut, de hibás összeget ad Az assembly operandusai hibásak objdump segítségével nézzük meg a tényleges utasítást; csak a hibás operandust javítsuk
A programot Cortex-M firmware-ként próbáljuk használni Összekevertük az ISA-t és a futási környezetet Az ELF-fejléc alapján azonosítsuk a célt; ne átnevezéssel próbáljuk átalakítani

Az utolsó hiba nem javítható azzal, hogy másik QEMU-programot választunk. A Cortex-M3 célhoz más gépi kód, más indulási környezet és firmware-hez tartozó memóriakiosztás kell. Az ELF konténerformátum mindkét világban használható, de ettől a benne levő programok nem kompatibilisek.

2.4.9 Feladatok

  1. Magyarázó feladat (alapszint — cél: megértés-ellenőrzés). Egy követelmény szerint a készülék összetett Linux-alkalmazásokat futtat; egy másik szerint szűk határidős vezérlési feladatot lát el; egy harmadik szerint kis firmware-rel szenzort kezel. Mely profilok tipikus kiindulópontok? Miért nem bizonyítja önmagában az R-profile választása a határidő betartását?

  2. Implementációs feladat (alapszint — cél: rutinépítés). A laborban add össze a 0xffffffffu és 1u előjel nélküli értékeket. Módosítsd az elvárt eredményt is. Mi legyen a kiírás és a kilépési állapot? Használd ugyanazt a build–futtatás lépéssort.

  3. Hibakeresési feladat (középszint — cél: rutinépítés). Az eredeti 19u, 23u bemenetek mellett módosítsd az A64 utasítást add w0, w0, w0 alakra. Fordítsd újra a programot. A kimenetből állíts fel hipotézist, majd objdump segítségével ellenőrizd. Egyetlen operandus módosításával javítsd a hibát.

2.4.10 Megoldások

1. megoldás — Segítség. Gondold át, melyik profil melyik tulajdonságot (gazdag OS-felszín, garantált válaszidő, minimális erőforrás) teszi lehetővé — és hogy a “garantált” szó miért nem a profilból, hanem a teljes rendszer tervezéséből következik.

1. megoldás — Teljes megoldás. Linux-alkalmazásokhoz az A-profile, határidős vezérléshez gyakran az R-profile, kis szenzoros firmware-hez az M-profile kézenfekvő. Ezek kiindulópontok, nem automatikus döntések. A határidőt a teljes rendszer legrosszabb eseti végrehajtási és várakozási ideje határozza meg; a profil nem ad önmagában végponttól végpontig tartó időgaranciát.

2. megoldás — Segítség. Számítsd ki 0xffffffffu + 1u-t 32 biten, moduló 2322^{32} — mi lesz az eredmény bitmintája, és ez milyen C unsigned int értéknek felel meg?

2. megoldás — Teljes megoldás. A két módosított C-sor:

/* I. rész: ugyanaz az AArch64 Linux laborprofil. */
unsigned int result = lab_add(0xffffffffu, 1u);
/* A printf változatlanul marad. */
return result == 0u ? 0 : 1;

A levezetés: a 32 bites előjel nélküli legnagyobb érték 232−12^{32}-1. Egyet hozzáadva 2322^{32} keletkezik, amelynek alsó 32 bitje nulla. Az A64 W0 eredménye ezért 0, összhangban a 32 bites C unsigned int moduláris aritmetikájával. A várt kiírás vége osszeg=0, a kilépési állapot 0.

3. megoldás — Segítség. Számítsd ki előre, mit adna 19+19 az elvárt 19+23 helyett — ha a kimenet ezt a hibás összeget mutatja, az operandus-cserére gyanakodj, és ezt objdump-pal igazold.

3. megoldás — Teljes megoldás. A várt hibás eredmény 38, a kilépési állapot 1. Az A64 utasítás kétszer az első argumentumot adja össze: 19 + 19. Az objdump kimenetében a harmadik operandus A64 W0, nem A64 W1. Ennek javítása és újrafordítás után ismét 42, illetve 0 várható. Ez funkcionális ellenőrzés, nem teljesítménymérés.

2.4.11 Rövid összefoglaló

Az ARM családon belül a profil, a verzió, a végrehajtási állapot és a konkrét processzormag külön fogalom. Az A-profile tipikusan összetett operációs rendszereket, az R-profile határidős rendszereket, az M-profile mikrokontrolleres feladatokat szolgál. A könyv először AArch64 Linux-alkalmazásokkal tanítja a gépi programozást, majd Cortex-M3 firmware-rel teszi láthatóvá az indulást, a megszakításokat és a perifériákat. A közös alapelvek átvihetők, a gépi kód és a futtatási szerződés nem. A laborban az ELF-fejléc a célt, a visszafejtett utasítások és a kimenet a működést ellenőrzik; egyik sem igazol hardveres teljesítményt.

2.4.12 Források és ellenőrzési hiányok

A linkek és a dokumentumverziók közös állapota az Olvasói útmutató forrásállapot-táblájában van; itt csak az alfejezet saját megállapításai szerepelnek.

Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is fennmaradnak: a példát itt nem fordítottuk és nem futtattuk. A telepített Ubuntu 24.04-csomagverziókat, a statikus célkönyvtárak elérhetőségét és a II. részhez szükséges mps2-an385 gépmodell helyi támogatását a laborban kell ellenőrizni.