Uke 13 - Plattformavhengighet, Java-threads/tråder, Docker compose

Oppgaver til mandag 24. - fredag 28. mars

Først noen praktiske oppgaver om plattformavhengighet, deretter noen praktiske og teoretiske oppgaver om tråder, så et par Docker compose oppgaver og til slutt som vanlig en konkurranse-oppgave.

Det er ikke flere obligatoriske innleveringer, så betrakt oppgaver som er merket (Oblig) som de oppgavene det er viktigst å få med seg i forbredelsene til eksamen.

  1. (Oblig) Ta utgangspunkt i følgende C-program:
    #include <stdio.h>
    main()
    {
        printf("c: Hello world!\n");
    }
    
    Skriv dette inn i en fil hello.c på data2500 eller os-VM ved å starte f. eks. editoren jed:
    $ jed hello.c
    
    lagre filen og kompiler den med
    $ gcc hello.c
    
    gcc er C-kompilatoren. For å kunne kompilere og kjøre C-programmet, må gcc være installert, slik den er på data2500 og os-VM. Den lager nå maskinkode som lagres i den kjørbare filen a.out. For å få lov til å kjøre den, må filen gis kjørerettigheter og det gjør du med kommandoen
    $ chmod 700 a.out
    
    Kjør programmet med
    $ a.out
    
    og du bør nå få ut "Hello world" i terminalvinduet. Logg så inn på en Windows maskin (eventuelt på en Mac og gjør det tilsvarende som beskrevet for Windows). Kopier a.out med Winscp eller på annen måte, og kjør det fra der du har lagt det med
    C:\>a.out
    
  2. Ta utgangspunkt i følgende Java-program
    class Hello 
    {
        public static void main(String args[])
        {
    	System.out.println("Java: Hello world!");
        }
    }
    
    og lagre det i filen Hello.java på Linux-VM eller data2500. Java er installert her, på andre servere kan man installere java og java-kompilatoren javac med
    apt-get install default-jdk
    
    På Linux kompileres et Java-program med
    $ javac Hello.java
    
    det lages da byte-kode som lagres i en fil Hello.class som kjøres med
    $ java Hello
    
    og sjekk at den kjører som den skal. Kopier over Hello.class til en Windows-PC. Får du den til å kjøre her? Vil det i prinsippet være mulig?
  3. Hvilke av følgende programmeringsspråk kan betegnes som plattformuavhengige? C, C++, Java og Python. Forklar svaret kort.
  4. (Oblig) Kopier Java-programmet Calc.java fra avsnitt 10.9 i forelesningen til Linux-VM og lagre det med samme navn. Kompiler så med
    javac Calc.java
    
    og sjekk at det lages en Calc.class fil. Kjør programmet med
    java Calc
    
    Om det går for fort, gang verdien på telleren i programmet med 10 eller mer, kompiler og kjør igjen. Endre koden slik at det starter tre tråder istedet for to og tving igjen alle til å kjøre på samme CPU med taskset. Tast H i top hvis tråder ikke vises. Hint: Se på nTH = Number of Threads i top eller bruk ps -L. Hvis man taster 'u' i top og så skriver inn sitt brukernavn (f.eks. group100) vil man kun se denne brukerens prosesser/tråder. En annen mulighet er å installere htop som viser tråder.
  5. (Oblig) Studer og kjør følgende kode på din Linux-VM:

     
    import java.lang.Thread;
    import java.io.PrintStream;

    class CalcThread extends Thread
    {
       static int count = 0;
       int id;
       long res=0;
       
       CalcThread()
            {
             count++;
             id = count;
            }

       public void run()
            {
             System.out.println("Thread nr." + id + " is starting");
             res = work();
            }

       private long work()
            {
           long i,j;
           if(id == 1)
             {
            System.out.println("Thread nr." + id + " calculating");
            for(i = 1;i <= 4000000000L;i++)
              {
                 res += i;
              }
             }
           return(res);
        }
    }

    class Calc
    {
       public static void main(String args[])
       {
          System.out.println("Starts two threads!");
          CalcThread s1 = new CalcThread();
          System.out.println("Thread s1 has id " + s1.id);
          s1.start(); // Allokerer minne og kaller s.run()
          
          CalcThread s2 = new CalcThread();
          System.out.println("Thread s2 has id " + s2.id);
          s2.start();
          System.out.println("s2 started !\n");
          try
        {
           s1.join();
        }
          catch (InterruptedException e)
        {
        }
          
          try
        {
           s2.join();
        }
          catch (InterruptedException e)
        {
        } 
          System.out.printf("Thread nr. 1 calculated %d\n",s1.res);
          System.out.printf("Thread nr. 2 calculated %d\n",s2.res);
       }
    }

    Resultatet av denne kjøringen er et veldig stort heltall, derfor heter variablene long istedet for int, long er 64 bit lange, mens int er 32 bit. Det er også årsaken til at tallet 4 milliarder i work-løkken slutter med en L, den må med når så lange siffer skal skrives inn i koden. Den ene tråden i koden regner ut summen av alle tall mellom 1 og 4 milliarder, mens den andre tråden ikke gjør noe. Kallene s1.join og s2.join inne i main gjør at main venter til begge trådene er ferdige med sine beregninger før den avslutter.

    Ta tiden på hvor lang tid beregningen tar. Klarer du å få programmet til å regne ut samme sluttresultat dobbelt så fort ved å fordele beregningene på begge trådene og utnytte to CPUer samtidig?

  6. I 2020 når Linux-VM-ene virkelig var virtuelle maskiner, hadde vi følgende oppgave: Hvis du har problemer med diskplass på Linux VM'en, kan du forsøke å slette noen filer og starte med følgende: Hvorfor er ikke denne oppgaven like relevant i år? Hvilke deler av den er fortsatt relevant?
  7. (Oppgave 2.8 i Tanenbaum). I en tabell i boken blir registere listet som en "Per thread item" sammen med Program Counter og stack, og ikke som en "Per process item" sammen med globale variabler, åpne filer og child prosesser. Hvorfor? En maskin har jo bare ett sett med registere?
  8. Oppgave 2.9 i Tanenbaum: Hvorfor vil en tråd noengang frivillig gi fra seg CPU'en ved å kalle yield()? Siden det ikke finnes noe periodisk klokke-interupt innenfor en prosess, kan det jo være at den aldri får igjen CPU'en.
  9. Oppgave 2.10 i Tanenbaum: Kan en tråd bli preempted(tvunget til å stoppe å kjøre) av et klokke-interrupt? Isåfall, under hvilke omstendigheter? Hvis ikke, hvorfor ikke?
  10. Problem 2.18 i Tanenbaum. I et system med threads, er det en stack per thread eller en stack per prosess når user-level threads blir brukt? Hva når kernel-level threads blir brukt? Forklar.
  11. Lag en docker-compose.yaml fil som starter opp to nginx-containere som begge er bygget fra den samme Dockerfile som ligger i en mappe du kan kalle nginx. Når de starter opp skal de to containerne som kjører nginx-webserveren på henholdsvis port 8080 og 8081 gi data fra to forskjellige index.html filer fra mapper på host'en som containerene kjører på. Omtrent slik som i eksempelet fra forelesningsnotatene. Dockerfile skal sørge for at både iputils-ping og net-tools er installert når de to containerene starter opp. Vis så at du kan gå inn på en av containerene og pinge den andre. Hvis også ved hjelp av kommandoen ifconfig inne i containerene hvilke IP-adresser de har fått på det lokale nettverket. Er selve host'en som kjører docker også på dette nettverket?
  12. (Oblig) Lag på Linux-VM en docker-compose.yaml fil som starter opp to nginx-containere web1 og web2 som begge kjører nginx-webserver på port 80 og som gir to små men forskjellige index.html filer fra mapper på host'en som containerene kjører på. Innholdet kan være navnet på webserveren, slik at man kan se forskjell. Neste steg er å legge til en container som kan kalles loadbalancer i docker-compose.yaml. Lag først en mappe nginx som inneholder følgende fil med navn default.conf:
    upstream backend {
            random;
            server web1;
            server web2;
    }
     
    server {
           listen 80;
           location / {
               proxy_pass http://backend;
           }
    }
      
    Dette er en config fil som gjør nginx om til en load balancer og dermed blir en container som fordeler web-forespørsler på port 80 som kommer inn til Linux-VMen likt mellom de to webeserverene web1 og web2. Lag i nginx-mappen en Dockerfile som laster inn nginx og så kopierer filen default.conf til /etc/nginx/conf.d/default.conf på loadbalancer-containeren. Legg så til kode i docker-compose.yaml som gjør at loadbalancer bygges slik at den tar imot trafikk inn til Linux-VM inn på sin port 80. Bygg så hele systemet med
      root@os131:~/load# docker compose up --build -d
      
    og sjekk at loadbalancer virker som den skal. Opsjonen --build gjør at det alltid bygges på nytt fra Dockerfile om det har blitt gjort noen endringer. Når alt er satt opp slik det skal, kan det utenifra se ut som følger:
    haugerud@data2500:~$ for i in {1..10}; do curl os131.vlab.cs.oslomet.no; done
    web2
    web1
    web2
    web2
    web2
    web1
    web1
    web2
    web1
    web1
      
    hvor vi ser at web-serverene bytter på å svare (om man ikke tar med 'random' i nginx-konfigurasjonen tar det litt tid før den begynner å bytte på). Og på Linux-VM kan det se omtrent slik ut:
    root@os131:~/load# docker ps
    CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS          PORTS                NAMES
    090e8fb7be56   nginx:latest        "/docker-entrypoint.…"   21 minutes ago   Up 21 minutes   80/tcp               load_web2_1
    ebcee4527e57   nginx:latest        "/docker-entrypoint.…"   21 minutes ago   Up 21 minutes   80/tcp               load_web1_1
    b3dd3867c9bf   load_loadbalancer   "/docker-entrypoint.…"   21 minutes ago   Up 21 minutes   0.0.0.0:80->80/tcp   load_loadbalancer_1
    root@os131:~/load# 
    
  13. (Oblig) Dette er ukens konkurranse-oppgave som egentlig løses på en hvilken som helst server som kjører docker og for eksempel på s-serveren eller på os-serveren. Du skal laste ned fra repositoriet haugerud/os25 på Dockerhub en versjon av os-containeren som har samme tag/versjonsnummer som din s-gruppe. For eksempel versjonsnummer s68 om du har s-nummer 68 på s-serveren. NB! Pass på at du bruker ditt eget s-nummer (og ikke os-nummer), ellers vil du løse oppgaven for en annen! Se mer om hvordan man laster ned containere i avsnitt 9.2 Dockerhub i forelesningsnotatene. Deretter skal du starte denne containeren som er en nginx webserver. Det er en vanlig web-server, men den er spesiell på den måten at man må koble seg til på port 12345 for å se innholdet, og ikke port 80 som er standard for webservere. Når du kobler deg til denne webserveren vil det eneste innholdet på denne være en 10-tegns kode med tilfeldige tegn som viser at du har løst oppgaven.

    For å sjekke at du har funnet det rette ordet, skriv strengen med 10 tegn inn på siden os.php uke 13 og du får beskjed om du har skrevet riktig. I tilegg blir du da med på OS-konkurransen om å finne denne koden fortest mulig!