Librerie Linux, glibc e file .so: cosa c’è sopra il kernel
Scopri cosa c’è sopra il kernel Linux: librerie di sistema, glibc, file .so, utility, applicazioni, servizi, dipendenze e system call spiegati semplice.
Quando utilizziamo Linux, vediamo soprattutto la parte più vicina all’utente: il browser, il file manager, il terminale, l’editor di testo, i programmi grafici e i comandi della shell.
Sotto questa superficie esiste però un insieme di componenti meno visibili, ma fondamentali per il funzionamento del sistema: librerie di sistema, librerie condivise, utility, applicazioni, servizi e interfacce che permettono ai programmi di comunicare con il kernel.
Nel primo episodio della serie Capire Linux abbiamo rappresentato il sistema operativo come una struttura a livelli. In basso si trova l’hardware, sopra l’hardware lavora il kernel e, al di sopra del kernel, troviamo lo user space, cioè lo spazio in cui vengono eseguiti i programmi utilizzati dagli utenti e dai servizi di sistema.
In questo articolo completiamo proprio questa parte della mappa. Vedremo che
cosa sono le librerie Linux, quale ruolo svolge la glibc, che cosa indicano
i file con estensione .so, perché esistono le dipendenze e come utility e
applicazioni arrivano al kernel attraverso le system call.
Lo user space non è un unico componente
Quando si parla di user space si potrebbe immaginare una singola area contenente genericamente “tutti i programmi”.
Il concetto è corretto, ma lo user space è molto più articolato.
Al suo interno troviamo la shell, le utility da riga di comando, le applicazioni grafiche, i servizi in background, i demoni di sistema e le librerie utilizzate da altri programmi.
Ognuno di questi componenti svolge un ruolo differente.
Una shell interpreta i comandi dell’utente. Un browser permette di visitare siti web. Una utility come grep cerca testo. Una libreria fornisce funzioni riutilizzabili. Un servizio può restare attivo in background e rispondere a richieste provenienti dal sistema o dalla rete.
Tutti questi elementi hanno però un punto in comune: vengono eseguiti normalmente nello spazio utente e non possono accedere liberamente alle risorse più critiche della macchina.
Quando hanno bisogno di leggere un file, utilizzare la rete, creare un processo o comunicare con un dispositivo, devono rivolgersi al kernel.
Programma e processo non sono la stessa cosa
Prima di parlare delle librerie è utile riprendere una distinzione fondamentale: quella tra programma e processo.
Un programma è codice memorizzato nel filesystem. Può essere un file eseguibile, uno script oppure un insieme di componenti composto da eseguibili, configurazioni, risorse e librerie.
Un processo è invece un programma mentre viene eseguito.
Il browser installato sul disco è un programma. Quando lo apriamo, Linux crea uno o più processi, assegna loro memoria, un identificativo numerico chiamato PID e le risorse necessarie per funzionare.
Lo stesso programma può essere avviato più volte e generare processi differenti.
Una libreria si colloca ancora in un’altra categoria. Contiene codice, ma normalmente non viene avviata direttamente dall’utente come un’applicazione. Viene utilizzata da altri programmi per svolgere determinate operazioni.
Che cos’è una libreria Linux
Una libreria software è una raccolta di funzioni pensate per essere utilizzate da altri programmi.
Immaginiamo di dover sviluppare un’applicazione che deve gestire stringhe, aprire file, allocare memoria, mostrare finestre grafiche, cifrare dati e comunicare in rete.
In teoria potremmo scrivere ogni funzione da zero. Sarebbe però un lavoro enorme, soggetto a errori e difficile da mantenere.
Le librerie mettono a disposizione componenti già realizzati e organizzati attraverso interfacce definite. Il programma può richiamare una determinata funzione senza dover conoscere ogni dettaglio della sua implementazione.
Possiamo immaginare il programma come una persona che costruisce qualcosa e la libreria come una cassetta degli attrezzi.
La libreria non sostituisce il programma e non decide quale applicazione realizzare. Offre però strumenti pronti per svolgere operazioni comuni.
Perché i programmi utilizzano le librerie
Il primo vantaggio delle librerie è il riutilizzo del codice.
Se molti programmi devono eseguire la stessa operazione, non è necessario che ciascuno contenga una propria implementazione completamente indipendente.
Utilizzando componenti condivisi si riduce la duplicazione del codice, si velocizza lo sviluppo e si rende il comportamento delle applicazioni più coerente.
Una correzione applicata a una libreria può inoltre risolvere un problema per tutti i programmi che la utilizzano.
Questo meccanismo rende il sistema più modulare, ma introduce anche una relazione di dipendenza. Se un’applicazione richiede una determinata libreria, quella libreria deve essere installata e deve essere compatibile con la versione del programma.
Le dipendenze non sono quindi un difetto di Linux. Sono la conseguenza naturale di un sistema in cui i componenti software collaborano e riutilizzano codice comune.
Le librerie di sistema
Tra le librerie troviamo quelle che vengono definite librerie di sistema.
Questi componenti offrono funzioni fondamentali utilizzate da moltissimi programmi: gestione della memoria, manipolazione delle stringhe, input e output, gestione dei file, processi, date, comunicazione e risoluzione dei nomi.
Alcune funzioni possono essere eseguite completamente nello user space. Per esempio, elaborare una stringa già presente nella memoria del processo non richiede necessariamente l’intervento del kernel.
Altre funzioni, invece, devono accedere a risorse controllate dal sistema operativo. In questo caso la libreria prepara la richiesta e, quando necessario, utilizza una system call.
Una libreria può quindi nascondere parte della complessità tecnica, offrendo al programma un’interfaccia più semplice e uniforme.
Che cos’è la glibc
Una delle librerie più importanti presenti su molte distribuzioni Linux è la glibc, abbreviazione di GNU C Library.
La glibc implementa numerose funzioni standard del linguaggio C e mette a disposizione servizi utilizzati da una grande quantità di programmi.
Tra le sue responsabilità troviamo la gestione delle stringhe, l’allocazione della memoria, l’input e output, la gestione dei processi, la risoluzione dei nomi, le operazioni su date e orari e molte altre funzioni fondamentali.
La glibc offre inoltre dei wrapper per molte system call del kernel.
Un wrapper può essere immaginato come un involucro che presenta al programma una funzione più semplice da utilizzare. Il programma non deve necessariamente preparare direttamente ogni dettaglio della chiamata al kernel: invoca una funzione della libreria, che si occupa di tradurre la richiesta nel formato corretto.
La glibc rappresenta quindi uno dei principali ponti tra i programmi nello user space e i servizi offerti dal kernel Linux.
La glibc non è l’unica libreria
La glibc è importante, ma non rappresenta tutte le librerie disponibili su Linux.
Esistono implementazioni alternative della libreria C, come musl, utilizzata in alcuni sistemi e in alcune distribuzioni particolarmente leggere.
Esistono inoltre moltissime librerie specializzate.
OpenSSL offre funzioni legate alla crittografia e alle connessioni protette. GTK e Qt forniscono strumenti per realizzare interfacce grafiche. Altre librerie si occupano di immagini, audio, video, database, compressione, protocolli di rete o calcoli matematici.
Una singola applicazione può dipendere da più livelli di librerie.
Un programma grafico può utilizzare la libreria C per le operazioni fondamentali, un toolkit come GTK o Qt per costruire l’interfaccia e altre librerie per elaborare immagini, file o connessioni di rete.
Librerie statiche e librerie condivise
Le librerie possono essere collegate a un programma in modi differenti.
Con il collegamento statico, il codice necessario viene incorporato nell’eseguibile durante la fase di costruzione del programma.
Il risultato è un eseguibile più autonomo, perché contiene già il codice di cui ha bisogno. L’aspetto negativo è che il file può diventare più grande e duplicare funzioni che sono già presenti in altri programmi.
Con il collegamento dinamico, invece, l’eseguibile utilizza una libreria presente separatamente nel sistema.
In questo caso il programma conserva un riferimento alla libreria, che viene individuata e caricata quando il programma viene eseguito.
Il collegamento dinamico riduce la duplicazione, facilita gli aggiornamenti centralizzati e consente a più programmi di condividere parte del codice.
Che cosa sono i file .so
Su Linux le librerie condivise hanno spesso nomi contenenti l’estensione .so.
La sigla significa shared object, cioè oggetto condiviso.
Possiamo incontrare file come:
libqualcosa.so
libqualcosa.so.1
libqualcosa.so.1.2
I numeri possono indicare versioni differenti della libreria.
Quando un programma viene eseguito, un componente chiamato dynamic linker, o caricatore dinamico, individua le librerie richieste e collega le funzioni utilizzate dal programma alle implementazioni presenti nei file .so.
Il concetto fondamentale è che una parte del codice necessario all’applicazione può trovarsi fuori dal file eseguibile principale.
Per questo motivo copiare soltanto l’eseguibile di un programma da un sistema a un altro non garantisce che esso possa funzionare. Potrebbero mancare le librerie o le versioni richieste.
Perché le librerie condivise sono utili
Le librerie condivise riducono innanzitutto la duplicazione sul disco.
Se dieci programmi usano lo stesso componente, non è necessario che ognuno ne contenga una copia completa.
Possono inoltre portare benefici nell’utilizzo della memoria. Alcune pagine di codice possono essere condivise tra processi differenti, evitando di caricare più volte gli stessi dati.
Un altro vantaggio riguarda gli aggiornamenti di sicurezza.
Se viene individuata una vulnerabilità in una libreria comune, la distribuzione può aggiornare quel componente e correggere indirettamente molti programmi che lo utilizzano.
Questa centralizzazione richiede però attenzione alla compatibilità. Una modifica incompatibile potrebbe impedire l’avvio delle applicazioni che dipendono da una determinata versione.
Dipendenze e gestore pacchetti
Quando un programma necessita di una libreria esterna, quella libreria diventa una dipendenza.
Una dipendenza mancante o incompatibile può impedire al programma di avviarsi.
È uno dei motivi per cui sulle distribuzioni Linux conviene installare software attraverso il gestore pacchetti ufficiale.
Il gestore pacchetti non scarica soltanto l’eseguibile principale. Controlla anche quali librerie sono necessarie, verifica le versioni compatibili e installa i componenti richiesti.
Strumenti come apt, dnf, pacman o zypper svolgono proprio questo lavoro all’interno delle rispettive distribuzioni.
La gestione centralizzata delle dipendenze riduce il rischio di modificare manualmente file critici e mantiene una mappa coerente del software installato.
Il ruolo del dynamic linker
Il dynamic linker entra in gioco quando viene avviato un programma collegato dinamicamente.
Prima che il programma possa eseguire le proprie istruzioni, il linker individua le librerie richieste e collega i riferimenti alle funzioni corrispondenti.
Possiamo immaginarlo come un responsabile che, prima dell’apertura di un laboratorio, verifica che tutti gli strumenti necessari siano disponibili.
Se le librerie sono presenti e compatibili, il programma può continuare l’esecuzione.
Se manca un componente essenziale, l’avvio può interrompersi immediatamente con un errore relativo a una libreria non trovata.
Le utility di sistema
Oltre alle librerie, nello user space troviamo le utility di sistema.
Una utility è un programma specializzato nello svolgimento di un compito preciso.
Comandi come ls, cp, mv, grep, chmod, df, ps, top, ip e curl sono utility.
Non fanno parte del kernel e non sono funzioni magiche del terminale.
Sono veri programmi che la shell individua e avvia come processi.
Molte utility seguono la filosofia Unix: svolgere bene un compito specifico e poter essere combinate con altri strumenti.
grep cerca testo, sort ordina, wc conta, df mostra lo spazio disponibile sui filesystem e ps visualizza informazioni sui processi.
La shell permette di collegare queste utility attraverso pipe, redirezioni e script, creando flussi di lavoro complessi a partire da strumenti relativamente semplici.
Comandi interni ed esterni della shell
Non tutto ciò che scriviamo nel terminale corrisponde però a un eseguibile separato.
Alcuni comandi sono integrati direttamente nella shell e vengono definiti builtin.
Un esempio importante è cd.
Il comando cd deve cambiare la directory corrente della shell. Se fosse eseguito soltanto da un processo esterno, cambierebbe la directory di quel processo figlio, che poi terminerebbe, lasciando la shell nella posizione precedente.
Per questo motivo cd è normalmente un comando interno.
Comandi come ls, grep o cp sono invece programmi esterni, individuati dalla shell attraverso le directory presenti nella variabile PATH.
Dal punto di vista dell’utente appaiono tutti come comandi, ma dal punto di vista architetturale non funzionano nello stesso modo.
Applicazioni utente
Le applicazioni sono programmi progettati per svolgere attività utili per l’utente o per un determinato contesto professionale.
Un browser, un editor di testo, un IDE, un media player, un programma di grafica o un software di montaggio video sono applicazioni.
Anche software utilizzati sui server, come database, motori di ricerca e piattaforme web, possono essere considerati applicazioni complesse.
La distinzione tra utility e applicazione non è sempre assoluta.
In generale, una utility è uno strumento più piccolo e specializzato, mentre un’applicazione offre un insieme più ampio di funzionalità e una forma di interazione più articolata.
Dal punto di vista del kernel, però, entrambe sono programmi nello user space.
Applicazione grafica e terminale usano lo stesso sistema
Un’applicazione grafica può sembrare molto diversa da un comando del terminale, ma la struttura sottostante è simile.
Un file manager grafico può chiedere al kernel di leggere una directory, copiare un file o modificarne gli attributi.
Il comando ls può ottenere l’elenco di una directory e cp può copiare gli stessi dati.
Cambia l’interfaccia attraverso cui l’utente esprime la richiesta.
L’applicazione grafica utilizza finestre, pulsanti e icone. La shell utilizza comandi, opzioni e argomenti.
Entrambe vengono eseguite come processi, utilizzano librerie, rispettano i permessi dell’utente e passano attraverso il kernel quando devono accedere alle risorse del sistema.
Il terminale non rappresenta quindi un Linux differente. È semplicemente un’interfaccia diversa verso lo stesso sistema operativo.
Applicazioni e servizi
Un servizio è un programma progettato per fornire una funzione in modo continuativo o su richiesta, spesso lavorando in background.
Un server web rimane in ascolto e risponde alle richieste. Un servizio SSH consente connessioni remote. Un database attende interrogazioni da altre applicazioni. Un servizio di logging raccoglie eventi dal sistema.
Quando è in esecuzione, un servizio è comunque composto da uno o più processi.
La differenza riguarda soprattutto il ruolo.
Un’applicazione desktop viene normalmente aperta dall’utente, utilizzata e chiusa.
Un servizio può partire automaticamente all’avvio, non mostrare alcuna finestra e continuare a funzionare senza interazione diretta.
Su molte distribuzioni Linux moderne, servizi e demoni vengono gestiti da systemd, che può avviarli, fermarli, controllarne lo stato e gestirne le dipendenze.
Programma, libreria, utility e servizio
Possiamo ora riordinare i concetti principali.
Un programma è codice che può essere eseguito.
Una libreria contiene funzioni pensate principalmente per essere utilizzate da altri programmi.
Una utility è un programma specializzato in un compito preciso.
Un’applicazione è un programma orientato a un’attività dell’utente o a una funzione più articolata.
Un servizio è un programma, o un insieme di processi, pensato per fornire una funzione nel tempo, spesso in background.
Queste categorie non sono sempre separate da confini assoluti. Un database è un’applicazione complessa, ma viene normalmente eseguito come servizio. Un comando può essere usato direttamente dall’utente oppure richiamato da uno script o da un altro programma.
L’importante è comprenderne il ruolo all’interno dell’architettura Linux.
Perché i programmi non controllano direttamente l’hardware
Se ogni applicazione potesse controllare liberamente il disco, la memoria o la scheda di rete, il sistema sarebbe estremamente fragile.
Due programmi potrebbero modificare contemporaneamente la stessa risorsa. Un errore potrebbe sovrascrivere dati critici. Un’applicazione compromessa potrebbe prendere il controllo dell’intera macchina.
Il kernel evita questa situazione centralizzando la gestione delle risorse.
Controlla processi, memoria, filesystem, rete e dispositivi. Utilizza i driver per comunicare con l’hardware e applica permessi, isolamento e regole di accesso.
I programmi descrivono ciò di cui hanno bisogno. Il kernel verifica che la richiesta sia valida e che il processo abbia i privilegi necessari.
Questa separazione è una delle basi della sicurezza e della stabilità di Linux.
Le system call
Le system call, o chiamate di sistema, sono il meccanismo attraverso cui un processo nello user space richiede un servizio al kernel.
Quando un programma vuole aprire un file, leggere dati, scrivere su disco, creare un processo, mappare memoria o aprire un socket di rete, deve utilizzare le interfacce offerte dal kernel.
Alcuni esempi concettuali sono:
open
read
write
close
execve
mmap
socket
Non è necessario memorizzarle tutte.
Il punto importante è comprendere che una system call rappresenta il passaggio controllato dallo user space al kernel space.
Il programma non comunica direttamente con il disco o con la scheda di rete. Formula una richiesta al kernel, che applica i controlli necessari e utilizza il sottosistema e il driver appropriati.
Libreria e system call non sono la stessa cosa
Una libreria e una system call svolgono ruoli differenti.
La libreria offre al programma funzioni più comode, riutilizzabili e spesso di livello più alto.
La system call è l’interfaccia specifica attraverso cui un processo richiede un servizio al kernel.
Non tutte le funzioni di una libreria generano una system call. Un’operazione su dati già presenti nello spazio di memoria del processo può svolgersi interamente nello user space.
In altri casi una funzione della libreria può corrispondere a una singola system call oppure combinare più chiamate, controlli, buffer e gestione degli errori.
Librerie e system call sono quindi livelli distinti della stessa architettura.
Esempio: copiare un file
Consideriamo il comando:
cp sorgente.txt destinazione.txt
La shell legge la riga, individua l’eseguibile cp e avvia un nuovo processo.
L’utility analizza i percorsi e le eventuali opzioni. Utilizza funzioni offerte dalle librerie di sistema.
Quando deve aprire il file sorgente, leggere i dati, creare il file di destinazione e scrivere il contenuto, effettua richieste al kernel.
Il kernel controlla i permessi, individua i file attraverso il filesystem, gestisce cache e input/output e comunica con il dispositivo di archiviazione attraverso i driver.
Alla fine il risultato torna all’utility, che può terminare correttamente oppure mostrare un messaggio di errore.
Una semplice copia coinvolge quindi molti livelli:
utente → shell → utility → librerie → system call → kernel → filesystem → driver → hardware
Esempio: aprire un sito web
Anche l’apertura di una pagina web coinvolge la stessa architettura.
Il browser utilizza librerie per mostrare l’interfaccia, gestire font e immagini, comprimere dati e proteggere la connessione attraverso protocolli crittografici.
Quando deve contattare il server remoto, utilizza funzioni di rete che portano alla creazione di socket e alle system call del kernel.
Il kernel gestisce stack TCP/IP, routing, firewall e interfacce di rete.
Il driver comunica con la scheda fisica o virtuale.
La risposta percorre il cammino inverso e viene elaborata dal browser fino a trasformarsi nella pagina visualizzata dall’utente.
Quello che appare come un semplice clic è in realtà il risultato della collaborazione tra applicazioni, librerie, servizi di rete, system call, kernel, driver e hardware.
La mappa completa dello user space Linux
Possiamo riassumere l’architettura seguendo il percorso di una richiesta.
In alto si trova l’utente, che interagisce con una shell oppure con un’applicazione grafica.
La shell può avviare utility e altri programmi.
Applicazioni e utility usano librerie per riutilizzare funzioni già disponibili.
Quando devono accedere a una risorsa protetta, utilizzano una system call.
La richiesta entra nel kernel, che sceglie il sottosistema appropriato: processi, memoria, filesystem, rete o gestione dei dispositivi.
Se l’operazione coinvolge l’hardware, il kernel utilizza il driver corrispondente.
Il risultato ritorna quindi verso l’alto, dal kernel al processo e infine all’utente.
Perché questa architettura è importante
Questa struttura a livelli può sembrare complessa, ma è proprio la separazione delle responsabilità a rendere Linux stabile, flessibile e modulare.
Il kernel gestisce le risorse critiche.
Le librerie offrono funzioni comuni.
Le utility eseguono compiti specializzati.
Le applicazioni costruiscono esperienze più complete.
I servizi forniscono funzioni continuative.
Ogni componente può evolvere mantenendo interfacce definite con gli altri livelli.
Questa mappa è utile anche per diagnosticare i problemi.
Se un programma non parte a causa di una libreria mancante, il problema non riguarda necessariamente il kernel.
Se un comando non riesce a modificare un file, potrebbe essere un problema di permessi.
Se una periferica non funziona, potrebbe mancare un driver o un firmware.
Capire l’architettura significa quindi anche imparare a cercare ogni problema nel livello corretto.
Conclusione
Librerie, utility, applicazioni e servizi costituiscono una parte fondamentale dello user space Linux.
La glibc mette a disposizione molte funzioni standard e facilita l’accesso ai servizi del sistema. I file .so permettono di condividere codice tra più applicazioni. Le utility offrono strumenti piccoli e specializzati. Le applicazioni forniscono funzionalità più complete, mentre i servizi lavorano spesso in background.
Quando questi componenti devono accedere a file, memoria, rete o dispositivi, utilizzano le system call per comunicare con il kernel.
Nel prossimo episodio della serie scenderemo ancora di un livello e vedremo come Linux comunica con l’hardware attraverso driver, firmware, interrupt, DMA e file speciali presenti nella directory /dev.