METU Department of Computer Engineering CEng 536 - Advanced Unix Fall 2011-2012 Midterm (Take Home / Due: Dec 1, 18:00 AM/4 questions, 110 points, 5 pages) Name: No: Signature: Note: You are not expected to give complete codes in any of the questions. Just provide significant and critical parts of the code. Your code is not expected to work, just describe primary parts of such a program. Question 1 : (30 pts) Some web servers implemente a process pool based service handling and load balancing. Since the handling of the request takes some time, during the service other connections has to be accepted by processes in the pool. At startup, m service processes are forked. One simple way to handle load balancing is to make accept() call handled by any available (currently not serving) process in the pool exclusively. Only one process should accept from a listened connection at any instance of time. After accepting, it should call handlerequest(int sock) to handle connection and then loop for the following connection. a) Write the code part starting m processes and having them call accepthandleconnection(int listened ). listened parameter is the socket that is listened to. b) Write the main loop of accepthandleconnection(int). Beware of the synchronization you might need. c) Accepting on the parent process and letting children to handle the connection on already accepted socket is another option as given below. State your solution if it can be done trivially. Otherwise discuss briefly why it is not trivial. r = l i s t e n ( s, 10); while (( ns = a c c e p t ( s ))!= -1) h a n d l e a c c e p t e d c o n n e c t i o n ( ns, p i d ); /* pid is an available process */ d) Assume I want to run my service on multiple ports (ie. 80, 8080, 8888). Instead of blocking on a single accept(), I can block on multiple file descriptors simultaneously as we can do with multiple socket descriptors to read. Give code of multipleaccept(int args[]) that returns the new socket returned by accept(). args[] is a -1 terminated array of socket descriptors that are listened to. int args[] = 5, 7, 3, -1; 1
Question 2 : (30 pts) Assume you have a fixed number of worker threads. You don t want to have the cost of starting a new thread per task, so you use existing worker pool to assign tasks. When a worker thread finishes a task, it sets itself free and wait for another task to be assigned as in the pseudo code: struct Task... /* details of a task is irrelevant */ ; struct Worker w o r k e r s [NUMWORKERS]; int t h r e a d w o r k e r i d ; /* worker threads index in workers array */ w o r k e r ( w o r k e r p o i n t e r )... /* initializations here (ie. workerid ) */ while (! t e r m i n a t e ) s e t f r e e () /* workerid : index in workers array */ w a i t f o r t a s k () /* assigned task is put in workers [ workerid ]. task */ p r o c e s s () In order to assign tasks, there is a main thread cycling over all worker threads for a free one and assign tasks to workers. This thread is the only thread reading tasks from a queue and assigning workers. m a i n t h r e a d (...) while (! t e r m i n a t e ) w = f i n d n e x t f r e e w o r k e r () while (w <0) /* no free worker found */ w a i t f o r f r e e w o r k e r () w = f i n d n e x t f r e e w o r k e r () t = r e a d f r o m q u e u e (Queue) /* somebody pushes tasks to Queue */ *( w o r k e r [w]. t a s k ) = t /* set task of the worker */ wakeupworker (w) Main thread will assume the workers array to be a cyclic array and go over them one by one. When a full cycle completed without a free worker. It will block (without polling) until a worker gets free. Complete the marked parts of the following code struct Worker int i d ; /* workerid */ int f r e e ; p t h r e a d m u t e x t *mut; /* protects the worker */ struct Task * t a s k ; /* current task info */ /* this part is to be completed */ a ; void s e t f r e e () /* mark the worker thread free. main thread might be interested in this */ b void w a i t f o r t a s k () /* worker waits for a task to be assigned */ c void wakeupworker ( wid ) /* wake up the worker with wid */ d void w a i t f o r f r e e w o r k e r () /* main thread waits for a free worker (no polling!!!) */ e 2
int f i n d n e x t f r e e w o r k e r () static s t a r t = 0; int i = s t a r t ; while (1) p t h r e a d m u t e x l o c k ( w o r k e r s [ i ].mut) if ( w o r k e r s [ i ]. f r e e ) w o r k e r s [ i ]. f r e e = 0; p t h r e a d m u t e x u n l o c k ( w o r k e r s [ i ].mut) s t a r t = ( i +1) % NUMWORKERS; return i ; p t h r e a d m u t e x u n l o c k ( w o r k e r s [ i ].mut) i = ( i +1) % NUMWORKERS; if ( i == s t a r t ) /* we made a full cycle but no free */ return -1; 3
Question 3 : (25 points) and reference information. You are given the following 8 full pages of physical memory with the memory page Page # Virtual page # Referenced Modified 0 0x0B 1 1 1 0x00 0 0 2 0x14 1 0 Back hand 3 0x0A 0 1 4 0x24 0 0 5 0x1A 1 1 6 0x10 1 0 Front hand 7 0x1F 1 1 Trace the System V R4 paging out algorithm for 2 calls of pageout(). Follow the sequence: process front hand increment front hand process back hand increment back hand for each scan. Assume desscan is 4. Between 2 pageout() calls, assume the process working on CPU writes the page 0x0E, reads pages 0x24 and 0x0B (do pagein if necessary). You can always use the smallest free page for paging-in whenever necessary. Do not skip over already freed pages. When a hand is on a free page do nothing and increment/process the hand in the next iteration. Increment nscan in free page case too. a) Provide the steps of your trace as a sequence of following phrases: clear the reference bit of page.. page out (free) the page... write the virtual page... in page... to disk set (back or front) hand to page...... bit of the page... is set page... is paged in to page... Also give the state of the memory pages after your trace. b) Instead of inserting stolen pages into a freelist system inserts them into cachelist, then the last recently used pages in cachelist are inserted into freelist. Why do we need this? What is the advantage? Answer briefly (in 5 senteces at most) 4
Question 4 : (25 points) Assume you are writing some kernel code. Give the small code segments to accomplish the given task. Ignore any mutual exclusion etc. to access kernel structures. a) A loop to iterate on all threads of the current process in Linux. Leave loop body empty, each thread/task will be on variable task struct *t. b) A loop to iterate on user structures of all processes in OpenSolaris. Leave loop body empty, each user structure will be on variable struct user *up. c) Assume you know struct file * filep ; value of an open file in Linux. You want to tell if this file is mapped in your (currently running tasks) memory. Write the code to set the struct vm area struct *vm; value to the corressponding area in case the file is mapped, otherwise it is set to NULL. d) Assume you know struct file * filep ; value of an open file in OpenSolaris. You want to tell if this file is mapped in your (currently running processes) memory. Write the code to set the struct seg *sg; value to the corressponding area in case the file is mapped, otherwise it is set to NULL. e) What does linux kernel has to do in order to implement thread local storage, e.g. global variables as: int thread elements[100]; No code parts are required in answer of this item. Just state verbally. Keep the thread implementation of Linux in mind. Solaris hints: p r o c t * p r a c t i v e /* start of the active processes */ p r o c t * p r o c i n i t /* init process */ t t o p r o c ( c u r t h r e a d ) /* current process */ s y s / p r o c.h s y s / u s e r.h s y s / vnode.h struct proc, p r o c t struct u s e r, u s e r t Linux hints: struct t a s k s t r u c t * c u r r e n t ; /* current process */ i n c l u d e / l i n u x / s c h e d.h i n c l u d e / l i n u x / f i l e.h 5