Uke 14 - Threads, serialisering, race conditions, virtualisering og PowerShell.

Oppgaver til mandag 31. - fredag 4. apr

  1. 
    Linux-JVM i samarbeid med Linux-kjernen tar ikke hensyn til prioriteten, det samme skjer om alle prioritetene er like. Om man som root kjører java med opsjonen -XX:ThreadPriorityPolicy=1 vil det settes nice-verdier fra -5 til 4 på trådene. 
    (men ikke til negative verdier på docker-containeren, se over; her er det ikke forskjell på root og vanlig bruker, 
    begge kan kun sette prioriteten ned) Kun root kan sette opp prioriteten. Men dette kunne vært gjort slik at prioritetene ble mappet til niceverdi 0-9. 
    
    
  2. Windows-JVM i samarbeid med NT-kjernen tar ihensyn til prioriteten, det er stor forskjell på prioritet 4 og 5.
  3. (Oblig)

     
    // Kompileres med  javac NosynchThread.java
    // Run: java NosynchThread

    import java.lang.Thread;

    class SaldoThread extends Thread
    {
        static int MAX = 50000000;
        static int count = 0;
        public static int saldo; // Felles variable, gir race condition
        int id; 
        
        SaldoThread()
        {
            count++;
            id = count;
        }
        
        public void run()
        {
        System.out.println("Thread nr. "+ id +", med prioritet " + getPriority() + " starter");
        updateSaldo();
        }
        
        public void updateSaldo()
        {
        int i;
        if(id == 1) 
        {
            for(i = 1;i < MAX;i++) 
            {
            saldo++;
            }
        }
        else      
        {
            for(i = 1;i < MAX;i++)
            {
                saldo--;
            }
        }
        System.out.println("Thread nr. " + id + " ferdig. Saldo: " + saldo);
        }
    }

    class NosynchThread extends Thread
    {
       public static void main (String args[])
       {
           int i;
           System.out.println("Starter to threads!");

           SaldoThread s1 = new SaldoThread();
           SaldoThread s2 = new SaldoThread();
           s1.start();
           s2.start();

           try{s1.join();} catch (InterruptedException e){}
           try{s2.join();} catch (InterruptedException e){}

           System.out.println("Endelig total saldo: " +SaldoThread.saldo);
       }
    }

    
    Når trådene kjøres med native-threads vil de startes som to uavhengige Linux-threads,
    schedules av Linux-kjernen og vil nå kjøre samtidig, typisk på hver sin CPU, når du kjører på et system
    med flere CPUer. Om de kjører på samme CPU, vil en context switch midt i saldo-oppdateringen være like ødeleggende som om to CPUer laster ned samme saldo og så legger resultatet tilbake uten å synkronisere.
    Dette er fordi en saldo-oppdatering består av flere 
    JVM-instruksjoner som man kan se med $ javap -c SaldoThread
    
          17: getstatic     #17                 // Field saldo:I
          20: iconst_1
          21: iadd
          22: putstatic     #17                 // Field saldo:I
    
    I en context switch lagres registerverdiene og dette fører til at oppdateringer på den felles variabelen saldo kan forsvinne når den andre tråden overtar. Resultatene vil derfor variere for hver kjøring og kan avvike fra null med flere hundre tusen.

    Med taskset vil alle trådene tvinges til å kjøre på samme CPU, men context switcher vil likevel kunne ødelegge. Når trådene kjører på samme CPU vil de kjøre en av gangen, slik at ingen av operasjonene utføres reellt sett samtidig. Så en "kollisjon" kan bare skje hvis det kommer en context switch midt i operasjonen som øker eller minker saldo med en. Og hvis programmet kjører kun ett sekund, skjer det kanskje bare 10 context switcher, så dette vil ikke skje hver gang. Når trådene derimot kjører på hver sin CPU, vil en tråd 10 millioner ganger kunne lese/skrive til variabelen saldo i RAM reellt sett helt samtidig som den andre tråden på en annen CPU gjør det samme. Og det vil da være 10 millioner muligheter for kollisjoner hvor de to trådene overskriver hverandre.

  4. Race condition: Når to eller flere prosesser skriver/leser til en eller annen form for delte data og sluttresultatet er avhengig av hvilket tidspunkt prosessene kjører. Minst en av prosessene må skrive.
  5. (Oblig)

    Men minimal.s blir alltid resultatet det samme fordi variabelen svar blir endret med en enkelt instruksjon og når trådene blir tvunget til å kjøre på samme CPU med taskset, vil ikke en context switch kunne ødelegge. Uten takset blir resultatet forskjellig hver gang fordi trådene da kjører på hver sin CPU og henter data fra RAM uavhengig av hverandre.

    Når man kompilerer med en.c istedet, lager kompilatoren flere maskinkodelinjer som oppdaterer svar og det kan man se av en.s:

    	movl	svar(%rip), %eax
    	addl	$1, %eax
    	movl	%eax, svar(%rip)
    
    Dermed vil en sjelden gang en context switch inne i denne koden ødelegge resultatet. Når man bruker opsjonen -O til gcc, vil koden optimaliseres for hastighet og da vil oppdateringen kun bestå av en linje:
    	addl	$1, svar(%rip)
    
    Dermed vil det blir riktig resultat hver gang på samme måte som når vi brukte minimal.s.
  6.  
    main(){
       int i,S = 0;
       for(i=1;i < 5;i++){
          S = S + i;
       }
    }

    -0x4(%rbp) tilsvarer løkke-variabelen i og -0x8(%rbp) tilsvarer summen S. Variabelen i starter lik 1 og den legges til S for hver runde og økes deretter med 1. Så lenge i er mindre eller lik 4 (jle), hoppes det tilbake til start i koden og dermed kjøres løkken 4 ganger som den skal og i får verdiene 1,2,3 og 4. Når den er ferdig ligger dermed tallet 10 i -0x8(%rbp).
  7. (Oblig)

     
    class For{
       public static void main(String args[]){
          int i,S = 0;
          for(i=1;i < 5;i++){
              S = S + i;
          }      
       }
    }

  8. (Oblig)

    Gjeste-OS kjører i user mode og vil ikke fungere som på vanlig hardware om en slik instruksjon droppes. Det kan føre til at OS crasher i verste fall. Disse sensitive instruksjonene ble endret slik at de trap'er til kernel mode når de utføres i user mode, istedet for å ikke gjøre noe.
  9. (Oblig)
  10. (Oblig)
    
        Scriptet kan kjøres som det er.
        
          Function:mkdir
          Alias cd = Set-Location
          Alias echo = Write-Output
          Alias mv = Mov-Item
          Alias cp = Copy-Item
          Alias rm = Remove-Item
    
  11. (Oblig)
    PS C:\> $PSVersionTable
    
  12. (Oblig) PS C:\> (Get-Childitem fil.txt).CreationTime
  13. (Oblig)
    PS C:\> (ls C:\Windows\system.ini).CreationTime.Year
    
  14. PS C:\> (ls fil.txt).FullName
  15. (Oblig) PS C:\> (ps idle).id
  16. (Oblig)
    PS C:\> (ps notepad).Kill()
    PS C:\> kill (ps idle).id
    
    Begge blir drept hvis det er to notepad-prosesser.
  17. 
    Den første måten henter ut størrelsen, direkte fra feltet "Length", mens den andre måten kaller
    metoden get_Length().
    
  18. (Oblig)
    
    
    for($i=2; $i -lt 10; $i += 2){
        echo "Linje $i"
        }
    
    Alternativt
    for($i=1; $i -lt 5; $i++){echo "Linje $(2*$i)"}
    
    eller med foreach som også er en slags løkke
    foreach ($t in 2,4,6,8){"Linje $t"}
    
  19. ps | foreach {if($_.name -eq "notepad") {kill $_.Id}}
    
    En enda enklere (og bedre) løsning er følgende
    kill -Name notepad
    
  20. (Oblig)