I Microsoft Windows-miljö (vilken är min primära plattform för systemprogramutveckling) används följande termer:

Relevanta dokument
Synkronisering. Ordning och reda

Summering av fält. Synkronisering. Summering av fält. Bounded Buffer. Bounded Buffer (get) Bounded Buffer (put)

Recap Mera om nya typer Kort Fält. Programmering. Sommarkurs Verónica Gaspes. IDE-sektionen.

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

Concurrency Saker händer samtidigt. Process En instans av ett program

Tentamen i TDP004 Objektorienterad Programmering Lösningsförslag

Operativsystem ID1200/06 (ID2200/06 6hp) Tentamen :00-18:00

Enkla datatyper minne

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Mekanismer. (implementation)

Outline. Datorsystemtekni. Kravspecifikation. Kravspecifikation (forts.)

Objektorienterad Programkonstruktion. Föreläsning 11 6 dec 2016

Objektorienterad Programkonstruktion. Föreläsning dec 2015

TDDIU81. Processer och trådar. Andreas Dahlberg, Jonathan Doherty, Tony Magnusson, Patrik Ottosson, Rasmus Siljedahl

Karlstads Universitet, Datavetenskap 1

Föreläsning 2 Datastrukturer (DAT037)

Avancerad SSL-programmering I

Synkronisering - Semaforen. Om att vänta men inte i onödan

Introduktion. Klasser. TDP004 Objektorienterad Programmering Fö 2 Objektorientering grunder

Operativsystem ID1200/06 och ID2200/06 Tentamen TENA 6 hp :00-18:00

Kunskapsbank ICARUS DB

Försättsblad till skriftlig tentamen vid Linköpings Universitet Cover page for written exam at Linköping University

F4. programmeringsteknik och Matlab

C++ Slumptalsfunktioner + switch-satsen

F5 Selektion och iteration. ID1004 Objektorienterad programmering Fredrik Kilander

Introduktion till formella metoder Programmeringsmetodik 1. Inledning

Namn: (Ifylles av student) Personnummer: (Ifylles av student) Tentamensdatum: Tid: Hjälpmedel: Inga hjälpmedel

TDIU01 - Programmering i C++, grundkurs

Föreläsning 3: Booleans, if, switch

Programmering B med Visual C

Föreläsning 9 Datastrukturer (DAT037)

SP:PROG3 HT12 Tenta

TENTAMEN OOP

Exam Concurrent and Real-Time Programming

E02 "The Review" Föreläsning 2, HT2013 Grunderna, repetition. Johan Leitet. Kurs: 1dv403 Webbteknik I

Examination i. PROGRAMMERINGSTEKNIK F1/TM1 TIN212 (Dugga) Dag: Onsdag Datum: Tid: (OBS 3 tim) Rum: V

Grundkurs i programmering, 6 hp (725G61) Dugga 2 tillfälle 2

Hjälpmedel: Inga hjälpmedel förutom penna, suddgummi och glatt humör.

Programmeringsteknik med C och Matlab

Deluppgift 17 Processhantering: exec, sleep, exit, plist

DD2387 Programsystemkonstruktion med C++ Tentamen 1 Tisdagen den 28 oktober 2014, 08:00-12:00

Operativsystem ID1200/06 Tentamen :00-18:00

Tentamen i ID2206, ID2200 samt IS1350 Operativsystem

Dagens föreläsning Programmering i Lisp. - Bindning av variabler (avs 14.6) fria variabler statisk/lexikalisk och dynamisk bindning

DD2387 Programsystemkonstruktion med C++ Tentamen 1 Tisdagen den 28 oktober 2014, 08:00-12:00

Tentamen i Introduktion till programmering

Att skriva till och läsa från terminalfönstret

Tommy Färnqvist, IDA, Linköpings universitet

Formell Verifiering. Hur vet man att ett system fungerar korrekt? Lisa Kaati

DIG IN TO Dator och nätverksteknik

Schemaläggning Unix. Minneshantering etc. Linux. Schemaläggning av trådar (kernel threads) Detaljer. Operativsystem - Lektion 7

TDIU01 Programmering i C++

Föreläsning 5 Innehåll

BINÄRA TRÄD. (X = pekarvärdet NULL): struct int_bt_node *pivot, *ny; X X X 12 X X 12 X X -3 X X

Operativsystem ID1200/06 (ID2200/06 6hp) Tentamen :00-18:00

Parallellism, återblick

Föreläsning 2 Datastrukturer (DAT037)

Realtidsprogrammering. En introduktion Implementering (med exempel från PIC)

Fö 7: Operativsystem. Vad är ett operativsystem? Målsättning med operativsystem. Styr operativsystemet datorn?

F12 - Collections. ID1004 Objektorienterad programmering Fredrik Kilander

Webbtjänster med API er

Föreläsning 5 Datastrukturer (DAT037)

Försättsblad till skriftlig tentamen vid Linköpings Universitet Cover page for written exam at Linköping University

Kunskapsbank ICARUS DB

Tentamen EDAF30 Programmering i C++

Universitetet i Linköping Institutionen för datavetenskap Anders Haraldsson

DD2387 Programsystemkonstruktion med C++ Tentamen 2

ÖVERVAKNING AV SQL SERVER

Outline. Objektorienterad Programmering (TDDC77) En frukt har ett namn. Man kan lägga en frukt i en korg... Hashing. Undantag. Ahmed Rezine.

Fö 5+6 TSEA81. Real-time kernel + Real-time OS

729G04 Programmering och diskret matematik. Föreläsning 7

Tentamen PC-teknik 5 p

OOP Objekt-orienterad programmering

Lunds Tekniska Högskola Datorarkitektur med operativsystem EITF60. Superscalar vs VLIW. Cornelia Kloth IDA2. Inlämningsdatum:

Tentamen Datastrukturer (DAT036)

Klassdeklaration. Metoddeklaration. Parameteröverföring

OBJEKTORIENTERAD PROGRAMVARUUTVECKLING. Övningstentamen 1

Polymorfi. Objektorienterad och komponentbaserad programmering

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

Hyper-Threading i Intelprocessorer

Operativsystem (IS1350) :00-12:00

ITK:P1 Föreläsning 1. Programmering. Programmeringsspråket Java. Stark typning Explicit typning Strukturerat Hög säkerhet

Datastrukturer, algoritmer och programkonstruktion (DVA104, VT 2015) Föreläsning 6

Föreläsning 7: Transaktioner

Datorteknik 2 (AVR 2)

Pekare och arrayer. Indexering och avreferering

TDIU01 - Programmering i C++, grundkurs

Lösningsförslag. Programmeringsmetodik, KV: Java och OOP. 17 januari 2004

SAS USER FORUM SWEDEN 2017 USER FORUM. SAS Viya och öppenhet en gemomgång av LUA-, Java-, Python- och REST-gränssnitten mot SAS Viya

Introduktion till programmering D0009E. Föreläsning 1: Programmets väg

Skizz till en enkel databas

Operativsystem - input/output, skydd, virtualisering

Föreläsning 4 Datastrukturer (DAT037)

Tung bakgrundsaktivitet t.ex. Aktiva objekt t.ex. Animering, simulering. DD2385 Programutvecklingsteknik Några bilder till föreläsning 9 6/5 2013

Fakulteten för ekonomi, kommunikation och IT. Corba. Datum: Mathias Andersson

Deluppgift 10 Starta den första processen (3.5h förarbete, 30min kodning)

Objektorienterad Programmering (TDDC77)

Systemnära programmering Tentamen. Systemnära programmering, 7.5hp 5 november 2012

Designmönster, introduktion. Vad är det? Varför skall man använda mönster?

Lösningsförslag till omtentamen för TDA540 Objektorienterad Programmering

Transkript:

Ett inte alltför ovanligt problem i multitråd-program är deadlocks. Men först en liten sammanfattning av några begrepp som används i påföljande diskussion. I Microsoft Windows-miljö (vilken är min primära plattform för systemprogramutveckling) används följande termer: Process: En process är den samling av resurser ett program använder. Dessa resurser består av ett virtuellt adressutrymme inom vilket applikationen arbetar, den exekverbara koden, handles till använda systemobjekt, en säkerhetskontext, en unik programidentifierare, en prioritetsklass, min- och max-gränsvärden för arbetsminnet för direktbearbetning (working set), samt minst en exekveringstråd. Varje process startas med en exekveringstråd, vilken kallas den primära tråden, men kan skapa ytterligare trådar från vilken tråd som helst. Tråd: En tråd är en begreppsenhet inom en process som kan schemaläggas för exekvering av kod. Alla trådar i en process delar på samma virtuella adressutrymme och systemresurser. Dessutom hanterar varje tråd felhantering, prioriteten för trådens schemaläggning, lokal lagring av psuedogemensamma trådvariabler, och andra strukturer som operativsystemet använder för att spara trådens kontext tills dess att den schemaläggs för exekvering. En trådkontext inkluderar trådens CPU-register, kernelstacken, miljövariabler för tråden, och en programstack i processens adressutrymme. Trådar kan också ha sin egen säkerhetskontext för att kunna uppträda som andra klienter. Trådar (och skilda processer) exekverar parallellt med varandra (det är lite mer komplicerat än så, men för vår diskussion nedan är detta tillfyllest). Det betyder att ifall flera trådar delar på någon gemensam resurs kommer vi snabbt att få ett problem: resursen kommer att bli fördärvad. 1 / 5

Ett mycket enkelt exempel: ta två trådar, T1 och T2. De delar på en datavariabel som vi kan kalla V. I T1 sätts nu V till ett visst värde. Så tar T2 över och sätter ett annat värde på variabeln. T1 exekverar obekymrat på och läser värdet på V för att ta ett beslut. Det blir dock ett helt galet beslut, eftersom det värde som T1 satte lite tidigare har gått förlorat. Detta blir orsaken till i bästa fall slumpmässiga konstigheter i programmet, och i värsta fall till en total programkrasch. Det är alltså oerhört viktigt att kunna förhindra att operativsystemet på ett oväntat sätt låter multipla trådar manipulera delade resurser på detta sätt. För att kunna ta kontroll över detta finns det synkroniseringsobjekt, populärt kallat för lås. Alla multitråd-operativsystem innehåller åtminstone någon form av enkla lås. En tråd tar ägarskap över ett lås, och om låset inte är tillgängligt suspenderar operativsystemet trådens exekvering fram tills dess låset släpps fritt, och tråden äntligen kan ta ägarskapet över det. När tråden så sedan har använt den gemensamma resursen och inte behöver det längre, öppnas låset och vilken annan tråd som helst kan ta över ägarskapet, och därmed också manipulera resursen enligt eget tycke. Det finns dock ett subtilt problem som lurar ifall man har fler delade resurser som skyddas av åtskilda lås. Man kan råka ut för ett klassiskt deadlock. I ett enkelt sådant scenario kan vi åter tänka oss två trådar, T1 och T2, som delar på två datavariabler, VA och VB. Dessa skyddas av sina respektive lås, LA och LB. Nu tänker vi oss att följande händer: 1. T1 låser LA för att exklusivt kunna manipulera VA. 2. T2 låser LB för att exklusivt kunna manipulera VB. 3. T1 försöker låsa LB för att kunna manipulera VB, men den är redan låst av T2. 4. T2 försöker låsa LA för att kunna manipulera VA, men den är redan låst av T1. T1 T2 LA 1 0 LB 0 1 2 / 5

[ 1 = deadlock ] Om nu låsen väntar utan tidsbegränsning, kommer de båda trådarna för evigt att vänta på att den andra tråden ska släppa fri den andra resursen som den vill ha, och vi har fått något som är en klassisk deadlock-situation. Ett sätt att försöka lösa upp denna situation är att se till att låsen alltid låses i samma ordning, t.ex. genom att låsa dem med stigande minnesadress. I ett enkelt program går detta alldeles utmärkt, men med stigande komplexitet (och med allt fler programmerare inblandade) blir det snabbt mycket svårt att kunna implementera det på detta sätt. Olyckan kommer förr eller senare att inträffa, och då behöver vi en metod att exakt kunna hitta var i programmet en deadlock uppstår (speciellt när det sker ute hos kund). I det system som jag f.n. arbetar med är det dessutom mer komplicerat än så. I det finns det två typer av lås, "readlock" och "writelock", som i många fler olika kombinationer kan orsaka deadlock: T1(LA->LB) r->r r->w w->r w->w T2(LB->LA) r->r 0 0 0 r->w 0 1 0 w->r 0 0 1 w->w 1 1 1 [ r = readlock, w = writelock, 1 = deadlock ] 3 / 5

Nå, behovet av ett system för att kunna detektera deadlocks är alltså oerhört viktigt. Hur angripa detta problem? I Dr. Dobbs läste jag en artikel som tog upp deadlockproblematiken, och det blev inspirationen till följande lösning. All kod som jag härefter skriver är i C++ och WIN32 API, eftersom det är vad som den applikation som jag jobbar med idag använder. Vad vi behöver är en helt separat tråd i programmet, vars enda uppgift är att detektera ifall vi uppnått en deadlock-situation enligt ovanstående sanningstabell. För att kunna uppnå detta behövs först att varje gång en tråd vill ta över ett lås, skickas först ett meddelande till detektortråden om att låset strax kommer att begäras, att låset har övertagits, alternativt att en tidsgräns har överskridits och tråden misslyckats ta över låset, samt att tråden släpper låset fritt igen. I ett Windowsprogram är den mest uppebara kandidaten för att skicka sådana meddelanden till detektortråden en Windows-meddelandepump. Alltså ska själva grunden i detektortråden vara en enkel meddelandepump (ErrorHandler och MessageHandler är egna funktioner som tar hand om detaljer som inte är viktiga för denna diskussion): MSG msg = { 0 }; for ( ; ; ) { BOOL retval = ::GetMessage(&msg, NULL, 0, 0); switch (retval) { case -1: // Error return ErrorHandler(::GetLastError()); case FALSE: // Received WM_QUIT return 0; case TRUE: (VOID) ::DispatchMessage(&msg); // Necessary so that any Timer Callback Function is called. MessageHandler(msg); break; default: _ASSERT(false); // Should be impossible! return 42; } if (!m_isrunning &&!::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { (VOID) ::PostThreadMessage(::GetCurrentThreadId(), WM_QUIT, WPARAM(0), LPARAM(0)); // Gracefully exit this thread. } } Nästa steg är att formge de meddelanden som vi vill använda i deadlock-detektortråden. Den information vi behöver är vad det är för typ av lås (readlock/writelock), vilket läge låset befinner sig i (strax före låsförsöket/när låset har blivit taget/när låset släppt fritt igen/om låsförsöket misslyckades), trådens identitet, låsets identitet, var i koden händelsen skett, samt i vilken ordning (tidpunkten) händelsen på låset skett. Vi lagrar denna information i en datastruktur och skickar den till detektortråden: 4 / 5

enum LOCKATTEMPT_TYPE { typeunknown = 0, readlock = 1, // is a read lock writelock, // is a write lock }; enum LOCK_STATE { stateunknown = 0, stateattempting = 1, // state: is attempting a lock statelocked, // state: is successfully locked stateunlocked, // state: is successfully unlocked statefailed, // state: attempt failed }; typedef struct { } Object;typedef const Object* TRWMutexPtr; typedef DWORD THREADID;typedef DWORD TIMEOUT; typedef UINT64 SESS_COUNTER; typedef INT64 SESS_COUNTER_DIFF; struct LOCKSESSION { SESS_COUNTER m_counter; // Locksession counter THREADID m_threadid; // thread ID TRWMutexPtr m_lockid; // the involved mutex LOCKATTEMPT_TYPE m_locktype; // what type of lock LOCK_STATE m_lockstate; // state of the session std::string m_sourcefilename; // Path to the source file where the lock operation was registered. UINT m_sourcelinenumber; // Line number of the source file where the lock operation was registered. }; Nu behöver vi bara registrera några Windows-meddelanden, och se till att den kod som låser skickar rätt meddelanden vid rätt tillfälle: const UINT AM_TRYGETLOCK = ::RegisterWindowMessage("AM_TRYGETLOCK"); const UINT AM_OWNLOCK = ::RegisterWindowMessage("AM_OWNLOCK"); const UINT AM_RELEASELOCK = ::RegisterWindowMessage("AM_RELEASELOCK"); const UINT AM_FAILEDGETLOCK_TIMEOUT = ::RegisterWindowMessage("AM_FAILEDGETLOCK_TIMEOUT"); const UINT AM_FAILEDGETLOCK = ::RegisterWindowMessage("AM_FAILEDGETLOCK"); LOCKSESSION psess = new LOCKSESSION; _ASSERT(pSess!= NULL); if (psess!= NULL) { psess->m_lockid = TRWMutexPtr(pMutex); psess->m_locktype = locktype; psess->m_lockstate = stateattempting; psess->m_threadid = threadid; psess->m_sourcefilename = sourcefilename; psess->m_sourcelinenumber = sourcelinenumber; if (!::PostThreadMessage(m_threadID, AM_TRYGETLOCK, WPARAM(pSess), LPARAM(0))) { delete psess; } } Resten är, som man säger, detaljer. Den mottagande koden (MessageHandler ovan) läser den information som vi registrerat i psess ovan. Första gången händelsekedjan på låset registreras i en tråd sparas informationen i en lista (en std::map närmare bestämt) och uppdateras därefter fram tills dess meddelandet AM_RELEASELOCK, AM_FAILEDGETLOCK_TIMEOUT, eller AM_FAILEDGETLOCK tas emot, då den i stället tas bort från listan. Med hjälp av t.ex. en timer kontrolleras med jämna mellanrum innehållet i listan, och ifall en deadlock enligt schemat upptäcks, rapporteras det på lämpligt vis. 5 / 5