Pentru ca sistemul să fie inițializat corect după pornirea kernel-ului, este necesar ca toate script-urile și executabilele necesare pentru a porni daemon-ul de inițializare, init, și restul proceselor user-space, să existe în anumite locații în sistemul de fișiere. Acest sistem minimal de fișiere necesar la inițializare poartă numele de root file system sau rootfs. În cazul sistemelor Unix, rootfs-ul are ca rădăcină directorul /
și înglobează o serie de directoare ce contin restul de fișiere necesare. De obicei, în directorul /
nu se află niciun fișier, doar subdirectoare. Aceste subdirectoare sunt organizate în funcție de fișierele pe care le conțin:
/bin
- conține programe și comenzi necesare la inițializare ce pot fi folosite apoi de către un utilizator neprivilegiat în timpul unei sesiuni/sbin
- conține programe asemănătoare cu cele din /bin ca utilitate, dar care pot fi utilizate în general doar de utilizatorul privilegiat root/etc
- conține fișierele de configurare specifice sistemului/lib
- conține bibliotecile folosite de programele din rootfs (cele din /bin
și /sbin
)/dev
- conține referințe la toate dispozitivele periferice; aceste referințe sunt în general fișiere speciale/boot
- conține fișierele necesare bootării și imaginea kernel-ului; este posibil ca acestea să fie păstrate pe un sistem de fișiere separat de rootfs/tmp
- conține fișiere temporare și este de obicei curățat la repornirea sistemului/opt
- conține în principal aplicațiile third-party/proc
, /usr
, /mnt
, /var
, /home
- sunt directoare care, în general, reprezintă puncte în care se pot monta alte sisteme de fișiere pentru a stoca log-uri, biblioteci și alte aplicații și programe folosite de către utilizatori.Standardul Unix recomandă ca rootfs-ul să aibă dimensiuni relativ mici, deoarece orice problemă sau corupere a acestuia poate împiedica inițializarea corectă a sistemului. Dacă, totuși, rootfs-ul devine corupt, există modalități prin care poate fi remediat. O soluție des folosită este de a monta acest sistem de fișiere într-un alt sistem, funcțional, pentru a putea fi explorat și verificat.
În cazul sistemelor embedded, de multe ori spațiul de stocare pe care îl avem la dispoziție este deja limitat: < 32MB. De cele mai multe ori, o bună parte din acest spațiu este ocupat de imaginea kernel-ului. De aceea, în majoritatea cazurilor, rootfs-ul pentru aceste sisteme este fie compus doar din minimul de programe necesar pentru inițializare, fie este stocat pe un server accesibil prin rețea și montat de către kernel la inițializare.
Imaginea recomandată pentru RaspberryPi, Raspbian, conține două partiții: o partiție FAT
folosită pentru boot și o partiție ext4
pentru rootfs. Fiecare dintre aceste partiții începe de la un anumit offset în cadrul imaginii. Atunci când dorim să creăm o imagine nouă pentru RaspberryPi, trebuie să ne asigurăm că respectăm aceste partiții și formatele lor. Pașii pe care trebuie să îi urmăm sunt:
Pentru fiecare dintre acești pași există utilitare ce ne ajută să realizăm operațiile necesare.
De multe ori, avem nevoie ca sistemele embedded sa porneasca pentru diverse servicii. Aceste servere, de obicei, expun un API de care ne putem folosi pentru a trimite comenzi si date sistemului. In acest laborator, vom invata cum sa implementam un server minimal si cum sa sa il facem sa porneasca la boot-time.
REST (REpresentational State Transfer) este un set de arhitecturi software folosit pentru a ușura comunicarea intre clientul web și server-ul web. Implementarea server-ului și a clientului pot fi decuplate una de cealaltă, iar clientul poate face cereri (requests) către server pentru a primi sau modifica structurile de date de pe server, iar server-ul va raspunde acestor cereri (responses).
REST se bazează pe cele 4 tipuri de comenzi HTTP:
Cele 4 comenzi au rol similar celor din baze de date: CREATE, READ, UPDATE și DELETE
Similar cum API-ul (Application Programming Interface) din cadrul unui program C/Java/Python asigură inter-operatibilitatea componentelor acelui program, la fel si comunicarea REST dintre client și server se bazeaza pe un API.
Dacă în C/Java/Python apelăm funcții și pasăm argumente acelor funcții, în cazul REST-ului, în momentul în care clientul face un request către server, el are posibilitatea de a trimite funcția (adresa paginii) și argumentele funcției in următoarele trei feluri:
În cadrul exercițiilor de astăzi vom folosi a treia modalitate de pasare a argumentelor.
JSON (JavaScript Object Notation) este un format de date similar cu XML-ul, folosit pentru a trimite date independent de limbajul de programare.
Un exemplu de structura JSON este acesta:
{ "firstName": "John", "lastName": "Smith", "isAlive": true, "age": 27, "address": { "streetAddress": "21 2nd Street", "city": "New York", "state": "NY", "postalCode": "10021-3100" }, "phoneNumbers": [ { "type": "home", "number": "212 555-1234" }, { "type": "office", "number": "646 555-4567" } ], "children": [], "spouse": null }
În Python avem la dispoziție mai multe framework-uri web, printre care cel mai cunoscut este Django. Spre deosebire de Django, care deține mai multe tool-uri de folos pentru programator, Flask este un framework simplu ce permite usor crearea de prototipuri pentru proiecte mici.
Un program minimalist in Flask poate fi acesta:
import flask app = flask.Flask(__name__) @app.route('/', methods=['GET']) def home(): return "<h1>Hello World</h1>" app.run()
În momentul rulării programului de Python, framework-ul va deschide un server web pe portul TCP 5000 (valoarea default), și va aștepta cereri HTTP de la un browser. În funcție de pagina cerută, Flask-ul va ruta cererea catre funcția de Python asociata (procedeu cunoscut drept mapping). În cazul acestui exemplu, când browser-ul va cere pagina principala, serverul web va returna un simplu header (H1) in format HTML.
Serviciile sunt procese care sunt executate automat de catre systemd in baza unor reguli definite pentru acel serviciu. Aceste reguli se regasesc in cadrul unui unit configuration file si ofera informatii despre fisierul care trebuie executat, precum si cand trebuie executat, dependente al serviciului, etc.
[Unit] Description=Demo service After=network.target StartLimitIntervalSec=0 [Service] Type=simple Restart=always RestartSec=1 ExecStart=/usr/bin/myserver.sh [Install] WantedBy=multi-user.target
Fisierul care configureaza serviciul trebuie sa se regaseasca in cadrul directorului /etc/systemd/system/
.
Pentru a porni serviciul la start-up, acesta trebuie sa fie enabled
:
#: systemctl status demo.service ○ demo.service - Demo service Loaded: loaded (/etc/systemd/system/demo.service; disabled> Active: inactive (dead) #: systemctl enable demo.service ○ demo.service - Demo service Loaded: loaded (/etc/systemd/system/demo.service; enabled> Active: inactive (dead)
Pentru a porni serivicul manual vom folosi systemctl start
:
#: systemctl start demo.service ● demo.service - Demo service Loaded: loaded (/etc/systemd/system/demo.service; enabled> Active: active (running) since Sun 2022-11-06 23:00:40 UTC; 6s ago Main PID: 697 (python3) Tasks: 3 (limit: 978) Memory: 50.0M CPU: 5.833s
1. Dorim să ne creăm propria imagine pentru RaspberryPi-urile noastre. Pentru aceasta, vom folosi imaginea Debian 13 (Bookworm) pentru Raspberry Pi 3B+ ca și referință pentru imaginea noastră.
ls -l --block-size=M
).dd
.fdisk
.x
în consola deschisa de fdisk
pentru a intra în expert mode. Folosiți comanda m
pentru a vedea toate opțiunile și modificați disk identifier încât să corespunda cu cel listat pentru imaginea oficială.
t
în cadrul consolei deschise de fdisk
, urmată de codul asociat tipului pe care îl dorim.
2. În acest moment, cele doua partiții nu au niciun sistem de fișiere creat. Creați un loop block device pentru fiecare dintre cele doua imagini. Creați-le sistemele de fișiere, având grija la tipul acestora (revedeți secțiunea Formatul imaginii RaspberryPI). Odată create sistemele de fișiere, montați fiecare partiție într-un director.
Optiunea -L
a comenzii mkfs.ext4
, respectiv optiunea -n
a comenzii mkfs.vfat
ne ofera posibilitatea de a atribui un label atunci cand cream sistemul de fisiere.
Paritia de boot trebuie sa aiba label-ul RASPIFIRM
, iar partitia pentru rootfs trebuie sa aiba label-ul RASPIROOT
.
3. Ne dorim să copiem conținutul fișierelor din imaginea oficiala în imaginea noastră. Copiați conținutul directoarelor imaginii oficiale în directoarele imaginii noastre. După ce procesul de copiere s-a terminat, demontați device-urile și ștergeți loop device-urile.
4. Acum că imaginea noastră este gata, vrem să vedem că funcționează. Bootați sistemul în qemu folosind rootfs-ul nou creat.
5. Porniți server-ul web scris cu framework-ul Flask de aici home-automation.tgz
Pentru a-l rula, aveți nevoie de următorii pași de executat in Qemu/RPi:
sudo apt install python3-pip pip3 install flask pip3 install flask-wtf
python3 home-automation.py
qemu-system-aarch64
:... -netdev user,id=net0,hostfwd=tcp::5555-:22,hostfwd=tcp::5000-:5000 \ ...
Temporary failure in name resolution
) , editati fisierul /etc/resolv.conf
astfel incat sa aveti un nameserver valid.
nameserver 8.8.8.8
6. Momentan server-ul nu poate servi decât cererile de GET, iar aceeași pagină va fi afișata mereu. Pentru a schimba starea/structurile interne ale server-ului, vom folosi o cerere de REST API contruită de browser-ul web, cu ajutorului unui script JavaScript.
Pentru ca browser-ul să ruleze acest script, el trebuie să fie returnat odată cu conținutul paginii principale (index.html). Copiați conținutul fișierului de aici rest-api-request.js.txt, in interiorului fisierului templates/index.html, în secțiunea <script>.
Reîncărcați pagina web și dați click pe butonul de pornit lumina. O cerere de REST API va fi trimisă către server, dar va fi nevoie de un refresh al paginii pentru a lua ultima stare a structurilor.
7. Creati un serviciu pentru server-ul de Flask, astfel incat acesta sa porneasca automat la start-up. Revedeti sectiunea despre configurarea serviciilor .