Copia della pagina https://etherpad.wikimedia.org/p/so.cs.unibo.it (20190604)

From Sistemi Operativi
Jump to navigation Jump to search
Renzo Operating Systems 2018-19 The Semester

Esercizio 2: siano dati tre processi "stampa" come segue: stampa(x):(x=A,T,R) process while (true) { synchro(x); print(x); } scrivere la fuzione synchro() facendo uso di semafori che consenta di avere in output una sequenza infinita di TARATATA (i.e. TARATATATARATATATARATATATARATATATARATATA......)

sempplificazione 1:
    stampa TARTARTARTAR
    
    while(1) {
        pre(x)
        stampa(x)
        post(x)
        
    semaphore st(1), sa(0), sr(0)

               case t: st.P(); break

               case a: sa.P(); break

               case r: sr.P(); break

    post(x): switch(x)

               case t: sa.V(); break

               case r: sa.V(); break

               case a: if (count % 4) st.V() else sr.V(); count ++; break

    x].P()x].P()

#define T=0, A=1, R=2
semaphore S[3] = (1,0,0)
bool first[3] = (true, true, true) 
int count = 0
 
synchro(x):
    if (not first[x]):
        if (x==A)
            if (count % 4)
                s[T].V()
            else s[R].V();
                count ++; 
        else s[A].V()
    first([x]) = false
    s[x].P()
    



-----------------------------------
Lezione di Mercoledi 15 Maggio 2019
-----------------------------------

esercizio 2018.05.28

 proc[x]: x='b',...,'z' 
 while True: 
     (c, string) = arecv(*)
     if c == None:
         asend(proc['a'], (None, string))
     else:

         print(x) 

         if (len(string) > 1) 

         asend(proc[string[0]], (x, string[1:])) 

    else

    asend(proc['a'], (x, None)


proc['a']:
     busy = false
     q = []
     While True: 
          (c, string) = arecv(*)
          if c == None:
               if Busy: 
                   q.add(string)
               else:
                    busy = True
                   asend(proc[string[0]], ('a', string)
          else if (string == None)
               if len(q) > 0:
                    string = q.dequeue()
                    asend(proc[string[0]], ('a', string)
               else
                     busy = False
          else:
                print(x)
                if (len(string) > 1) 
                      asend(proc[string[0]], (x, string[1:])) 
                else
                      asend(proc['a'], (x, None)


     e i clienti che vogliono stampare una stringa (non vuota) usano la funzione: 
def procstampa(s): asend(proc[s[0]], (None, s))


C1 - 12 Gennaio 2012

CONDITION: Stack<int, OK2EXIT> currentProcesses; // Quando un processo può uscire

PROCEDURE-ENTRY: void Enter(){
    currentProcesses.Push(new struct (GetPID(), new OK2EXIT()); 
}
 
PROCEDURE-ENTRY: void Exit(){
    int callerPID = GetPID();
    
    // Il processo si blocca    
    if (callerPID != currentProcesses.Top().PID)
        currentProcesses.FindByPID(callerPID).OK2EXIT.Wait();
    
    currentProcesses.Pop();
    
    // Il processo avverte il suo seguente di liberarsi
    if (currentProcesses.Count() > 0)
        currentProcesses.FindByPID(currentProcesses.Top().PID).OK2EXIT.Signal();
}

-----------

struct elem {pid_t pid, condition c}
S stack of struct elem

PROCEDURE-ENTRY: void Enter(){
    S.push({getpid(), c})
}

//esce solo l'"ultimo" entrato
PROCEDURE-ENTRY: void Exit(){
    mypid = getpid()
    {pid, c} = S.top()    //guarda chi è in cima allo stack (senza toglierlo)
    if (pid != getpid()) {
        myc = S.searchpid(mypid)
        myc.wait();
    }
    S.pop()
    if (! S.empty()) {
        {pid, c} = S.top()
        c.signal()
    }
}

----------//altro modo
//con i vettori invece dello stack

current = 0
pid_t pid[]
condition c[]

PROCEDURE-ENTRY: void Enter(){
    pid[current++]=getpid()
}
 
PROCEDURE-ENTRY: void Exit(){
    int i;
    mypid = getpid()
    for (i = 0; i < current; i++)
        if (pid[i] == mypid) break;
    if (i != current-1)
        c[i].wait()
    current--;
    if (current > 0)
        c[current-1].signal()
}

Esercizio g1 9 feb 2017

Una partizione contiene un file systems ext2. Nella directory radice all’interno della partizione ci sono tre directory: una chiamata “lost+found” (quella standard per il controllo di coerenza), una seconda chiamata “dev” ed una terza “dir”. La directory “dev” contiene un file speciale a caratteri denominato “null” che ha major number 1 e minor number 3. La directory “dir” contiene: * “null.phy” che è un link fisico al file “null” della directory “dev”. * “null.sym” è un link simbolico che punta a “../dev/null”. La directory “lost+found” è vuota. Spiegare come funziona il file system di tipo ext2 e mostrare il contenuto di tutte le strutture dati relative al caso qui illustrato

Superblocco radice=inode 1, size... last mount... //informazioni di controllo
bitmap dei blocchi (block 0 is MSB) 01110011000000000....
bitmap i-node 010011111000000000.......

i-node
1 - tipo d, modo 0755, root/root, nlink=5 size= 37 aree dati 3
4 - tipo d, modo 0755, root/root, nlink=2 size= aree dati 2
5 - tipo d, modo 0755, root/root, nlink=2 size= aree dati 1
6 - tipo d, modo 0755, root/root, nlink=2 size= aree dati 7
7 - tipo l, modo 0777, root/root, nlink=1 size=10 aree dati 6
8 - tipo c, modo 0755, root/root, nlink=2 rdev=1/3 aree dati ---- //special file, /dev/null

blocchi dati
1 (.,5)(..,1)       //lost+found
2 (.,4)(..,1)("null.phy",8)("null.sym",7)   //dir
3 (.,1)(..,1)("dir",4)("lost+found",5)(dev,6)
6 ../dev/null      //link simbolico
7 (.,6)(..,1)("null",8) //dev


//link fisico: più nomi allo stesso file
//link simbolico: stringa contente il path dell'altro file , si riferisce a quel file



-----------------------------------
Lezione di Mercoledi  8 Maggio 2019
-----------------------------------

es c1 11 sett 2017

monitor crossing

ok2dir[4];     //NESW
bool busy = false;

pe entra(int dir)
   if (busy)
      ok2dir[dir].wait();
   busy = true;


pe esci(int dir)
   busy = false;
   ok2dir[(dir + 1) % 4].signal();
   if (!busy)
      ok2dir[(dir + 2) % 4].signal();
   if (!busy)
      ok2dir[(dir + 3) % 4].signal();
   if (!busy)
      ok2dir[(dir + 4) % 4].signal();  //dir

//alternativa più compatta
pe esci(int dir)
   busy = false;
   for (i = 0; i < 4; i++)
   if (!busy)
      ok2dir[(dir + i + 1) % 4].signal();


   

//Esercizio C1 - 28 Maggio 2018

#define MAXPROC n

Condition: Ok2Entrare; //Quando un processo può entrare: quando entriesStack.Count() < MAXPROC
Condition: Ok2Uscire;  //Quando un processo può uscire: quando entriesStack.Top() == 1

Stack<int> entriesStack;

Procedure-entry: entra(int id)
{
    if (entriesStack.Count() >= MAXPROC)
    {
        Ok2Entrare.Wait();
    }
    
    entriesStack.Push(id);
    
    if (entriesStack.Count() == MAXPROC)
    {
        Ok2Uscire.Signal();
    }
}

Procedure-entry: esci(int id)
{
    while (entriesStack.Top() != id)
    {
        Ok2Uscire.Wait();
    }
    
    entriesStack.Pop();
    Ok2Uscire.Signal();
    
    if (entriesStack.Count() == 0)
    {
        Ok2Entrare.Signal();
    }
}



Stack<int> entriesStack;

int vid[MAX];
condition ok2esci[MAX]
int in = 0; out = -1;

Procedure-entry: entra(int id){

    if (in >= MAX)

    ok2entra.wait()

    vid[in++] = id;

    if (in < MAX)

    ok2entrai.signal()

    else

    out = MAX - 1;

}

Procedure-entry: esci(int id)

    for (i = 0; i< in; i++)

    if (vid[i] == id)

    break; //cerco l'indice di id nelvettore vid


    if (i != out)

    ok2esci[i].wait();


    if (out > 0) {

    out--; ok2esci[out].signal();

    } else {

      out--; in = 0; ok2entra.signal();

}






#define fromDX 1
#define fromSX 0
 
monitor bridge  {
  int carsOnBridge = 0;
  int shipping[2] = {0, 0}; //navi da entrambe le direzioni
 int status = NONE; //UP DOWN NONE
 int last2pass = 0;     //ultimo che ha passato il ponte
  int waitingC = 0;      //coda delle macchine che aspettano
  int waitingS[2] = {0,0};  //navi che stanno aspettando
   
  condition ok2ship[2]
  condition ok2drive
 
p.e. car_enter(int direction){
    if(carsOnBridge >= MAX || status == UP || waitingS[0] + waitingS[1] > 0 ){
        waitingC++;ok2drive.wait;waitingC--;}
    status = DOWN;
    carsOnBridge++
    if (carsOnBridge < MAX)
       ok2drive.signal();     //vanno tutte le MAX car che stanno aspettando
} 
p.e. car_exit(int direction){
    carsOnBridge--
    if (waitingS[0] + waitingS[1] == 0)

        ok2drive.signal();

    if(carsOnBridge == 0){
        ok2ship[0].signal(); ok2ship[1].signal();
    }
    if(carsOnBridge + waitingS[0] + waitingS[1] == 0) 

        status = NONE

}
 
p.e. ship_enter(int direction){

    if (bridge = DOWN || shipping[direction] != 0 || waitingC > 0)

        {waitingS[i]++; ok2ship[i].wait(); waitingS[i]--; }

    isBridgeUp = false;

    shipping[i] = 1;

 
p.e. ship_exit(int direction){
      shipping[i] = 0;
      if (waitingC == 0)

          ok2ship[i].signal()

    else  if(shipping[1-i] == 0)

    ok2drive.signal();

    if(carsOnBridge + waitingS[0] + waitingS[1] == 0) 

        status = NONE

}
    
Proposta di soluzione:
    
Monitor riempisvuota {
Int MAXPROCESSI;
Stack s;
condition ok2enter;
condition ok2exit;
 
p.e void entra(getPid()){ 
if( s.size() >= MAXPROCESSI)
    ok2enter.wait();
s.insert(getPid());
if(s.size() > MAXPROCESSI)
   ok2exit.signal() 
}
 
p.e void esci(getPid()){ 
if(s.size() > MAXPROCESSI && s.top == getPid()) {
   s.pop();
   ok2entra.signal()
}
else
   ok2exit.wait();
}

//Esercizio C1 - 12 Febbraio 2018

#define MAXCAR n

#define UP 1
#define DOWN 0 

Condition: Ok2TransitCars;     //Quando una macchina può transitare: quando il ponte è giù e sopra ci sono meno di MAXCAR
Condition: Ok2TransitBoats[2]; //Quando una barca può transitare: quando il ponte è su e nessuna altra barca sta trasitando nella sua stessa direzione

Condition: Ok2BridgeGoUp;      //Quando il ponte può alzarsi: quando non ho più auto che stanno transitando
Condition: Ok2BridgeGoDown;    //Quando il ponte può abbassarsi: quando non ho più barche che stanno transitando

bool boatGoingUp = false;
bool boatGoingDown = false;

int carsOnBridgeCounter = 0;

enum BridgeStates
{
    UP
    DOWN
}

int bridgeState = BridgeStates.DOWN;

Procedure-entry: car_enter(int direction)
{
    if (bridgeState != bridgeStates.DOWN || carsOnBridgeCounter >= MAXCAR)
    {
        Ok2TransitCar.Wait();
    }

    carsOnBridgeCounter++;
}

Procedure-entry: car_exit(int direction)
{
    carsOnBridgeCounter--;
    
    if (carsOnBridgeCounter == 0)
    {
        Ok2BridgeGoUp.Signal();
    }
    
    if (bridgeState == bridgeStates.DOWN && carsOnBridgeCounter < MAXCAR)
    {
        Ok2TransitCar.Signal();
    }
}

Procedure-entry: boat_enter(int direction)
{
    if (bridgeState != bridgeStates.UP || (direction == UP) ? /* 1 */ boatGoingUp : /* 0 */ boatGoingDown)
    {
        Ok2TransitBoats[direction].Wait();
    }
    
    if (direction == UP)
    {
        boatGoingUp = true;
    }
    else
    {
        boatGoingDown = true;
    }
}

Procedure-entry: boat_exit(int direction)
{
    if (direction == UP)
    {
        boatGoingUp = false;
    }
    else
    {
        boatGoingDown = false;
    }
    
    if (!boatGoingUp && !boatGoingDown)
    {
        Ok2BridgeGoDown.Signal();
    }
    
    if (bridgeState == BridgeStates.UP && (direction == UP) ? /* 1 */ !boatGoingUp : /* 0 */ !boatGoingDown)
    {
        Ok2TransitBoats[direction].Signal();
    }
}

-----------------------------------
Lezione di Giovedi 2 Maggio 2019
-----------------------------------

link sol proposte: http://so.v2.cs.unibo.it/wiki/index.php?title=Prove_svolte_e_soluzioni_proposte
esercizio del giorno GG/MM/AAAA : http://www.cs.unibo.it/~renzo/so/compiti/AAAA.MM.GG.tot.pdf

Esame 21/06/2018
2018.06.21.tot.pdf
Esercizio c.1

/* Monitor tmon */
 
#define MAX n
#define TERRAFERMA 1
#define ISOLA 2
#define NONE 0
 
condition ok2unload;
condition ok2load[2];
condition ok2ship;
condition empty;
 
int onboard = 0;
int onramp = 0;
int current_port = NONE;

Procedure entry: al_porto(int location)
{
    //arrivando
    current_port = location;
    ok2unload.signal();
    if (onboard > 0)
        empty.wait()
    ok2load[location].signal()
    if (onboard < MAX)
        ok2ship.wait()
    //partendo
    current_port = NONE;
}
 
Procedure entry: imbarca(int location)
{
    if (current_port != location || onramp > 0 || onboard >= MAX)
        ok2load[location].wait()
    onramp = 1;
}

Procedure entry: imbarcato(int location)
{
    onramp = 0; onboard++;
    if (onboard < MAX)
        ok2load[location].signal();
    else
        ok2ship.signal();
}
 
Procedure entry: sbarca(int location)
{
    if (current_port != location || onramp > 0)
        ok2unload.wait()
    onramp = 1;
}
 
Procedure entry: sbarcato(int location)
{
    onramp = 0; onboard--; 
    if (onboard == 0)
        empty.signal();
    else
        ok2unload.signal();
}
----------------------------

Esame 21/06/2018
2018.06.21.tot.pdf
Esercizio c.2 (da controllare)

#define N n // limite operazioni
semaphore mutex(1); // sezione critica
class lim_semaphore()
{
    semaphore plus;
    semaphore minus;
    int value;   //serve per significato
    
    init(x) : { value=x; plus.init(N+x); minus.init(N-x);}   //costruttore
    P() : { plus.P(); value--; minus.V(); }
    V() : { minus.P(); value++; plus.V(); }
}

class lim_semaphore()
{
    semaphore mutex(1);
    queue of semaphore okmin, okmax;
    int value;
    init(x) : { value=x; } // -N <= x <= N
    P() {
        mutex.P()
        if (value <= -N)  {
            s=semaphore(0);
            okmin.enqueue(s);
            mutex.V();
            s.P();
            free(s); //dealloco il semaforo
            mutex.V();
        } else if (okmax.length() > 0) {
            s = okmax.dequeue()
            s.V();
        } else {
            value--;
            mutex.V();
        }
    }
    V() {
        mutex.P()
        if (value >= N)  {
            s=semaphore(0);
            okmax.enqueue(s);
            mutex.V();
            s.P();
            free(s); //dealloco il semaforo
            mutex.V();
        } else if (okmin.length() > 0) {
            s = okmin.dequeue()
            s.V();
        } else {
            value++;
            mutex.V();
        }
    }
}



//soluzione proposta 

#define N n // limite operazioni
semaphore mutex(1); // sezione critica

class unsigned_semaphore()
{
    unsigned int value;
    semaphore plus;
    semaphore minus;
 
    P()
    {
        mutex.P();
        if(value < N)
        {
            if (value >= 0)
            {
                plus.P();
            }
            else
            {
                minus.V();
            }
            value--;
        }
        mutex.V();
    }
 
    V()
    {
        mutex.P();
        if(value > -N)
        {
            if (value <= 0)
            {
                minus.P();
            } 
            else
            {
                plus.V();
            }
            value++;
        }
        mutex.V();        
    }
}

------------------------------

Esame 21/06/2018
2018.06.21.tot.pdf
Esercizio g.1

//minimale
IC = 4,4,4     //massimo
COH = 1,1,1    //soldi in cassa
 
      MAX     ATT     RES  
P1 | 3,3,3 | 1,1,1 | 2,2,2
P2 | 3,3,3 | 1,1,1 | 2,2,2
P3 | 3,3,3 | 1,1,1 | 2,2,2

------------------------------

Esame 17/07/2018
2018.07.17.tot.pdf
Esercizio c.2

stack s;
semaphore mutex(1); // mutua esclusione
semaphore ok2consume(0);
 
Procedure entry: void push(int value){
    mutex.P();
    s.push(value)
    mutex.V()
    ok2consume.V()}
 
Procedure entry: int pop(){
    int value;
    ok2consume.P();
    mutex.P();
    value = s.pop();
    mutex.V();
    return value;}



-----------------------------------
Lezione di Mercoledi 17 Aprile 2019
-----------------------------------

NOTA: p.e. abbreviazione di procedure entry

2017.05.29.tot.pdf

Esercizio c.1:

condition okpartita
condition ok2chiama
condition ok2punteggio
condition ok2run[2][MAX]
int numpronti
int punteggio[2]
boolean partita=false, partitafinita=false
chiamati = []
contabandiera[2]

p.e. nuovapartita():
    punteggio[A]=punteggio[B]=numpronti=0; partita=true; okpartita.signal(); chiamati = [];
p.e. chiama(l):
    if numpronti<2*MAX ok2chiama.wait()
    chiamati = l; contabandiera[A] = contabandiera[B] = 0; winner = None
    for s in n:
        ok2run[A][s].signal(), ok2run[B][s].signal()
    ok2punteggio.wait()
    punteggio[winner++]
    if (max(punteggio) == 10)
       if numpronti<2*MAX ok2chiama.wait()
       partitafinita = true;
       for s  in A,B: for n in range(MAX): ok2run[s][n].signal()
    return punteggio
    
p.e. pronto(s, n):
    if !partita: okpartita.wait()
    numpronti++
    if numpronti>=2*MAX: ok2chiama.signal()
    if partitafinita: return 1
    else:
      if not(n in chiamati) ok2run[s][n].wait()
      numpronti--;
      return 0
    
allabandiera(s, n):
    contabandiera[s]++;
    if (Winner == None and contabandiera[s] == len(chiamati)): winner = s
    if (contabandiera[A] == len(chiamati) and contabandiera[B] == len(chiamati)):
       ok2punteggio.signal()


casi da discutere:    
se arrivano studenti a pronto prima che il prof dichiara nuovapartita succedono guai.

2018.09.19.tot.pdf

monitor dispatch
condition go_cart;
condition ok2station; //quando la stazione può caricare
current_station = None
//dizionario con chiave destinazione
luggages = {}

def countl(luggages):
    n=0
    for d in luggages:
        n += len(luggages[d]);

p.e. cartat(code):
    current_station = code //carrello arriva alla stazione
    if (code == BLQ || len(luggages[code]) > 0):
        ok2station[code].signal()
        go_cart.wait()
    current_station = None //il carrello sta viaggiando tra le stazioni

p.e. load(dst, owner):
    if (current_station != BLQ || countl(luggages) >= MAX):
        ok2station(BLQ).wait()
    luggages[dst].append(owner)
    if (countl(luggages) >= MAX)
        go_cart.signal()
    
p.e. unload(dst):
    if (current_station != dst || len(luggages[dst]) == 0)
    //lunghezza lista dei bagagli con questa destinazione
        ok2station(dst).wait()
    owner = luggages[dst].pop()
    if len(luggages[dst]) == 0)
        go_cart.signal()
    return owner




note:
se viene chiamata dispatch unload mentre il carrello non è lì deve bloccarsi.


2018.09.19.tot.pdf

Esercizio c.2: 

dati asend/arecv devo implementare lssend lsrecv

lssend(dst, msg):
    asend(dst, (MSG, myid(), msg))
    arecv(dst)

lsrecv(snd):
    asend(myid(), (TAG, myid(), NULL))    //mi mando un messaggio che riesco a riconoscere
    while True:
        (T, s, m) =  arecv(ANY)           //continua a ricevere finchè non ricevi il mio messaggio
        if (T != TAG) data.push(s, m)
        else break
    while (((s,m) = data.pop(snd) == NULL):   //cè il messaggio di src definito?
        (T, s, m) =  arecv(ANY)
        data.push(s, m)
    asend(s, (ACK, myid(), NULL)
    return m




nota:
    ls -> servizio di message passing sincrono e LIFO

2018.09.19.tot.pdf

Esercizio g.1:

P1(start=0): 5ms CPU, 5ms IO, 5ms CPU, 5ms IO, 2ms CPU
P2(start=4): 5ms CPU, 5ms IO, 5ms CPU, 5ms IO, 2ms CPU
P3(start=7): 5ms CPU, 5ms IO, 5ms CPU, 5ms IO, 2ms CPU

 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 
P|1 1 1|1 1|2 2 2|3 3 3|2 2|1 1 1|3 3|1 1|2 2 2*3 3 3|2 2|3 3|1 1|-|2 2|- - -|3 3|
I          |1 1 1 1 1|     |2 2 2 2 2|3 3 3 3 3|1 1 1 1 1|2 2 2 2 2|3 3 3 3 3|

r - - - - 2 - - 3 2 2 2 1 1 3 3 3 1 1 2 2 - - - 2 2 2 3 3 1 1
                      1 3 3

* 3 ha finito input output e 2 è ancora in esecuzione quindi cè da scegliere

----------------------------------
Lezione di Giovedi 11 Aprile 2019
----------------------------------



esame 16/07/2014 es c1:

BOUNDED BUFFER: (non richiesto dall'esercizio)
monitor bounded buffer
queue q;
condition oktoread; // q.length() > 0
condition oktowrite; // q.length() < MAX

procedure entry type read():
    if (q.length() == 0) oktoread.wait();   //controllo
    retval = q.dequeue();   //cambio lo stato
    //abilito coloro che possono essere abilitati dal cambiamento di stato
    oktowrite.signal() 
    return retval;

procedure entry void write(type elem):
    if (q.length() >= MAX) oktowrite.wait();   //controllo
    q.enqueue(elem);   //cambio lo stato
    oktoread.signal();   //abilito chi può essere abilitato

NOTE:
procedure entry ==> dichiarazione di funzioni (senza vedere l'implementazione dall'esterno)

Min-Max Monitor Bounded Buffer:

Queue Q;

//Condition: OKTOREAD: Q.Length > MIN
//Condition: OKTOWRITE: Q.Length < MAX

procedure entry: Type Read():
{

    if (Q.Length <= MIN) OKTOREAD.Wait(); //Controllo

    retval = Q.Dequeue(); //Cambio di stato

    OKTOWRITE.Signal(); //Abilito chi vuole scrivere


    return retval; //Qui sono sicuro perchè ne ho eliminato uno prima

}

procedure entry: void Write(Type elem):
{

    if (Q.Length >= MAX) OKTOWRITE.Wait() //Controllo

    Q.Enqueue(elem); //Cambio di stato

    if (Q.Length > MIN) OKTOREAD.Signal(); //Abilito chi vuole leggere

}

esame 16/07/2014 es c2:
Semaphore mutex = 1;

struct Elem
{
        Semaphore s;
        int counter;
}

sturct Elem V[]; //Vettore a dimensione variabile. I nuovi elementi sono initializzati a s = 0, counter = 0.

void RendezVouz(int n)
{
        mutex.P(); //Blocco, decremento di 1 (il processo si blocca se il semaforo vale 0)
        V[n].counter++;

        if (V[n].counter < n)
        {
                mutex.V(); //Rilascia, incrementa di 1
                V[n].s.P();
        }

        V[n].counter--;

        if (V[n].counter > 0)
                V[n].s.V();
        else
                mutex.V();       
}

esame 16/07/2014 es g1:
    0241302
(0)2   2200
(1)3   1111
(2)4   4442
(3)0   0333

    2 3 4 0 1 2 3 4  0  1  2  3
2           1 1 1 1  0  0  0  0
3           3 2 2 2  2  1  1  1
4           4 4 3 3  3  3  2  2
0           0 0 0 4  4  4  4  3


esame 17/07/2018 es c1:
    monitor delirium {
        condition ok2load;
        condition ok2pour[]; //one element per type
        int availablebeer[]; //one element per type
        queue req[] // one element per type
        queue reqt;
    
procedure entry void pour(type t, quantity c) {
    if (c > availablebeer[t]) {     //se non cè birra a sufficienza
        c -= availablebeer[t];      //togli la richiesta
        availablebeer[t] = 0;       //bidone vuoto
        req[t].enqueue(c);          //richiesta per quel tipo di birra
        if (req[t].length == 1) {   //se è il primo che fa la richiesta
            reqt.enqueue(t);        //coda tipo di birra
             ok2load.signal()};
        ok2pour[t].wait();          //
        req[t].dequeue();
        availablebeer[t] -= c;
   } else      //se cè birra a sufficenza già all'inizio
        availablebeer[t] -= c; // riempio il bicchiere
    

procedure entry type isempty() {
    if (reqt.length() == 0)
        ok2load.wait();
    return reqt.dequeue();

procedure entry loaded(type t, capacity c) {
    availablebeer[t] += c;
    while (req[t].length > 0 && availablebeer[t] >  req[t].head())

        ok2pour[t].signal();    //ok, risveglio il barista

    if (req[t].length > 0) {    //se la coda si è riempita troppo rispetto alla capacità del fusto

    reqt.enqueue(t);

    ok2load.signal();

    }

}

Monitor Delirum:

Condition: Ok2Load;
Condition: Ok2Pour[]; //Un elemento per Type

int availableBeer[]; //Un elemento per Type 

Queue requests[]; //Un elemento per Type
Queue pendingRequests;

Procedure entry: void Pour(Type t, Quantity c)
{
        if (c > availableBeer[t]) //Richiesta maggiore della birra disponibile
        {
                c -= availableBeer[t];
                availableBeer[t] = 0;
                
                requests[t].Enqueue(c);

                if (requests[t].Length == 1) //Risveglio un magazziniere solo se è la prima richiesta di questo tipo di birra
                {
                        pendingRequests.Enqueue(t);
                        Ok2Load().Signal();
                }

                Ok2Pour[t].Wait();
                requests[t].Dequeue(); //Quando ho ottenuto la birra che voglio, elimino la mia richiesta
        }

        availableBeer[t] -= c;
}

Procedure entry: Type isEmpty()
{
        if (pendingRequests.Length == 0)
        {
                Ok2Load.Wait();
        }

        return pendingRequests.Dequeue();
}

Procedure entry: Loaded(Type t, Capacity c)
{
        availableBeer[t] += c;

        while (requests[t].Length > 0 && availableBeer[t] > requests[t].head())
        {
                Ok2Pour[t].Signal();
        }

        if (requests[t].Length > 0) //serve per evitare che a causa di un magazziniere lento si accodino cosi tante richieste che mentre si sta caricando si svuota subito il fusto
        {
                pendingRequests.Enqueue(t);
                Ok2Load.Signal();
        }
}

----------------------------------
Lezione di Mercoledi 10 Aprile 2019
----------------------------------

Prima presentazione delle specifiche phase 2


----------------------------------
Lezione di Giovedi 04 Aprile 2019
----------------------------------

certificati per autenticazione
sa -> password
ha -> card (+ password) banca
è
autenticazione:
    con password -> sapete
        attaccata: tentativi, recuperare password, shoulder spying.
    alcuni siti autenticano senza https ma con http.
    come memorizziamo la password?
        
    la macchina come riconosce che ho inserito la password?
        si salva l'hash della password + salt il numero random in chiaro
            il salt (sale) serve per non avere un dizionario che ha
            come chiave la hash della password e come valore la password in chiaro.

https://haveibeenpwned.com/
     
     challenge e response
         nonce

Principi fondamentali della sicurezza:

    accesso mediato, qualisasi tentativo di accesso deve avere un (unico) mediatore per l'accesso;

    separazione dei privilegi, a particolari attività corrispondono particolari condizioni;

    least privilege: le applicazioni devono avere il minimo insieme di privilegi possibile per "sopravvivere";

    fail-safe defaults: nulla deve essere dato per automatico.


    capability
           diritti di accesso dati al soggetto
    control list

posix access control list

----------------------------------
Lezione di Mercoledì 03 Aprile 2019
----------------------------------

errore: trap, fattori esterni: interrupt

non esiste security by obscurity (sicurezza per oscurità)
es. macchina Enigma, sicura la chiave non l'algoritmo.
se perdiamo solo una chiave verrà decifrato solo ciò che viene criptato con quella chiave
se perdiamo la sicurezza dell'algoritmo abbiamo perso tutto il sistema.

niente di peggio della falsa sensazione di sicurezza
trust: sicurezza PERCEPITA (può non c'entrare nulla con la reale sicurezza fornita)

hardware
es: stacco il filo
es: leggo la ram
es: guardo un bit vietato studiandone i suoi sideffect

firmware
bios

attacchi al sistema operativo

applicazioni

umani: attacchi di ingegneria sociale (es. phishing)
"l'utente è l'anello più debole di questa catena".

attacchi attivi e passivi

attacchi interni ed esterni.

crittografia chiave simmetrica e chiave doppia (pubblica privata)
encryption - decryption

DES:
L R
L' = R'
R' = XOR (L, owf ( K, R ) )
encryption ripetuta per 16 volte

2^64
2^10 circa 1k (1000)
2^64 sono circa 1k^6
per generare le chiavi due numeri primi si usa una "buona probabilità" che siano primi.

RSA:
pq = n
(p-1)(q-1) = phi
mcm (e, phi) = 1
d*e = 1 mod n
m^e mod phi = Q
Q^d mod phi -> m
p= 11 q= 13
pq=143
(p-1)(q-1) = 120
ecnription e= 7
primo con 120
d = 103

autenticazione
autorizzazione

problemi che possono causare attacchi
buffer overflow
Record di Attivazione

es. https://xkcd.com/1354/

Attacchi TOCTOU (Time of Check, Time of Usage): sostituendo l'entità in uso dal momento del controllo al momento dell'uso.

Uova di Pasqua (Easter Eggs): funzionalità nascoste all'interno dei programmi.
Cavallo di troia (Trojan Horse): uova di Pasqua maligni.

- Rootkit: programmi che servono per fare, o mantenere, privilege escalation. Per fare ciò sfrutta: codice con bug, amministratore poco accorto, cattive abituidini di uso.
- Logical bomb: porzione di codice inserito in un programma apparentemente innocuo e che resta latente fino al verificarsi di particolari condizioni che "attivano la bomba", ad esempio in un programma di gestione di un database una bomba logica può attivarsi al raggiungimento di un certo numero di recordsalvati oppure quando viene cancellato un preciso dato
- Backdoor: (vedere WarGames per cultura personale) 
- Worm (vermi): sono interi programmi, non si diffondono come sideeffect di un operazione umana ma sono autonomi
              nella loro diffusione (autoreplicanti)
        primo warm esistito:
              Morris
              carmedy mellon
              1. sendmail : mta (mail trasport agent) comando in pipe
              2. rsh: (sun, solaris) carattere tilde ~ 
              3. finger
              Morris: "voglio fare un programma che fa in automatico questo tipo di attacco, se ci riesce copia se stesso
                 non faceva nessun danno volutamente, ma di fatto ha bloccato internet per un intera giornata
                 provare al mondo che era tutto molto fragile.
                 effetto a cascata saturata la rete
        dai worms ci si difende anche aggiornando il sistema
    - un virus non è un programma, modifica codice esistente, inserisce nel codice modalità non desiderate
               similitudine con i virus biologici.

    sicurezza assoluta non esiste

    non bisogna nascondere un problema di sicurezza

    aggiornamento continuativo e attento

    CERT centro controllo incidenti

    creato in risposta al worms di Morris



----------------------------------
Lezione di Giovedi 28 Marzo 2019
----------------------------------

prove scritte 28.05 18.06 15.07
prove pratiche 29.05 19.06 16.07
prova pratica:

    testo di 3 esercizi

    1 obbligatorio (da 20 punti) da svolgere in C

    (coinvolge le SYSCALL) dialoga con il sistema

    2 facoltativi (da 10 punti ciascuno), un ulteriore esercizio in C / Python / Bash

    si può consultare lo scibile umano purchè non sia una persona... e ci sia licenza PENSATI LIBERA.

    spiegare perchè abbiamo fatto quelle scelte.



---------------- da correggere:

    grub capire struttura di ext2 ext3 ext4

    con grub se cambiate il kernel e lo rimettete allo stesso path funziona

    grub update

    lilo (più silly) si teneva traccia di dove erano i blocchi dati del kernel

    poteva non funzionare perché era messo in blocchi dati differenti

    nuovi boot loader


    ext2.backup

    /usr/include/ext2_fs.h


    ghex /tmp/e2/e2


    hd 


----- end

Quando il File System trova un i-node valido ma non raggiungibile, lo posiziona nella cartella /lost+found, contrassegnandolo con il relativo valore dell'iNode (es. #12).


SICUREZZA
    Concetto, obiettivi teorici
Protezione
    meccanismi
Trust
    percezione di sicurezza

sicurezza, protezione, trust sono indipendenti tra loro
bisogna stare attenti che siano uguali per non avere allarmi esagerati o allarmi inesistenti.

principi chiave di sicurezza:
1. non esiste alcun sistema completamente sicuro
2. la sicurezza non è uno stato, ma un processo

    per ottenere l'ideale di sicurezza è un attività che dura nel tempo, non è instantaneo


componenti della sicurezza
hardware
software
humanware

Keep
It
Simple
Stupid

teorema del Kiss : Keep It Simple Stupid
ogni programma ha una probabilità non nulla di contenere errori
per ogni misura la probabilità di contenere errori

    es. 1x larghezza 2x sicura 

semplice il meccanismo di protezione

La probabilità di errori in un programma cresce in maniera più che proporzionale con la dimensione dello stesso.

garanzie della sicurezza: 
1. confidenzialità / riservatezza
2. integrità
3. continuità di servizio

ognuna di queste ha il suo tipo di errore
confidenzialità -> errore di disclosure, rivelate cose che non dovevano essere rivelate
continuità -> denial of service DOS
integrità -> alterazione

problemi pratici:
sniffing traffico di rete che viaggia in chiaro:
    viola confidenzialità
man in the middle
    integrità
kernel panic
    DOS

licenze:
    cosa è? un contratto che lega chi crea conoscenza con chi fruisce conoscenza
    stabilisce cosa si può fare e cosa non si può fare con la conoscenza
per la conoscenza c'è un AUTORE
non ha le stesse regole della MATERIA

categorie di licenze:
    licenze proprietarie
    licenze libere
           definizione di Richard Stallman
                1. utilizzata per qualsiasi scopo
                2. studiata
                3. copiata
                4. possibilità di divulgare la conoscenza modificata
            General Public Licence
                5. se la rilascia ulteriormente non può cambiare licenza






----------------------------------
Lezione di Mercoledì 27 Marzo 2019
----------------------------------

INODE: contiene tutte le informazioni del file.

    il nome serve solo per identificarlo.


VNODE: copia dell'INODE estesa con le informazioni

    di controllo per l'uso corrente.

    esempio: quanti processi hanno quel file aperto.

    VNODE condiviso con tutti i processi.


file system
come vengono realizzati?
come messi mem secondaria, partizionamenti nelle lezioni precedenti
e struttura interna
struttura dati globale (il super blocco)
struttura dati per gestione aree utilizzate e libere
aree dati dove mettere il "vero" contenuto

come realizzare il concetto di file
archivio
entità atomica gestita dal file system
memoria secondaria come vettore di blocchi
file - vettore di blocchi
file

    certo numero di blocchi in sequenza


allocazione:
1o metodo:
     contigua
         pro:
            comodo
            consente accesso diretto molto rapido
         contro:
            scomodo per file system dinamico
        visione teorica
2o metodo:
allocazione concatenata
    il blocco punta al blocco successivo
        pro:
           non c'è limite al file
        contro:
            scandire per la lettura finchè non si arriva al punto desiderato
3o metodo: allocazione indicizzata

    esempio: FAT File Allocation Table (MS-DOS)
       usa un tipo di concatenata
       gli indici non sono nel blocco ma fuori
       una tabella che ha tanti elementi quanti sono i blocchi dati del disco
       vettore di indici
           guadagni:
               c'è la potenza di 2
               se bisogna scandire bisogna scandire un solo blocco di FAT
               lo abbiamo mitigato nella ricerca
            problema:
                FAT è fragile, se si rompe la tabella non si riesce a ricostruire la trama
                
     esempio 2: BFFS Back File System (UNIX)
       usa un tipo di indicizzata
       quantità limitata di puntatori
       dopo i primi n (es. 10) ce ne sono altri 3
       l'ultimo punta ad altri puntatori di altri dati
       puntatore ai blocchi diretti
       puntatore ai blocchi indiretti
       puntatore ai blocchi 2 volte indiretti
       puntatore ai blocchi 3 volte indiretti
       diventa molto grande ma è limitata
          larghe porzioni del file riescono a essere recuperate

lista aree libere

    fat usa lo stesso fat per le aree libere


alberi
la directory contiene infomazioni sui file
FAT - nella directory
    pro:
        c
    contro:
        non si possono fare link simbolici
UNIX - lista di coppie, nome e numero di I-Node
    pro:
        più nomi che puntano allo stesso I-Node
    contro:
        c


FAT file system:
    Superblocco
    FAT (tabella)
    blocchi dati (il primo e' la dir radice)
    
UNIX (BFFS):
    superblocco
    Bit map i-node liberi
    Bit map blocchiliberi
    inode
    blocchi dati
    

----------------------------------
Lezione di Giovedì 21 Marzo 2019
----------------------------------
123415
111444
x22211
xx3335

123415
111115
x22222
xx3333
xxx444

----------------------------
kernel

|-----------------------|
|      FILE SYSTEM      |
|--------|-----|--------|
| CD-Rom | SSD | Dischi |
|--------|-----|--------|
----------------------------
HW (hardware)

file: ha contenuto e attributi
   contenuti dei file:
       data, payload
   attributi:
       nome, tipo, locazione, dimensione, tempi, proprietà
       tipo: (pratiche diverse devono essere gestite in modo diverso)
       locazione: (andare a recuperare il contenuto dei file, es. schedario biblioteca)
       proprietà: permessi e privacy.
       
contenuto:
    sapere l'"alfabeto" esempio ASCII, Unicode, UTF-8
    file binari: contengono dei numeri espressi in forma binaria, esempio
tipo:
    eseguibile, sorgente, oggetto, libreria, documento di testo, documento elettronico
    come riconoscere il tipo di un file?
    1. gestione appositamente fatta dal S.O.
           indicazione nel tipo
    2. associare delle estensioni
           esempio .c
           indicazione è nel nome
    3. magic number
           nel numero cè l'indicazione del tipo di file
           indicazione è nel contenuto
potrebbe essere a byte o a record
    blocchi contenenti diverse informazioni correlate tra loro. stesse informazioni di tante identità
        esempio rubrica con nome, telefono ecc.


HFS e HFS+:  il tipo è negli attributi del file. Due campi -- creatore e tipo
esempio: prima apro sull' editor e poi sul documento
oppure click sul documento e si apre direttamente il work processor

Estensione
MSDOS 8 caratteri + 3 caratteri per estensione
UNIX ha scelto una gestione minimale:
    tutti i file sono stringhe di byte
    riconosce solo gli eseguibili in maniera diversa
    scelta per la semplicità del sistema
    di fatto usa un mix tra suffissi e magic number

Approfondimento dal terminale:    
file *

<ripasso su domanda relativa al trashing (ruscamento)>
1234156710
4 frame

12121232323
2 frame

Workingset fa il conto dei frame necessari per elaborare i processi
finestra di n riferimenti
</ripasso su domanda relativa al trashing>

Viene utilizzato per creare dei fake file utilizzati solo per dare nomi alle cose. Lo fa il mondo Unix, che ha file speciali , "icone", per qualcos'altro. Ad esempio:c(rwxrwxrwx) e b(rwxrwxrwx) non hanno contenuto ma solo nome.

Tipo di accesso ai file:
    - accesso sequenziale: è normale leggerli dal primo all' ultimo carattere.
    - accesso diretto: non ha nella struttura logica una sequenza.
    - accesso a indici: per la locazione.
          L'indice è una chiave quindi non è detto che sia numerica.

k ora in un certo senso abbiam visto la visione di cos'è un file e l' altra unità chiave sono le directory


------------------ visione dell'utente --> directory sono logicamente contenitori in grado di contenere file o directory per poter creare una visione gerarchica di tutto questo l'insieme dei documenti , quindi l'utente bene o male le vede anche se non è programmatore anche l' utente che nella vita scrive solo lettere con il word processor sa che una cosa è scrivere con il word processor una cosa e l' eseguibile, sa andiamo a vedere invece la visione del programmatore perche nel file system ci saranno 3 visioni utente programmatore e implementatore.

    andiamo a vedere la visione del programmatore , vista nei file system che hanno a che fare con i file system (?)

quali sono le system call utilizzate per accedere ai file [open,read,write,close,seek,lseek] che consentono di aprire e leggere un file 
poi ci sono s call relative alla directory cioè crea directory , cancella directory, lista i contenuti della directory mkdir mk Amir (?)
poi ci sono le system call che servono per la scritture del file system per le informazioni di controllo quell che servono per cambiare i permessi chmod per cambiare la proprietà, chown per limitare la lunghezza o cambiare l'ampiezza di un file ecc.. e cose del genere mmm anche per cancellare , alcune di queste sono ben collocate mentre altre invece a seconda delle implementazioni  quando togliete l'ultimo nome cancellate il file
quando parliamo delle directory nella visione utente

le directory a seconda dell'implementazione del file system e anche nella visione utente possono essere a singolo livello, ad albero, a grafo aciclico o a grafo.

a singolo livello vuol dire che c'è un elenco di file nel file sys
directory:
    sono contenitori di file o ulteriori directory per costruire una gestione gerarchica dei documenti.
- a singolo livello:
     un elenco di file
        fat16 prima versione
- ad albero
     directory e sottodirectory
- a grafo aciclico
     alcuni nodi (tipo foglie) comuni a più percorsi
        usato da Unix
- a grafo
     il più generale

pk non farla a grafo completo?
perchè le visite del grafo sono molto più "difficili" per evitare di entrare in loop nei nodi non visitati
le visite dell'albero basta una ricorsiva


-------visione dell' implementatore:
    dal livello più basso, file system nelle unità di memorizzazione; un disco è visto al di sopra del livello di accesso, come una sequenza di blocchi. Questa visione è scomoda per andare ad implementare al di sopra la struttura del file system .
    ESIGENZE:
        1) non ci interessa vedere il disco come un'unica entità ma vorremmo vedere il disco come più file system esistenti
        2) il disco può servire per memorizzare le cose, ma anche contenere il Master Boot Record(MBR), caricamento del kernel da un'unità di memorizzazione secondaria-
       

     Metodi di partizionmento:

          MBR, Master Boot Record---->creato per il ottenere dal nome stesso il Master Boot Record (Primo blocco), al cui interno conteneva, vettore di 4 elementi, con inizio, ampiezza, (alias, geometria del disco, settore , cilindro, e testina)

                 [blocco pensato da 512Byte] 446, per codice di prima partenza, rimane spazio per 4 elementi da 16Byte, e l'ultimo è un magic number.

          GPT, GUID Partition Table---->quando si partiziona, se si perde la tabella delle partizioni, c'è una replica della tabella stessa in fondo. Esistono delle Magic Number per riconoscere che tipo di partizioni sono state caricate.


partizioni:
    1. MBR MasterBootRecord: blocco inizio e ampiezza , geometria del disco 4 possibilità di mettere 4 partizioni
    2. GPT la tabella delle partizioni è ripetuta alla fine del disco perchè critica




visione del programmatore
    


open close read write seek
    ^ livello file

crea directory, cancella directory, lista il contenuto della directory
mkdir, rmdir, getdents (non da usare)
    ^ livello directory

chmod, chown, truncate
    ^ livello struttura

visione implementatore:
      
livello basso
i file system vengono memorizzati in unità di memorizzazione secondaria
un disco è visto al livello driver come una sequenza di blocchi
è scomoda per 

    esigenze:

    1. più file system

    2. contenere non solo il filesystem ma anche altri elementi (es. master boot record)


Allocazione contigua: cominci da un file e continui con gli altri per trovarlo. Il problema sono le modifiche.

1.ISO9660
   masterizzato in CD, non lo si può modificare
   allocazione contigua, aspetto positivo: velocità di accesso, anche difetti
   i CD sono intrinsecamente ad accesso sequenziale
2.Puntatori
   dinamico, ma l'accesso diretto è scomodissimo
   allocazione concatenata (puntatore al successivo)
   effetto positivo: file lungo a piacere (unico vincolo n blocchi)
3. Allocazione indicizzata
   file di 1 blocco contiene puntatori ai blocchi, non dati
   esempio:
     se i blocchi sono da 4KB se voglio accedere a un punto random lo calcolo
  vincolo non riesco a memorizzare più di mille blocchi,
     risolvo con allocazione indicizzata multipla

quindi: contigua, concatenata, indicizzata che può essere multipla

potenza di due salta non potendo fare shift, divisione le risk

----------------------------------
Lezione di Mercoledì 20 Marzo 2019
----------------------------------

un algoritmo è a stack se:
  -  per ogni stringa di riferimento dato
  -  per ogni memoria
la situazione della memoria che ne risulta è un sottoinsieme di una memoria più ampia.

esempio:
        A      A
        B      B
        C      C
               D
se c'è page fault in ... indicato con [...]
[A,B,C] x      x
[D]     v      x
[altro] v      v
          t-1               t
        A      A         A      A
        B      B
        C      C
               D

[A,B,C] -> S_t = S_{t-1}
[D]     -> 
   D    A    A    A    A    A
   B    B    D    D    B    B
   C    C    C    C    D    C
        D         D         D
E notin {A, B, C, D}
   E    A    A    A    A    A
   B    B    E    B    B    B
   C    C    C    C    E    C
        E         E         E

Quanto posso contare sulla memoria virtuale?
Se ho una memoria reale di 1GB, posso avere una memoria virtuale di 100GB? la risposta è no.
continua sempre a fare richieste di pagine mancanti mentre altri processi gli tolgono altre pagine, fenomeno di trashing (circolo vizioso).

la memoria virtuale si può implementare con locazione globale o divisa per i processi

Trashing: Durante il periodo in cui sta aspettando una pagina perde le pagine a lui utili.  Quando usano più tempo per la paginazione che per eseguire codice utile.
quando avviene? quando il processo durante il periodo per recuperare una pagina utile a se stesso perde altre pagine utili.


esempio:
se abbiamo un programma che deve fare la somma di due numeri (A, B) e mettere il risultato in C.
load A
load B
sum A B
store C

il tempo di recuperare la pagina è dell'ordine dei millisecondi.
va in trashing se nel frattempo viene cancellata una pagina utile.

come si fa a vedere e risolvere il problema?
(grafico a schiena di balena, se si chiede troppo crollano)
come si valuta se un sistema sta andando in trashing?
si guarda il motivo per cui va in trashing
occorre che le pagine utili rimangono da quando succede pagefault a quando si risveglia.

ripeterà accessi a vari programmi

working set

starvation
ritarda almeno un processo, altrimenti ritardi tutti, altrimenti vanno tutti così lenti che per l'occhio umano sembra tutto bloccato.

-------------------------------

gestione degli altri dispositivi di Input/Output

i dispositivi di I/O sono tanti, diversi ma li possiamo caratterizzare in classi omogenee

unità a caratteri

    terminali

unità a blocchi

    floppi disk (il disco è capace di accedere a settori interi non singoli caratteri)


accesso sequenziale

    terminali, stampanti, cd (spirale, si cerca di arrivare al punto giusto)

accesso random



modalità di accesso sincrona o asincrona

masterizzatori hanno bisogno di accesso sincrono
il terminale è asincrono

condivisibile
non condivisibile

    (dipende dalle unità)


come migliorare accesso a I/O?
tecniche generali
-buffering

    memoria temporanea per ammortizzare diverse velocità di trasferimento della macchina e dell'unità di I/O

-caching

    (ha molte cose in comune)

    copia veloce di dati accessibili più lentamente

    si accede alla copia veloce se possibile


    doppia copia

    dopo richiede l'aggiornamento della memoria ufficiale

-spooling

    caso di buffering estremo

    esempio: bufferizzo tutto il file da stampare e poi mando tutto insieme.

-scheduling

    a turni, per condividere i dispositivi

    algoritmi di scheduling dipendono dal tipo di dispositivo


disco rotazionale

    ovvero magnetico

    molto comune

    chiamato disco ma è fatto da tanti dischi sovrapposti che ruotano all'unisono e cè un pettine delle testine, sul cui ci sono due testine magnetizzabili, a che velocità?

    dipende da ciò che abbiamo preso.

    questo pettine si muove e a seconda della posizione riesce a scrivere su tutti i dati che stanno a equal distanta su discord, per accedere ad altri le testine devono essere spostate.

    leggere, scrivere, seek (spostare le testine, meccanica, molto costosa dal punto di vista temporale)

    nel cilindro corrente, se si vuole cambiare un altro cilindro si può fare la seek


    quanto ci metto a leggere? quanto a scrivere?

    - ritardo rotazionale in media mezzo giro

    se prendiamo un disco con 5400 giri al minuto farà 90 giri al secondo. 5ms per arrivare lì in media.

    - tempo di trasferimento

    quanto ci mette dal disco al bus

    - tempo di seek

    non si riescono a limare più di tanto perchè coinvolgono parti meccaniche

    per migliorare di poco bisogna spendere molto di più


  il sistema operativo può intervenire sui tempi di seek,
  usando scheduling per minimizzare la seek
  
  come le mettiamo in ordine?
   in modo brutale fifo, proprietà fondamentale non dà starvation
   
   algoritmo dell'ascensore
   verso di percorrenza
   le ordiniamo in ordine crescente
   due code

Algoritmo look (algoritmo dell'ascensore), non applicato se non sugli ascensori reali
 
   richiesta(c)

    se direzione ^:

       se c > currcyl => aggiungi alla coda corrente

       altrimenti aggiungi alla coda dir opposta

    se direzione v:

       se c < currcyl => aggiungi alla coda corrente

       altrimenti aggiungi alla coda dir opposta


    ATTENZIONE!

     la disuguaglianza è una disuguaglianza stretta perchè altrimenti c'è starvation.

    Esempio: Se sono al 3' piano e mi arriva una richiesta per il 3' piano, la accodo altrimenti mi bloccherei lì.


=======
while 1:

    se direzione ^:

    se coda corrente vuota

    inverti le code, direzione = v

     altrimenti

      scegli il minimo nella coda corrente e fai seek

    se direzione v:

    se coda corrente vuota

    inverti le code, direzione = v

     altrimenti

      scegli il massimo nella coda corrente e fai seek


si può fare meglio?

    non dà starvation

    ma le richieste sono distribuite lungo tutti i cilindri

    quelle centrali sono privilegiate

    invertiamo le code ma facciamo la seek al numero più basso della coda

    Algoritmo "Circular Look" (C LOOK)


    RAID disco

    se mi servono 10Tera e ho solo dischi da 1Tera con una velocità entro cui non riesco andare che faccio?

    prendo 10 dischi

    pro: più veloce complessivamente, restando fissa la tecnologia dei dischi.

    contro: è più fragile (ho maggiori possibilità che uno dei 10 dischi si rompa)


ogni disco ha il suo mirroring (copia di riserva)
time between failure (dovrebbero rompersi 2 dischi, dà sicurezza)
tempi di accesso

    lettura (metà in una metà nell'altra, migliora)

    scrittura (devo aggiornare entrambe le copie, peggiora)


più sistemi di RAID (noi studiamo 4 teorico e 5, 6 reali)

tempi medi di guasto

bit di parità per decidere se il dato è corretto o meno

esempio
10 tera in 10 dischi da 1 tera
mettiamone un 11 esimo che contiene il bit di parità di tutti e 10 i dischi
esempio con meno dischi 3, invece di 1 tera 4 bit
1011, 1101, 0111 | 0001 (disco di parità) 
se si rompe il disco di parità,
se se ne rompe un altro con la parità e tutti gli altri posso ottenere tutti gli altri
XOR: (in qualsiasi ordine) A xor B xor C = Parità, ma anche A xor C xor Parità = B
questo è RAID4

lettura:
scrittura:  serve 1 lettura e 2 scritture
il disco di parità diventa il collo di bottiglia

Parita(n) = Parita(n-1) XOR NuoviBit

RAID5
è RAID4 con parità a rotazione tra tutti i dischi
i blocchi ciclano in modo che la parità sia distribuita
e possiamo permetterci di perderci un disco, il sistema riesce a recuperare.
serve per guasti hardware

RAID6
meno applicato più costoso, ha due dischi di parità
e tollera 2 dischi rotti
più pesante l'algoritmo
più costoso.
valido se quello che stiamo facendo è altamente critico

dischi allo stato solido
molti diffusi oggi
numero finito di cicli di scrittura
si scrivono a livello di blocco, si cancellano a livello di banco
possiamo scrivere solo in area vuota, blank
blocchi molto grandi nell'ordine delle 256 pagine
dovremmo riscriverle tutte per modificare anche solo una sola pagina...

trim: il sistema dice che quelle aree non gli servono

write amplification





--------------------------------
Lezione di Giovedì 14 Marzo 2019
--------------------------------

Cravatta di oggi: "Terrazza del caffè la sera, Place du Forum, Arles" di Vincent van Gogh, al limite tra creatività e pazzia.

Memoria virtuale:
    paginazione su richiesta = usa la paginazione
    due bit in più nell'indirizzo delle pagine
    bit di validità
    (nelle lezioni prossime) -> bit dirty (pagina è stata modificata)

Se un processo tenta di accedere a :
    |00000000000000000010|000000000000| (pagina 2, offset 0)
    la MMU cerca l'elemento 2 nella tabella delle pagine
    
    2 casi:

    bit V acceso: okay la MMU risolve l'indirizzo (hardware) 

    bit V spento: si genera un PAGE FAULT

    .... e' una trap (interrupt software)

il kernel riconosce che manca la pagina, carica da disco la pagina in un frame libero, aggiorna la tabella delle pagine (l'elemento 2 ha ora indirizzo di frame valido e bit V=1) e riattiva il processo.

PAGER:

    aspetta richiesta di pagine mancante P

    cerca un frame libero F

    se non c'e':

    cerca il frame "meno utile" G (qua intervengono gli algoritmi di rimpiazzamento)

    dichiara G invalido

    se G e' dirty salva G su disco

    F <- G

    dichiara F occupato

    carica la pagina nel frame F

    metti F nella tabella delle pagine all'indice P

    dichiara F valido

    riattiva il processo


Algoritmi di rimpiazzamento: scelgono la pagina meno utile

stringa di riferimenti :

    sequenza dei # pagina acceduti


    [ # pagina : 20 bit | offset : 12 bit ] 

hp scolastica pagine 256 byte
se fossero 8 bit per # pagina
 dato un processo che acceda agli indirizzi
 aa7e 3432 9053 aaff cc21 3442
 la stringa dei riferimenti risulta essere:
 aa 34 90 aa cc 34

prendiamo la stringa di riferimenti di accesso alle pagine:

 5 2 1 0 2 4 2 3 0 4 2 4 0 1 0 2 1 5 2 1
FIFO
(5)5 5(0)0 0 0(3)3 3(2)2 2 2 2 2 2(5)5 5      (1o frame)
 x(2)2 2 2(4)4 4(0)0 0 0 0(1)1 1 1 1(2)2      (2o frame)
 x x(1)1 1 1(2)2 2(4)4 4 4 4(0)0 0 0 0(1)     (3o frame)
 ^ ^ ^ ^   ^ ^ ^ ^ ^ ^     ^ ^     ^ ^ ^ (15 Page faults)
politica FIFO  = la pagina vittima e' quella da piu' tempo in memoria

(anomalia di BELADY)
 4 3 2 1 4 3 5 4 3 2 1 5
 
(4)4 4(1)1 1(5)5 5 5 5 5
 x(3)3 4(4)4 4 4 4(2)2 2
 x x(2)2 2(3)3 3 3 3(1)1
 ^ ^ ^ ^ ^ ^ ^     ^ ^  (9 page fault)
    
(4)4 4 4 4 4(5)5 5 5(1)1
 x(3)3 3 3 3 3(4)4 4 4(5) 
 x x(2)2 2 2 2 2(3)3 3 3
 x x x(1)1 1 1 1 1(2)2 2
 ^ ^ ^ ^     ^ ^ ^ ^ ^ ^(10 page fault)
 
non è detto che con una memoria abbiamo meno page fault
si chiama anomalia di Belady.

Least Recent Used (LRU) quella meno recentemente utilizzata
 5 2 1 0 2 4 2 3 0 4 2 4 0 1 0 2 1 5 2 1
LRU
(5)5 5(0)0 0 0(3)3 3(2)2 2(1)1 1 1 1 1 1
 x(2)2 2 2 2 2 2 2(4)4 4 4 4 4(2)2 2 2 2
 x x(1)1 1(4)4 4(0)0 0 0 0 0 0 0 0(5)5 5
 ^ ^ ^ ^   ^   ^ ^ ^ ^     ^   ^   ^    (12 Page faults)


MIN = la pagina vittima e' quella acceduta in un futuro piu' remoto
fatta a posteriori per calcolare quanti sono i page faults minimi.
 5 2 1 0 2 4 2 3 0 4 2 4 0 1 0 2 1 5 2 1
MIN
(5)5 5(0)0 0 0 0 0 0 0 0 0 0 0 0 0(5)5 5
 x(2)2 2 2 2 2(3)3 3(2)2 2 2 2 2 2 2 2 2
 x x(1)1 1(4)4 4 4 4 4 4 4(1)1 1 1 1 1 1
 ^ ^ ^ ^   ^   ^     ^     ^       ^    (9 Page faults)
questo è "ideale", ma non è un algoritmo che si può usare durante l'esecuzione.

LRU e MIN non soffronto della anomalia di Belady.

(esempio di registro a scorrimento per LRU approssimata)
se modifico ogni 1ms
[10000000] = 128   1ms
[01000000] = 64    2ms
[00100000] = 32    3ms
[00010000] = 16    4ms
[10010000] = 144   5ms
.. questo e' solo un esempio e lo capirete solo se avete partecipato alla lezione.

Def: Un algoritmo di rimpiazzamento A e' a stack sse:
    Per ogni sequenza s, per ogni istante di tempo t,
    chiamato State(A,n,s,t) l'insieme delle pagine in memoria al tempo t
    applicando l'algoritmo A alla stringa s data una memoria di n frame,
    per ogni n,m | n < m    State(A,n,s,t) ⊂ State(A,m,s,t)
    
Teorema:
Se un algoritmo è a stack non può soffrire dell'anomalia di Belady
Dim:
    Per la anomalia di Belady dobbiamo avere un page fault nella memoria più grande che non c'è nella memoria più piccola. Non è nel complementare di quelle usate.

MIN e' a stack: induzione su t

    caso base :

    t=0 Stato(n,t) vuoto Stato(n+1,t) vuoto

    caso induttivo :

    caso in cui l'accesso fuori da entrambe

    caso in cui si prende quello fuori dalla piccola ma dentro la grande

    caso di intersezione


Algoritmo di second chance o dell'orologio.

Reference bit: un bit che la MMU accende quando accede a quella pagina

le pagine vengono pensate come in una struttura circolare (lista) e quando avviene un page fault si scandisce la lista circolare, se il reference bit è vero lo si azzera e si passa alla pagina alla successiva, la prima che ha il bit spento è la vittima.
quando trovo una pagina vittima mi trovo a scandire da quella dopo la vittima.

non è a stack
chiede solo un bit alla MMU
in casi normali si comporta bene
è equo, è pratico.

 5 2 1 0 2 4 2 3 0 4 2 4 0 1 0 2 1 5 2 1
second chance
(5)5 5(0)0 0 0(3)
 x(2)2 2 2 2 2 2
 x x(1)1 1(4)4 4
 ^ ^ ^ ^   ^   ^ ( Page faults)

legenda:
    sottolineato = la lancetta è qui
    corsivo = il bit è a 1, la pagina è acceduta
    /*alg di rimpiazzamento: cercano quale tra le pagine in memoria è la meno utile ep ossa essere scaricata

Stringa di riferimenti: sequenza dei # pagina acceduti

Esempio: pagine da 256byte, ovvero  8 bit
AA7E 3432 9053 AAFF CC21 3432

la stringa dei riferimenti sarà: AA 34 90 AA CC 34 (prendo solo il i primi 16 bit)

prendiamo la stringa di riferimenti: 5 2 1 0 2 4 2 3 0 4 2 4 0 1 0 2 1 5 2 1

t0 x x x
t1 5 x x
t2 5 2 x
t3 5 2 1

Politica FIFO: scarico la pagina che da più tempo è in memoria (nell'es tolgo)

Non è detto che aumentando la capienza della "cache" la probabilita di page fault diminuisca (anomalia di Belady)

Least Recenty Used: butto fuori la pagina che da più tempo non è utilizzata*/

----------------------------------
Lezione di Mercoledì 13 Marzo 2019
----------------------------------

GERARCHIA DELLE MEMORIE

    Primaria: volatile, veloce (ordine del nanosecondo), solitamente poco capiente

    Secondaria: non-volatile, lenta (ordine del millisecondo), solitamente molto capiente

    Terziaria: non-volatile, molto lenta (tempi umani), solitamente molto capiente


Tutti e tre le tipologie danno accesso alla loro memoria attraverso una API composta da due istruzioni: load e store.

BINDING
Processo che lega gli indirizzi logici a quelli fisici. Come si può implementare?

    Momento della compilazione:

    Pro: non c'è bisogno di supporto hardware, valida opzione per sistemi embedded e kernel;

    Contro: il compilatore deve sapere quali altri processi stiano attivi (cosa quasi mai possibile), non è possibile avere più istanze dello stesso programma.

    Momento del caricamento: il compilatore genera un codice con indirizzi "relativi" ad un punto idale di caricamento, una volta caricato gli indirizzi fisici sono calcolati partendo dall'indirizzo relativo.

    Pro: non c'è bisogno di supporto hardware, posso avere più istanze dello stesso programma;

    Contro: poco flessibile, non gestisce processi che richiedono memoria a runtime.

    Momento dell'esecuzione:

    Pro: molto flessibile, permette di limitare il range degli indirizzi accessibili da un processo (restituendo un chiamata trap);

    Contro: c'è bisogno di un support hardware (componente chiamamo MMU, che "trasforma" gli indirizzi logici in fisici).


MEMORY MANAGER
Componente interno al kernel che gestisce la memoria, ovvero si occupa di:

    allocazione (fare spazio per...);

    gestione degli spazi liberi.


Frammentazione interna: la differenza fra la memoria assegnata ad un processo e quella effettivamente richiesta da quest’ultimo.
Frammentazione esterna: quando lo spazio libero complessivo nella memoria è sufficiente per soddisfare una richiesta, ma non è contiguo.

COME ALLOCARE
Contigua, viene assegnato un singolo intervallo continuo di memoria; 
Non contigua, vengono assegnati diversi intervalli di memoria.

L'allocazione non contigua è piu flessibile, ma richiede una MMU più complessa.

Binding statico
Binding dinamico

Partizionamento della memoria in blocchi fissi di varie dimensioni ("Allocazione a partizioni fisse").

    Pro: facile da implementare;

    Contro: limita il parallelismo, crea situazioni di frammentazione.


Quando muoiono i processi lasciano la parte vuota frammentata, è quindi necessario compattare di tanto in tanto la memoria per migliorare la località e risolvere la frammentazione esterna. Il costo di questa operazione è alto ed aumenta linearmente con la dimensione della memoria.

Per limitare la frammentazione esterna è possibile scegliere tra varie politiche per l'assegnazione di aree libere di memoria, quali:

    Best fit, si sceglie la più piccola area di memoria in grado di contenere la pagina (costa O(n), genera aree libere man mano più piccole);

    Worst fit, si sceglie la più grande area di memoria in grado di contenere la pagina (costa O(n));

    First fit, si sceglie la prima area di memoria in grado di contenere la pagina, partendo dall'inizio della memoria;

    Next fit, si sceglie la prima area di memoria in grado di contenere la pagina, partendo dall'ultima posizione precedentemente visitata (utilizzato in quanto distribuisce i blocchi su tutta la memoria, in modo più omogeneo del FF).


COME GESTIRE GLI SPAZI LIBERI
Esistono due modi per tenere la contabilità delle aree libere:

    liste, quando allochiamo si estrae un elemento dalla lista, quando il processo termina e la memoria viene rilasciata posso avere un elemento in più (se aggiungo una area libera isolata), un elemento in meno (se due aree libere si fondono in un'unica);

    mappe di bit, creaiamo aree di memoria (ad esempio 4K) e associamo un bit a 1 se sono occupate o un bit a 0 se sono libere.


PAGINAZIONE
Frame: area di memoria fisica grande come una pagina ed allineata
Pagina: area di memoria logica

queste unità hanno ampiezza 2-4KB, oggi più utilizzata è la 4KB, quindi servono 12 bit per indirizzarlo.
abbiamo 4095 byte buttati al massimo (?)
frame liberi a caso, presi da dove vuole
un processo ha bisogno di 10 pagine? bene può prendere 10 pagine dove vuole in memoria

Un indirizzo logico è formato da [ # pagina | offset ]

La MMU associa ad ogni processo un vettore degli indirizzi dei relativi frame, detto Tabella delle pagine.

Dove posizionamo le tabelle delle pagine?
Non in memoria centrale, sennò pago due volte il costo di una richiesta (uno per accedere alla TLB, una per accedere all'indirizzo).
Non nella MMU, che diventerebbe sì velocissima ma anche altrettanto costosa.

Soluzione:

    cerchiamo una via di mezzo, introduciamo nella MMU una Translation Lookaside Buffer (TLB), che salva pochi elementi (32-64) in modo da accederci velocemente. Ogni volta che si vuole risolvere un indirizzo di una pagina si controlla prima nella TLB, altrimenti si genera una chiamata trap (di tipo TLBMiss) e il sistema operativo ricarica la TLB. Nel caso ottimo il costo è O(1), ma sfruttando il principio di località temporale ciò è molto probabile. Non soffriamo di frammentazione esterna e solo nei casi limite abbiamo frammentazione interna.


Codice reentrant: le istruzioni vengono condivise su istanze diverse, mentre lo stack e i dati sono diversi (es. aree condivise per il codice eseguibile, aree riservate per i dati).

SEGMENTAZIONE
Aree di memoria di dimensione variabile che contengono informazioni omogenee. Ogni segmento è visibile dal programma (a differenza della pagina) e ha assegnato un nome (es. dati, testo, ecc.).

Un indirizzo logico è formato da [ # segmento | # pagina (interna al segmento) | offset ]

Vista del programma: indirizzo logico [ # segmento | # pagina | offset ]
Vista della memoria: indirizzo fisico

Loading dinamico: i processi richiedono di caricare ulteriore codice/librerie esterne a runtime.
Linking dinamico: i programmi non hanno all'interno tutte le funzioni che sono richiamate, ma esse vengono linkate alle relative librerie a runtime. Ciò facilita la manutenzione e la condivisione di librerie tra più processi (caricando una sola istanza della libreria a runtime).

Le funzioni utilizzate per il caricamento dinamico di librerie sono:

    dlopen(filename, flags), carica in memoria la libreria dinamica specificata;

    dlclose(handle), rilascia la libreria dinamica specificata.


---------------------------------
Lezione di Giovedì 7 Marzo 2019
---------------------------------

    Schedulazione in Sistemi in tempo reale: sistemi dove la correttazza del risultato dipende non solo dalla correttezza della computazione, ma anche dal tempo in cui il risultato è prodotto. Sistemi in tempo reale programmati con linguaggi ad hoc. Non vi sono fattori che potrebbero causare eventi probabili - solo determinismo.

    Lo scheduler è calcolato in fase non critica, quindi non ne sono aggiunti altri in fase critica.

    esempio: alettoni di aerei, tempo reale

    video, soft real time (al massimo crea fastidio ma non danni)

- TIPI DI PROCESSI REAL TIME E NON:
    1) PERIODICI: deadline dei processi = periodo pre-impostato (es termostato centrale nucleare)
    2) APEPERIODICI: Silenti, reagisce dopo un certo tempo dal fenomeno (es. Sistema antincendio).


Scheduler rate-monotonic: periodo più basso ha priorità più alta

- regola (Liu & Layland - preemptive) : https://en.wikipedia.org/wiki/Rate-monotonic_scheduling

    (nella regola, n: numero di processi)

    Utilizzando il 70% della CPU, si permette che tutti i processi arrivino a deadline.


- Earliest deadline first (EDF) - Introdotta durante la lezione del 27/2/2019

- Deadlock avviene, come detto ieri, sse il sistema ha risorse: non condivisibili, non prerilasciabili, non richiesta bloccante, attesa circolare (deve avvenire l'and delle 4 (ovvero: non condivisibili, non prerilasciabile, non richiesta bloccante, attesa circolare; come nella lezione di ieri)).

- detection, grafo di Holt che fotografa lo stato delle risorse al tempo t, la **riducibilita'** avviene lavorando su una copia di tale grafo.
  - prevection
  - avoidance, il sistema non puo produrre deadlock anche se questo puo peggiorare l'uso delle risorse
  - ignorare il deadlock

// vedere KNOT (insiemi non banali di nodi tali che l'insieme di raggiungibilità sia l'insieme stesso, (chiusura transitiva)).

sarebbe utile non avere deadlock bloccando una delle 4 (in modo opportuno)

------ DEADLOCK PREVENTION ------ 
- Attaccare la mutua esclusione:

    **mutua esclusione**, la risorsa non puo essere usata contemporaneamente => l'uso puo essere virtualmente **bufferizzato** (aggiungendo delle attese)

    Mutua esclusione per la stampante viene risolta con il meccanismo SPOOL di stampa crea stampanti virtuali sempre disponibili che bufferizzano le cose da stampare).

    In questo modo la stampante diventa condivisibile, ma richiede tempo.

    Non in tutti i sistemi è applicabile (es. stampanti banca che non hanno SPOOLER).

- Attaccare la richiesta bloccante: viene chiesto ai processi di fare allocazione globale.
- Attaccare attesa circolare: Utilizzare allocazione gerarchica. 
Le risorse vengono divise in gruppi di priorità (più preziose, meno preziose).
Es. Se ho risorse di priorità 3, e richiedo priorità 4, posso farlo.
Se da priorità 4 richiedo priorità 2, rilascio la 4, la 3, e poi posso prenderla. Non vengono creati cicli?
- Attaccare preemption: o c'è o non c'è, è possibile o meno salvare lo stato.

allocazione gerarchica a 
le frecce vanno solo a destra

   proc1
  ^        \
  |         \
 ___         v___          ___
|   |        |   |        |   |
| 1 |        | 2 |        | 3 |
|___|        |___|        |___|
  |            |            ^
  |            v           /
  ---------> proc2 -------
  
----DEADLOCK AVOIDANCE----


Algoritmo del banchiere: https://it.wikipedia.org/wiki/Algoritmo_del_banchiere

Dati: 
    N = numero di clienti
    IC = initial cash €$¥£ (monovaluta)
    c[i] = max credit to client i, credit (fido italiano)
    l[i] = sum leased to client i, prestito attuale
    
    COH = cash on hand (saldo di cassa) //COH = IC - SUM_{i=1,N} l[i]
    COH >= 0
    
    "Il denaro in cassa deve poter soddisfare almeno un cliente.
    Con il denaro restituito, si vede se se ne può soddisfare un secondo. " 
    E così via con gli altri clienti.
    Se ciò avviene lo stato è SAFE.
    
    s[1]...s[N] permutazione di 1,..,N
    AVAIL[1] = COH
    AVAIL[i+1] = AVAIL[i] + l[s[i]] (somma disponibile per ciascun cliente/processo) (con i = 1,...,N-1)
    si ha: per ogni i: AVAIL[i] >= (c[s[i]] - l[s[i]])
    
    in altre parole se definiamo n[i] = c[i] - l[i] (c - l = il massimo ancora richiedibile)
    si ha: per ogni i: AVAIL[i] >= n[i] 
    
    Ogni processo può chiedere: c[i]-n[i]
    
    C=Credito massimo richiedibile, c[p1], ...
    L=Credito richiesto, l[p1], ...
    N=Rimamente:  n[p1], n[p2], n[p3]
    N = C - L

-------------------------------------------   
    COH = 0
        C    L    N   AVAIL
    p1 10    5    5
    p2 8     2    6
    p3 4     4    0
    
    p3 4     4    0    0       //posso soddisfare p3 perchè mi chiede 0

       //ma ha già chiesto il suo massimo richiedibile

                           4          //la banca ha solo 4, non può consegnare 6 a p2 se lo richiedesse
    ^unsafe
-------------------------------------------   
    COH = 0
        C    L    N   AVAIL
    p1 10    5    5
    p2 8     6    2
    p3 4     4    0
    
    p3 4     4    0    0
    p2 8     6    2    4
    p1 10    5    5    10
    
    ^safe
    

       // AVAIL = AVAIL del processo che ha rilasciato + L del processo

-------------------------------------------

1° problema) Non si può pensare che esista un solo tipo di risorsa in un sistema (banchiere monovaluta, come negli esempi precedenti);
2° problema) Limite dell'algoritmo


"i greci chiamavano ADE l'inferno perchè Agenzia Delle Entrate era troppo lungo" cit.unknown

Banchiera Multivaluta:


Dati: 
    (I valori in grassetto non sono scalari ma vettori)
    valute non convertibili

    N = numero di clienti
    IC = initial cash [¥,€,$] //vettore con elemento in yen, in euro ed in dollaro 
    c[i] = max credit to client i, credit (fido italiano) //al cliente io concedo al massimo tot di ogni valuta
    l[i] = sum leased to client i, prestito attuale //al cliente i concedo tot di ogni valuta, in l[i] c'è la somma 
    //(somma prestata al cliente in ogni valuta)
    COH = cash on hand (saldo di cassa) //COH = IC - SUM_{i=1,N} l[i] (differenza vettoriale, tra stesse valute)
    COH >= 0 //per ogni valuta deve essere maggiore-uguale di 0 (ovvero, ogni componente del vettore COH deve essere >= 0)

    n[i] = c[i] - l[i]

    s[1]...s[N] permutazione di 1,..,N
    AVAIL[1] = COH
    AVAIL[i+1] = AVAIL[i] + l[s[i]] (somma disponibile per ciascun cliente/processo) (con i = 1,...,N-1)
    si ha: per ogni i: AVAIL[i] >= n[i] 
    //non è più una relazione di ordine totale, esistono elementi inconfrontabili (es. vettore [2,3] e vettore [3,2])
    //multibanchiere diverso da applicare separatamente per ogni valuta
    //complessità ((n^2)/2)

    COH = 0,2     // esempio 0 euro e 2 dollari
         C      L      N      AVAIL
    p1 10,5    5,3    5,2
    p2  8,4    6,2    2,2
    p3  4,4    4,2    0,2


Controllo se AVAIL (a quel passo) >= N(p1)|N(p2)|N(p3)


         C      L      N      AVAIL
    p3  4,4    4,2    0,2    0,2
    p2  8,4    6,2    2,2    4,4    //AVAIL(p2) = (L(p3)+AVAIL(p3))
    p1 10,5    5,3    5,2    10,6
    


    //SAFE


Esercizio g.1, http://www.cs.unibo.it/~renzo/so/compiti/2018.09.19.tot.pdf

Pa: (start t=0) cpu 5ms, io 5ms, cpu 5ms, io 5ms, cpu 2ms
Pb: (start t=4) cpu 5ms, io 5ms, cpu 5ms, io 5ms, cpu 2ms
Pc: (start t=7) cpu 5ms, io 5ms, cpu 5ms, io 5ms, cpu 2ms

tempo:
0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
CPU                                           *
 a a a|a a|b b b|c c c|b b|a a a|c c|a a|b b b|c c c|b b|a a|c c|x|b b|x x x|c c|
RQ
       b       c b   ba  ac    c   a
IO
          |a a a a a|     |b b b b b|c c c c c|a a a a a|b b b b b|c c c c c|   

Nota * : qui potevamo scegliere sia c che b
x = RQ (Ready Queue) vuota quindi lo scheduler lancia idle

---------------------------------
Lezione di Mercoledì 6 Marzo 2019
---------------------------------

"Non tutti i processi sono uguali: alcuni processi sono più uguali degli altri." cit. La fattoria di Renzo Davoli.

Esempio:
(M) processo per la gestione della mail
(V) processo per la riproduzione video

V è più intensivo e quindi avra una priorità maggiore rispetto a M nell'ordine di esecuzione.


SCHEDULER PREEMPTIVE
Tipo di implementazione nella quale lo scheduler sceglie il processo a priorità maggiore, estraendolo da un readyQueue. Ciò però crea starvation.

AGING
Ho due valori:

    priorità nominale

    priorità attuale


Allo stato iniziale la priorità attuale del processo equivale alla priorità nominale. Ogni volta che si attende la sua priorità attuale aumenta finchè non arriva il suo momento di gloria, quindi viene eseguito. Una volta terminato la sua priorità ritorna al valore nominale.

La readyQueue può essere vuota? Sì.

Se lo scheduler non deve gestire nulla:

    Opzione 1: si lancia un (finto) processo (che non viene accodato alla readyQueue) che genera un ciclo infinito, il quale termina quando si riceve un interrupt;

    Opzione 2: si mette la CPU in stato di WAIT (disponibile solo nei processori moderni).


SCHEDULER CON CLASSI DI PRIORITA
Tipo di implementazione nella quale lo scheduler sceglie la readyQueue con priorità più alta. Ogni funzionalità (es. multimedia, server, ecc.) ha una readyQueue associata.

SCHEDULER MULTILIVELLO
Tipo di implementazione nella quale si uniscono le caratteristiche degli scheduler precedenti. Ha classi di priorità per ogni tipo di processo, ed ognuna di queste classi è gestita utilizzando algoritmi specifici, in base alle esigenze.

Esempio:

    Priorità massima: multimedia (priorità statica);

    Priorità alta: demoni/server (FCFS);

    Priorità media: interattivi (RR);

    Priorità minima: batch (SRTF);

    Priorità infima: IDLE.

        

NICE
nice(int _inc)
nice(20)

System call che cambia la priorità del processo chiamante settandola al valore _inc. La priorità massima per processi utente é 0,la priorità massima per processi root é -20. Ogni valore di _inc fuori dal range {19(min), -20(max)} verrà clampato.

Maggiori informazioni: http://man7.org/linux/man-pages/man2/nice.2.html

----------------------------------------------

GESTIONE delle RISORSE

Problema: evitare che il sistema vada in deadlock.

Risorse possono essere:

    - Fisiche  (CPU, RAM, I/O, ...)

    - Logiche (descrittori di processi, ...)


Classificazione delle risorse:

- Fungibili: risorse possono essere intercambiali, una vale l'altra. (es. banconote da 10 euro)
- Non fungibili: risorse specifiche che non possono essere sostituite da altre. (es. bambino all'uscita della scuola)


Le risorse possono essere divise in classi. Due risorse appartenenti alla stessa classe sono equivalenti per il processo.

Le risorse possono essere assegnate 

    - Staticamente: il processo mantiene quella risorsa (es. descrittore del processo, a cui non si toglie il processo finché non è finito); 

    - Dinamicamente: i processi richiedono le risorse durante la loro esistenza, le usano una volte ottenute e le rilasciano una volta una volta che non sono più necessarie


- L'assegnazione delle risorse ai processi può essere bloccante o non bloccante

- La richiesta di risorse può essere singola o multipla: fa una richiesta alla volta per classe, o ne fa per classi multiple;

- Condivisibili (non seriali, utilizzabili insieme) o non condivisibili (seriali)

- Preemptable (es. allocazioni di memoria): se la posso salvare in modo da ripristinarla successivamente
- non preemptable: l'opposto della preentable.

Tutto ciò dipende dalla natura della risorsa.

Quando non c'è deadlock?
- Quando non c'è contesa nell'accesso, ovvero se la risorsa è condivisibile, altrimenti può esserci deadlock.

- Se la risorsa è prerilasciabile non c'è deadlock.

- se la richiesta non è bloccante non c'è deadlock.

    esempio: processo P e processo Q

         processo P: get A, get B.

         processo Q: get B, get A.

              se la richiesta è bloccante c'è deadlock.


per deadlock quindi avremo almeno: non condivisibili, non prerilasciabile, non richiesta bloccante, attesa circolare.

necessarie e/o sufficenti

- Come si può gestire/trattare il deadlock.
1. deadlock detection: vogliamo scoprire se c'è il deadlock
2. "gestire" deadlock prevention (strutturalmente non si presenta) e deadlock avoidance (mantiene l'attribuizione di risorse in stato SAFE, nel quale abbiamo garanzia che in futuro non possa avvenire deadlock).
3. Algoritmo "Ostrich" (mettere la testa sotto la sabbia): l'algoritmo opera come se il deadlock non esistesse.

Come capire se il sistema è in deadlock: 
- Tenere contabilità dell'attribuzione delle risorse. Visione complessiva.
- Tenere traccia delle richieste tramite struttura dati creata per gestire le richieste del tipo: Grafo di Holt (è un grafo diretto bipartito)

     Grafo di Holt presenta  -> due partizioni di nodi: sottoinsieme dei nodi-processo e sottoinsieme nodi-risorse. 

    -> archi che vanno da processo a risorsa e viceversa.

Direzione archi:

    processo -> risorsa: il processo sta richiedendo quella risorsa.

    risorsa -> processo: il processo sta utilizzando la risorsa.

\
1° Teorema: se il grafo di Holt ha un ciclo (attesa circolare), allora c'è deadlock (in multirisorse è falso).
 
- Tramite il grafo di Holt si può vedere se c'è deadlock:
               1° metodo: "processo di riduzione di un grafo di Holt": dato un grafo di Holt multirisorsa, si avrà deadlock sse il grafo di Holt non è completamente riducibile. 
 
- Riducibilità del grafo di Holt: si dice Riducibile se esiste almeno un nodo processo che ha soli archi entranti, ed allora si può fare un passo di riduzione: si prende il processo con i soli archi entranti, si cancellano gli archi entranti, e le risorse liberate vengono assegnate agli altri processi richiedenti tali risorse; 
 
- Un grafo si dice "Completamente Riducibile" se, dopo n passi di riduzione, si ottiene un grafo privo di archi;



esempio:
legenda: ● processo □ risorsa
A□ -> P● -> B□ -> Q● -> A□ è deadlock
1● <- A□ -> 2● -> B□ -> 3● -> A□ non è deadlock
(in questo caso 1, 2, 3 rappresentano l'ordine in cui si sbloccano con passi di riduzione)
P● -> A□ -> Q● -> B□ -> R● -> A□ è deadlock e cambia solo il verso della freccia


- Gli archi possono anche essere pesati: il peso rappresenta le risorse ancora disponibili.

KNOT: Tutti (processi E risorse) hanno lo stesso insieme di raggiungibilità; non è per forza tutto il grafo, anche un sottoinsieme del grafo.

2o Teorema: Dato un grafo di Holt multirisorsa, Deadlock c'è sse c'è un KNOT.

E ora che abbiamo scoperto il deadlock? 
Si può utilizzare un Checkpoint: si riprende dal punto di salvataggio del processo.
Non sempre però è possibile/utile (esempio banca, in cui si ritira ma il sistema non ha processato il ritiro).


-----------------------------------
Lezione di Giovedì 28 Febbraio 2019
-----------------------------------

SCHEDULER
Lo scheduler organizza le risorse del sistema (es. memoria, file system, ecc.). 
L'astrazione di processo che egli fornisce è realizzata attraverso l'uso di descrittori (ovvero strutture dati) detti Process Control Block (o PCB in breve), i quali racchiudono tutte le informazioni necessarie a descrivere un processo, quali:

    identificazione, che permette di referenziare il processo univocamente (utile per il debugging);

    stato, che può assumere valori "Ready", "Running" o "Blocked";

    controllo, che racchiude tutte le informazioni necessarie al controllo (identità, processo padre, processi figli, privilegi, ecc.).


STATO DEI PROCESSI
.............
.  Nascita  .
.............
     |
     v
|---------|  <---  |---------|
|  Ready  |  --->  | Running |
|---------|        |---------|
          ^         |
          |         |
          |         v
        |-------------|
        |   Blocked   |
        |-------------|

Ready   -> Running, se il processo viene scelto dallo scheduler.
Running -> Blocked, se il processo chiede di fare un'operazione bloccante.
Running -> Ready, dipende dal tipo di scheduler (es. processi con limite di tempo).
Blocked -> Ready, quando l'operazione bloccante viene completata.

Se un processo da Ready passa a Blocked (e quindi non termina completamente) è necessario salvare all'interno del relativo descrittore una "fotografia" della CPU e dei registri, in modo da poter continuare la computazione in un secondo momento.

COME FUNZIONA IL KERNEL
Esistono due principali modalità in cui un processo può essere eseguito:

    kernel mode: modalità nella quale le istruzioni eseguite hanno completo accesso all'hardware sottostante e qualsiasi area di memoria.

    user mode: modalità nella quale le istruzioni eseguite non hanno accesso all'hardware sottostante ed a specifiche aree di memoria.


Se il processo in user mode richiede una SysCall, l'Interrupt handler, riconoscendo la richiesta di SysCall, esegue il relativo frammento di codice in kernel mode. Terminata poi l'esecuzione il controllo ritorna al processo in user mode.

Il cambiamento della modalità di esecuzione di un processo (da kernel a user mode, e viceversa) crea mode switch.

La gestione delle modalità kernel mode e user mode viene implementata attraverso un componente hardware detto MMU (ovvero Memory Managment Unit), istruito dalla CPU, il quale mappa indirizzo logici ad indirizzi fisici (quando possibile).

MMU(IL) = IF

IL: indirizzo logico
IF: indirizzo fisico

In user mode gli IF non consentiti non fanno parte del codominio della funzione e quindi non sono raggiungibili.

        (Kernel mode)                      ¦             (User mode)
                                           ¦
|-----------------|                        ¦
|       init      |                        ¦
|-----------------|                        ¦
|  Creazione del  |                        ¦
|  processo init  | (1)                    ¦
|-----------------|                        ¦
         |                                 ¦
_________|_________________________________¦_______________________________________ (punto del non ritorno)
         |                                 ¦
         |   |---------------------|<------¦--------------------------------|
         |   |  interrupt handler  |       ¦                                |
         |   |---------------------|<------¦----------|                     | (3)
         |     |                           ¦          | (3)                 |
         |     |                           ¦          |                     |
         v     v                           ¦          |                     |
|-----------------|                        ¦    |------------|        |------------|
|    scheduler    |                        ¦    |  processo  |        |  processo  |
|-----------------|                        ¦    |------------|        |------------|
         |                                 ¦           ^                    ^
         |               (2)               ¦           |                    |
         ----------------------------------¦---------------------------------

(1) Viene generato un processo radice, detto init, che sarà il primo processo ad essere dato in pasto allo scheduler. La generazione del suddetto processo è unica durante tutto il ciclo vitale del kernel (ovvero fino al successivo avvio).
(2) Lo scheduler decide quale dei processi nelle ReadyQueue (ovvero la coda dei processi pronti per essere eseguiti) verrà eseguito, in base alle specifiche politiche ed implementazioni.
(3) I processi comunicano e mandano segnali allo scheduler attraverso uno specifico componente, l'Interrupt handler.

Nella maggior parte dei sistemi operativi i processi sono organizzati in forma gerarchica ad albero (vi è quindi una relazione di tipo padre-figlio tra i processi). Questa scelta implementativa porta diversi vantaggi, quali:

    semplificazione del procedimento di creazione di processi;

    le caratteristiche e i parametri non specificati vengono ereditati dal padre.


MULTITHREADING
La nozione di processo discussa in precedenza assume che ogni processo abbia una singola linea di controllo, ovvero esegua una singola sequenza di istruzioni alla volta.

Tutti i sistemi operativi moderni supportano l’esistenza di processi multithreaded in cui esistono molte linee di controllo, ognuna delle quali può eseguire un diverso insieme di istruzioni. Questa specifica architettura richiede l'introduzione di due nuovi campi all'interno del descrittore:

    PID, process identifier;

    TID, thread identifier.


Un PID può avere più TID associati (?).
    |                 |
    v                 |
|-----|               v
| PCB |----------- | TCB
|-----|            | TCB
                   | TCB
                   | TCB

Esempi:

    In un web browser, lo scaricamento di due differenti pagine;

    in un word processor, l'inserimento di nuovo testo in un wordprocessor mentre viene eseguito il correttore ortografico.


TIPOLOGIE DI SCHEDULER
Il diagramma di Gantt è uno strumento di supporto alla gestione dei processi.
 |-----------------|-----------------|-----------------|
 |        P1       |        P2       |        P3       | Processi
 |-----------------|-----------------|-----------------|
T0                T1                T2       

Questo diagramma mostra che la risorsa (in questo caso il processore) e' usato dal processo P1 dal tempo T0 al tempo T1 poi da P2 dal tempo T1 al tempo T2,e cosi' via...

La terminazione/sospensione di un processo a favore di un'altro (arbitrato dall'Interrupt handler e scheduler) crea un content switch.
La terminazione/sospensione di un processo può essere causata da:

    syscall bloccante;

    terminazione "naturale";

    passaggio di esso da Running a Ready;

    passaggio di un altro processo da Blocked a Ready (es. quando termina l'I/O).


Scheduler non preemptive: scheduler che non permette al kernel di avere autorità sui processi (compatibile con i casi 1-2);
Scheduler preemptive: scheduler che permette al kernel di avere autorità sui processi (compatibile con i casi 1-2-3-4).

COME VALUTARE UNO SCHEDULER
La valutazione di uno scheduler dipende dall'uso che si fa di esso, a seconda delle necessità. Alcuni parametri sono:

    utilizzo della CPU;

    quantità di processi completati per quanto tempo (throughtput);

    tempo di risposta.


CPU burst: utilizzo della CPU da parte di un processo.

I processi possono essere:

    CPU bound, quanto utilizzano intensivamente la CPU e poi richiedono I/O (es. raytracing);

    I/O bound, quando utilizzano brevemente la CPU ma richiedono un intenso I/O (es. lettura di dati dal disco).


CPU burst lunghi causano CPU bound.
CPU burst corti causano I/O bound.

MEDIA ESPONENZIALE
Tau(i) = a * Real(i-1) + (1-a) * Tau(i-1)

Tau(i): tempo stimato per l'i-esimo burst;
Real(i): tempo effettivo dell'i-esimo burst.

'a' e' il peso della predizione, ed e' un numero compreso tra 0 ed 1. 

Un valore di a alto caratterizza poca inerzia e cambiamento repentino;
Un valore di a basso caratterizza molta inerzia e cambiamento graduale.

COME IMPLEMENTARE UNO SCHEDULER
Esistono varie tecniche, quali:

    FCFS (First Come First Served): non preemptive, può causare l'effetto convoglio (processi con CPU burst lunghi possono essere privilegiati, creando una coda di processi con CPU burst corti) e la monopolizzazione della CPU da parte di pochi processi;

    SJF (Shortest Job First): non preemptive, usa la media esponenziale (spiegata sopra) per stimare la durata del prossimo CPU burst;

    SRTF (Shortest Remaining Time First): funziona come SJF ma e' preemptive... un processo che viene riattivato da un interrupt (quindi passa da stato blocked a stato ready) e ha una stima di durata del CPU burst inferiore al tempo rimanente per il processo ora running viene sostituito a quest'ultimo nel controllo del processore

    RR Robin): basato sull'Interval Timer (componente hardware capace di inviare un segnale all'Interrupt handler dopo un determinato lasso di tempo), garantisce un time slice per ogni processo (in quanto un segnale interrupt viene generato se il processo richiede I/O o se l'IT interviene), risultando però troppo democratico. Indicato per sistemi interattivi.


         (___)    /¯¯¯¯¯\
     (oo)  <  MOOO |
   /------\_/     \_____/
  / |    ||   
 *  /\---/\ 
    ~~   ~~ 

    SUPER COW POWERS


-------------------------------------
Lezione di Mercoledì 27 Febbraio 2019
-------------------------------------

COME SVILUPPARE UN SISTEMA
Per sviluppare il kernel (nucleo) di un sistema operativo occorrono diversi componenti.

Necessari:

    scheduler, realizza l'astrazione sui processi;

    gestione dei processi, consente la gestione dei processi (inizializzazione, terminazione, ecc);

    gestione della mem. principale, allocazione e deallocazione di aree di memoria per i processi richiedenti;

    gestione della mem. secondaria, gestione efficace delle unità di memorizzazione;

    gestione del file system, realizza l'astrazione che rende accessibile la memoria secondaria;

    gestione del networking, gestore dello stack TCP/IP e dei suoi vari protocolli;

    gestione della sicurezza, supporti per il controllo dei diritti/accesso (es. evitare accessi non autorizzati);

    gestione dispositivi di I/O, per il controllo delle periferiche.


Opzionali:

    sistemi di virtualizzazione dell'hardware, che permettono la gestione di spazi di nomi separati (instruction set), permettendo il funzionamento di ambienti separati sullo stesso kernel;

    shell, ovvero un'interfaccia che permette la comunicazion con il kernel (disponibile in macOS, non in Unix).


COME IMPLEMENTARE UN SISTEMA
Struttura semplice: agglomerato di singole librerie compilate insieme per ottenere il kernel (es. FreeDos (già MSDos)).

    Pros:

    efficienza, ogni funzionalità viene chiamata come chiamata di funzione.

    Cons: 

    compatibilità, ogni dispositivo utilizza device diversi e necessita di librerie specifiche;

    manutenzione, ogni modifica (a qualsiasi libreria) richiede la ricompilazione completa;

    debugging, ogni errore causa sempre kernel_panic().


Struttura a strati: gerarchia di astrazioni successive in cui ogni strato parla un proprio linguaggio interno (es-. VFS per il fileSystem), si occupa di uno specifico task e offre serivizi all'esterno tramite una interfaccia/API. 
La complessita è distruibuta sui vari livelli. 
A livello più alto l'hardware non è nient'altro che un linguaggio con cui si puo interagire.

    Pros:

    trasparenza delle interfacce;

    intercambiabilità, possibilità di caricare alcuni strati come librerie dinamiche;

    modularità, possibilità di creare sistemi complessi "componendo" le varie librerie.

    Cons: 

    minore efficenza.


Kernel monolitico: comprende tutte le funzionalità disponibili in un unico eseguibile (analogo all'architettura CISC nei processori).
Esempio: Linux ha linguaggi interni per ognuno dei suoi strati (stack di rete, file system, ecc.).

Microkernel: comprende un insieme ridotto di funzionalita (e.g. send, receive), le quali possono essere integrate caricando moduli aggiuntivi (analogo all'architettura RISC nei processori).
Esempio: MacOS si inizializza e lancia un unico processo che crea un'astrazione di FreeBSD.

Kernel flessibili (già proposti da Renzo Davoli(?)): kernel ibridi che posso avere parti di microkernel (per flessibilità) congiunte a parti monolitiche (per le performance).


E' sempre possibile "snellire" il kernel eliminandone le funzionalità superflue? Dipende dalle situazioni e dall'uso che si vuole fare del sistema.

Bisogona inoltre essere in grado di separare:

    politica, cosa si deve fare;

    meccanismi, come lo si fa.


MACCHINA VIRTUALE
Cosa si intende per "virtuale"? Data A una generica entità, B si dice A-virtuale se riesce a sostituire in modo efficiente e trasparente A.
L'hardware, come già visto, parla un linguaggio quindi se scrivo un SO che parli lo stesso linguaggio dell'HW, posso creare astrazioni su di esso (ovvero l'HW).
Instruction set processore + chiamate di accesso a bus, dove realmente esiste una corrispondente fisica al dispositivo.


Casi di utilizzo:

    sandboxing, ovvero creazione di un ambiente sicuro ed isolato nel quale l'utente possa testare gli applicativi;

    vendita di servizi a terzi (?)

    architetture hardware diverse: es. sviluppo di applicazioni ARM su architetture x86.


QEMU: è un software che implementa un particolare sistema di emulazione (traduzione dinamica) il quale permette di ottenere un'architettura informatica nuova e disgiunta in un'altra che si occuperà di ospitarla.

1) Cross-emulation platform
2) Dynamic Translation
VirtualBox: è un software che supporta vari sistemi di virtualizzazione hardware (VT-x per Intel, AMD-V per AMD). Al generarsi di eccezioni, si richiama la hypervisor per il controllo. 


File presenti nel percorso /boot:

    system map, symbol table relativa al kernel;

    config, file di configurazione usato per la creazione del kernel;

    vmlinuz, immagine compressa del kernel.

'EDF': https://en.wikipedia.org/wiki/Eliest_deadline_first_scheduling (not a quote)
is a programming policy for periodic real-time processes.
 the process with the closest expiry is chosen each time
 is called "a dynamic priority" because the relative priority of two different processes at different times
It uses 100% of the CPU but despite the rule you look great, for security you prefer to stay on the simple and you prefer to use the rule of static forecast for real-time processes.
Scheduler rate-monotonic: lowest period has highest priority