Karaktergivende rapportopgave i Styresystemer og multiprogrammering



Relaterede dokumenter
Processer og tråde. dopsys 1

Synkronisering af sekventielle processer

Specifikation Abstrakt OO OS-API Rev Specifikation. Abstrakt, objektorienteret operativsystem-api

Abstrakte datatyper C#-version

Programmering i C. Lektion december 2008

AAU, Programmering i Java Intern skriftlig prøve 18. maj 2007

Sproget Six. Til brug i rapportopgaven på kurset Oversættere. Vinter Abstract

DATALOGI 1E. Skriftlig eksamen torsdag den 3. juni 2004

PARALLELIZATION OF ATTILA SIMULATOR WITH OPENMP MIGUEL ÁNGEL MARTÍNEZ DEL AMOR MINIPROJECT OF TDT24 NTNU

DM14-1. Obligatorisk opgave F.06. System Call. Jacob Aae Mikkelsen Ingen andre gruppe medlemmer. 6. marts 2005

Design Systemkald. User-mode Linux, The Linux kernel/

Design by Contract. Design and Programming by Contract. Oversigt. Prædikater

Ugeseddel 4 1. marts - 8. marts

Skriftlig eksamen i Datalogi

Project Step 7. Behavioral modeling of a dual ported register set. 1/8/ L11 Project Step 5 Copyright Joanne DeGroat, ECE, OSU 1

Løsning af skyline-problemet

Hvordan vælger jeg dokumentprofilen?

SmartAir TS1000. Daglig brug

Videregående Programmering for Diplom-E Noter

A Profile for Safety Critical Java

VægtAgenten Betjeningsvejledning Version 3.0

Netværk & elektronik

HTX, RTG. Rumlige Figurer. Matematik og programmering

DM507 Algoritmer og datastrukturer

DM507 Algoritmer og datastrukturer

VMware og dopsys-linux

Studieretningsprojektet i 3.g 2007

Kursus navn: Indledende programmering Kursus nr

Indholdsfortegnelse resultat- & kritikprogrammet.

MIPS, registerallokering og MARS

Schedulering af SAS job i MS Task Scheduler, som sender med log og statuskode ved fejl

Privat-, statslig- eller regional institution m.v. Andet Added Bekaempelsesudfoerende: string No Label: Bekæmpelsesudførende

Systemkald i Unix/Linux

Virkefeltsregler i Java

DM507 Algoritmer og datastrukturer

18 Multivejstræer og B-træer.

Klasser og Objekter i Python. Uge 46 Learning Python: kap 15-16,

applies equally to HRT and tibolone this should be made clear by replacing HRT with HRT or tibolone in the tibolone SmPC.

DM507 Algoritmer og datastrukturer

Introduktion. Unifaun Online

Start på Arduino og programmering

ESP30076 임베디드시스템프로그래밍 (Embedded System Programming)

Citrix CSP og Certificate Store Provider

IFC Egenskaber. Mohammad Hussain Parsianfar s BYG DTU

Mandatory Assignment 1

Systemkald DM Obligatoriske opgave. Antal sider: 7 inkl. 2 bilag Afleveret: d. 18/ Afleveret af: Jacob Christiansen,

Øvelse 9. Klasser, objekter og sql-tabeller insert code here

Internt interrupt - Arduino

Rapport Bjælken. Derefter lavede vi en oversigt, som viste alle løsningerne og forklarede, hvad der gør, at de er forskellige/ens.

Spørgsmål & Svar. Udbud af Klinisk ernæring til sygehusene i Region Syddanmark 10/14041

IDAP manual Analog modul

Side 1 af 9. SEPA Direct Debit Betalingsaftaler Vejledning

METODER ARV KLASSER. Grundlæggende programmering Lektion 5

Wii Software Modificering. Uber Guide

Fang Prikkerne. Introduktion. Scratch

Peter Kellberg. Rundt om Danmarks Statistiks makroer. Design, Standardisering, Teknik

Transkript:

Karaktergivende rapportopgave i Styresystemer og multiprogrammering 1. marts 2005 Den karaktergivende rapportopgave i Styresystemer og multiprogrammering stilles tirsdag den 1. marts 2004 og skal afleveres senest onsdag den 6. april kl. 14:00 i DIKU s 1.delsadministration, Universitetsparken 1, 2100 København Ø. Besvarelser, der sendes med posten, skal være DIKU i hænde senest ved afleveringsfristens udløb. Besvarelser kan udarbejdes individuelt eller i grupper på op til tre deltagere. Der gives ingen reduktion i opgavens omfang ved individuel aflevering, hvorfor det anbefales at opgaven løses i grupper. Gruppesammensætningen behøver ikke at være den samme i denne opgave og i den tidligere godkendelsesopgave. Vigtige meddelelser vedrørende denne opgave vil blive meddelt på kursets hjemmeside http://www.diku.dk/undervisning/2005f/dat-os/ og i kursets nyhedsgruppe diku.dat-os. 1 Generelt om opgaven Denne opgave består af to dele; en gruppedel og en individuel del. Gruppedelen af opgaven drejer sig om design og implementation af et trådbibliotek på brugerniveau i programmeringssproget C. Opgaven er beregnet til løsning på DIKUs Linux systemer, men andre Intel X86 baserede Linuxsystemer kan også anvendes. Den individuelle del består i at hvert gruppemedlem skal besvare en række spørgsmål fra lærebogen. Disse spørgsmål vil hovedsageligt dreje sig om stof fra lærebogen der ikke direkte berøres af gruppedelen af opgaven. Der er i alt 3 sæt af spørgsmål; et per gruppemedlem. 2 Grænseflade til trådbiblioteket Grænsefladen til det trådbibliotek, I skal implementere, er en reduceret udgave af POSIX trådunderstøttelsen i Linux. Disse funktioner starter normalt med præfixet pthread men dette præfix er i opgavens grænseflade omdøbt til othread for at undgå problemer med navnesammenfald. I det følgende vil alle påkrævede funktioner blive beskrevet, dog vil alle returværdierne for de enkelte funktioner ikke nødvendigvis blive angivet. Der henvises til appendiks A for den komplette information omkring returværdier. Returværdiernes symbolske navne er defineret i standard UNIX headerfilen errno.h; prøv man errno for en nærmere beskrivelse. 2.1 Trådadministration I dette afsnit introduceres kaldene til at oprette og nedlægge tråde. Desuden beskrives hvordan trådattributter kan bruges til at påvirke afviklingen af trådene. Trådene skal afvikles ved frivilligt processkift dog med mulighed for at der kan skiftes tråd under udførslen af et kald til trådbiblioteket. Der skal anvendes prioriteret skedulering af trådene hvori der anvendes 16 prioritetsniveauer nummereret fra 0 til 15, hvor 0 er højeste prioritet. For 1

at undgå udsultning af tråde skal der implementeres ældning. Hvordan ældningen implementeres er op til opgaveløseren. Vær opmærksom på at der altid vil kunne forekomme udsultning idet en proces jo kun frivilligt afgiver processoren, så håndteringen af udsultningen skal foretages under antagelse af at en vilkårlig tråd jævnligt kalder trådbiblioteket. Inden for hver prioritet skal trådene skeduleres i FIFO orden. 2.1.1 Trådkontrol En ny tråd startes via kaldet: int othread_create(othread_t *thread, othread_attr_t *attr, void * (*start_routine)(void *), void *arg); der givet en funktionspeger start routine til en funktion der tager en parameter af typen void * og en argumentpeger arg starter en ny tråd, der begynder med at eksekvere funktionen givet via funktionspegeren med argumentpegeren som parameterværdi. En tråd identificeres via en identifikation af typen othread t, og en peger til en sådan struktur skal gives med som parameteren thread til othread create. Endelig kan man specificere nogle trådattributter attr - disse vil blive forklaret nedenfor i afsnit 2.1.2. Angives ingen trådattibuter men i stedet NULL anvendes standardattributterne for tråden (her drejer det sig kun om prioriteten der som standard sættes til 7). En tråd bliver udført indtil den kalder: void othread_exit(void *retval); eller indtil den returnerer fra sin hovedfunktion (den funktion, der blev specificeret i othread create. En tråd kan returnere en statusværdi via parameteren retval af typen void *, der senere kan aflæses af andre via kaldet: int othread_join(othread_t th, void **thread_return); der venter på at tråden th afslutter. Statuspegeren fra denne tråd returneres i thread return. Hvis flere tråde forsøger at kalde othread join på den samme tråd, er det kun den første der må få lov til at joine - resten skal have returneret fejlkoden EPERM. Endelig kan en tråd frivilligt opgive processoren ved at kalde: int othread_yield (void); der placerer tråden bagest i klarkøen for den prioritet tråden har, hvorefter der aktiveres en ny tråd. En tråd kan finde sin egen identifikation ved at kalde: othread_t othread_self(void); der returnerer den variabel af typen othread t der er knyttet til den kørende tråd. Endelig kan en tråd undersøge om to trådidentifikationer thread1 og thread2 er ens via kaldet: int othread_equal(othread_t thread1, othread_t thread2); der skal returnerer en positiv heltalsværdi hvis de to tråde er ens og ellers 0. 2

2.1.2 Trådattributter Trådbibliotekets håndtering af de enkelte tråde kan styres ved at angive en mængde attributter for en given tråd når den oprettes. Man kan således forestille sig at man via disse attributter kan styre hvilken skeduleringsmekanisme en tråd skal bruge eller hvor stor en stak der skal allokeres til tråden. I denne opgave er attributterne begrænsede til at håndtere hvilken prioritet en tråd skal anvende i den prioriterede skedulering. Trådattributterne skal indeholdes i strukturen othread attr t, der initialiseres ved kaldet: int othread_attr_init(othread_attr_t *attr) Når en tråd er oprettet kan attributterne nedlægges ved kaldet: int othread_attr_destroy(othread_attr_t *attr) der kan frigive eventuelle dynamisk allokerede elementer i attributstrukturen. Når en attributstruktur er blevet initialiseret kan den modificeres ved hjælp af en række othread attr set* kald; i denne opgave skal følgende kald til opdatering af skeduleringsparameteren (i dette tilfælde prioriteten) implementeres: int othread_attr_setschedparam(othread_attr_t *attr, const struct sched_param *param); Strukturen struct sched param er defineret i includefilen sched.h der typisk er placeret i /usr/include og bl.a. indeholder feltet int sched priority til fastlæggelse af en prioritet. Kaldet af othread attr setschedparam skal returnere fejlkoden EINVAL hvis prioriteten ikke ligger i intervallet fra 0 til 15. Man kan desuden undersøge indholdet af eksisterende attributstrukturer ved hjælp af en række othread attr get* kald; i denne opgave skal følgende kald til læsning af trådens prioritet implementeres: int othread_attr_getschedparam(const othread_attr_t *attr, struct sched_param *param); 2.1.3 Implementationsnoter Typen othread t er et eksempel på hvordan man skjuler den faktiske implementation af både identifikationen af en tråd og trådens kontrolblok for de programmer der bruger et kodemodul. Al manipulation af othread t typen sker via funktionskald, e.g., othread equal. I skal altså selv sørge for at allokere den nødvendige plads til de tråde der oprettes i jeres trådbibliotek. Dette indbefatter trådens stak, hvor selve stakstørrelsen skal være 4KB. I modsætning til othread t er othread attr t ikke skjult men defineret direkte i othread.h. I er selv ansvarlige for at fastlægge en passende datastruktur som angivet i othread.h. Det er ligeledes op til jer hvordan I implementere køer med mere til brug ved skedulering af tråde; dog skal selve trådskift og trådoprettelse implementeres som skitseret i afsnit 3. Endelig skal I bemærke at returværdien af en tråd ikke kan fjernes før en anden tråd har udført othread join med den afsluttede tråd. 2.2 Gensidig udelukkelse Trådbiblioteket understøtter gensidig udelukkelse via låse (i othread API et kaldet mutexer). En lås oprettes via kaldet: 3

int othread_mutex_init(othread_mutex_t *mutex, const othread_mutex_attr_t *mutexattr); der sørger for initialiseringen af den lås, der peges på af mutex. Som ved oprettelse af tråde, kan der knyttes en mængde attributter til låsene (her specificeret via parameteren mutexattr af typen othread mutex attr t. I denne opgave bruges attributterne til at angive hvilken af følgende tre typer en lås skal initialiseres som: NORMAL indeholder ingen kontrol af hvilke tråde, der forsøger at tage eller frigive en lås. Hvis en tråd forsøger at tage en lås, den allerede selv holder, vil den blokere og der vil opstå en baglås. Desuden kan en tråd frigive en lås, den ikke selv har taget. RECURSIVE tillader at en tråd kan tage låse, den allerede holder. Låsen skal frigives det antal gange den er taget før den er tilgængelig for andre tråde. ERRORCHECK returnerer fejlkoden EDEADLK hvis den kaldende tråd allerede har taget låsen. Desuden returneres fejlkoden EPERM hvis tråden, der forsøger at frigive en lås, ikke er den tråd, der har taget låsen. Ud over brugen af othread mutex init kan en lås også initialiseres statisk ved brug af konstanterne: OTHREAD_NORMAL_MUTEX_INITIALIZER OTHREAD_RECURSIVE_MUTEX_INITIALIZER OTHREAD_ERRORCHECK_MUTEX_INITIALIZER En initialiseret lås kan nedlægges med kaldet: int othread_mutex_destroy(othread_mutex_t *mutex); hvori eventuelle dynamisk allokerede strukturer kan frigives. En lås kan kun frigives hvis den ikke er låst ellers returneres fejlkoden EBUSY. En lås kan henholdsvis tages med kaldet: int othread_mutex_lock(othread_mutex_t *mutex); der blokerer indtil låsen er tilgængelig for tråden (returværdien afhænger af hvilken type lås det drejer sig om). En lås kan frigives med operationen: int othread_mutex_unlock(othread_mutex_t *mutex); hvis opførsel er afhængig af låsetypen. Drejer det sig om en NORMAL lås, resulterer kaldet altid i at låsen er frigivet når kaldet returnerer (det vil sige selv om låsen i forvejen var ledig returneres der ingen fejlmeddelelse). Er det en RECURSIVE lås, reducers antallet af gange låsen er låst af den kaldende tråd med 1 og kun hvis antallet er 0 frigives låsen. Hvis der er tale om en ERRORCHECK låsen frigives låsen kun hvis den er låst og hvis det er den kaldende tråd der holder mutexen - i alle andre tilfælde returneres fejlkoden EPERM. Der er en variant af låsekaldet der ikke blokerer: int othread_mutex_trylock(othread_mutex_t *mutex); der fungerer som othread mutex lock hvis låsen er ledig. I det tilfælde hvor låsen ikke er ledig, returneres i stedet fejlkoden EBUSY med det samme. Mutexattributterne initialiseres og nedlægges ved kaldene: 4

int othread_mutexattr_init(othread_mutexattr_t *attr); int othread_mutexattr_destroy(othread_mutexattr_t *attr); ækvivalent til trådattributterne. Mutexattributterne bruges kun til at specificere hvilken type mutexen har. Dette gøres ved kaldet: int othread_mutexattr_settype(othread_mutexattr_t *attr, int kind); hvor kind angiver typen, specificeret ved en af følgende tre konstanter: OTHREAD MUTEX NORMAL, OTHREAD MUTEX RECURSIVE eller OTHREAD MUTEX ERRORCHECK. Indholdet af en eksisterende mutexattribut kan aflæses ved hjælp af kaldet: int othread_mutexattr_gettype(const othread_mutexattr_t *attr, int *kind); 2.3 Betingelsesvariable Trådbiblioteket skal understøtte betingelsesvariable med en funktionalitet lignende den kendt fra monitorer, men i trådbiblioteket er betingelsesvariable knyttet til mutex er i stedet for monitorer (hvordan dette gøres fremgår af funktionerne der forklares nedenfor). En betingelsesvariabel initialiseres med et kald af: int othread_cond_init(othread_cond_t *cond); og nedlægges med et kald af: int othread_cond_destroy(othread_cond_t *cond); hvori eventuelle dynamisk allokerede strukturer kan frigives. En betingelsesvariabel kan kun nedlægges hvis der ikke er tråde der venter på den. En betingelsesvariabel kan også initialiseres statisk ved brug af konstanten OTHREAD COND INITIALIZER. En tråd kan vente på en betingelsesvariabel cond ved at kalde: int othread_cond_wait(othread_cond_t *cond, othread_mutex_t *mutex); hvor mutexen mutex skal være låst ved kaldets start. othread cond wait skal sætte processen til at vente på cond og frigive mutex udeleligt. Når tråden returnerer fra kaldet skal den igen være indehaver af låsen mutex. En tråd, der venter på betingelsesvariablen cond kan aktiveres igen med kaldet: int othread_cond_signal(othread_cond_t *cond); der starter en vilkårlig tråd af de tråde der venter på cond. Desuden kan alle tråde, der venter på en betingelsesvariabel cond aktiveres med kaldet: int othread_cond_broadcast(othread_cond_t *cond); Således svarer othread cond signal og othread cond broadcast til metoderne notify og notifyall kendt fra de synkroniserede objekter i Java. Endelig er det muligt at vente på en betingelsesvariabel i et begrænset tidsrum med operationen: int othread_cond_timedwait(othread_cond_t *cond, othread_mutex_t *mutex, const struct timespec *abstime); 5

der fungerer som othread cond wait med den forskel at den ventende tråd aktiveres når tidspunktet specificeret via abstime er overskredet. Bemærk at det er et absolut tidspunkt der specificeres, det vil sige at en timeout på 5 sekunder skal specificeres som nuværende tidspunkt + 5 sekunder og ikke blot 5 sekunder. Selve strukturen timespec er defineret i standardheaderen time.h og har formen: struct timespec { time_t tv_sec; long int tv_nsec; }; hvor tv sec angiver antallet af sekunder og tv nsec angiver antallet af nanosekunder. I denne opgave skal timeouts kun behandles med en opløsning i sekunder. 3 Implementation I dette afsnit introduceres de mekanismer (ud over et generelt kendskab til C) der er nødvendige for at løse opgaven. Vi starter med at se på hvordan man skifter mellem forskellige tråde for derefter at se på hvordan man opretter nye tråde. Endelig ser vi på hvordan timerafbrydelser understøttes i UNIX programmel. 3.1 Trådskift Et trådskift fra tråd A til tråd B realiseres groft sagt ved at gemme registersættet for den kørende tråd (A) og derefter erstatte det med et tidligere gemt registersæt for tråd B. På denne måde gemmes hele As tilstand: registersættet indeholder jo en stakpeger og en programpeger, så aktiveringsposter på stakken og det aktuelle udførselspunkt gemmes også via registersættet. Tilstanden for tråd B bliver ligeledes reetableret når Bs gemte registersæt bliver gjort til den aktuelle tilstand for processoren. De gemte registersæt placeres oftest i en kontrolblok for tråden sammen med andre kontrolinformationer omkring tråden, f.eks. ID og prioritet. Disse kontrolblokke vil typisk være placeret i forskellige ventekøer: der er typisk en klarkø der indeholder tråde der venter på adgang til processoren (bruges der som i denne opgave prioriteret skedulering vil der være et antal klarkøer) og desuden vil der være en ventekø per lås og per betingelsesvariabel. Desuden skal der gemmes en reference til kontrolblokken for den aktive tråd, idet denne jo ikke er placeret i nogen proceskø - dette gøres typisk i en current variabel. Et processkift mellem trådene A og B vil derfor typisk en form som vist i figur 1. De fleste programmeringssprog understøtter ikke direkte det at gemme og hente registersættet for en tråd. I C kan dette indirekte håndteres ved hjælp af kaldene setjmp og longjmp. Disse to kald er understøttet med henblik på at kunne foretage en ikke-lokal goto-operation, det vil sige en goto til en label der ikke er erklæret i samme funktionskrop. Ideen er at man via setjmp(jmp buf env) gemmer tilstanden for den udførende tråd i parameteren env og at man på et senere tidspunkt kan kalde longjmp(jmp buf env, int value) hvorefter man på magisk vis returnerer for anden gang fra kaldet af setjmp. Når man returnerer fra setjmp første gang (også kaldet det direkte kald) returneres altid 0 hvorimod man anden gang returnerer med returværdien angivet i value, hvor value skal være forskellig fra 0. På denne måde kan man skelne mellem om man netop har gemt tilstanden af tråden eller om den er blevet reetableret som vist i figur 2. Antager vi nu at vi har to kontrolblokke for trådene A og B, så kunne man forestille sig selve opdateringen af tilstande i forbindelse med et trådskift implementeret ved noget i retning af skitsen i figur 3. 6

.. tråd As afvikling.. <gem trådens tilstand i kontrolblok> <indsæt kontrolblok i ventekø> <udtag kontrolblok for ny tråd fra klarkø> <opdater current peger> <reetabler trådens tilstand fra kontrolblok>.. tråd Bs afvikling.. Figur 1: Skitse af processkift mellem trådene A og B if(setjmp(env) == 0) /* vi ender her ved et direkte kald */ else /* vi ender her når nogen kalder longjmp(env) */ Figur 2: Eksempel på anvendelse af setjmp if(setjmp(a->env) == 0) /* Tråd A har netop gemt sin tilstand */ longjmp(b->env); /* Vi genskaber Bs tilstand */ else /* Her fortsætter A sin eksekvering når den engang bliver aktiveret igen */ Figur 3: Skitse af trådskift baseret på setjmp og longjmp 3.2 Oprettelse af tråde Når man starter et enkelttrådet C program så vil der kun være knyttet en enkelt tråd til afviklingen af dette. Vil man så, som i denne opgave, oprette yderligere tråde og aktivere dem, skal man foretage en kloning af den gamle tråds tilstand via setjmp. Den nye tråd vil fortsætte fra det punkt hvor setjmp blev kaldt af den gamle tråd, når den nye tråd aktiveres første gang med et kald af longjmp. Dette kræver dog at den nye tråd har fået oprettet sin egen stak, og at den tilstand der anvendes af longjmp er blevet modificeret til at anvende den nye stak (dette vil typisk sige at den gemte stakpeger skal opdateres med placeringen af den nye stak). Desuden kan det være nødvendigt at kopiere lokale variable fra stakken for den gamle tråd til stakken for den nye tråd. Figur 4 skitserer trinene i at oprette en ny tråd B. Manipulationen af stakpegere samt andre registre er selvsagt specifikke for en enkelt arkitektur og vi vil i det følgende udelukkende beskæftige os med Intel X86 arkitekturen. I Intel X86 gror stakken mod lavere adresser og for en tråd ser stakken ud som skitseret i figur 5. I figuren ses det at der anvendes to registre til at holde styr på en funktions stakramme; en basepeger (EBP) og en stakpeger (ESP). Ligeledes fremgår det at en funktions lokale variable er placeret 7

<alloker trådtilstand for B> if(setjmp(b->env) == 0) { /* Tråd B bliver aktiveret for første gang */ <kald B s hovedfunktion med argumentet> <hvis B returnerer hertil skal den afsluttes> } <kopier dele af kørende tråds stak til Bs stak> <opdater stakpeger og andre registre, der anvender stakken til at pege på den nye stak> <indsæt B i klarkøen> Figur 4: Skitse af oprettelse af tråde baseret på setjmp på stakken mellem EBP og ESP. Lokale variable for den kaldende funktion Mod lavere adresser Parametre overført til den kaldte funktion Grænse mellem stakrammer Returadresse EBP register Lokale variable for den kaldte funktion Toppen af stakken ESP register Figur 5: Strukturen af en Intel x86 ved funktionskald Når vi via setjmp gemmer en tråds tilstand vil vi således også gemme EBP og ESP registrene. Når vi opretter en ny tråd, skal vi oprette en ny stak, og dermed er det nødvendigt at opdatere de to pegere i trådens tilstand til at pege på den nye stak. Vi kan få fat i de to registre ved hjælp af konstanterne JB SP og JB BP som vist i figur 6, hvor vi opfatter tilstanden som en tabel af typen int. Dette er baseret på den faktiske implementation af jmp buf i /usr/include/bits/setjmp.h, der indeholder den x86 specifikke del af definitionerne af setjmp. Denne form vil altså ikke kunne anvendes på andre arkitekturer, men til brug i denne opgave burde det ikke give problemer. Slutteligt er der blot at tilføje at den oprettede tråd ikke må referere til data, der ligger på 8

jmp_buf env; int *envp; if(setjmp(env)) { /* Her returnerer et longjmp til */... } envp = (int *) &env; printf("jb_bp: %p\n", (void *) envp[jb_bp]); printf("jb_sp: %p\n", (void *) envp[jb_sp]); Figur 6: Eksempel på tilgang til EBP og ESP i jmpbuf den gamle tråds stak; f.eks. parametrene og lokale variable i tidligere funktioner. Dette betyder bl.a. at I skal gemme den nye tråds funktionpeger og argumentpeger i lager, der overlever efter at den gamle tråd er returneret fra funktionen, der opretter den nye tråd. 3.3 Timerafbrydelser I forbindelse med implementering af othread cond timedwait er det et krav til jeres implementation at I anvender UNIX signalmekanismen til at holde styr på timeouts. UNIX signaler giver styresystemet mulighed for at komme med en asynkron markering af at en hændelse er indtrådt; det anvendes bl.a. til at implementere asynkron I/O men også til at håndtere suspender og dræb kommandoer til en proces sendt af shellen. Alle funktioner vedrørende signaler er defineret i signal.h. I vores tilfælde er det interessante signal SIGALRM, der sendes til en proces når en timer, der tidligere er sat af processen, udløber. For at en proces kan foretage noget fornuftigt, når den modtager et signal, skal den installere en signalhåndteringsroutine. Dette gøres ved kaldet signal: void (*signal(int signo, void (*func)(int)))(int); der installerer funktionen func til at håndtere signalet signo. signal returnerer en funktionspeger til den hidtidige signalhåndteringsroutine hvis den nye funktion bliver installeret korrekt (derfor ser prototypen noget sær ud - returværdien er en funktionspeger) og ellers fejlkoden SIG ERR. Signalhåndteringsroutinen får det modtagne signal med som argument, når den aktiveres. Man kan ved at definere en passende type for funktionen få en definition 1 af kaldet som er mere læsevenlig: typedef void Sigfunc(int); Sigfunc *signal(int, Sigfunc *); I figur 7 gives et eksempel på hvordan en proces fanger et SIGALRM signal der genereres af den timer der er sat via alarm kaldet. Vær opmærksom på at som udgangspunkt kan et signal aktivere en håndteringsroutine når som helst. Dette betyder at hvis en signalhåndteringsroutine ændrer i datastrukturer som også kan ændres af andre funktioner, så kan disse datastrukturer komme i en udefineret tilstand. For at beskytte sig mod dette kan man enten lukke for signaler når sådanne datastrukturer behandles 1 Dette fif er oprindeligt fra bogen The Standard C Library, Prentice-Hall 1992 af P. J. Plauger. 9

#include <stdio.h> #include <signal.h> static int flag; void sig_alrm(int signo) { flag = 1; } int main(void) { if(signal(sigalrm, sig_alrm) == SIG_ERR) { printf("kunne ikke installere signalhåndteringsroutine\n"); exit(-1); } } alarm(10); /* Genererer en SIGALRM om 10 sekunder */ while(!flag) { } Figur 7: Eksempel på opfangelse af signalet SIGALRM eller man kan ændre på håndteringsroutinen så den blot sætter et flag, der så på et senere tidspunkt kan undersøges af den almindelige udførsel af processen, og først på dette tidspunkt udføres den kritiske operation. Mængden af signaler der blokeres for en proces administreres hjælp af funktionen: int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); der enten kan tilføje signalerne i set til mængden af blokerede signaler ved at angive how = SIG BLOCK, fjerne signalerne i set fra mængden af blokerede signaler (how = SIG UNBLOCK) eller sætte mængden af blokerede signaler til set ved at angive how = SIG SETMASK. Den gamle mængde af blokerede signaler returneres i oldset. Mens der er blokeret for signaler kan man undersøge hvorvidt et signal er indtruffet ved hjælp af kaldet: int sigpending(sigset_t *set); Selve signalmængden sigset t kan manipuleres med følgende operationer: int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum); 10

der henholdsvis initialiserer en signalmængde til tom, til fuld, tilføjer et signal til mængden, fjerner et signal fra mængden og undersøger hvorvidt et signal er en del af en mængde. Der henvises til UNIX man siderne for yderligere oplysninger omkring signalhåndtering. 4 Milepæle I forbindelse med rapporten er der et milepælsmøde med en instruktor; hensigten er at I ved dette møde diskuterer hvordan og hvorvidt I har nået de nedenfor definerede milepæle. Intentionen er at I kan få hurtig tilbagemelding på de løsningsforslag I er i gang med at implementere og at I samtidig har nogle holdepunkter i projektforløbet der jo strækker sig over fem en halv uge. Milepælsmødet skal afholdes efter cirka tre uger, og vil have en varighed på cirka 1 time. Ved mødet bør følgende være designet og implementeret: oprettelse og nedlæggelse af tråde frivilligt processkift håndtering af trådattributter trådskedulering og ældning Desuden bør følgende være designet og implementationen bør være påbegyndt: låseoperationer betingelsesvariable inklusiv venten med timeout afprøvningsstrategi I vil blive tildelt en instruktor ud fra fordelingen af godkendelsesopgaven. I forbindelse med at I får feedback på godkendelsesopgaven kan I aftale et mødetidspunkt for milepælsmødet. Hvis I ændrer gruppesammensætning til denne opgave skal I give instruktoren der har rettet jeres G-opgave besked. 5 Udleveret materiale I forbindelse med opgaven udleveres følgende filer: En næsten færdig headerfil til othread biblioteket (othread.h) En række eksempelprogrammer, der viser forskellige anvendelser af othread trådbiblioteket: create.c: oprettelse af en tråd. yield.c: brug af frivilligt processkift. param.c: brug af parametre til trådenes hovedfunktion. mutex.c: brug af låse. trylock.c: bruger ikkeblokerende låsekald. bounded.c: bounded buffer eksempel der anvender låse og betingelsesvariabel. Ovennævnte filer kan findes i kataloget: /usr/local/del1/dat-os/k/src. 11

6 Individuelle opgaver I forbindelse med rapportopgaven skal hvert gruppemedlem lave en individuel opgave bestående af en række opgaver fra lærebogen: Silberschatz, Galvin & Gagne: Operating System Concepts with Java, 6th edition,, ISBN 0-471-45249-1. Opgaverne fordeles i 3 mandsgrupper således at det yngste gruppemedlem løser opgave A, det næstyngste gruppemedlem løser opgave B og det ældste gruppemedlem løser opgave C. Hvis der er tale om en tomandsgruppe løses opgaverne A og B med samme tildeling som ovenfor og hvis der er tale om en enmandsgruppe skal man løse opgave C. 6.1 Opgave A Opgave A består af følgende delopgaver: 4.2, 5.3, 6.4a, 8.8, 9.2, 10.4a-d, 11.1, 12.1a+b+d, 13.2, 14.2a+b, 14.3. 6.2 Opgave B Opgave B består af følgende delopgaver: 4.4, 5.6, 6.4b, 8.3, 9.3, 10.1, 11.2, 12.6, 13.4, 14.2c+d, 14.5 (uden sammenligningen med 14.3(c). 6.3 Opgave C Opgave C består af følgende delopgaver: 4.1, 6.3a, 6.3b, 8.1, 9.1, 10.3, 11.10, 12.1c+e+f, 13.6, 14.2e+f, 14.17. 7 Rapport Det udførte arbejde skal beskrives og dokumenteres i en rapport. Kravene til rapporten er: 1. Omfanget må maksimalt være 30 sider, eksklusiv bilag og de individuelle opgaver. Der lægges vægt på, at rapporten er velstruktureret og præcis. Materiale, der udleveres i forbindelse med opgaven skal ikke afleveres, men eventuelle ændringer skal angives. 2. Indledningen skal indeholde en kort statusrapport, der klargør i hvor høj grad trådbiblioteket virker. 3. Rapporten skal indeholde en analyse af de relevante problemstillinger, f.eks: Oprettelse og afslutning af tråde. Trådbeskrivelser samt håndtering af trådskift. Trådskedulering ( short-term scheduling ), specielt med hensyn til prioriteret skedulering og strategi for ældning af processer. Håndtering af låse (mutexer) og betingelsesvariable Trådbibliotekets håndtering af signaler. 4. Rapporten skal indeholde en kort beskrivelse af trådbibliotekets funktioner og struktur. 5. Rapporten skal kort beskrive forenklende antagelser og begrænsninger. 12

6. Rapporten skal dokumentere at den i opgaven specificerede grænseflade overholdes; dette kan til dels gøres ved at dokumentere korrekt afvikling af de udviklede eksempelprogrammer, men da disse ikke foretager en udtømmende afprøvning af grænsefladen bør I også dokumentere egenudviklede afprøvningsprogrammer. Præciser, hvilke krav trådbiblioteket opfylder og hvilke krav den ikke opfylder. 7. Der skal være en individuel opgaver per gruppemedlem: de individuelle opgaver skal være tydeligt mærket med navn samt cpr-nummer. 8. Vær opmærksom på at afleverede kildetekster skal være letlæseligt. Vær især opmærksom på at L A TEX ikke respekterer tabuleringer, hvilket kan betyde at kildeteksterne bliver vanskelige at læse. Rapporten behøver ikke at at omfatte en brugsvejledning. 13

A Grænseflade for trådbibliotek /* Osmthreads - en forsimplet udgave af Posix trådbiblioteket i Linux */ /* Dette er en modificeret udgave af headerfilen normalt placeret i */ /* /usr/include/pthreads som er copyright (C) 1996 Xavier Leroy */ /* (Xavier.Leroy@inria.fr). Se den fil for nærmere bestemmelser omkring */ /* brugen af #ifndef _OTHREAD_H #define _OTHREAD_H 1 #include <sched.h> #include <time.h> /* Type definitions */ /* othread_t */ typedef unsigned long int othread_t; /* othread_attr_t */ typedef struct { /* Denne skal I selv definere */ } othread_attr_t; /* othread_mutex_t */ typedef struct { /* Denne skal I selv definere */ } othread_mutex_t; /* othread_mutexattr_t */ typedef struct { /* Denne skal I selv definere */ } othread_mutexattr_t; /* othread_cond_t */ typedef struct { /* Denne skal I selv definere */ } othread_cond_t; /* Initializers. */ #define OTHREAD_NORMAL_MUTEX_INITIALIZER \ /* Denne skal I selv definere */ # define OTHREAD_RECURSIVE_MUTEX_INITIALIZER \ /* Denne skal I selv definere */ # define OTHREAD_ERRORCHECK_MUTEX_INITIALIZER \ /* Denne skal I selv definere */ #define OTHREAD_COND_INITIALIZER /* Denne skal I selv definere */ /* Values for attributes. */ enum { OTHREAD_MUTEX_NORMAL, OTHREAD_MUTEX_RECURSIVE, OTHREAD_MUTEX_ERRORCHECK }; /* Function for handling threads. */ /* Create a thread with given attributes ATTR (or default attributes if ATTR is NULL), and call function START_ROUTINE with given arguments ARG. EAGAIN Not enough system resources were available to create thread */ int othread_create (othread_t *threadp, const othread_attr_t *attr, void *(*start_routine) (void *), void *arg); 14

/* Obtain the identifier of the current thread. */ othread_t othread_self (void); /* Compare two thread identifiers. 0 The threads are not equal >0 The thread are equal */ int othread_equal (othread_t thread1, othread_t thread2); /* Terminate calling thread. Never returns :-) */ void othread_exit (void *retval); /* Make calling thread wait for termination of the thread TH. The exit status of the thread is stored in *THREAD_RETURN, if THREAD_RETURN is not NULL. ESRCH No thread could be found corresponding to that specified by th. EINVAL Another thread is already waiting on termination of th. EDEADLK The th argument refers to the calling thread. */ int othread_join (othread_t th, void **thread_return); /* Functions for handling attributes. */ /* Initialize thread attribute *ATTR with default attributes. Returns 0 always. */ int othread_attr_init (othread_attr_t *attr); /* Destroy thread attribute *ATTR. Returns 0 always. */ int othread_attr_destroy (othread_attr_t *attr); /* Set scheduling parameters (priority) in *ATTR according to PARAM. EINVAL the priority specified in param is outside the range of allowed priorities for the scheduling policy currently in attr (0 to 15) */ int othread_attr_setschedparam (othread_attr_t *attr, const struct sched_param *param); /* Return in *PARAM the scheduling parameters of *ATTR. Returns 0 always. */ int othread_attr_getschedparam (const othread_attr_t *attr, struct sched_param *param); /* Yield the processor to another thread or process. Returns 0 always. */ int othread_yield (void); /* Functions for mutex handling. */ /* Initialize MUTEX using attributes in *MUTEX_ATTR, or use the default values if later is NULL. Returns 0 always. */ int othread_mutex_init (othread_mutex_t *mutex, const othread_mutexattr_t *mutex_attr); /* Destroy MUTEX. EBUSY the mutex is currently locked. */ int othread_mutex_destroy (othread_mutex_t *mutex); /* Try to lock MUTEX. EBUSY the mutex could not be acquired because it was currently locked. EINVAL the mutex has not been properly initialized. */ int othread_mutex_trylock (othread_mutex_t *mutex); /* Wait until lock for MUTEX becomes available and lock it. 15

EINVAL the mutex has not been properly initialized. EDEADLK the mutex is already locked by the calling thread ( error checking mutexes only). */ int othread_mutex_lock (othread_mutex_t *mutex); /* Unlock MUTEX. EINVAL the mutex has not been properly initialized. EPERM the calling thread does not own the mutex ( error checking mutexes only). */ int othread_mutex_unlock (othread_mutex_t *mutex); /* Functions for handling mutex attributes. */ /* Initialize mutex attribute object ATTR with default attributes (kind is OTHREAD_MUTEX_NORMAL). */ int othread_mutexattr_init (othread_mutexattr_t *attr); /* Destroy mutex attribute object ATTR. */ int othread_mutexattr_destroy (othread_mutexattr_t *attr); /* Set the mutex kind attribute in *ATTR to KIND (either OTHREAD_MUTEX_NORMAL, OTHREAD_MUTEX_RECURSIVE or OTHREAD_MUTEX_ERRORCHECK) EINVAL kind is neither OTHREAD_MUTEX_NORMAL OTHREAD_MUTEX_RECURSIVE nor OTHREAD_MUTEX_ERRORCHECK */ int othread_mutexattr_settype (othread_mutexattr_t *attr, int kind); /* Return in *KIND the mutex kind attribute in *ATTR. */ int othread_mutexattr_gettype (const othread_mutexattr_t *attr, int *kind); nor /* Functions for handling conditional variables. */ /* Initialize condition variable */ */ int othread_cond_init (othread_cond_t *cond); /* Destroy condition variable COND. EBUSY some threads are currently waiting on cond. */ int othread_cond_destroy (othread_cond_t *cond); /* Wake up one thread waiting for condition variable COND. */ int othread_cond_signal (othread_cond_t *cond); /* Wake up all threads waiting for condition variables COND. */ int othread_cond_broadcast (othread_cond_t *cond); 16

/* Wait for condition variable COND to be signaled or broadcast. MUTEX is assumed to be locked before. */ int othread_cond_wait (othread_cond_t *cond, othread_mutex_t *mutex); /* Wait for condition variable COND to be signaled or broadcast until ABSTIME. MUTEX is assumed to be locked before. ABSTIME is an absolute time specification; zero is the beginning of the epoch (00:00:00 GMT, January 1, 1970). ETIMEDOUT the condition variable was not signaled until the timeout specified by abstime */ int othread_cond_timedwait (othread_cond_t *cond, othread_mutex_t *mutex, const struct timespec *abstime); #endif /* othread.h */ 17