(Photo by Roman Spiridonov on Unsplash)Dopo aver definito i sistemi, possiamo ora definire la programmazione di sistemi come l’atto di costruire software di sistema usando linguaggi di programmazione di sistema. Abbastanza semplice, giusto?
Beh, c’è una cosa che abbiamo saltato, i linguaggi. La gente spesso parla dei linguaggi di programmazione di sistema in modi come “X è grande, è veloce, compilato e un linguaggio di programmazione di sistema”. Ma siamo tutti d’accordo su cosa sia un linguaggio di programmazione di sistemi?
Viste le nostre definizioni di sistemi, definirei i criteri per un linguaggio di programmazione di sistemi come:
- Compilato in binario nativo
- Può essere costruito senza dipendenze da altri software (incluso un kernel)
- Caratteristiche prestazionali simili ad altri linguaggi di programmazione di sistemi
Disclaimer: Questa è la mia definizione. Poiché non esiste un criterio prestabilito, sto ricavando una definizione da ciò che ha senso per me dato il contesto in cui ho definito il software di sistema.
Compilare in binario nativo
Se un linguaggio non può compilare in un eseguibile che sia direttamente interpretabile dalla CPU allora, per definizione, è in esecuzione su una piattaforma (es. JVM, Ruby VM, Python VM, ecc.). Ci possono essere alcune argomentazioni da fare qui, ma per semplicità penso che questo sia un criterio adatto.
Nessuna dipendenza
L’argomento è simile alla compilazione su un binario nativo. Se il linguaggio richiede sempre che qualche altro software sia presente per essere eseguito, allora è in esecuzione su una piattaforma. Un esempio di questo è Go e la sua libreria standard inclusa. Richiede il supporto del sistema operativo per eseguire azioni di base come l’allocazione della memoria, lo spawning dei thread (per le goroutine da eseguire), per il suo poller di rete integrato, e altre azioni. Mentre è possibile reimplementare queste funzioni di base, ciò crea una barriera all’uso in questo contesto ed è facile immaginare perché non tutti i linguaggi, anche quelli che compilano binari statici, sono intesi come linguaggi di programmazione di sistema.
Caratteristiche di prestazione simili
Questa è un po’ una scappatoia. Tuttavia, è per dire che all’interno del sistema di linguaggi tipicamente classificati come linguaggi di programmazione di sistema, non ci dovrebbero essere grandi (ordine di grandezza) differenze nelle caratteristiche di prestazione. Per caratteristiche mi riferisco esplicitamente alla velocità di esecuzione e all’efficienza della memoria.
Il golden standard per il confronto è C e/o C++ come è spesso rappresentato nei benchmark comparativi, che misurano la velocità di esecuzione in quanti ordini di grandezza i linguaggi sono più lenti di C/C++.
Nominandone alcuni
I linguaggi che vengono in mente immediatamente, data la definizione di cui sopra sono C e C++. Ma ci sono anche linguaggi più recenti come Rust e Nim che riempiono questa nicchia. Infatti, c’è già un sistema operativo scritto interamente in Rust (RedoxOS) e un kernel in Nim (nimkernel).
Parliamo di Go
Prima ho accennato al fatto che Go potrebbe non rientrare nella famiglia dei “linguaggi di programmazione dei sistemi”. Tuttavia, proprio come non tutte le applicazioni si adattano bene al software applicativo e al software di sistema, nemmeno i linguaggi lo fanno.
Spesso le persone chiamano Go un linguaggio di programmazione di sistemi e anche golang.org è citato come:
Go è un linguaggio generico progettato con la programmazione di sistemi in mente.
Tuttavia, anche questa non è una dichiarazione esplicita che Go è un linguaggio di programmazione di sistemi, semplicemente che è progettato con questo in mente. Trovo che si trovi piuttosto nel mezzo.
Sebbene Go compili in binari nativi, contenga utili concetti di basso livello (puntatori raw/unsafe, tipi nativi come byte e int32, e supporto all’assemblaggio in linea), ed è relativamente performante; ha ancora alcune sfide da superare. Go viene fornito con un runtime e un garbage collector.
Un runtime significa che il bootstrapping/sovrascrittura del runtime sarà richiesto per funzionare in ambienti senza kernel. Questo entra maggiormente nell’implementazione interna del linguaggio, che potrebbe cambiare nelle versioni future. I cambiamenti richiedono ulteriore lavoro di bootstrapping man mano che il linguaggio si evolve.
Un garbage collector (GC) significa che Go è limitato in quali domini applicativi può essere usato o che il GC deve essere disabilitato e sostituito con una gestione manuale della memoria. Nel caso in cui il GC non possa essere sostituito, il dominio in tempo reale (definito da operazioni che devono essere completate entro determinati limiti di tempo e/o le prestazioni sono misurate in nano-secondi) non potrebbe rischiare i tempi di pausa non deterministici di un GC.
Software per sistemi distribuiti
Con il crescente parlare di sistemi distribuiti, e applicazioni come Kubernetes che diventano molto popolari, si sente un sacco di nuovo vocabolario che (se dobbiamo essere onesti) la maggior parte di noi non capisce del tutto.
Fino a questo punto, ho visto i termini programmazione dei sistemi e ingegneri dei sistemi usati in contesti in cui ciò che intendevano veramente era programmazione dei sistemi distribuiti e ingegneri dei sistemi distribuiti.
Abbiamo definito il software di sistema, i linguaggi di sistema e la programmazione dei sistemi in questo post. Tuttavia, quando parliamo di sistemi distribuiti, il significato di sistema cambia. E mentre non ho intenzione di immergermi nelle differenze specifiche qui (principalmente perché ho ancora bisogno di afferrarle meglio io stesso), è importante che facciamo queste distinzioni mentali e usiamo un discorso più preciso quando possiamo per evitare confusione a coloro che stanno ancora imparando lo spazio.