Institutionen för datavetenskap Umeå Universitet

Systemprogrammering för ingenjörer: Lab 3 - Pthreads och synkronisering.

Anta att du är administratör för ett antal datorer som kör ett system. Alla datorerna genererar ibland logmeddelanden. Dessa logmeddelanden skickas till en av datorerna för arkivering. Alla logmeddelanden har samma storlek. De innehåller känslig information och måste därför krypteras. Du har av din chef fått en krypteringsalgoritm som ska användas för krypteringen. Det enda du egentligen behöver veta om krypteringsalgoritmen är att den är långsam (så långsam att den börjar ställa till problem). Du har (tyvärr) ingen möjlighet att påverka krypteringsalgoritmen (vilket annars hade varit en enklare lösning på ditt problem). Du bestämmer dig därför för att använda en av servrarna med flera processorer/kärnor för ditt system för kryptering och arkivering av logmeddelanden.

Du vill utnyttja att servern har multipla processorer/kärnor och vill därför låta varje logmeddelande hanteras av en tråd. Men att skapa en tråd per loggmeddelande kan innebära att för många trådar skapas så därför bestämmer du dig för att sätta en gräns över maximalt antal trådar. Du vill inte heller utnyttja allt för mycket minne för inlästa ej behandlade logmeddelanden. Du bestämmer dig därför för att man ska få bestämma antal buffrar och krypteringstrådar vid starten av programmet.

Du ska i denna laboration skriva ett program som hanterar logmeddelandena (som var och en är 4000 bytes långa). Programmet ska ta emot tre tal som kommandoradsargument. Den första kommandoradsparametern (C) är antalet datorer som kan generera logmeddelanden, Den andra komandoradsparametern (P) är antalet trådar som ska användas för krypteringen, och det tredje talet B är antalet buffrar att använda. Därefter ska C+P trådar startas.

De C lästrådarna ska var och en skapa en FIFO (namngiven med "1", "2", ..., "C" i katalogen där programmet startades ifrån) där loggmeddelandena läses från. (Datorerna kommer sedan skriva logmeddelanden till dessa FIFO:n via ett nätverksprogram som ni inte behöver implementera). När de har läst in den första byten ur ett meddelande ska dessa trådar vänta tills en någon buffert finns ledig, läsa in hela meddelandet i bufferten, och efter det återgå till att bevaka sin FIFO.

De P krypteringstrådana ska vänta tills ett meddelande lagts in i en buffert och sedan kryptera det samt då det är klart återlämna bufferten. Det första meddelandet från FIFO 1 ska sparas i en fil med namnet "1_1" nästa i "1_2", etc.

Krav

Program ska läsa in nyckeln (bestående av max 127 tecken) ur filen "keys" , skapa C+P trådar, och vänta på att någon av följande signaler inträffar:

  • När programmet motar signalen SIGHUP (som användarna av programmet skickar tex mha kill), ska den berätta för krypteringstrådarna att de inte ska påbörja en ny kryptering. När alla dessa trådar har slutfört påbörjade jobb ska programmet läsa in nyckeln ur filen "keys" igen, och säga åt krypteringstrådarna att återta arbetet.
  • När programmet motar signalen SIGINT ska det berätta för alla lästrådar att de inte ska fylla fler buffertar (påbörjade ska slutföras). När alla lästrådar är avslutade ska den låta krypteringstrådarna kryptera klart allt som är inläst och sedan avsluta programmet.

De C lästrådarna ska öppna FIFOn och läsa en byte från den. När en byte har mottagits ska de vänta på att en buffert blir tillgänglig, läsa in resten av meddelandet i bufferten, stänga FIFOn, öppna fifon igen och upprepa.

De P krypteringstrådarna ska vänta tills en buffert är helt fylld av en lästråd, kryptera meddelandet, spara det i rätt fil, och upprepa. Varje buffert ska förutom att ha plats för meddelandet också rymma från vilken FIFO det är läst samt meddelandets ordningsnummer på den FIFOn

4. Alla synkroniseringsproblem ska lösas med mutex och condition variabler på ett sätt som ger bra parallelitet. Du får anta att alla logmeddelanden är kompletta (dvs alla logmeddelanden kommer vara exakt 4000 bytes).

Hjälp

  • Krypteringsfunktionen hittar ni här: xorcrypt.c xorcrypt.h. Funktionen använder sig av en funktion som finns i biblioteket rt så därför måste ni lägga till flaggan -lrt då ni kompilerar för att den ska funka på solaris.
  • Kolla upp hur errno fungerar i multitrådade system på den plattform där du jobbar.
  • Se man-sidorna för pthread_create och pthread_join för hur man kan hantera trådarna.
  • Glöm inte bort att använda flaggan -lpthread när du kompilerar ditt program.
  • Detta är ett bounded buffer problem. Du ska använda en lösning som är lämpad för mutex and condition variabler. Varken mutex eller condition variabler har några värden associerade med sig. Ett mutex tillåter som mest att en tråd låser den (sammtidigt), det krävs också att det är samma tråd som sedan låser upp den. Se mansidorna för pthread_mutex_init, pthread_mutex_lock och pthread_mutex_unlock för detailjer. Condition variabler svarar mot händelser. En tråd kan vänta på en condition variabel (vänta på att en händelse inträffar). Trådar som får händelsen att inträffa ska signalera att det hänt. Detta kommer att väcka upp en eller alla av de väntande trådarna. För att undvika att det uppstår en race condition är en condition variabel alltid associerad med ett mutex. Du ska alltid se till att mutexen är låst när du väntar på eller signalerar händelsen. När man väntar på en condition variabel kommer mutexet automatiskt att låsas upp, när ett condition signalleras kommer mutex återtas innan signal avslutas. Se pthread_cond_init, pthread_cond_wait, pthread_cond_signal och pthread_cond_broadcast för detaljer.
  • Skapa två köer, en med lediga buffrar och en med fyllda. Ha en mutex till varje kö, och en condition för att representera händelsen att kön blir icke-tom. En läs-tråd (producent) kommer vänta tills något finns att läsa från FIFOn. Låsa mutexen för kön med tomma buffrar, vänta på lediga buffrar condition variabeln, ta en buffert ur kön och låsa upp mutexen. Därefter kan den läsa in resten av datat i bufferten. När den är färdig med det så låser den mutexen för kön med fulla buffrar, stoppar in bufferten i den kön och låser upp mutexen. Om buffertlistan var tom innan detta så ska den broadcasta att listan inte längre är tom. Krypteringstrådarna gör motsvarande (fast mer eller mindre tvärtom).
  • Vänta med att fixa signalhanteringen innan ni fått resten att fungera.
  • För att säkerställa att det inträffar en signal där du inte förutsett det ska alla trådar bockera signalerna SIGHUP och SIGINT (mha pthread_sigmask. Se också mansidan för sigsetops). När en signal är blockerad kommer den inte att gå att hantera. Föräldratråden kan kan istället använda sigwait för att vänta på att någon av de blockerade signalerna inträffar. Du kommer därmed inte att behöva skriva några signalhanterare för SIGHUP och SIGINT.
  • Det är lite bökigt att skriva koden för att hantera SIGINT så att alla trådar avslutas snyggt (dvs att inga loggmeddelanden har påbörjat att bearbetas, men ej hanterats klart). Problemet är att de flesta lästrådarna kommer att vara blockerade vid systemanropet read. Du kan skicka en signal till tråden mha pthread_kill för att avbryta systemanropet. Du kan också behöva använda pthread_cond_timedwait för att upprepa sändandet av signalen om tråden misslyckas med att reagera på signalen på grund av något race condition.

Dokumentation, filer och deadline

  • Laborationen dokumenteras med en fullständig rapport inklusive testkörningar och källkod. Tänk särskilt på att dokumentera hur trådsynkroniseringen åstadkomms på ett tydligt sätt
  • Källkoden samt en makefil ska även finnas tillgängligt i katlogen: ~/edu/5DV004/lab3/. Den exekverbara filen ska heta mcryptlogger, ligga i mappen ~/edu/5DV004/lab3/bin/ , och vara kompilerad för Solaris
  • Sista inlämningsdag: 13:00 tisdagen den 8 juni

Laborationen löses enskilt


http://www.cs.umu.se/kurser/5DV004/VT10/labbar/lab3/index.html
Ansvarig för sidan: (none)
Senast ändrad 2010-05-27