Perché esiste una politica del kernel Linux per non interrompere mai lo spazio utente?


38

Ho iniziato a pensare a questo problema nel contesto dell'etichetta sulla mailing list del kernel Linux. Essendo il progetto di software libero più conosciuto e probabilmente di maggior successo e importante al mondo, il kernel di Linux ottiene molta stampa. E il fondatore e leader del progetto, Linus Torvalds, non ha chiaramente bisogno di presentazioni qui.

Linus occasionalmente attira polemiche con le sue fiamme sull'LKML. Queste fiamme sono spesso, per sua stessa ammissione, a che fare con la rottura dello spazio dell'utente. Il che mi porta alla mia domanda.

Posso avere una prospettiva storica sul perché rompere lo spazio utente è una cosa così brutta? A quanto ho capito, rompere lo spazio utente richiederebbe correzioni a livello di applicazione, ma è una cosa così brutta, se migliora il codice del kernel?

Da quanto ho capito, la politica dichiarata di Linus è che non rompere lo spazio utente vince su tutto il resto, inclusa la qualità del codice. Perché è così importante e quali sono i pro ei contro di una tale politica?

(Esistono chiaramente alcuni svantaggi di tale politica, applicata in modo coerente, dal momento che Linus occasionalmente ha "divergenze" con i suoi principali luogotenenti sull'LKML su questo argomento. Per quanto ne so, si fa sempre strada nella questione.)


1
Hai sbagliato a scrivere il nome di Linus nell'introduzione.
Ismael Miguel,

Non sono stato io di sicuro, ma ho dimenticato di votare e ho dato il mio voto ora.
Ismael Miguel,

Risposte:


38

Il motivo non è storico ma pratico. Esistono molti molti molti programmi eseguiti sul kernel Linux; se un'interfaccia del kernel rompe questi programmi, allora tutti dovrebbero aggiornare quei programmi.

Ora è vero che la maggior parte dei programmi non dipendono direttamente dalle interfacce del kernel ( chiamate di sistema ), ma solo dalle interfacce della libreria standard C ( wrapper C attorno alle chiamate di sistema). Oh, ma quale libreria standard? Glibc? uClibc? Dietlibc? Bionico? MUSL? eccetera.

Ma ci sono anche molti programmi che implementano servizi specifici del sistema operativo e dipendono dalle interfacce del kernel che non sono esposte dalla libreria standard. (Su Linux, molti di questi sono offerti attraverso /proce /sys.)

E poi ci sono binari compilati staticamente. Se un aggiornamento del kernel rompe uno di questi, l'unica soluzione sarebbe ricompilarli. Se hai la fonte: Linux supporta anche software proprietario.

Anche quando la fonte è disponibile, raccoglierla può essere una seccatura. Soprattutto quando si aggiorna il kernel per correggere un bug con l'hardware. Le persone spesso aggiornano il proprio kernel indipendentemente dal resto del proprio sistema perché hanno bisogno del supporto hardware. Nelle parole di Linus Torvalds :

Rompere i programmi utente semplicemente non è accettabile. (...) Sappiamo che le persone usano vecchi binari per anni e anni e che fare una nuova versione non significa che puoi semplicemente buttarlo fuori. Puoi fidarti di noi.

Egli spiega anche che uno dei motivi per rendere questo una regola forte è quello di evitare la dipendenza inferno dove non ci resta che passare un altro programma per ottenere qualche più recente del kernel per il lavoro, ma anche di aggiornare ancora un altro programma, e un altro, e un altro , perché tutto dipende da una determinata versione di tutto.

E ' un po' ok per avere un ben definito dipendenza a senso unico. È triste, ma inevitabile a volte. (…) Ciò che NON va bene è avere una dipendenza bidirezionale. Se il codice HAL dello spazio utente dipende da un nuovo kernel, va bene, anche se sospetto che gli utenti sperino che non sia "kernel della settimana", ma più un "kernel degli ultimi mesi".

Ma se hai una dipendenza DUE VIE, sei fregato. Ciò significa che è necessario eseguire l'aggiornamento in fase di blocco e che NON È ACCETTABILE. È orribile per l'utente, ma ancora più importante, è orribile per gli sviluppatori, perché significa che non si può dire "si è verificato un bug" e fare cose come provare a restringerlo con la bisection o simili.

Nello spazio utente, tali dipendenze reciproche vengono generalmente risolte mantenendo diverse versioni della libreria; ma puoi eseguire solo un kernel, quindi deve supportare tutto ciò che le persone potrebbero voler fare con esso.

Ufficialmente ,

la compatibilità con le versioni precedenti di [chiamate di sistema dichiarate stabili] sarà garantita per almeno 2 anni.

In pratica però,

La maggior parte delle interfacce (come i syscalls) non dovrebbero mai cambiare e saranno sempre disponibili.

Ciò che cambia più spesso sono le interfacce che devono essere utilizzate solo dai programmi relativi all'hardware, in /sys. (d' /procaltra parte, che dall'introduzione di /sysè stato riservato a servizi non legati all'hardware, praticamente non si rompe mai in modo incompatibile.)

In sintesi,

la rottura dello spazio utente richiederebbe correzioni a livello di applicazione

e questo è male perché c'è solo un kernel, che le persone vogliono aggiornare indipendentemente dal resto del loro sistema, ma ci sono molte molte applicazioni là fuori con interdipendenze complesse. È più facile mantenere stabile il kernel che mantenere migliaia di applicazioni aggiornate su milioni di configurazioni diverse.


1
Grazie per la risposta. Quindi, le interfacce dichiarate stabili sono un superset delle chiamate di sistema POSIX? La mia domanda sulla storia è come si è evoluta questa pratica. Presumibilmente le versioni originali del kernel Linux non si preoccupavano della rottura dello spazio utente, almeno inizialmente.
Faheem Mitha,

3
@FaheemMitha Sì, dal 1991 . Non penso che l'approccio di Linus si sia evoluto, è sempre stato "le interfacce per le normali applicazioni non cambiano, le interfacce per i software che sono fortemente legate ai cambiamenti del kernel molto raramente".
Gilles 'SO- smetti di essere malvagio' il

24

In qualsiasi sistema interdipendente ci sono fondamentalmente due scelte. Astrazione e integrazione. (Non sto intenzionalmente usando termini tecnici). Con Abstraction, stai dicendo che quando effettui una chiamata a un'API che, mentre il codice dietro l'API può cambiare, il risultato sarà sempre lo stesso. Ad esempio, quando chiamiamo fs.open()non ci importa se si tratta di un'unità di rete, un SSD o un disco rigido, avremo sempre un descrittore di file aperto con cui possiamo fare cose. Con "integrazione" l'obiettivo è quello di fornire il modo "migliore" di fare una cosa, anche se il modo cambia. Ad esempio, l'apertura di un file potrebbe essere diversa per una condivisione di rete rispetto a un file su disco. Entrambi i modi sono ampiamente utilizzati nel moderno desktop Linux.

Dal punto di vista degli sviluppatori si tratta di "funziona con qualsiasi versione" o "funziona con una versione specifica". Un ottimo esempio di ciò è OpenGL. La maggior parte dei giochi è impostata per funzionare con una versione specifica di OpenGL. Non importa se stai compilando dalla fonte. Se il gioco è stato scritto per usare OpenGL 1.1 e stai cercando di farlo funzionare su 3.x, non ti divertirai. All'altra estremità dello spettro, alcune chiamate dovrebbero funzionare indipendentemente da cosa. Ad esempio, voglio chiamare fs.open(), non voglio preoccuparmi di quale versione del kernel mi trovo. Voglio solo un descrittore di file.

Ci sono vantaggi in ogni modo. L'integrazione offre funzionalità "più recenti" a scapito della compatibilità con le versioni precedenti. Mentre l'astrazione fornisce stabilità rispetto alle chiamate "più recenti". Anche se è importante notare che è una questione di priorità, non di possibilità.

Da un punto di vista comune, senza una ragione davvero valida, l'astrazione è sempre migliore in un sistema complesso. Ad esempio, immagina se ha fs.open()funzionato diversamente a seconda della versione del kernel. Quindi una semplice libreria di interazione del file system dovrebbe mantenere diverse centinaia di metodi "open file" (o blocchi probabilmente). Quando è uscita una nuova versione del kernel, non saresti in grado di "aggiornare", dovresti testare ogni singolo pezzo di software che hai usato. Il kernel 6.2.2 (falso) potrebbe semplicemente rompere il tuo editor di testo.

Per alcuni esempi del mondo reale, OSX tende a non preoccuparsi di interrompere lo spazio utente. Mirano a "integrazione" piuttosto che a "astrazione" più frequentemente. E ad ogni importante aggiornamento del sistema operativo, le cose si rompono. Questo non vuol dire che un modo sia migliore dell'altro. È una scelta e una decisione di progettazione.

Ancora più importante, l'ecosistema Linux è pieno di fantastici progetti open source, in cui persone o gruppi lavorano al progetto nel loro tempo libero o perché lo strumento è utile. Con questo in mente, il secondo smette di essere divertente e inizia a essere un PIA, quegli sviluppatori andranno da qualche altra parte.

Ad esempio, ho inviato una patch a BuildNotify.py. Non perché sono altruista, ma perché uso lo strumento e volevo una funzione. È stato facile, quindi qui, avere una patch. Se fosse complicato o ingombrante, non userei BuildNotify.pye troverei qualcos'altro. Se ogni volta che uscisse un aggiornamento del kernel il mio editor di testo si rompesse, avrei semplicemente usato un sistema operativo diverso. Il mio contributo alla comunità (per quanto piccolo) non continuerebbe o esisterebbe, e così via.

Quindi, la decisione di progettazione è stata presa per astrarre le chiamate di sistema, in modo che quando lo faccio fs.open()funzioni. Ciò significa mantenere la popolarità guadagnata fs.openmolto tempo dopo fs.open2().

Storicamente, questo è l'obiettivo dei sistemi POSIX in generale. "Ecco un insieme di chiamate e valori di ritorno attesi, ti capisci a metà." Ancora una volta per motivi di portabilità. Perché Linus scelga di usare quella metodologia è interno al suo cervello e dovresti chiedergli di sapere esattamente perché. Se fossi in me, sceglierei l'astrazione piuttosto che l'integrazione su un sistema complesso.


1
L'API per userspace, l'API 'syscall', è ben definita (in particolare il sottoinsieme POSIX) e stabile, perché rimuovendo qualsiasi parte di essa si romperà il software che le persone potrebbero aver installato. Ciò che non ha è un'API del driver stabile .
pjc50,

4
@FaheemMitha, è il contrario. Gli sviluppatori del kernel sono liberi di interrompere l'API del driver ogni volta che lo desiderano, purché riparino tutti i driver nel kernel prima della prossima versione. Sta rompendo l'API dello spazio utente o persino facendo cose non API che potrebbero interrompere lo spazio utente, producendo reazioni epiche da Linus.
Segna l'

4
Ad esempio, se qualcuno decide di modificarlo restituendo un codice di errore diverso da ioctl () in alcune circostanze: lkml.org/lkml/2012/12/23/75 (contiene imprecazioni e attacchi personali allo sviluppatore responsabile). Quella patch è stata respinta perché avrebbe rotto PulseAudio, e quindi tutto l'audio sui sistemi GNOME.
pjc50,

1
@FaheemMitha, in sostanza, def add (a, b); ritorna a + b; end --- def add (a, b); c = a + b; ritorno c; end --- def add (a, b); c = a + b +10; ritorno c - 10; fine - sono tutti la "stessa" implementazione di add. Ciò che lo rende così arrabbiato è quando la gente aggiunge (a, b); ritorno (a + b) * -1; end In sostanza, cambiare come funzionano le cose "interne" al kernel è ok. La modifica di ciò che viene restituito a una chiamata API definita e "pubblica" non lo è. Esistono due tipi di chiamate API "private" e "public". Sente che le chiamate API pubbliche non dovrebbero mai cambiare senza una buona ragione.
coteyr,

3
Un esempio non di codice; Vai al negozio, compri 87 ottani di gas. Tu, come consumatore, non "ti preoccupi" della provenienza del gas o di come è stato elaborato. Ti importa solo di prendere il gas. Se il gas ha subito un diverso processo di raffinazione, non ti interessa. Sicuramente il processo di raffinazione può cambiare. Esistono anche diverse fonti di petrolio. Ma quello che ti interessa è ottenere gas ottano 87. Quindi la sua posizione è cambiare le fonti, cambiare le raffinerie, cambiare qualsiasi cosa, purché ciò che esce dalla pompa sia un gas a 87 ottani. Tutte le cose "dietro le quinte" non contano. Finché c'è 87 ottano di gas.
coteyr,

8

È una decisione e una scelta progettuale. Linus vuole essere in grado di garantire agli sviluppatori dello spazio utente che, tranne in circostanze estremamente rare ed eccezionali (ad esempio legate alla sicurezza), i cambiamenti nel kernel non danneggeranno le loro applicazioni.

I pro sono che gli sviluppatori di userspace non troveranno il loro codice improvvisamente rompersi sui nuovi kernel per ragioni arbitrarie e capricciose.

I contro sono che il kernel deve mantenere per sempre il vecchio codice e le vecchie syscalls ecc. (O, almeno, molto oltre le loro date di scadenza).


Grazie per la risposta. Sei a conoscenza della storia di come si è evoluta questa decisione? Sono a conoscenza di progetti che hanno una prospettiva leggermente diversa. Ad esempio, il progetto Mercurial non ha un'API fissa e può e interrompere il codice che si basa su di esso.
Faheem Mitha,

No, scusa, non ricordo come sia successo. Puoi inviare un'e-mail a Linus o LKML e chiederglielo.
Cas

2
Mercurial non è un sistema operativo. L'intero punto di un sistema operativo è abilitare l'esecuzione di altri software su di esso e interrompere tale altro software è molto impopolare. In confronto, Windows ha anche mantenuto la compatibilità con le versioni precedenti per molto tempo; Il codice Windows a 16 bit era obsoleto solo di recente.
pjc50,

@ pjc50 È vero che Mercurial non è un sistema operativo, ma a prescindere, ci sono altri software, anche se solo script, che dipendono da esso. E può potenzialmente essere rotto da modifiche.
Faheem Mitha,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.