Anteprima
Vedrai una selezione di 4 pagine su 12
Appunti di Sistemi operativi Pag. 1 Appunti di Sistemi operativi Pag. 2
Anteprima di 4 pagg. su 12.
Scarica il documento per vederlo tutto.
Appunti di Sistemi operativi Pag. 6
Anteprima di 4 pagg. su 12.
Scarica il documento per vederlo tutto.
Appunti di Sistemi operativi Pag. 11
1 su 12
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

API POSIX

Le api posix sono una serie di librerie che contengono delle system call

standard che ogni sistema operativo deve implementare. Chi implementa i

sistemi operativi deve creare queste system call, ma sono queste system call si

va a mettere un layer di librerie in modo che posso rendere anche un

programma portabile. Quindi creando questo layer di librerie, che appunto sono

le librerie posix, posso utilizzare la funzione di questa libreria sia su un sistema

operativo Windows che su un sistema Mac. Le librerie, quindi, nascondono i

dettagli delle implementazioni delle system call, mentre le funzioni di

interfaccia offerte dalle librerie sono uguali dappertutto e proprio per questo

motivo non utile utilizzare la system call.

CREAZIONE DI UN PROCESSO

La creazione di un processo è una delle operazioni più importante di un sistema

operativo. Ogni sistema operativo possiede un’opportuna system call

(chiamata di sistema) di creazione di un processo. La tipica system call che

crea un processo è la fork(). Ogni processo è creato a partire da un altro

processo, il processo che ne crea un altro è detto processo padre, mentre il

processo creato è detto processo figlio. poiché ogni processo può a sua volta

creare altri processi, nel sistema si crea un albero di processi. Gli eventi che

creano un nuovo processo sono:

Start up del sistema operativo

 Esecuzione di un comando o di un programma

 Chiamata di un’opportuna system call in un programma

Un processo termina dopo l’esecuzione dell’ultima istruzione del suo codice,

oppure quando viene chiamata una system exit(). Sarà poi il sistema operativo

a provvedere a rimuovere le risorse che erano state allocate al processo

terminato. I dati in output del processo terminato possono essere inviati al

processo padre, se questo è in attesa per la terminazione del processo figlio

(wait()). In alcuni sistemi, un processo può ‘uccidere’ un altro processo

appartenente allo stesso utente con un’opportuna syscall kill o abort. Il sistema

stello può decidere anche di ‘uccidere’ un processo se:

Il processo sta usando troppo risorse

 Il suo processo padre è morto. In questo caso si verifica una terminazione

 a cascata

SCHEMA STANDARD DI UNA FUNZIONE FORK. FALLIMENTO DI UNA FORK

#include <unistd.h>

#include <stdio.h>

Int main(){

pid_t ret = fork(); // valore restituito di fork()

if (ret < 0){ // entry point (posizione successiva alla fork)

/* se il PID del figlio è minore di 0 è errore. Una fork fallisce quando non è

possibile creare un processo, questo accade quando non c’è più spazio in

memoria per creare nuovi processi */

} else if (ret == 0){

// codice figlio

} else{

// codice padre (PID>0)

}

// codice condiviso da padre e figlio

return 0;

}

Quando si esegue questo codice non c’è modo di sapere se verrà eseguito

prima il padre o il figlio.

Una fork fallisce quando non è possibile creare un processo, e tipicamente

questo accade quando non c’è più spazio in memoria per creare nuovi processi.

Esiste infatti una forma di attaccato chiamata fork bomb.

Schema fork bomb:

#include <unistd.h>

#include <stdio.h>

int main(){

while(1){

if(fork()<0){

printf(“Errore fork”);

}

return 0;

}

Ogni volta che io chiamo una fork, quello che accade è che viene creato un

processo.

PROCESSI ORFANI

Un processo orfano in un sistema operativo si verifica quando un processo

padre termina o viene terminato prima che il suo processo figlio rimane in

esecuzione anche se il genitore non esiste più per gestirlo. Quando un processo

padre termina volontariamente o a causa di un errore, tutti i suoi figli che sono

ancora in esecuzione diventano orfani. Ad esempio, se un’applicazione crea un

processo figlio per eseguire un’attività in background e poi termina

prematuramente, il figlio diventa orfano. Nei sistemi Unix/Linux i processi orfani

vengono automaticamente adottati dal processo con PID 1, ovvero init o il suo

equivalente moderno (come systemd). Questo garantisce che il sistema

continui a monitorare il processo orfano e che le risorse vengano rilasciate

correttamente una volta che il processo termina. Il processo init esegue il

‘reaping’ dei processi orfani chiamando la system call wait(), prevenendo

l’accumulo di processi zombie. Un processo orfano è ancora attivo e

funzionante, anche se il suo genitore non esiste più.

PROCESSI ZOMBIE

Un processo zombie in un sistema operativo è un processo che ha terminato la

sua esecuzione, ma la sua voce nella tabella dei processi rimane ancora

presente. Questo accade perché il processo padre non ha ancora letto lo stato

di uscita del figlio tramite una chiamata di sistema come wait().

Caratteristiche dei processi zombie:

Terminato ma non rimosso: il processo ha completato la sua esecuzione e

 ha liberato le risorse (CPU, memoria), ma conserva un record nella

tabella dei processi.

Non attivo: i processi zombie non sono attivi e non consumano risorse di

 sistema significative, ma occupano una voce nella tabella dei processi,

che è una risorsa limitata.

Transitorio o permanente: normalmente, i processi zombie sono

 transitori, poiché il processo padre legge presto il loro stato. Tuttavia, se il

padre non esegue i wait(), i processi zombie possono accumularsi.

Cause dei processi zombie:

Mancata chiamata a wait(): quando un processo figlio termina, invia un

 segnale al processo padre. Se il processo padre non gestisce questo

segnale o non chiama la funzione wait(), il processo figlio rimane

processo zombie.

Bug nel codice del processo padre: se il padre non è stato programmato

 per gestire correttamente i figli terminati, si possono generare zombie.

Processo padre in stato di blocco: se il processo padre è bloccato o

 occupato in un’altra attività e non recupera lo stato de figli, si creano

processi zombie temporanei.

Come gestisce il sistema operativo i processi zombie:

Rimozione tramite wait(): quando il processo padre chiama la funzione

 wait(), il sistema operativo rimuove la voce del processo figlio dalla

tabella dei processi.

Adottati da init: se il processo padre termina prima del figlio, il processo

 figlio viene adottato dal processo con PID 1, che esegue

automaticamente il reaping per eliminare i processi zombie.

Rischi dei processi zombie:

Eccessivo accumulo: se il sistema accumula molti processi zombie, la

 tabella dei processi piò esaurirsi, impedendo la creazione di nuovi

processi.

Indicatori di problemi nel software: la presenza di zombie persistenti può

 indicare errori nella gestione dei processi da parte dell’applicazione.

SYSTEM CALL EXEC IN C

La system call fork ci permette di creare un nuovo processo. Quando vado ad

eseguire un programma attraverso la shell, quello che accade è che la shell

crea un suo clone attraverso l’utilizzo della fork e poi tramite la system call

exec va a cambiare il codice del suo clone, mettendo dentro il codice che noi

vogliamo eseguire; quindi, mentre la system call fork ci permette di creare un

nuovo processo, la system call exec ci permette di sostituire il codice in

memoria che questo processo esegue. Se non fa altro che andare a fare una

sovrascrittura sul codice e sui dati statici del processo, inizializza anche i

registri e lo stack. Ciò che rimane uguale però una volta eseguita la system call

exec, è il PID, il padre PID e anche i file descriptor legati a quel processo.

Possiamo quindi capire che con fork ed exec possiamo andare a creare una

gerarchia dei processi.

Ci sono varie varianti che chiamano questa system call:

1. execl(“/bin/programma”, arg0, arg1, …, NULL);

2. execlp(“programma”, arg0, arg1, …, NULL);

3. execv(“bin/programma”, argv);

4. execvp(“programma”, argv);

Le prime due prendono una lista di argomenti terminata da NULL e gli altri due,

invece, prendono un array di puntatori a stringhe sempre terminato da NULL.

La presenza della p nella funzione che chiamiamo va ad indicare che viene

utilizzata la variabile globale puff che permette di non specificare il percorso

assoluto dell’eseguibile che voglio sfruttare.

LIMITED DIRECT EXECUTION

Il Limited Direct Execution è un modello per creare un processo su una CPU.

Come prima cosa interviene il sistema operativo. Il suo compito è quello di

creare una entry nella process list che si occupa di mantenere le varie

informazioni di tutti i processi del sistema, non solo il processo che è in

esecuzione ma anche tutti i processi che sono nella coda di wait e nella coda di

ready. Il sistema operativo deve allocare memoria necessaria per il processo e

caricare il programma in memoria, dopo di che si occupa di inizializzare i

registri, lo stack e settare il program counter sul main (cioè, andare a mettere

l’indirizzo della prima istruzione da eseguire del processo). A questo punto si

può passare all’esecuzione del programma. Il sistema operativo abbandona la

CPU, e questa manda in esecuzione il processo. Una volta terminato tutto il

codice da eseguire, ci sarà l’istruzione di return, che altro non fa che ripassare

il controllo al sistema operativo, che andrà a liberare la memoria e l’entry nella

process list. In questo modello abbiamo due problemi principali:

1. non abbiamo un intervento da parte del sistema operativo nel caso in cui

un processo semplice vuole interagire con dell’hardware; infatti, deve

esserci il sistema operativo che si mette di mezzo.

2. Non abbiamo il concetto di time sharing, ovvero in questo caso abbiamo

un processo che viene eseguito; quindi, utilizza la CPU continuamente fin

quando non termina la sua esecuzione. Ma se questo processo fosse

molto lungo potrei avere altri processi in attesa che la CPU venga

liberata.

RING O MODALITA’ DI ESECUZIONE

Per creare un processo in modo sicuro ci aiutano i ring. I due più importanti

sono:

1. User mode: viene utilizzata per eseguire il codice utente.

2. Kernel mode: viene utilizzata per eseguire il codice del sistema operativo

Struttura dei ring:

Ring 0: esegue il codice del sistema operativo (kernel).

 Ring 1: esegue il codice del device drivers.

 Ring 2: esegue il codice del device drivers.

 Ring 3: esegue il codice delle applicazioni.

Inoltre, esiste un registro che tiene traccia del ring in cui mi trovo nel momento

dell’esecuzione di un programma. Possiamo quindi immaginare che questo ring

register possa contenere il valore

Dettagli
Publisher
A.A. 2024-2025
12 pagine
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher kekka..03 di informazioni apprese con la frequenza delle lezioni di Sistemi operativi e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli studi di Napoli Federico II o del prof Natella Roberto.