GNU/Linux >> Linux Esercitazione >  >> Linux

Kernel UNIX:kernel rientranti, sincronizzazione e sezioni critiche

Questo articolo fa parte della nostra serie di panoramiche del kernel UNIX in corso.

Nel precedente articolo di questa serie, abbiamo discusso della panoramica del processo UNIX.

Questo articolo spiega ad alto livello i kernel Reentrant, la sincronizzazione e le sezioni critiche dell'architettura del kernel UNIX.

Kernel rientranti

Come suggerisce il nome, un kernel rientrante è quello che consente l'esecuzione di più processi in modalità kernel in un dato momento e anche questo senza causare problemi di coerenza tra le strutture dati del kernel.

Bene, sappiamo che in un sistema a processore singolo può essere eseguito solo un processo in un dato istante, ma potrebbero esserci altri processi bloccati in modalità kernel in attesa di essere eseguiti.

Ad esempio, in un kernel rientrante un processo in attesa di una chiamata 'read()' può decidere di rilasciare la CPU a un processo che è in attesa di essere eseguito in modalità kernel.

Ora, si potrebbe avere in mente una domanda sul perché un kernel viene reso rientrante? Bene, iniziamo con un esempio in cui un kernel non è rientrante e vediamo cosa succede se consente l'esecuzione di più processi in modalità kernel.

Supponiamo che un processo sia in esecuzione in modalità kernel e stia accedendo a una struttura dati del kernel e ad alcuni valori globali ad essa associati.

  • Supponiamo che il nome del processo sia 'A'.
  • Ora 'A' accede a una variabile globale per vedere se il valore è diverso da zero (in modo che possa fare dei calcoli ecc.) e appena prima che tenti di usare questo valore in parte della sua logica, un cambio di contesto per processare ' B' succede.
  • Ora questo processo 'B' tenta di accedere al valore della stessa variabile globale e lo decrementa.
  • Si verifica un altro cambio di contesto e il processo "A" torna in esecuzione.
  • Poiché 'A' non sa che 'B' ha già decrementato il valore, tenta di utilizzare nuovamente questo valore.
  • Quindi ecco il problema, il processo 'A' vede due diversi valori della variabile globale poiché il valore è stato modificato da un altro processo 'B'.

Quindi, ora sappiamo perché un kernel deve essere rientrante. Un'altra domanda che potrebbe sorgere è come rendere rientrante un kernel?

In una nota di base, si potrebbero considerare i seguenti punti per rendere rientrante un kernel:

  • Scrivere funzioni del kernel che modificano solo le variabili locali (stack) e non alterano le variabili globali o le strutture dati. Questo tipo di funzioni sono anche conosciute come funzioni rientranti.
  • Aderire rigorosamente all'uso delle sole funzioni rientranti in un kernel non è una soluzione fattibile. Quindi un'altra tecnica utilizzata sono i "meccanismi di blocco" che assicurano che solo un processo possa utilizzare una funzione non rientrante in un dato momento.

Dai punti precedenti è chiaro che l'uso di funzioni rientranti e meccanismi di blocco per le funzioni non rientranti è il fulcro della creazione di un kernel rientrante. Poiché l'implementazione di funzioni rientranti è più correlata a una buona programmazione, i meccanismi di blocco sono legati al concetto di sincronizzazione.

Sincronizzazione e sezioni critiche

Come discusso in precedenza in un esempio, un kernel rientrante richiede l'accesso sincronizzato alle variabili globali del kernel e alle strutture dati.

Il pezzo di codice che opera su queste variabili globali e strutture di dati è noto come una sezione critica.

Se un percorso di controllo del kernel viene sospeso (mentre si utilizza un valore globale o una struttura dati) a causa di un cambio di contesto, nessun altro percorso di controllo dovrebbe essere in grado di accedere allo stesso valore globale o alla stessa struttura dati. Altrimenti potrebbe avere effetti disastrosi.

Se guardiamo indietro e vediamo perché abbiamo bisogno della sincronizzazione? La risposta è usare in modo sicuro le variabili globali del kernel e le strutture dati. Bene, questo può essere ottenuto anche attraverso operazioni atomiche. Un'operazione atomica è un'operazione che verrà sempre eseguita senza che nessun altro processo sia in grado di leggere o modificare lo stato letto o modificato durante l'operazione. Sfortunatamente, le operazioni atomiche non possono essere applicate ovunque. Ad esempio, la rimozione di un elemento da un elenco collegato all'interno del kernel non può essere eseguita un'operazione atomica.

Ora torniamo a concentrarci su come sincronizzare i percorsi di controllo del kernel.

Disabilitazione della prelazione del kernel

La prelazione del kernel è un concetto in cui il kernel consente la sospensione/interruzione forzata di un'attività e porta in esecuzione un'altra attività ad alta priorità che è stata in attesa delle risorse del kernel.

In termini più semplici è il cambio di contesto dei processi in modalità kernel in cui il processo in esecuzione viene sospeso forzatamente dal kernel e l'altro processo viene messo in esecuzione.

Se seguiamo la definizione, ci rendiamo conto che è proprio questa capacità del kernel (di anticipare quando i processi sono in modalità kernel) che causa problemi di sincronizzazione. Una soluzione al problema è disabilitare la prelazione del kernel. Questo assicura che il cambio di contesto in modalità kernel avvenga solo quando un processo che è attualmente in esecuzione in modalità kernel rilascia volontariamente la CPU e si assicura che tutte le strutture dati del kernel e le variabili globali siano in uno stato coerente.

Chiaramente disabilitare la prelazione del kernel non è una soluzione molto elegante e questa soluzione non funziona quando utilizziamo sistemi multiprocessore poiché due CPU possono accedere contemporaneamente alla stessa sezione critica.

Disabilitazione interruzione

Un altro meccanismo che può essere applicato per ottenere la sincronizzazione all'interno del kernel è che un processo disabiliti tutti gli interrupt hardware prima di entrare in una regione critica e li abiliti dopo aver lasciato quella regione molto critica. Anche questa soluzione non è una soluzione elegante, poiché nei casi in cui la regione critica è ampia, gli interrupt possono essere disabilitati per molto tempo vanificando lo scopo proprio di essere un interrupt e possono causare il blocco delle attività hardware.

Semafori

Questo è un metodo molto diffuso per fornire la sincronizzazione all'interno del kernel.

È efficace sia su sistemi monoprocessore che multiprocessore. Secondo questo concetto, un semaforo può essere pensato come un contatore associato a ciascuna struttura di dati e controllato da tutti i thread del kernel quando tentano di accedere a quella particolare struttura di dati.

Un semaforo contiene informazioni sul valore del contatore, un elenco di processi in attesa di acquisire il semaforo (per accedere alla struttura dati) e due metodi per aumentare o diminuire il valore del contatore associato a questo semaforo.

La logica di lavoro è la seguente:

  • Supponiamo che un processo voglia accedere a una particolare struttura di dati, controllerà prima il contatore associato al semaforo della struttura di dati.
  • Se il contatore è positivo, il processo acquisirà Semaphore, diminuirà il valore del contatore, eseguirà la regione critica e aumenterà il contatore Semaphore.
  • Ma se un processo trova il valore del contatore come zero, il processo viene aggiunto all'elenco (associato al semaforo) dei processi in attesa di acquisire il semaforo.
  • Ora, ogni volta che il contatore diventa positivo tutti i processi in attesa del semaforo cercano di acquisirlo.
  • Quello che acquisisce nuovamente diminuisce il contatore, esegue la regione critica e quindi aumenta il contatore indietro mentre gli altri processi tornano in modalità di attesa.

Evitare i deadlock

Lavorare con uno schema di sincronizzazione come Semaphores comporta l'effetto collaterale di "Deadlock".

Facciamo un esempio:

  • Supponiamo che un processo A acquisisca un semaforo per una particolare struttura di dati mentre il processo B acquisisca un semaforo per un'altra struttura di dati.
  • Ora, nel passaggio successivo, entrambi i processi vogliono acquisire il semaforo per le strutture di dati che vengono acquisite l'una dall'altra, ovvero il processo A vuole acquisire il semaforo che è già acquisito dal processo B e viceversa.
  • Questo tipo di situazione in cui un processo è in attesa che un altro processo rilasci una risorsa mentre l'altro è in attesa che il primo rilasci una risorsa è noto come deadlock.
  • I deadlock possono causare il blocco completo dei percorsi di controllo del kernel.

Questo tipo di deadlock è più frequente nei progetti in cui viene utilizzato un numero enorme di blocchi del kernel. In questi progetti diventa estremamente difficile determinare che una condizione di deadlock non si verificherebbe mai. In sistemi operativi come Linux, i deadlock vengono evitati acquisendoli in ordine.


Linux
  1. Linux:differenza tra spazio utente e spazio kernel?

  2. Linux – I diversi kernel Linux/unix sono intercambiabili?

  3. Ukuu Kernel Manager – Installa e aggiorna i kernel Linux in Ubuntu

  4. Principali differenze tra il kernel Linux e UNIX

  5. Parametri comuni di Init.ora e Unix, parametri del kernel Linux e relazione tra loro

Come installare e gestire più kernel su Arch Linux

Qual è la differenza tra Linux e Unix?

Kernel Linux e le sue funzioni

Come installare Rclone in Linux e Unix

Come configurare l'indirizzo IP statico in Linux e Unix

Storia di Unix e Linux