Operativsystem: Lösningar till tentamen 2022-05-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 (7 p)

a) Vad är det för skillnad på operativsystemkärnan och ett systemprogram? Ange några olika saker som skiljer dem åt!

Svar:

Operativsystemkärnan är den del av operativsystemet som sköter om det viktigaste och mest centrala i en dator, som kontakten med hårdvaran, eller schemaläggning ("schedulering") och annan hantering av processer. På moderna, vanliga processorer körs operativsystemkärnan med särskilda privilegier, bland annat så att den kan utföra maskininstruktioner som vanliga program inte kan.

Ett systemprogram är ett vanligt program som utför delar av operativsystemets arbete. Det har inte samma privilegier som operativsystemkärnan har, och måste gå via systemanrop för att komma åt datorns resurser, på samma sätt som vanliga program. Om ett systemprogram kraschar kan man starta om det, men om operativsystemkärnan kraschar kan datorn förmodligen inte köra vidare. Ett exempel på ett systemprogram i Linux är skalet ("shell"), och som vi sett i kursen är det ett vanligt program, som man själv kan skriva en ny variant av.

b) Vad är det för skillnad på operativsystemkärnan och en hypervisor?

Svar:

En hypervisor hanterar och kör virtuella maskiner. Man kan se den som lagret nedanför (närmare hårdvaran) än gästoperativsystemen i de virtuella maskinerna. Hypervisorn kan vara ett program som körs under ett operativsystem (hypervisor typ 2), eller den kan köras direkt på datorn, så att den ersätter operativsystemet (hypervisor typ 1). En hypervisor är inte en operativsystemkärna eller ett operativsystem, men en hypervisor typ 1 är en ersättning för ett operativsystem, men med den mer specialiserade uppgiften att hantera och köra virtuella maskiner.

c) Vad är det för skillnad på en process och en tråd? Ange de viktigaste skillnaderna!

Svar:

En process har en minnesrymd, medan flera trådar kan dela på samma minnesrymd. Trådar kan vara userspacetrådar, som operativsystemkärnan inte vet om, men det kan inte processer. Allmänt kan man säga att trådar är mer "lättviktiga" än processer, i betydelsen att de går snabbare att skapa, ta bort och växla mellan, och att de tar mindre minne och andra resurser. Trådar och processer kan schemaläggas med samma algoritmer, men det går fortare att växla mellan två trådar i samma process än mellan två processer.

d) En mikrokärna är förstås mindre än andra typer av kärnor. Vad är det man har tagit bort, och var har man gjort av det?

Svar:

Man har tagit bort allt utom det som absolut måste vara i kärnan, som minneshantering och skapande, schedulering och annan hantering av processer. Allt annat, som hantering av filsystem och av användargränssnittet finns i form av vanliga användarprocesser, med eget minnesutrymme, som kommunicerar med kärnan.

Uppgift 2 (3 p)

Min skrivbordsdator har en 8-kärnig processor, med hyperthreading. Den kan köra flera program samtidigt, till exempel både webbläsaren och en snygg klocka som visar tiden och piper varje timme.

Min första egna dator hade bara en enkärnig processor, och ingen hyperthreading, men den kunde fortfarande köra flera program samtidigt. (Även om klockan hade färre pixlar och inte var lika snygg.) Förklara hur operativsystemet kan åstadkomma det på en enkärnig processor!

Svar:

Även om den gamla datorn inte hade någon parallellitet mellan olika processer, så hade den vad som brukar kallas samtidighet, nämligen att flera processer var startade och igång, och operativsystemet växlade mellan dem, även om bara en i taget kördes vid varje enskild tidpunkt. Genom att växla snabbt mellan dem ser det ut för användaren som om de körs parallellt. Med preemptive multitasking (på svenska "tidsdelad multikörning") behöver programmen inte skrivas på något särskilt sätt, utan när det är någon annans tur att köra avbryter operativsystemet den körande processen och sparar undan de data som behövs för att senare återstarta den.

Uppgift 3 (5 p)

Här är ett C-program:

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

int x = 1;

int main(void) {
    int y = 1;
    if (fork() == 0) {
        x = x + 1;
        y = y + 1;
        printf("Ett! x = %d, y = %d!\n", x, y);
    }
    else {
        x = x + 1;
        y = y + 1;
        printf("Två! x = %d, y = %d!\n", x, y);
    }
    fork();
    x = x + 1;
    y = y + 1;
    printf("Tre! x = %d, y = %d!\n", x, y);
}

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

Svar:

Ett! x = 2, y = 2!
Två! x = 2, y = 2!
Tre! x = 3, y = 3!
Tre! x = 3, y = 3!
Tre! x = 3, y = 3!
Tre! x = 3, y = 3!

(Raderna kan komma i en delvis annan ordning.)

b) Om man kör programmet flera gånger kan utskrifterna bli olika mellan gångerna. Förklara varför!

Svar:

Med fork-anropen i programmet skapas nya processer. Processerna schemaläggs sen av operativsystemet, och som vanligt påverkas schemaläggningen av vilka processorkärnor som finns och vilka andra processer som körs. I det här fallet har vi ingen synkronisering mellan processerna (mer än att de startas av fork), och exakt hur de kommer att köras kan därför variera.

Uppgift 4 (5 p)

Här är ett C-program. Det startar 1000 trådar. Varje tråd ökar variabeln data en miljon gånger.

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

volatile int data = 0;
pthread_mutex_t lock;

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

int main(void) {
    pthread_t threads[1000];
    pthread_mutex_init(&lock, NULL);

    printf("Starting the threads...\n");

    for (int i = 0; i < 1000; ++i) {
        if (pthread_create(&threads[i], NULL, thread_body, (void*)i) != 0) {
            printf("Error: Couldn't create thread %d.\n", i);
            exit(EXIT_FAILURE);
        }
    }

    printf("Waiting for the threads to finish...\n");

    for (int i = 0; i < 1000; ++i) {
        if (pthread_join(threads[i], NULL) != 0) {
            printf("Error: Couldn't join thread %d.\n", i);
            exit(EXIT_FAILURE);
        }
    }

    printf("All threads have finished.\n");
    printf("Result: data = %d\n", data);

    return EXIT_SUCCESS;
} // main

Utskrifter från när vi kör programmet:

Starting the threads...
Waiting for the threads to finish...
All threads have finished.
Result: data = 1000000000

a) Kan vi få deadlock i programmet?

Svar:

Nej. Deadlock innebär att två eller flera processer som har olika lås (eller andra resurser) väntar på varandra, i en cykel. I det här programmet finns det bara ett enda lås, och därför kan man aldrig få en cykel med processer som har olika lås och som väntar på varandra.

b) Vi tar bort anropen till pthread_mutex_lock och pthread_mutex_unlock. Vad händer nu när vi kör programmet? Blir utskriften annorlunda?

Svar:

Programmet går förmodligen mycket snabbare att köra, dels förstås eftersom det är två miljarder låsanrop som det inte behöver göra, men dels också eftersom trådarna inte behöver vänta på varandra. Med största sannolikhet blir variabelns värde inte längre 1000000000, utan antagligen något tal som är mycket mindre.

c) Kan vi få deadlock när vi tagit bort pthread_mutex_lock och pthread_mutex_unlock?

Svar:

Nej, hur skulle det gå till? Ingen tråd väntar på något lås.

d) Min dator har en 8-kärnig processor med hyperthreading, så den kan köra 16 trådar parallellt. Men här kör vi 1000 trådar samtidigt! Hur är det möjligt? Förklara!

Svar:

Det är av samma anledning som i svaret på fråga 2 ovan, nämligen att datorn växlar snabbt mellan att köra de olika trådarna. Trådarna startas, och ligger i någon form av kö som väntar på att få köra, och operativsystemet lägger ut dem på de olika processorkärnorna så de får köra en stund. Då och då (vilket betyder många gånger per sekund) byter operativsystemet vilken tråd som kör på respektive kärna.

Om vi använder userspace-trådar kan det också vara så att det inte är kärnan som byter mellan trådarna, utan att det är trådpaketet som gör det, till exempel vid ett låsanrop.

Observera att det inte är så att vi bara kan ha 16 kernel threads på en processor som kan köra 16 trådar parallellt. Det är inte det som kernel thread betyder. En kernel thread är bara en tråd som kärnan känner till och därför kan schemalägga, på liknande sätt som den känner till och kan schemalägga en process. Det kan mycket väl vara så att det skapas 1000 kernel threads i det här programmet. Det fungerar så på Linux, men inte på alla operativsystem.

Observera också att det inte är så att den först kör klart en tråd, eller sexton, innan den börjar på nästa, utan den byter mellan de 1000 trådarna hela tiden, med upp till 16 som körs parallellt.

Uppgift 5 (7 p)

Vi har en byteadresserad dator som arbetar med minnesadresser på 14 bitar, både för virtuella och fysiska adresser. Av de 14 bitarna används 10 för offset inom minnessidan.

a) Hur stor är den virtuella adressrymden?

Svar:

214, dvs 16384 adresser.

b) Hur stor (dvs hur många byte) är en minnessida ("page")?

Svar:

210, dvs 1024 byte.

c) Hur många minnessidor är den virtuella minnesrymden indelad i?

Svar:

214-10, dvs 24, dvs 16 minnessidor.

d) Hur mycket fysiskt minne kan man ha i datorn?

Svar:

214, dvs 16384 byte. (Det kan vara mindre, om datorn har färre minnesplatser eller en adressbuss med färre bitar, eller mer om man på något vis kan växla mellan olika minnesbankar som använder samma adresser.)

e) Hur många frames (dvs platser för minnessidor) kan man ha i datorn?

Svar:

214-10, dvs 24, dvs 16 frames.

f) Vi tittar i sidtabellen ("page table") för en process, och ser att virtuell minnessida 2 är lagrad i fysisk frame 1. (Båda numreras med början på noll.) Vilken fysisk adress är den virtuella minnesadressen 2050 lagrad på? Visa hur översättningen görs.

Svar:

Den fysiska adressen är 1026. Vi ser att minnesadressen 2050 är 2*1024 + 2, dvs den har offset 2 på minnessida 2. Minnessida 2 var lagrad i fysisk frame 1. Frame 1 börjar på fysisk adress 1*1024, så den fysiska adressen blir 1*1024 + 2.

Uppgift 6 (3 p)

När man installerar applikationsprogram ("appar") på en mobiltelefon, brukar man kunna ange för varje sådant program vad det får och inte får göra, till exempel om det får göra uppkopplingar via Internet.

På en vanlig Linux-dator (en server, eller en stationär eller bärbar dator) kan man normalt inte göra det, utan alla applikationsprogram som man installerar får alla rättigheter som den användare som kör dem har, och det går inte (särskilt lätt) att begränsa detta.

Varför är det gjort så?

Svar:

Det beror på att Linux-datorerna och mobiltelefonerna har olika hotmodeller, dvs de hot man försöker skydda mot.

Linux är baserat på Unix i hur det fungerar, även om källkoden är helt egen. Unix skapades på 1970-talet, och mycket av säkerhetsmekanismerna har funnits med sedan då. På den tiden hade man flera användare som delade på en dator, och den datorn sköttes av en systemadministratör. Systemadministratören installerade program, som levererades av leverantörer som IBM eller Oracle. Man litade både på systemadministratören och programleverantörerna, och i stället var det användarna som var hotet. Systemet måste skyddas från användarna, och användarna måste skyddas från varandra.

En mobiltelefon har bara en användare, och här är det de program som man installer som är hoten. Man laddar ner dem från någon form av butik eller från andra platser som man ofta inte har någon anledning att lita på, och det är också känt att de ibland innehåller skadlig kod av olika slag.

(Här bör man kanske påpeka att även i Linux har man på senare tid utvecklat tekniker för att isolera applikationerna, så de inte lika lätt kan orsaka skada.)


Thomas Padron-McCarthy (thomas.padron-mccarthy@oru.se), 9 juni 2022