Operativsystem: Lösningar till tentamen 2024-08-30

Observera att detta är förslag på lösningar. Det kan finnas andra lösningar som också är korrekta. Det kan hända att en del av lösningarna är mer omfattande än vad som krävs för full poäng på uppgiften, eller att de bara hänvisar till var man kan läsa svaren. Dessutom har det inträffat i världshistorien att lärare skrivit fel i lösningsförslagen. Jag har förstås aldrig gjort det, men andra. Är det verkligen någon som läser såna här inledande texter? Kanske skummar de början och hoppar över resten. Jag vet inte. Det kan vara så. Rabarber rabarber rabarber. Det har jag hört att statisterna får säga på filminspelningar när det ska vara bakgrundssorl från en restaurang eller liknande. Här nedan kommer lösningsförslagen till uppgifterna.

Uppgift 1 (3 p)

Interrupt ("avbrott" på svenska) är en hårdvarumekanism i processorn som kan underlätta operativsystemets arbete och göra det effektivare (dvs, med mindre resursanvändning och därmed snabbare). På vilka sätt används avbrott av operativsystemet, och hur gör de att arbetet blir effektivare?

Svar:

På flera sätt:
  • Om en långsam hårdvaruoperation har startats, till exempel läsning från disk, behöver processorn inte polla, dvs då och då se efter, för att veta när operationen är klar. Operativsystemkärnan som kör på processorn bara startar läsningen eller vad det nu är för extern process det gäller, och sen får hårdvaran (i det här fallet disken) arbeta vidare utan inblandning av processorn. När operationen är färdig signalerar hårdvaran till processorn med ett avbrott, och då kan processorn i sin avbrottsrutin ta hand om resultatet från den operationen.
  • Om det finns processer eller trådar som väntar på att få köra, behöver de just nu körande processerna inte "samarbeta" genom att själva lämna över resurser (så kallad "cooperative multitasking"), utan med hjälp av avbrott (från en timer) kan man stoppa en körande process, och operativsystemkärnan kan starta en annan process på processorn.

Uppgift 2 (3 p)

a) Vad menas i operativsystemsammanhang med schedulering ("schemaläggning")?

Svar:

Att bestämma vilken process (eller tråd) som ska få köra på processorn. Det kan innebära att man gör upp ett schema i förväg, till exempel för realtidssystem, men på vanliga datorer görs det under körningen. Det är operativsystemkärnan som schemalägger processer, och det kan innebära både att starta och stoppa processerna. (Trådprogram med user threads kan schemalägga trådar utan inblandning av operativsystemkärnan.)

b) Ange en scheduleringsalgoritm, och beskriv hur den fungerar.

Svar:

Kursboken tar upp ett antal olika algoritmer för schemaläggning:
  • First-Come, First-Served Scheduling (5.3.1)
  • Shortest-Job-First Scheduling (5.3.2)
  • Shortest-Remaining-Time-First Scheduling (s 209)
  • Round-Robin Scheduling (5.3.3)
  • Priority Scheduling (5.3.4)
  • Multilevel Queue Scheduling (5.3.5)
  • Multilevel Feedback Queue Scheduling (5.3.6)
  • Rate-Monotonic Scheduling (5.6.3)
  • Earliest-Deadline-First Scheduling (5.6.4)
  • Proportional Share Scheduling (5.6.5)
Som exempel kan vi ta First-Come, First-Served. Det är den enklase algoritmen, och innebär att processerna körs i den ordning de anländer. Man använder en enkel FIFO-kö, startar processerna i ordning, och varje process körs tills den är färdig eller (brukar man mena) "med I/O yield", dvs tills den inte längre kan köra på processorn eftersom den väntar på I/O. Algoritmen är alltså inte preemptive, dvs operativsystemet stoppar aldrig en process som kör på processorn och flyttar den till ready-kön.

Uppgift 3 (3 p)

Man brukar skilja på samtidighet (engelska: concurrency) och parallellitet (engelska: parallelism). Vad är skillnaden?

Svar:

Samtidighet innebär att flera olika processer (eller trådar) finns på datorn, och att de i alla fall ser ut att köras samtidigt. Det kan ske genom att man snabbt byter mellan de olika processerna, så snabbt att det för en människa ser ut som att de körs samtidigt.

Parallellitet innebär att de på riktigt körs samtidigt, vilket förstås kräver att det finns flera olika processorer eller processorkärnor.

Uppgift 4 (5 p)

a) Man brukar skilja på de så kallade exekveringsmoderna kernel mode och user mode, och ibland också på kernel space och user space. Vad innebär dessa termer?

Svar:

User mode, som är det tillstånd eller körsätt som processorn befinner sig i när vanliga användarprogram körs, innebär att processorn kan köra vanliga, opriviligierade maskininstruktioner, som till exempel aritmetik, åtkomst av processens minnesrymd, och funktionsanrop. I kernel mode, som är den mode som operativsystemkärnan kör i, kan processorn dessutom köra priviligerade instruktioner, som att starta och stoppa processer, ändra på minnesrymden och interagera med hårdvaran, till exempel sekundärminne och nätverkskort. När ett systemanrop görs byter processorn från user mode till kernel mode.

User space är den vanliga minnesrymd som går att komma åt i user mode. Kernel space är det ytterligare minne som bara operativsystemkärnan har tillgång till.

b) Varför finns denna uppdelning?

Svar:

För att ett användarprogram inte av misstag, eller avsiktligt, ska kunna störa andra användarprogram, och att de inte ska kunna komma åt hårdvara och operativsystemets känsliga data utan att gå via systemanrop och operativsystemkärnan.

c) Vid virtualisering, som med VirtualBox och VMware, vill man ha fler än två olika exekveringsmoder. Varför?

Svar:

Man vill fortfarande att användarprogrammen inte ska kunna störa varandra, eller komma åt hårdvara och operativsystemets känsliga data utan att gå via systemanrop och operativsystemkärnan. Men dessutom vill man att operativsystemkärnan på en virtuell maskin (gäst) inte ska kunna störa andra virtuella maskiner, eller komma åt hårdvara och hypervisorns känsliga data utan att gå via hypervisorns gränssnitt. Då behöver processorn kunna hantera minst tre nivåer av rättigheter: user mode, kernel mode och vad man kan kalla "hypervisor mode".

Uppgift 5 (3 p)

För en programmerare skulle en disk (eller SSD) vara ganska krånglig att använda, om man behövde arbeta direkt mot hårdvaran. Man skulle själv behöva hålla reda på vilka diskblock man lagt sina data i, och läsa och skriva de rätta diskblocken.

Hur underlättar operativsystemet programmerarens arbete med diskarna?

Svar:

Operativsystemet erbjuder filer, som kan öppnas, skrivas och läsas, utan att programmeraren behöver bry sig om hur och var deras innehåll lagras. Filerna går att identifiera med namn, och är ofta organiserade i någon form av trädstruktur så de blir lätta att hitta. Dessutom har många operativsystem mekanismer för att skydda filerna från obehörig åtkomst.

(Inoder är en implementationsdetalj i hur Unix och Unix-liknande system lagrar filerna. Andra operativsystem kan göra på andra sätt.)

Uppgift 6 (5 p)

Här är ett C-program för Linux:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define STUFFS_TO_DO 3

int global = 0;

void do_stuff(int number) {
    int local = 0;
    ++local;
    ++global;
    printf("number %d: local = %d, global = %d\n", number, local, global);
}

int main(void) {
    printf("forks: starting!\n");
    for (int i = 0; i < STUFFS_TO_DO; ++i) {
        int fork_result = fork();
        if (fork_result == -1) {
            fprintf(stderr, "forks: fork failed\n");
            exit(EXIT_FAILURE);
        }
        else if (fork_result == 0) {
            do_stuff(i);
            exit(EXIT_SUCCESS);
        }
        else {
            // Nothing
        }
    }

    printf("forks: finished!\n");
}

a) Beskriv vad programmet gör. Skriv en sammanfattning, alltså inte rad för rad vad koden gör.

Svar:

Programmet startar tre nya processer med hjälp av fork-anrop. Var och en av processerna har en variabel som heter global och en variabel som heter local. De har från början innehållet noll, men båda ökas med ett, och därefter skriver processen ut deras innehåll. Föräldraprocessen väntar inte på att de nya processerna ska bli färdiga, och det sker ingen synkronisering mellan de fyra (inklusive föräldraprocessen) processerna.

b) Vad skrivs ut när programmet körs?

Svar:

Så här kan utskriften bli. Observera att varje process har sin egen minnesrymd, och alltså en egen variabel global.
forks: starting!
number 0: local = 1, global = 1
forks: finished!
number 1: local = 1, global = 1
number 2: local = 1, global = 1
Ett tillägg: Om man omdirigerar utmatningen till en fil, så den inte är radbuffrad som när man matar ut den direkt till terminalen, blir det mer komplicerat. Då kan "forks: starting!" skrivas ut flera gånger.

c) Kan utskrifterna bli olika från gång till gång, och vad beror det i så fall på?

Svar:

Ja. Det sker ingen synkronisering mellan de fyra (inklusive föräldraprocessen) processerna, och föräldraprocessen väntar inte på att de nya processerna ska bli färdiga, så utskrifterna kan komma i olika ordning.

d) Om utskrifterna kan bli olika från gång till gång, vad skulle man kunna lägga till i programmet så det alltid blir samma utskrifter?

Svar:

Enklast med hjälp av wait-anrop i föräldraprocessen. Om de görs efter loopen kommer i alla fall utskriften "forks: finished!" sist. Om de görs inuti loopen körs bara en barnprocess åt gången, och då kommer utskrifterna i ordning. Alternativt kan man använda semaforer, antingen med sem_init, sem_post och sem_wait eller med semget och semop. Semaforer liknar mutexar, och kan användas mellan olika processer. Man kan också använda mutexar (som i trådprogrammet nedan) om man allokerar delat minne för processerna (med mmap), som man sedan kan placera en mutex i.

Uppgift 7 (5 p)

Här är ett annat C-program:

#include <stdio.h>
#include <pthread.h>

#define NR_THREADS 10

volatile long long int data = 0;
pthread_mutex_t lock;

void *thread_body(void *arg) {
    int this_thread_number = (int)arg;
    for (int i = 0; i < 1000000; ++i) {
        pthread_mutex_lock(&lock);
        ++data;
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main(void) {
    printf("Starting %d threads...\n", NR_THREADS);
    pthread_mutex_init(&lock, NULL);
    pthread_t threads[NR_THREADS];
    for (int i = 0; i < NR_THREADS; ++i)
        pthread_create(&threads[i], NULL, thread_body, (void*)i);
    for (int i = 0; i < NR_THREADS; ++i)
        pthread_join(threads[i], NULL);
    printf("The value of data is %lld.\n", data);
}

a) Beskriv vad programmet gör. Skriv en sammanfattning, alltså inte rad för rad vad koden gör.

Svar:

Programmet startar tio nya trådar med hjälp av pthread_create-anrop. Programmet har en variabel data som var och en av trådarna ökar med ett en miljon gånger. Huvudtråden inväntar de tio trådarna med pthread_join-anrop, och skriver därefter ut värdet som finns i variabeln data.

b) Vad skrivs ut när programmet körs?

Svar:

Starting 10 threads...
The value of data is 10000000.

c) Vilken funktion har anropen till pthread_mutex_lock och pthread_mutex_unlock?

Svar:

De gör att att endast en tråd åt gången kan utföra satsen ++data;, för att förhindra felaktiga resultat vid samtidig åtkomst.

d) Vad skulle hända om man tog bort de anropen?

Svar:

Då skulle flera trådar kunna öka data med ett samtidigt, och det kan ge felaktiga resultat. Att öka en variabels värde med ett kan normala processorer inte göra direkt i primärminnet, där variabeln är lagrad, utan variabelns innehåll måste först hämtas till ett register i processorn, därefter sker ökningen i processorn, och till sist skrivs resultatet tillbaka till variabeln i minnet.

Om flera trådar försöker göra detta samtidigt kan det till exempel bli så att tråd 1 hämtar värdet (låt oss säga 17) och ökar värdet med ett i ett register till 18. Därefter, innan tråd 1 hinner skriva tillbaka värdet till variabelns plats i minnet hinner tråd 2 hämta värdet (fortfarande 17) från variabeln. Tråd 1 skriver tillbaka 18 till variabeln, tråd 2 ökar sitt hämtade värde 17 i ett register till 18, och till sist skriver tråd 2 tillbaka 18 till variabeln. Trots två ökningar med ett har värdet bara ökat med ett, inte två.

Uppgift 8 (7 p)

En ny, modern processor har virtuellt minne, med virtuella minnesadresser som består av 16 bitar, och fysiska minnesadresser som är 20 bitar. 12 bitar används som offset. Datorn är byteadresserad, så på varje minnesadress kan man lagra en byte.

a) Vilka användningsområden kan man tänka sig för den här processorn? Kan den användas för stora beräkningar och simuleringar, är det en processor för vanliga skrivbordsdatorer och bärbara datorer, är den tänkt för små, inbyggda system, eller vad? Motivera svaret!

Svar:

Fysiska minnesadresser på 20 bitar motsvarar 1 mebibyte (1048576 byte) adresserbart minne, vilket innebär att man kan ha högst så mycket fysiskt minne, i alla fall utan att börja krångla med olika minnesbankar som man på något sätt kan byta mellan. Vanliga datorer idag går knappast att köpa med mindre än 8 gibibyte fysiskt minne, dvs mer än 8000 gånger mer än vad den här processorn maximalt kan använda. Det är alltså en mycket begränsad processor, och den kan kanske vara lämplig för små, inbyggda system.

b) Hur stort virtuellt minne kan man adressera?

Svar:

216 = 65536 byte = 64 kibibyte

c) Hur stort fysiskt minne kan man adressera?

Svar:

220 = 1048576 byte = 1 mebibyte

d) Hur stor är en virtuell minnessida ("page" på engelska)?

Svar:

212 = 4096 byte = 4 kibibyte

e) Hur stor är en fysisk frame ("ram" på svenska)?

Svar:

Den är förstås lika stor som en minnessida, dvs 4096 byte.

f) Processorn kan köra flera processer, som var och en har sin egen virtuella minnesrymd. Hur många processer kan vara igång vid samma tidpunkt? Motivera svaret!

Svar:

Här går det att svara på många olika sätt.

Först kan man fråga sig vad "vara igång" betyder. Betyder det samtidighet eller parallellitet? Om det är parallellitet, beror svaret på hur många kärnor processorn har.

Därefter kan man titta på minnet. Om varje process använder hela sin virtuella minnesrymd, och alla processernas hela virtuella minnesinnehåll ska vara inläst i det fysiska minnet (till exempel om man inte har något sekundärminne, och inte kan använda paging eller swapping) kan man ha 220 / 216 = 16 processer.

Om varje process behöver ha minst en virtuell minnessida i det fysiska minnet, kan man ha 220 / 212 = 256 processer.

Om man har sekundärminne och kan utnyttja paging eller swapping, beror det på hur stort sekundärminnet är. Med ett tillräckligt stort sekundärminne skulle man kunna få plats med många helt utswappade processer som helst. (Men då betyder "igång" förstås inte parallellitet.)


Thomas Padron-McCarthy (thomas.padron-mccarthy@oru.se), 10 september 2024