Os scheduling

hloice29
assign33.zip

__MACOSX/._assign3

assign3/.DS_Store

__MACOSX/assign3/._.DS_Store

__MACOSX/assign3/._assign3_part2

__MACOSX/assign3/._assign3_part1

assign3/assign3_part2/list.c

#include <stdio.h> #include "list.h" /* list helper functions */ int list_size(thread_info_list *list) { int cnt = 0; if (!list) return -1; pthread_mutex_lock(&list->lock); list_elem *le = list->head; while (le) { cnt++; le = le->next; } pthread_mutex_unlock(&list->lock); return cnt; } int list_insert_head(thread_info_list *list, list_elem *new) { if (!list || !new) return -1; pthread_mutex_lock(&list->lock); new->next = list->head; new->prev = 0; if (new->next) { new->next->prev = new; } list->head = new; if (list->tail == 0) { list->tail = new; } pthread_mutex_unlock(&list->lock); return 0; } int list_insert_tail(thread_info_list *list, list_elem *new) { if (!list || !new) return -1; pthread_mutex_lock(&list->lock); new->prev = list->tail; new->next = 0; if (new->prev) { new->prev->next = new; } list->tail = new; if (list->head == 0) { list->head = new; } pthread_mutex_unlock(&list->lock); return 0; } int list_remove(thread_info_list *list, list_elem *old) { if (!old || !list) return -1; pthread_mutex_lock(&list->lock); if (old->next) { old->next->prev = old->prev; } if (old->prev) { old->prev->next = old->next; } if (list->tail == old) { list->tail = old->prev; } if (list->head == old) { list->head = old->next; } old->next = old->prev = 0; pthread_mutex_unlock(&list->lock); return 0; } void print_list(thread_info_list *list) { pthread_mutex_lock(&list->lock); list_elem *le = list->head; while (le) { printf("0x%X,", (unsigned int)le->info); le = le->next; } pthread_mutex_unlock(&list->lock); printf("\n"); }

__MACOSX/assign3/assign3_part2/._list.c

assign3/assign3_part2/worker.c

#include <stdio.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include "scheduler.h" /******************************************************************************* * * Implement these functions. * ******************************************************************************/ /* Handler for SIGTERM signal */ void cancel_thread() { printf("Thread %u: terminating.\n", (unsigned int)pthread_self()); /* signal that done in queue */ sem_post(&queue_sem); pthread_exit(NULL); } /* TODO: Handle the SIGUSR1 signal */ void suspend_thread() { printf("Thread %u: suspending.\n", (unsigned int)pthread_self()); /*add your code here to wait for a resume signal from the scheduler*/ printf("Thread %u: resuming.\n",(unsigned int) pthread_self()); } /******************************************************************************* * * * ******************************************************************************/ /* * waits to gain access to the scheduler queue. */ static int enter_scheduler_queue(thread_info_t *info) { /* * wait for available room in queue. * create a new list entry for this thread * store this thread info in the new entry. */ sem_wait(&queue_sem); list_elem *item = (list_elem*)malloc(sizeof(list_elem)); info->le = item; item->info = info; item->prev = 0; item->next = 0; list_insert_tail(&sched_queue, item); return 0; } /* * leaves the scheduler queue */ void leave_scheduler_queue(thread_info_t *info) { printf("Thread %lu: leaving scheduler queue.\n", info->thrid); /* * remove the given worker from queue * clean up the memory that we malloc'd for the list * clean up the memory that was passed to us */ list_remove(&sched_queue, info->le); free(info->le); free(info); } /* * Initialize thread, enter scheduling queue, and execute instructions. * arg is a pointer to thread_info_t */ void *start_worker(void *arg) { thread_info_t *info = (thread_info_t *) arg; float calc = 0.8; int j = 0; /* TODO: Block SIGALRM and SIGUSR2. */ /* TODO: Unblock SIGUSR1 and SIGTERM. */ /* compete with other threads to enter queue. */ if (enter_scheduler_queue(info)) { printf("Thread %lu: failure entering scheduler queue - %s\n", info->thrid, strerror(errno)); free (info); pthread_exit(0); } printf("Thread %lu: in scheduler queue.\n", info->thrid); suspend_thread(); while (1) { /* do some meaningless work... */ for (j = 0; j < 10000000; j++) { calc = 4.0 * calc * (1.0 - calc); } } }

__MACOSX/assign3/assign3_part2/._worker.c

assign3/assign3_part2/Makefile

CC = gcc CCOPTS = -c -g -Wall LINKOPTS = -g -pthread -Wall EXEC=scheduler OBJECTS=scheduler.o worker.o list.o smp5_tests.o testrunner.o all: $(EXEC) $(EXEC): $(OBJECTS) $(CC) $(LINKOPTS) -o $@ $^ -lrt %.o:%.c $(CC) $(CCOPTS) -o $@ $^ clean: - $(RM) $(EXEC) - $(RM) $(OBJECTS) - $(RM) *~ - $(RM) core.* test: scheduler - ./scheduler -test -f0 rr - killall -q -KILL scheduler; true pretty: indent *.c *.h -kr

__MACOSX/assign3/assign3_part2/._Makefile

assign3/assign3_part2/scheduler.h

#ifndef __SCHEDULER_H_ #define __SCHEDULER_H_ #include <pthread.h> #include <semaphore.h> #include "list.h" #define QUANTUM 1 /* typedefs */ typedef struct thread_info { pthread_t thrid; int quanta; list_elem* le; /*added for evalution bookkeeping*/ struct timespec suspend_time; struct timespec resume_time; long wait_time; long run_time; } thread_info_t; /* functions */ void *start_worker(void *); long time_difference(const struct timespec*, const struct timespec*); int smp5_main(int argc, const char** argv); /* shared variables */ extern sem_t queue_sem; /* semaphore for scheduler queue */ extern thread_info_list sched_queue; /* list of current workers */ #endif /* __SCHEDULER_H_ */

__MACOSX/assign3/assign3_part2/._scheduler.h

assign3/assign3_part2/smp5_tests.c

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/ #define _GNU_SOURCE #include <stdio.h> #undef _GNU_SOURCE #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include "testrunner.h" #include "list.h" #include "scheduler.h" #include "worker.h" //#define quit_if(cond) do {if (cond) exit(EXIT_FAILURE);} while(0) #define quit_if(cond) do {if (cond) {printf("Line %d.",__LINE__);exit(EXIT_FAILURE);}} while(0) void args_to_nums(int argc, const char **argv, int *num_workers, int *queue_size, int **quanta) { int i; quit_if(argc < 4); *num_workers = atoi(argv[1]); *queue_size = atoi(argv[2]); *quanta = malloc(*num_workers * sizeof(int)); quit_if(*quanta == NULL); for(i=3;i<argc;i++) quanta[0][i-3] = atoi(argv[i]); } void nums_to_args(int num_workers, int queue_size, int *quanta, int *argc, char ***argv) { int i; *argc = num_workers+3; *argv = malloc(*argc*sizeof(char *)); quit_if(*argv==NULL); argv[0][0] = "scheduler"; argv[0][1] = malloc(3*sizeof(char)); quit_if(argv[0][1]==NULL); sprintf(argv[0][1],"%d",num_workers); argv[0][2] = malloc(3*sizeof(char)); quit_if(argv[0][2]==NULL); sprintf(argv[0][2],"%d",queue_size); for(i=0;i<num_workers;i++) { argv[0][i+3] = malloc(3*sizeof(char)); quit_if(argv[0][i+3]==NULL); sprintf(argv[0][i+3],"%d",quanta[i]); } argv[0][i+3]=NULL; } /* Prepare input, reroute file descriptors, and run the program. */ void run_test(int argc, const char **argv) { //int fork_pid = fork(); //if (fork_pid == 0) { /* Reroute standard file descriptors */ freopen("smp5.out", "w", stdout); /* Run the program */ quit_if(smp5_main(argc, argv) != EXIT_SUCCESS); fclose(stdout); //} else if (fork_pid > 0) { //waitpid(fork_pid, 0, 0); //} else { //fprintf(stderr, "run_test: fork() error\n"); //} } int test_output(FILE *stream, int nw, int qs, int *q) { int queue_size, queue_index; int num_workers, worker_index; int rv, in_queue, term, susp; unsigned long *queue, *workers, tid, prev, newwork, dummyl; int *remaining, *quanta; char dummyc; float tot_wait, tot_run, ave_wait, ave_run; int my_run, my_wait; rv = fscanf(stream,"Main: running %d workers with queue size %d for quanta:\n",&num_workers, &queue_size); quit_if(rv != 2 || num_workers != nw || queue_size != qs); queue = malloc(queue_size*sizeof(long)); workers = malloc(num_workers*sizeof(long)); quanta = malloc(num_workers*sizeof(int)); remaining = malloc(queue_size*sizeof(int)); for(worker_index=0;worker_index<num_workers;worker_index++) { quit_if(fscanf(stream, " %d", quanta+worker_index) != 1); quit_if(quanta[worker_index]!=q[worker_index]); } fscanf(stream,"\n"); for(worker_index=0;worker_index<num_workers;worker_index++) { quit_if(fscanf(stream, "Main: detaching worker thread %lu.\n",workers+worker_index) != 1); } quit_if(fscanf(stream, "Main: waiting for scheduler %lu.\n",&dummyl) != 1); for(;queue_index<queue_size;queue[queue_index++]=0); worker_index = queue_index=0; in_queue = 0; quit_if(fscanf(stream, "Scheduler: waiting for workers.%c",&dummyc)!=1 || dummyc != '\n'); for(queue_index = 0;queue_index < queue_size && queue_index < num_workers;queue_index++) { quit_if(fscanf(stream, "Thread %lu: in scheduler queue.\n",&tid)!= 1 || tid != workers[worker_index]); quit_if(fscanf(stream, "Thread %lu: suspending.\n",&tid)!= 1 || tid != workers[worker_index]); queue[queue_index]=tid; remaining[queue_index] = quanta[worker_index]; worker_index++; in_queue++; } my_run=0; my_wait = num_workers; queue_index = 0; term = susp = 0; while(worker_index < num_workers || in_queue > 0) { while(!queue[queue_index]) queue_index= (queue_index+1)%queue_size; quit_if(fscanf(stream, "Scheduler: scheduling.%c",&dummyc)!=1 || dummyc != '\n'); quit_if(fscanf(stream, "Scheduler: resuming %lu.\n",&tid) != 1); quit_if( tid != queue[queue_index]); if (prev == tid) { if(term) { quit_if(fscanf(stream, "Thread %lu: terminating.\n",&tid) != 1 || tid != prev); } else if (susp){ quit_if(fscanf(stream, "Thread %lu: suspending.\n",&tid) != 1); quit_if( tid != prev); } quit_if(fscanf(stream, "Thread %lu: resuming.\n",&tid) != 1); quit_if(tid != queue[queue_index]); } else { quit_if(fscanf(stream, "Thread %lu: resuming.\n",&tid) != 1 || tid != queue[queue_index]); if(term) { if(queue_size == 1) quit_if(fscanf(stream, "Scheduler: waiting for workers.%c",&dummyc)!=1 || dummyc!='\n'); quit_if(fscanf(stream, "Thread %lu: terminating.\n",&tid) != 1 || tid != prev); if (in_queue == queue_size) { quit_if(fscanf(stream, "Thread %lu: in scheduler queue.\n",&tid)!=1||tid != newwork); quit_if(fscanf(stream, "Thread %lu: suspending.\n",&tid)!=1 || tid!=newwork); } } else if (susp && in_queue>1){ quit_if(fscanf(stream, "Thread %lu: suspending.\n",&tid) != 1); quit_if( tid != prev); prev = tid; } } quit_if(fscanf(stream, "Scheduler: suspending %lu.\n",&tid) != 1); quit_if(tid != queue[queue_index]); if(!--remaining[queue_index]) { quit_if(fscanf(stream, "Thread %lu: leaving scheduler queue.\n",&tid)!=1 || tid != queue[queue_index]); term = 1; if(worker_index < num_workers) { queue[queue_index] = workers[worker_index]; remaining[queue_index] = quanta[worker_index]; newwork = workers[worker_index]; worker_index++; if(queue_size == 1) { prev = tid; quit_if(fscanf(stream, "Scheduler: waiting for workers.%c",&dummyc)!=1 || dummyc!='\n'); quit_if(fscanf(stream, "Thread %lu: terminating.\n",&tid) != 1 || tid != prev); quit_if(fscanf(stream, "Thread %lu: in scheduler queue.\n",&tid)!= 1 || tid != newwork); quit_if(fscanf(stream, "Thread %lu: suspending.\n",&tid)!= 1 || tid != newwork); term = 0; susp = 0; my_wait++; } } else { queue[queue_index] = 0; in_queue--; } } else { term = 0; susp = 1; } prev = tid; my_run++; my_wait += in_queue+(num_workers-worker_index)-1+term; queue_index= (queue_index+1)%queue_size; } quit_if(fscanf(stream, "Th%c",&dummyc) != 1); if (dummyc=='r') { quit_if(fscanf(stream, "ead %lu: terminating.\nThe",&tid)!=1 || tid != prev); } quit_if(fscanf(stream, " total wait time is %f seconds.\n",&tot_wait) != 1); quit_if(fscanf(stream, "The total run time is %f seconds.\n",&tot_run) != 1); quit_if(fscanf(stream, "The average wait time is %f seconds.\n",&ave_wait) != 1); quit_if(fscanf(stream, "The average run time is %f seconds.\n",&ave_run) != 1); if (dummyc=='e') quit_if(fscanf(stream, "Thread %lu: terminating.\nThe",&tid) != 1|| tid != prev); quit_if(abs(tot_wait-my_wait)>1); quit_if(abs(tot_run-my_run)>1); quit_if(abs(tot_wait/num_workers-ave_wait)>.5); quit_if(abs(tot_run/num_workers-ave_run)>.5); return 0; } int general_test(int argc, const char **argv) { FILE *f; int nw, qs, *q; run_test(argc,argv); f = fopen("smp5.out","r"); args_to_nums(argc,argv,&nw,&qs,&q); test_output(f,nw,qs,q); return EXIT_SUCCESS; } int specific_test(int nw, int qs, int *q) { FILE *f; int argc; char **argv; nums_to_args(nw,qs,q,&argc,&argv); run_test(argc,(const char **)argv); f = fopen("smp5.out","r"); test_output(f,nw,qs,q); return EXIT_SUCCESS; } int test_3_1_2_2_2() { int q[3] = {2,2,2}; return specific_test(3,1,q); } int test_2_2_2_2() { int q[2]={2,2}; return specific_test(2,2,q); } int test_5_7_1_2_1_2_1() { int q[5] = {1,2,1,2,1}; return specific_test(5,7,q); } int test_4_1_1_2_3_4() { int q[4] = {1,2,3,4}; return specific_test(4,1,q); } int test_3_3_4_3_2() { int q[3] = {4,3,2}; return specific_test(3,3,q); } /* * Main entry point for SMP% test harness */ int run_smp5_tests(int argc, const char **argv) { /* Tests can be invoked by matching their name or their suite name * or 'all' */ testentry_t tests[] = { {"test_3_1_2_2_2", "rr", test_3_1_2_2_2}, {"test_2_2_2_2", "rr", test_2_2_2_2}, {"test_5_7_1_2_1_2_1", "rr", test_5_7_1_2_1_2_1}, {"test_4_1_1_2_3_4", "rr", test_4_1_1_2_3_4}, {"test_3_3_4_3_2", "rr", test_3_3_4_3_2}, {"general", "gen", general_test} }; int result = run_testrunner(argc, argv, tests, sizeof(tests) / sizeof(testentry_t)); unlink("smp5.out"); return result; } /* The real main function. */ int main(int argc, const char **argv) { if (argc > 1 && !strcmp(argv[1], "-test")) { return run_smp5_tests(argc - 1, argv + 1); } else { return smp5_main(argc, argv); } }

__MACOSX/assign3/assign3_part2/._smp5_tests.c

assign3/assign3_part2/testrunner.h

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/ typedef int (*test_fp) (int, const char **); typedef struct { const char *name; const char *suite; test_fp test_function; } testentry_t; int run_testrunner(int argc, const char **argv, testentry_t * entries, int entry_count); void set_testrunner_default_timeout(int s); void set_testrunner_timeout(int s);

__MACOSX/assign3/assign3_part2/._testrunner.h

assign3/assign3_part2/worker.h

#ifndef __WORKER_H_ #define __WORKER_H_ void cancel_thread(); void suspend_thread(); void leave_scheduler_queue(thread_info_t*); #endif

__MACOSX/assign3/assign3_part2/._worker.h

assign3/assign3_part2/list.h

#ifndef __LIST_H_ #define __LIST_H_ #include <pthread.h> typedef struct list_elem { struct list_elem *prev; struct list_elem *next; void *info; } list_elem; typedef struct thread_info_list { list_elem *head; list_elem *tail; pthread_mutex_t lock; } thread_info_list; int list_size(thread_info_list *list); int list_insert_head(thread_info_list *list, list_elem *new); int list_insert_tail(thread_info_list *list, list_elem *new); int list_remove(thread_info_list *list, list_elem *new); void print_list(thread_info_list *list); #endif /* __LIST_H_ */

__MACOSX/assign3/assign3_part2/._list.h

assign3/assign3_part2/README.txt

SMP5: Scheduler with Signals ============================ This MP is a variation of SMP4. In the last MP, we built a simulated OS process scheduler. The scheduler can hold only a certain number of processes (workers) at one time. Once the process has been accepted into the scheduler, the scheduler decides in what order the processes execute. We implemented two scheduling algorithms: FIFO and Round Robin. In this MP, we are to simulate a time-sharing system by using signals and timers. We will only implement the Round Robin algorithm. Instead of using iterations to model the concept of "time slices" (as in the last MP), we use interval timers. The scheduler is installed with an interval timer. The timer starts ticking when the scheduler picks a thread to use the CPU which in turn signals the thread when its time slice is finished thus allowing the scheduler to pick another thread and so on. When a thread has completely finished its work it leaves the scheduler to allow a waiting thread to enter. Please note that in this MP, only the timer and scheduler send signals. The threads passively handle the signals without signaling back to the scheduler. The program takes a number of arguments. Arg1 determines the number of jobs (threads in our implementation) created; arg2 specifies the queue size of the scheduler. Arg3 through argN gives the duration (the required time slices to complete a job) of each job. Hence if we create 2 jobs, we should supply arg3 and arg4 for the required duration. You can assume that the autograder will always supply the correct number of arguments and hence you do not have to detect invalid input. Here is an example of program output, once the program is complete: % scheduler 3 2 3 2 3 Main: running 3 workers with queue size 2 for quanta: 3 2 3 Main: detaching worker thread 3075926960. Main: detaching worker thread 3065437104. Main: detaching worker thread 3054947248. Main: waiting for scheduler 3086416816. Scheduler: waiting for workers. Thread 3075926960: in scheduler queue. Thread 3075926960: suspending. Thread 3065437104: in scheduler queue. Thread 3065437104: suspending. Scheduler: scheduling. Scheduler: resuming 3075926960. Thread 3075926960: resuming. Scheduler: suspending 3075926960. Scheduler: scheduling. Scheduler: resuming 3065437104. Thread 3065437104: resuming. Thread 3075926960: suspending. Scheduler: suspending 3065437104. Scheduler: scheduling. Scheduler: resuming 3075926960. Thread 3075926960: resuming. Thread 3065437104: suspending. Scheduler: suspending 3075926960. Scheduler: scheduling. Scheduler: resuming 3065437104. Thread 3065437104: resuming. Thread 3075926960: suspending. Scheduler: suspending 3065437104. Thread 3065437104: leaving scheduler queue. Scheduler: scheduling. Scheduler: resuming 3075926960. Thread 3075926960: resuming. Thread 3065437104: terminating. Thread 3054947248: in scheduler queue. Thread 3054947248: suspending. Scheduler: suspending 3075926960. Thread 3075926960: leaving scheduler queue. Scheduler: scheduling. Scheduler: resuming 3054947248. Thread 3054947248: resuming. Thread 3075926960: terminating. Scheduler: suspending 3054947248. Scheduler: scheduling. Scheduler: resuming 3054947248. Thread 3054947248: suspending. Thread 3054947248: resuming. Scheduler: suspending 3054947248. Scheduler: scheduling. Scheduler: resuming 3054947248. Thread 3054947248: suspending. Thread 3054947248: resuming. Scheduler: suspending 3054947248. Thread 3054947248: leaving scheduler queue. Thread 3054947248: terminating. The total wait time is 12.062254 seconds. The total run time is 7.958618 seconds. The average wait time is 4.020751 seconds. The average run time is 2.652873 seconds. The goal of this MP is to help you understand (1) how signals and timers work, and (2) how to evaluate the performance of your program. You will first implement the time-sharing system using timers and signals. Then, you will evaluate the overall performance of your program by keeping track of how long each thread is idle, running, etc. The program will use these four signals: SIGALRM: sent by the timer to the scheduler, to indicate another time quantum has passed. SIGUSR1: sent by the scheduler to a worker, to tell it to suspend. SIGUSR2: sent by the scheduler to a suspended worker, to tell it to resume. SIGTERM: sent by the scheduler to a worker, to tell it to cancel. You will need to set up the appropriate handlers and masks for these signals. You will use these functions: clock_gettime pthread_sigmask pthread_kill sigaction sigaddset sigemptyset sigwait timer_settime timer_create Also, make sure you understand how the POSIX:TMR interval timer works. There are two ways you can test your code. You can run the built-in grading tests by running "scheduler -test -f0 rr". This runs 5 tests, each of which can be run individually. You can also test you program with specific parameters by running "scheduler -test gen ..." where the ellipsis contains the parameters you would pass to scheduler. Programming =========== Part I: Modify the scheduler code (scheduler.c) ----------------------------------------------- We use the scheduler thread to setup the timer and handle the scheduling for the system. The scheduler handles the SIGALRM events that come from the timer, and sends out signals to the worker threads. Step 1. Modify the code in init_sched_queue() function in scheduler.c to initialize the scheduler with a POSIX:TMR interval timer. Use CLOCK_REALTIME in timer_create(). The timer will be stored in the global variable "timer", which will be started in scheduler_run() (see Step 4 below). Step 2. Implement setup_sig_handlers(). Use sigaction() to install signal handlers for SIGALRM, SIGUSR1, and SIGTERM. SIGALRM should trigger timer_handler(), SIGUSR1 should trigger suspend_thread(), and SIGTERM should trigger cancel_thread(). Notice no handler is installed for SIGUSR2; this signal will be handled differently, in step 8. Step 3. In the scheduler_run() function, start the timer. Use timer_settime(). The time quantum (1 second) is given in scheduler.h. The timer should go off repeatedly at regular intervals defined by the timer quantum. In Round-Robin, whenever the timer goes off, the scheduler suspends the currently running thread, and tells the next thread to resume its operations using signals. These steps are listed in timer_handler(), which is called every time the timer goes off. In this implementation, the timer handler makes use of suspend_worker() and resume_worker() to accomplush these steps. Step 4. Complete the suspend_worker() function. First, update the info->quanta value. This is the number of quanta that remain for this thread to execute. It is initialized to the value passed on the command line, and decreases as the thread executes. If there is any more work for this worker to do, send it a signal to suspend, and update the scheduler queue. Otherwise, cancel the thread. Step 5. Complete the cancel_worker() function by sending the appropriate signal to the thread, telling it to kill itself. Step 6. Complete the resume_worker() function by sending the appropriate signal to the thread, telling it to resume execution. Part II: Modify the worker code (worker.c) ------------------------------------------ In this section, you will modify the worker code to correctly handle the signals from the scheduler that you implemented in the previous section. You need to modify the thread functions so that it immediately suspends the thread, waiting for a resume signal from the scheduler. You will need to use sigwait() to force the thread to suspend itself and wait for a resume signal. You need also to implement a signal handler in worker.c to catch and handle the suspend signals. Step 7. Modify start_worker() to (1) block SIGUSR2 and SIGALRM, and (2) unblock SIGUSR1 and SIGTERM. Step 8. Implement suspend_thread(), the handler for the SIGUSR1 signal. The thread should block until it receives a resume (SIGUSR2) signal. Part III: Modify the evaluation code (scheduler.c) -------------------------------------------------- This program keeps track of run time, and wait time. Each thread saves these two values regarding its own execution in its thread_info_t. Tracking these values requires also knowing the last time the thread suspended or resumed. Therefore, these two values are also kept in thread_info_t. See scheduler.h. In this section, you will implement the functions that calculate run time and wait time. All code that does this will be in scheduler.c. When the program is done, it will collect all these values, and print out the total and average wait time and run time. For your convenience, you are given a function time_difference() to compute the difference between two times in microseconds. Step 9. Modify create_workers() to initialize the various time variables. Step 10. Implement update_run_time(). This is called by suspend_worker(). Step 11. Implement update_wait_time(). This is called by resume_worker(). Questions ========== Question 1. Why do we block SIGUSR2 and SIGALRM in worker.c? Why do we unblock SIGUSR1 and SIGTERM in worker.c? Question 2. We use sigwait() and sigaction() in our code. Explain the difference between the two. (Please explain from the aspect of thread behavior rather than syntax). Question 3. When we use POSIX:TMR interval timer, we are using relative time. What is the alternative? Explain the difference between the two. Question 4. Look at start_worker() in worker.c, a worker thread is executing within an infinite loop at the end. When does a worker thread terminate? Question 5. When does the scheduler finish? Why does it not exit when the scheduler queue is empty? Question 6. After a thread is scheduled to run, is it still in the sched_queue? When is it removed from the head of the queue? When is it removed from the queue completely? Question 7. We've removed all other condition variables in SMP4, and replaced them with a timer and signals. Why do we still use the semaphore queue_sem? Question 8. What's the purpose of the global variable "completed" in scheduler.c? Why do we compare "completed" with thread_count before we wait_for_queue() in next_worker()? Question 9. We only implemented Round Robin in this SMP. If we want to implement a FIFO scheduling algorithm and keep the modification as minimum, which function in scheduler.c is the one that you should modify? Briefly describe how you would modify this function. Question 10. In this implementation, the scheduler only changes threads when the time quantum expires. Briefly explain how you would use an additional signal to allow the scheduler to change threads in the middle of a time quantum. In what situations would this be useful?

__MACOSX/assign3/assign3_part2/._README.txt

assign3/assign3_part2/scheduler.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <signal.h> #include "scheduler.h" #include "worker.h" /* * define the extern global variables here. */ sem_t queue_sem; /* semaphore for scheduler queue */ thread_info_list sched_queue; /* list of current workers */ static int quit = 0; static timer_t timer; static thread_info_t *currentThread= 0; static long wait_times; static long run_times; static int completed = 0; static int thread_count = 0; static void exit_error(int); /* helper function. */ static void wait_for_queue(); /******************************************************************************* * * Implement these functions. * ******************************************************************************/ /* * This function intializes the queue semaphore and the queue itself. */ /* * Update the worker's current running time. * This function is called every time the thread is suspended. */ void update_run_time(thread_info_t *info) { /* TODO: implement this function */ } /* * Update the worker's current waiting time. * This function is called every time the thread resumes. */ void update_wait_time(thread_info_t *info) { /* TODO: implement this function */ } static void init_sched_queue(int queue_size) { /* set up a semaphore to restrict access to the queue */ sem_init(&queue_sem, 0, queue_size); /* initialize the scheduler queue */ sched_queue.head = sched_queue.tail = 0; pthread_mutex_init(&sched_queue.lock, NULL); /* TODO: initialize the timer */ } /* * signal a worker thread that it can resume. */ static void resume_worker(thread_info_t *info) { printf("Scheduler: resuming %lu.\n", info->thrid); /* * TODO: signal the worker thread that it can resume */ /* update the wait time for the thread */ update_wait_time(info); } /*send a signal to the thread, telling it to kill itself*/ void cancel_worker(thread_info_t *info) { /* TODO: send a signal to the thread, telling it to kill itself*/ /* Update global wait and run time info */ wait_times += info->wait_time; run_times += info->run_time; completed++; /* Update schedule queue */ leave_scheduler_queue(info); if (completed >= thread_count) { sched_yield(); /* Let other threads terminate. */ printf("The total wait time is %f seconds.\n", (float)wait_times / 1000000); printf("The total run time is %f seconds.\n", (float)run_times / 1000000); printf("The average wait time is %f seconds.\n", (float)wait_times / 1000000 / thread_count); printf("The average run time is %f seconds.\n", (float)run_times / 1000000 / thread_count); } } /* * signals a worker thread that it should suspend. */ static void suspend_worker(thread_info_t *info) { int whatgoeshere = 0; printf("Scheduler: suspending %lu.\n", info->thrid); /*update the run time for the thread*/ update_run_time(info); /* TODO: Update quanta remaining. */ /* TODO: decide whether to cancel or suspend thread */ if(whatgoeshere) { /* * Thread still running: suspend. * TODO: Signal the worker thread that it should suspend. */ /* Update Schedule queue */ list_remove(&sched_queue,info->le); list_insert_tail(&sched_queue,info->le); } else { /* Thread done: cancel */ cancel_worker(info); } } /* * this is the scheduling algorithm * pick the next worker thread from the available list * you may need to add new information to the thread_info struct */ static thread_info_t *next_worker() { if (completed >= thread_count) return 0; wait_for_queue(); printf("Scheduler: scheduling.\n"); /* return the thread_info_t for the next thread to run */ return sched_queue.head->info; } void timer_handler() { thread_info_t *info = 0; /* once the last worker has been removed, we're done. */ if (list_size(&sched_queue) == 0) { quit = 1; return; } /*suspend the current worker*/ if (currentThread) suspend_worker(currentThread); //resume the next worker info = next_worker(); /* Update currentThread */ currentThread = info; if (info) resume_worker(info); else quit = 1; } /* * Set up the signal handlers for SIGALRM, SIGUSR1, and SIGTERM. * TODO: Implement this function. */ void setup_sig_handlers() { /* Setup timer handler for SIGALRM signal in scheduler */ /* Setup cancel handler for SIGTERM signal in workers */ /* Setup suspend handler for SIGUSR1 signal in workers */ } /******************************************************************************* * * * ******************************************************************************/ /* * waits until there are workers in the scheduling queue. */ static void wait_for_queue() { while(!list_size(&sched_queue)) { printf("Scheduler: waiting for workers.\n"); sched_yield(); } } /* * runs at the end of the program just before exit. */ static void clean_up() { /* * destroy any mutexes/condition variables/semaphores that were created. * free any malloc'd memory not already free'd */ sem_destroy(&queue_sem); pthread_mutex_destroy(&sched_queue.lock); } /* * prints the program help message. */ static void print_help(const char *progname) { printf("usage: %s <num_threads> <queue_size> <i_1, i_2 ... i_numofthreads>\n", progname); printf("\tnum_threads: the number of worker threads to run\n"); printf("\tqueue_size: the number of threads that can be in the scheduler at one time\n"); printf("\ti_1, i_2 ...i_numofthreads: the number of quanta each worker thread runs\n"); } /* * prints an error summary and exits. */ static void exit_error(int err_num) { fprintf(stderr, "failure: %s\n", strerror(err_num)); exit(1); } /* * creates the worker threads. */ static void create_workers(int thread_count, int *quanta) { int i = 0; int err = 0; for (i = 0; i < thread_count; i++) { thread_info_t *info = (thread_info_t *) malloc(sizeof(thread_info_t)); info->quanta = quanta[i]; if ((err = pthread_create(&info->thrid, NULL, start_worker, (void *)info)) != 0) { exit_error(err); } printf("Main: detaching worker thread %lu.\n", info->thrid); pthread_detach(info->thrid); /* TODO: initialize the time variables for each thread for performance evalution*/ } } /* * runs the scheduler. */ static void *scheduler_run(void *unused) { wait_for_queue(); /* TODO: start the timer */ /*keep the scheduler thread alive*/ while( !quit ) sched_yield(); return NULL; } /* * starts the scheduler. * returns 0 on success or exits program on failure. */ static int start_scheduler(pthread_t *thrid) { int err = 0; if ((err = pthread_create(thrid, NULL, scheduler_run, 0)) != 0) { exit_error(err); } return err; } /* * reads the command line arguments and starts the scheduler & worker threads. */ int smp5_main(int argc, const char** argv) { int queue_size = 0; int ret_val = 0; int *quanta,i; pthread_t sched_thread; /* check the arguments. */ if (argc < 3) { print_help(argv[0]); exit(0); } thread_count = atoi(argv[1]); queue_size = atoi(argv[2]); quanta = (int*)malloc(sizeof(int)*thread_count); if (argc != 3 + thread_count) { print_help(argv[0]); exit(0); } for ( i = 0; i < thread_count; i++) quanta[i] = atoi(argv[i+3]); printf("Main: running %d workers with queue size %d for quanta:\n", thread_count, queue_size); for ( i = 0; i < thread_count; i++) printf(" %d", quanta[i]); printf("\n"); /*setup the sig handlers for scheduler and workers*/ setup_sig_handlers(); /* initialize anything that needs to be done for the scheduler queue. */ init_sched_queue(queue_size); /* creates a thread for the scheduler. */ start_scheduler(&sched_thread); /* creates the worker threads and returns. */ create_workers(thread_count, quanta); /* wait for scheduler to finish */ printf("Main: waiting for scheduler %lu.\n", sched_thread); pthread_join(sched_thread, (void **) &ret_val); /* clean up our resources */ clean_up(); /* this will wait for all other threads */ pthread_exit(0); } long time_difference(const struct timespec *time1, const struct timespec *time2) { return (time1->tv_sec - time2->tv_sec) * 1000000 + (time1->tv_nsec - time2->tv_nsec) / 1000; }

__MACOSX/assign3/assign3_part2/._scheduler.c

assign3/assign3_part2/testrunner.c

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/ /* A simple testrunner framework Original Author: L. Angrave */ #include <sys/types.h> #include <sys/wait.h> #include <errno.h> #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <unistd.h> #include "testrunner.h" /* Constants */ #define false (0) #define true (1) #define test_killed (2) /* defaults */ static int default_timeout_seconds = 15; static int timeout_seconds; void set_testrunner_default_timeout(int s) { assert(s > 0); default_timeout_seconds = s; } void set_testrunner_timeout(int s) { assert(s > 0); timeout_seconds = s; } /* --- Helper macros and functions --- */ #define DIE(mesg) {fprintf(stderr,"\n%s(%d):%s\n",__fname__,__LINE__,mesg); exit(1);} static int eql(const char *s1, const char *s2) { return s1 && s2 && !strcmp(s1, s2); } /* Callback function for qsort on strings */ static int mystrcmp(const void *p1, const void *p2) { return eql((const char *) p1, (const char *) p2); } /* Stats of all tests run so far */ typedef struct { int ran, passed, failed; } stats_t; /* -- Signal handlers -- */ static pid_t child_pid; static int sent_child_timeout_kill_signal; static void kill_child_signal_handler(intsigno) { if (!child_pid) return; char m[] = "-Timeout(Killing test process)-"; write(0, m, sizeof(m) - 1); kill(child_pid, SIGKILL); sent_child_timeout_kill_signal = 1; } /* Internal function to run a test as a forked child. The child process is terminated if it runs for more than a few seconds */ static int invoke_test_with_timelimit(testentry_t * test, int redirect_stdouterr, int argc, const char **argv) { char fname[255]; int wait_status; pid_t wait_val; struct sigaction action; assert(!child_pid); assert(test && test->test_function && test->name); set_testrunner_timeout(default_timeout_seconds); errno = 0; child_pid = fork(); if (child_pid == -1) { fprintf(stderr, "-fork failed so running test inline-"); return test->test_function(argc, argv); } if (child_pid == 0) { if (redirect_stdouterr) { snprintf(fname, (int) sizeof(fname), "stdout-%s.txt", test->name); fname[sizeof(fname) - 1] = 0; freopen(fname, "w", stdout); memcpy(fname + 3, "err", 3); freopen(fname, "w", stderr); } exit(test->test_function(argc, argv)); } else { wait_status = -1; sigemptyset(&action.sa_mask); action.sa_handler = kill_child_signal_handler; sigaction(SIGALRM, &action, NULL); sent_child_timeout_kill_signal = 0; alarm(timeout_seconds); wait_val = waitpid(child_pid, &wait_status, 0); int child_exited_normally = WIFEXITED(wait_status); int child_exit_value = WEXITSTATUS(wait_status); int child_term_by_signal = WIFSIGNALED(wait_status); int child_term_signal = WTERMSIG(wait_status); if (child_term_by_signal) { fprintf(stderr, "testrunner:Test terminated by signal %d\n", child_term_signal); fprintf(stderr, "testrunner:waitpid returned %d (child_pid=%d,wait_status=%d)", wait_val, child_pid, wait_status); } if (child_pid != wait_val) fprintf(stderr, "testrunner: strange... wait_val != child_pid\n"); int passed = (child_pid == wait_val) && (child_exit_value == 0) && (child_exited_normally != 0); alarm(0); kill(child_pid, SIGKILL); child_pid = 0; return sent_child_timeout_kill_signal ? test_killed : passed ? 0 : 1; } } /* * run a test and update the stats. The main guts of this functionality is provided by invoke_test_with_timelimit * This outer wrapper updates thes output and statistics before and after running the test. */ static int run_one_test(stats_t * stats, testentry_t * test, int redirect_stdouterr, int argc, const char **argv) { int test_result; assert(stats && test->name && argc > 0 && argv && *argv); stats->ran++; stats->failed++; printf("%2d.%-20s:", stats->ran, test->name); fflush(stdout); test_result = invoke_test_with_timelimit(test, redirect_stdouterr, argc, argv); if (test_result == 0) { stats->failed--; stats->passed++; } printf(":%s\n", (test_result == 0 ? "pass" : test_result == 2 ? "TIMEOUT * " : "FAIL *")); return test_result != 0; } /* Help functionality to print out sorted list of test names and suite names */ static void print_targets(testentry_t tests[], int count) { const char **array; const char *previous; int i; array = (const char **) calloc(sizeof(const char *), count); /* Sort the test names and print unique entries */ for (i = 0; i < count; i++) array[i] = tests[i].name; qsort(array, count, sizeof(array[0]), mystrcmp); printf("\nValid tests : all"); for (i = 0, previous = ""; i < count; i++) if (!eql(previous, array[i])) printf(" %s", (previous = array[i])); /* Sort the suite names and print unique entries */ for (i = 0; i < count; i++) array[i] = tests[i].suite; qsort(array, count, sizeof(array[0]), mystrcmp); printf("\nValid suites:"); for (i = 0, previous = ""; i < count; i++) if (!eql(previous, array[i])) printf(" %s", (previous = array[i])); printf("\n"); } /* * Main entry point for test harness */ int run_testrunner(int argc, const char **argv, testentry_t tests[], int test_count) { const char *test_name, *target; int i; stats_t stats; int target_matched, max_errors_before_quit, redirect_stdouterr; memset(&stats, 0, sizeof(stats)); max_errors_before_quit = 1; redirect_stdouterr = 0; assert(tests != NULL); assert(test_count > 0); assert(argc > 0 && argv && *argv); while (true) { target = argc > 1 ? argv[1] : ""; assert(target); if (*target != '-') break; argc--; argv++; if (target[1] == 'f' && target[2]) max_errors_before_quit = atoi(target + 1); else if (target[1] == 'r') redirect_stdouterr = 1; } target_matched = false; for (i = 0; i < test_count && (max_errors_before_quit < 1 || stats.failed != max_errors_before_quit); i++) { test_name = tests[i].name; assert(test_name); assert(tests[i].suite); assert(tests[i].test_function); if (eql(target, test_name) || eql(target, "all") || eql(target, tests[i].suite)) { if (!target_matched) printf("Running tests...\n"); target_matched = true; run_one_test(&stats, &tests[i], redirect_stdouterr, argc - 1, argv + 1); } } if (!target_matched) { fprintf(stderr, "Test '%s' not found", (strlen(target) > 0 ? target : "(empty)")); print_targets(tests, test_count); } else { printf("\nTest Results:%d tests,%d passed,%d failed.\n", stats.ran, stats.passed, stats.failed); } return stats.passed == stats.ran && target_matched ? 0 : 1; }

__MACOSX/assign3/assign3_part2/._testrunner.c

assign3/assign3_part1/sched_impl.c

#include "scheduler.h" #include "sched_impl.h" /* Fill in your scheduler implementation code below: */ static void init_thread_info(thread_info_t *info, sched_queue_t *queue) { /*...Code goes here...*/ } static void destroy_thread_info(thread_info_t *info) { /*...Code goes here...*/ } /*...More functions go here...*/ static void init_sched_queue(sched_queue_t *queue, int queue_size) { /*...Code goes here...*/ } static void destroy_sched_queue(sched_queue_t *queue) { /*...Code goes here...*/ } /*...More functions go here...*/ /* You need to statically initialize these structures: */ sched_impl_t sched_fifo = { { init_thread_info, destroy_thread_info /*, ...etc... */ }, { init_sched_queue, destroy_sched_queue /*, ...etc... */ } }, sched_rr = { { init_thread_info, destroy_thread_info /*, ...etc... */ }, { init_sched_queue, destroy_sched_queue /*, ...etc... */ } };

__MACOSX/assign3/assign3_part1/._sched_impl.c

assign3/assign3_part1/list.c

#include <stdlib.h> #include "list.h" void list_init(list_t *lst) { if (lst != NULL) lst->head = lst->tail = NULL; } int list_size(list_t *lst) { list_elem_t *elt; int count = 0; for (elt = lst->head; elt != NULL; elt = elt->next) count++; return count; } void list_elem_init(list_elem_t *elt, void *datum) { if (elt != NULL) { elt->prev = elt->next = NULL; elt->datum = datum; } } void list_insert_head(list_t *lst, list_elem_t *elt) { if (lst->head == NULL) { elt->prev = elt->next = NULL; lst->head = lst->tail = elt; } else { elt->prev = NULL; elt->next = lst->head; if (lst->head) lst->head->prev = elt; lst->head = elt; } } void list_insert_tail(list_t *lst, list_elem_t *elt) { if (lst->tail == NULL) { elt->prev = elt->next = NULL; lst->head = lst->tail = elt; } else { elt->prev = lst->tail; if (lst->tail) lst->tail->next = elt; elt->next = NULL; lst->tail = elt; } } list_elem_t *list_get_head(list_t *lst) { return lst->head; } list_elem_t *list_get_tail(list_t *lst) { return lst->tail; } void list_remove_elem(list_t *lst, list_elem_t *elt) { if (elt->prev) { elt->prev->next = elt->next; } else if (lst->head == elt) { lst->head = elt->next; } if (elt->next) { elt->next->prev = elt->prev; } else if (lst->tail == elt) { lst->tail = elt->prev; } elt->next = elt->prev = NULL; } void list_foreach(list_t *lst, void (*fn)(list_elem_t *)) { list_elem_t *elt; for (elt = lst->head; elt != NULL; elt = elt->next) fn(elt); }

__MACOSX/assign3/assign3_part1/._list.c

assign3/assign3_part1/Makefile

CC = gcc CCOPTS = -Wall -c -g -ggdb LINKOPTS = -Wall -g -ggdb -pthread EXEC=scheduler OBJECTS=scheduler.o sched_impl.o list.o dummy_impl.o smp4_tests.o testrunner.o all: $(EXEC) $(EXEC): $(OBJECTS) $(CC) $(LINKOPTS) -o $@ $^ #%.o:%.c # $(CC) $(CCOPTS) -o $@ $^ scheduler.o: scheduler.c scheduler.h sched_impl.h $(CC) $(CCOPTS) -o $@ scheduler.c sched_impl.o: sched_impl.c scheduler.h sched_impl.h $(CC) $(CCOPTS) -o $@ sched_impl.c list.o: list.c list.h $(CC) $(CCOPTS) -o $@ list.c dummy_impl.o: dummy_impl.c scheduler.h $(CC) $(CCOPTS) -o $@ dummy_impl.c smp4_tests.o: smp4_tests.c list.h scheduler.h testrunner.h $(CC) $(CCOPTS) -o $@ smp4_tests.c testrunner.o: testrunner.c testrunner.h $(CC) $(CCOPTS) -o $@ testrunner.c test: scheduler - ./scheduler -test -f10 fifo - ./scheduler -test -f10 rr - killall scheduler clean: - $(RM) $(EXEC) - $(RM) $(OBJECTS) - $(RM) *~ - $(RM) core.* - $(RM) smp4.in smp4.out smp4.err

__MACOSX/assign3/assign3_part1/._Makefile

assign3/assign3_part1/scheduler.h

#ifndef __SCHEDULER__H__ #define __SCHEDULER__H__ struct thread_info; /* To be defined in sched_impl.h */ typedef struct thread_info thread_info_t; struct sched_queue; /* To be defined in sched_impl.h */ typedef struct sched_queue sched_queue_t; /* A worker thread must be able to call the following operations. * See scheduler.c:worker_proc() for an example of usage. */ typedef struct worker_thread_ops { /* Initialize a thread_info_t */ void (*init_thread_info) (thread_info_t *info, sched_queue_t *queue); /* Release the resources associated with a thread_info_t */ void (*destroy_thread_info) (thread_info_t *info); /* Block until the thread can enter the scheduler queue. */ void (*enter_sched_queue) (thread_info_t *info); /* Remove the thread from the scheduler queue. */ void (*leave_sched_queue) (thread_info_t *info); /* While on the scheduler queue, block until thread is scheduled. */ void (*wait_for_cpu) (thread_info_t *info); /* Voluntarily relinquish the CPU when this thread's timeslice is * over (cooperative multithreading). */ void (*release_cpu) (thread_info_t *info); } worker_thread_ops_t; /* The scheduler thread must be able to call the following operations. * See scheduler.c:sched_proc() for an example of usage. */ typedef struct sched_ops { /* Initialize a sched_queue_t */ void (*init_sched_queue) (sched_queue_t *queue, int queue_size); /* Release the resources associated with a sched_queue_t */ void (*destroy_sched_queue) (sched_queue_t *queue); /* Allow a worker thread to execute. */ void (*wake_up_worker) (thread_info_t *info); /* Block until the current worker thread relinquishes the CPU. */ void (*wait_for_worker) (sched_queue_t *queue); /* Select the next worker thread to execute. * Returns NULL if the scheduler queue is empty. */ thread_info_t * (*next_worker) (sched_queue_t *queue); /* Block until at least one worker thread is in the scheduler queue. */ void (*wait_for_queue) (sched_queue_t *queue); } sched_ops_t; /* A scheduler implementation consists of the above sets of operations. */ typedef struct sched_impl { worker_thread_ops_t worker_ops; sched_ops_t sched_ops; } sched_impl_t; /* For this MP you will implement two different schedulers. * Hint: most of the operations will be the same in both. */ extern sched_impl_t sched_fifo, sched_rr; /* To be defined in sched_impl.c */ extern sched_impl_t sched_dummy; /* Defined in dummy_impl.c */ int smp4_main(int argc, const char **argv); #endif /* __SCHEDULER__H__ */

__MACOSX/assign3/assign3_part1/._scheduler.h

assign3/assign3_part1/dummy_impl.c

#include "scheduler.h" void dummy_ti_sq(thread_info_t *info, sched_queue_t *queue) { } void dummy_ti(thread_info_t *info) { } void dummy_sq_i(sched_queue_t *queue, int queue_size) { } void dummy_sq(sched_queue_t *queue) { } thread_info_t *ti_dummy_sq(sched_queue_t *queue) { return 0; } sched_impl_t sched_dummy = { { dummy_ti_sq, dummy_ti, dummy_ti, dummy_ti, dummy_ti, dummy_ti }, { dummy_sq_i, dummy_sq, dummy_ti, dummy_sq, ti_dummy_sq, dummy_sq } };

__MACOSX/assign3/assign3_part1/._dummy_impl.c

assign3/assign3_part1/smp4_tests.c

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/ #define _GNU_SOURCE #include <stdio.h> #undef _GNU_SOURCE #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include "testrunner.h" #include "list.h" #include "scheduler.h" #define quit_if(cond) do {if (cond) exit(EXIT_FAILURE);} while(0) /* Prepare input, reroute file descriptors, and run the program. */ void run_test(int argc, const char **argv) { int fork_pid = fork(); if (fork_pid == 0) { /* Reroute standard file descriptors */ freopen("smp4.out", "w", stdout); /* Run the program */ exit(smp4_main(argc, argv)); fclose(stdout); } else if (fork_pid > 0) { waitpid(fork_pid, 0, 0); } else { fprintf(stderr, "run_test: fork() error\n"); } } void read_header(FILE *in, int *workers, int *queue_size, int *iterations) { char buf[1024]; *workers = *queue_size = *iterations = 0; while (!feof(in)) { int workers_tmp, queue_size_tmp, iterations_tmp; fgets(buf, 1024, in); if (sscanf(buf, "Main: running %d workers on %d queue_size for %d iterations\n", &workers_tmp, &queue_size_tmp, &iterations_tmp) == 3) { *workers = workers_tmp; *queue_size = queue_size_tmp; *iterations = iterations_tmp; } } } int check_for_done(FILE *in) { char buf[1024]; int done = 0; while (!feof(in)) { fgets(buf, 1024, in); if (!feof(in)) { done = !strcmp(buf, "Scheduler: done!\n"); } } return done; } void compute_queue_size(FILE *in, int *final_threads, int *threads_max, int *threads_min, int *admitted_threads) { char buf[1024]; *final_threads = *threads_max = *threads_min = *admitted_threads = 0; while (!feof(in)) { unsigned long thread_id; char nl; fgets(buf, 1024, in); if (sscanf(buf, "Thread %lu: in scheduler queue%c", &thread_id, &nl) == 2) { admitted_threads[0]++; final_threads[0]++; if (*final_threads > *threads_max) *threads_max = *final_threads; } else if (sscanf(buf, "Thread %lu: exiting%c", &thread_id, &nl) == 2) { final_threads[0]--; if (*final_threads < *threads_min) *threads_min = *final_threads; } } } unsigned long *lookup_bucket(unsigned long *buckets, int id) { int i; for (i = 2; i < buckets[0]*2+2; i += 2) { if (buckets[i] == id) return &buckets[i+1]; } if (buckets[0] < buckets[1]) { buckets[buckets[0]*2+2] = id; buckets[buckets[0]*2+3] = 0; buckets[0]++; return &buckets[(buckets[0]-1)*2+3]; } else { return NULL; } } int bucket_exists(unsigned long *buckets, int with_value) { int i; for (i = 2; i < buckets[0]*2+2; i += 2) { if (buckets[i+1] == with_value) return 1; } return 0; } int check_executed(FILE *in, int num_threads, int num_iterations) { char buf[1024]; unsigned long buckets[2+num_threads*2]; buckets[0] = 0; buckets[1] = num_threads; while (!feof(in)) { unsigned long thread_id; int iter; char nl; fgets(buf, 1024, in); if (sscanf(buf, "Thread %lu: in scheduler queue%c", &thread_id, &nl) == 2) { unsigned long *b = lookup_bucket(buckets, thread_id); if (b == NULL) return 0; b[0] = 0; } else if (sscanf(buf, "Thread %lu: loop %i%c", &thread_id, &iter, &nl) == 3) { unsigned long *b = lookup_bucket(buckets, thread_id); if (b == NULL) return 0; if (b[0] != iter) return 0; b[0]++; } else if (sscanf(buf, "Thread %lu: exiting%c", &thread_id, &nl) == 2) { unsigned long *b = lookup_bucket(buckets, thread_id); if (b == NULL) return 0; if (b[0] != num_iterations) return 0; } } return buckets[0] == buckets[1]; } int check_executed_fifo(FILE *in, int num_threads, int num_iterations) { char buf[1024]; unsigned long prev_thread = 0; int counter = 0; while (!feof(in)) { unsigned long thread_id; int iter; char nl; fgets(buf, 1024, in); if (sscanf(buf, "Thread %lu: loop %i%c", &thread_id, &iter, &nl) == 3) { if (prev_thread == 0) prev_thread = thread_id; else if (prev_thread != thread_id) return 0; counter++; } else if (sscanf(buf, "Thread %lu: exiting%c", &thread_id, &nl) == 2) { if (counter != num_iterations) return 0; prev_thread = 0; counter = 0; } } return 1; } int check_executed_rr(FILE *in, int num_threads, int num_iterations) { char buf[1024]; unsigned long buckets[2+num_threads*2]; int stepnum = 1; list_t rrq; list_init(&rrq); buckets[0] = 0; buckets[1] = num_threads; while (!feof(in)) { unsigned long thread_id; int iter; char nl; fgets(buf, 1024, in); if (sscanf(buf, "Thread %lu: in scheduler queue%c", &thread_id, &nl) == 2) { unsigned long *b = lookup_bucket(buckets, thread_id); if (b == NULL) return 0; b[0] = stepnum; } else if (sscanf(buf, "Thread %lu: loop %i%c", &thread_id, &iter, &nl) == 3) { unsigned long *b = lookup_bucket(buckets, thread_id); list_elem_t *elt; if (b == NULL) return 0; if (b[0] > stepnum) { // Check if anyone's left at stepnum if (bucket_exists(buckets, stepnum)) { return 0; } // Otherwise move on to the next step stepnum++; } b[0] = stepnum+1; if (iter == 0) { elt = (list_elem_t *) malloc(sizeof(list_elem_t)); if (elt == NULL) return 0; list_elem_init(elt, (void *) thread_id); list_insert_tail(&rrq, elt); } else { elt = list_get_head(&rrq); if (elt == NULL) return 0; if (elt->datum != (void *) thread_id) return 0; list_remove_elem(&rrq, elt); list_insert_tail(&rrq, elt); } } else if (sscanf(buf, "Thread %lu: exiting%c", &thread_id, &nl) == 2) { unsigned long *b = lookup_bucket(buckets, thread_id); list_elem_t *elt = list_get_head(&rrq); if (b == NULL) return 0; b[0] = 0; if (elt == NULL) return 0; if (elt->datum != (void *) thread_id) return 0; list_remove_elem(&rrq, elt); free(elt); } } return 1; } int check_rudimentary(FILE *in, int workers, int queue_size, int iterations) { int final_threads, threads_max, threads_min, admitted_threads; int actual_workers, actual_queue_size, actual_iterations; rewind(in); read_header(in, &actual_workers, &actual_queue_size, &actual_iterations); if (!((workers == actual_workers) && (queue_size == actual_queue_size) && (iterations == actual_iterations))) return 0; rewind(in); if (!check_for_done(in)) return 0; rewind(in); compute_queue_size(in, &final_threads, &threads_max, &threads_min, &admitted_threads); if (!((threads_min == 0) && (threads_max <= queue_size) && (final_threads == 0) && (admitted_threads == workers))) return 0; rewind(in); if (!check_executed(in, workers, iterations)) return 0; return 1; } int check_fifo(FILE *in, int queue_size, int workers, int iterations) { if (!check_rudimentary(in, workers, queue_size, iterations)) return 0; rewind(in); if (!check_executed_fifo(in, workers, iterations)) return 0; return 1; } int check_rr(FILE *in, int queue_size, int workers, int iterations) { if (!check_rudimentary(in, workers, queue_size, iterations)) return 0; rewind(in); if (!check_executed_rr(in, workers, iterations)) return 0; return 1; } void test_whatever(const char *impl, int queue_size, int workers, int iterations) { char qs[16], wk[16], it[16]; const char *args[] = { "./scheduler", impl, qs, wk, it, NULL }; sprintf(qs, "%d", queue_size); sprintf(wk, "%d", workers); sprintf(it, "%d", iterations); run_test(5, args); } int test_fifo(int queue_size, int workers, int iterations) { FILE *out; test_whatever("-fifo", queue_size, workers, iterations); out = fopen("smp4.out", "r"); quit_if(!check_fifo(out, queue_size, workers, iterations)); return EXIT_SUCCESS; } int test_rr(int queue_size, int workers, int iterations) { FILE *out; test_whatever("-rr", queue_size, workers, iterations); out = fopen("smp4.out", "r"); quit_if(!check_rr(out, queue_size, workers, iterations)); return EXIT_SUCCESS; } int test_fifo_var(int argc, const char **argv) { quit_if(argc != 4); return test_fifo(atoi(argv[1]), atoi(argv[2]), atoi(argv[3])); } int test_rr_var(int argc, const char **argv) { quit_if(argc != 4); return test_rr(atoi(argv[1]), atoi(argv[2]), atoi(argv[3])); } int test_fifo_1_2_3(int argc, const char **argv) { return test_fifo(1, 2, 3); } int test_fifo_10_2_3(int argc, const char **argv) { return test_fifo(10, 2, 3); } int test_fifo_7_1_30(int argc, const char **argv) { return test_fifo(7, 1, 30); } int test_fifo_7_5_5(int argc, const char **argv) { return test_fifo(7, 5, 5); } int test_fifo_5_7_5(int argc, const char **argv) { return test_fifo(5, 7, 5); } int test_rr_1_2_3(int argc, const char **argv) { return test_rr(1, 2, 3); } int test_rr_10_2_3(int argc, const char **argv) { return test_rr(10, 2, 3); } int test_rr_7_1_30(int argc, const char **argv) { return test_rr(7, 1, 30); } int test_rr_7_5_5(int argc, const char **argv) { return test_rr(7, 5, 5); } int test_rr_5_7_5(int argc, const char **argv) { return test_rr(5, 7, 5); } /* * Main entry point for SMP4 test harness */ int run_smp4_tests(int argc, const char **argv) { /* Tests can be invoked by matching their name or their suite name * or 'all' */ testentry_t tests[] = { {"fifo_var", "var", test_fifo_var }, {"rr_var", "var", test_rr_var }, {"fifo_1_2_3", "fifo", test_fifo_1_2_3 }, {"fifo_10_2_3", "fifo", test_fifo_10_2_3}, {"fifo_7_1_30", "fifo", test_fifo_7_1_30}, {"fifo_7_5_5", "fifo", test_fifo_7_5_5 }, {"fifo_5_7_5", "fifo", test_fifo_5_7_5 }, {"rr_1_2_3", "rr", test_rr_1_2_3 }, {"rr_10_2_3", "rr", test_rr_10_2_3 }, {"rr_7_1_30", "rr", test_rr_7_1_30 }, {"rr_7_5_5", "rr", test_rr_7_5_5 }, {"rr_5_7_5", "rr", test_rr_5_7_5 } }; int result = run_testrunner(argc, argv, tests, sizeof(tests) / sizeof(testentry_t)); unlink("smp4.out"); return result; } /* The real main function. */ int main(int argc, const char **argv) { if (argc > 1 && !strcmp(argv[1], "-test")) { return run_smp4_tests(argc - 1, argv + 1); } else { return smp4_main(argc, argv); } }

__MACOSX/assign3/assign3_part1/._smp4_tests.c

assign3/assign3_part1/testrunner.h

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/ typedef int (*test_fp) (int, const char **); typedef struct { const char *name; const char *suite; test_fp test_function; } testentry_t; int run_testrunner(int argc, const char **argv, testentry_t * entries, int entry_count); void set_testrunner_default_timeout(int s); void set_testrunner_timeout(int s);

__MACOSX/assign3/assign3_part1/._testrunner.h

assign3/assign3_part1/sched_impl.h

#ifndef __SCHED_IMPL__H__ #define __SCHED_IMPL__H__ struct thread_info { /*...Fill this in...*/ }; struct sched_queue { /*...Fill this in...*/ }; #endif /* __SCHED_IMPL__H__ */

__MACOSX/assign3/assign3_part1/._sched_impl.h

assign3/assign3_part1/list.h

#ifndef __LIST__H__ #define __LIST__H__ /* Element of a doubly linked list */ typedef struct list_elem { struct list_elem *prev; struct list_elem *next; void *datum; } list_elem_t; /* Doubly linked list */ typedef struct list { list_elem_t *head; list_elem_t *tail; } list_t; /* Initialize a list with NULL head and tail. */ void list_init (list_t *lst); /* Compute the size of a list (O(n) time). */ int list_size (list_t *lst); /* Initializes a list element with the given datum value * and NULL prev and next. */ void list_elem_init (list_elem_t *elt, void *datum); /* Insert an element at the head of a list. * When this function returns, lst->head == elt */ void list_insert_head (list_t *lst, list_elem_t *elt); /* Insert an element at the tail of a list. * When this function returns, lst->tail == elt */ void list_insert_tail (list_t *lst, list_elem_t *elt); /* Returns the element at the head of the list (or NULL if empty). */ list_elem_t *list_get_head(list_t *lst); /* Returns the element at the tail of the list (or NULL if empty). */ list_elem_t *list_get_tail(list_t *lst); /* Removes an element from a list (assuming it's in the list). */ void list_remove_elem (list_t *lst, list_elem_t *elt); /* Applies function fn to each element of the list in order, starting from * the head. Useful, for example, to print out the contents of a list. */ void list_foreach (list_t *lst, void (*fn)(list_elem_t *)); #endif /* __LIST__H__ */

__MACOSX/assign3/assign3_part1/._list.h

assign3/assign3_part1/README.txt

SMP4: Thread Scheduler ====================== INSTRUCTIONS ============ 1. OVERVIEW =========== In this MP, you will write a user-mode thread scheduler. The basic purpose of a scheduler is to multiplex use of the computer across several threads of execution. This MP deals with two different scheduling policies: FIFO and Round Robin. You will implement both, for use in a simple cooperative multi-threading system. Along the way, you'll also learn about implementing object-oriented constructs in low-level procedural languages like C. This assignment consists of implementing the core functionality of the scheduler (Step 4) and answering 10 questions (Step 5). Code for Step 4 goes in sched_impl.c and sched_impl.h. 2. THEORY OF OPERATION ====================== The given code in the MP defines the skeleton of a scheduler together with a parameterized dummy workload. The idea is when you run the MP, you specify a scheduling policy, scheduler queue size, some number of worker threads to create, and, optionally, the number of iterations for which the worker threads should run. The basic code that parses command line arguments and creates these worker threads is provided in the MP, but you must implement the core synchronization and scheduling operations. As provided, the MP only includes the "dummy" scheduling algorithm, which doesn't even try to do anything. You can run it like this: make ./scheduler -dummy 0 N # where N is some number of worker threads All threads run right away regardless of the queue size (even zero!), and are scheduled by the operating system. The goal of this MP is to create scheduler implementations which are a bit more controlled and predictable. For example, once you have completed the MP, the following should work: ./scheduler -fifo 1 2 3 Main: running 2 workers on 1 queue_size for 3 iterations Main: detaching worker thread 3075984304 Main: detaching worker thread 3065494448 Main: waiting for scheduler 3086474160 Thread 3075984304: in scheduler queue Thread 3075984304: loop 0 Thread 3075984304: loop 1 Thread 3075984304: loop 2 Thread 3075984304: exiting Thread 3065494448: in scheduler queue Thread 3065494448: loop 0 Thread 3065494448: loop 1 Thread 3065494448: loop 2 Thread 3065494448: exiting Scheduler: done! The command line options used above specify: -fifo Use FIFO scheduling policy 1 One thread can be in the scheduler queue at a time 2 Create 2 worker threads 3 Each thread runs for 3 time slices Here's another example: ./scheduler -rr 10 2 3 Main: running 2 workers on 10 queue_size for 3 iterations Main: detaching worker thread 3075828656 Main: detaching worker thread 3065338800 Main: waiting for scheduler 3086318512 Thread 3075828656: in scheduler queue Thread 3065338800: in scheduler queue Thread 3075828656: loop 0 Thread 3065338800: loop 0 Thread 3075828656: loop 1 Thread 3065338800: loop 1 Thread 3075828656: loop 2 Thread 3065338800: loop 2 Thread 3075828656: exiting Thread 3065338800: exiting Scheduler: done! The command line options used above specify: -rr Use Round Robin scheduling policy 10 Ten threads can be in the scheduler queue at a time 2 Create 2 worker threads 3 Each thread runs for 3 time slices Things to observe: In both examples, the worker threads are created at the beginning of execution. But in the case with queue size 1, one of the threads has to wait until the other thread exits before it can enter the scheduler queue (the "in scheduler queue" messages). Whereas in the case with queue size 10, both threads enter the scheduler queue immediately. The FIFO policy would actually have basically the same behavior even with a larger queue size; the waiting worker threads would simply be admitted to the queue earlier. The Round Robin scheduling policy alternates between executing the two available threads, until they run out of work to do. 3. FILE LAYOUT ============== The MP distribution consists of the following source files: scheduler.c Includes the skeleton of a scheduler (sched_proc()) and a parameterized dummy workload (worker_proc()). The main() function accepts several parameters specifying the test workload (see description below). The scheduler relies on a scheduler implementation (sched_impl_t) to implement the specifics of its scheduling policy (to be provided by you in sched_impl.[hc]) scheduler.h Describes the interface to which your scheduler implementation must adhere. The structures containing function pointers are similar to Java interfaces or C++ pure virtual base classes. This file declares that you must define two sched_impl_t structures, sched_fifo and sched_rr in another file (sched_impl.c). dummy_impl.c Implements the dummy scheduling algorithm, which just lets the OS schedule all threads, regardless of queue size. sched_impl.h (define your data structures here) This is where you will define the data structures stored per scheduler instance (struct sched_queue) and per worker thread (struct thread_info). This will likely include synchronization constructs like semaphores and mutexes, and a list of threads available to be scheduled. sched_impl.c (implement your code here) This is where you will define the functions implementing the core behavior of the scheduler, including the FIFO and Round Robin scheduling policies. The only way functions defined in this file are made available to the main program (scheduler.c) is by placing function pointers in the sched_impl_t structures sched_fifo and sched_rr. list.h Defines the basic operations on a bidirectional linked list data structure. The elements of the list, of type list_elem_t, include a void *datum where you can store pointers to whatever kind of data you like. You don't have to use this linked list library, but it will probably come in handy. list.c Implements the linked list operations. smp4_tests.c testrunner.c testrunner.h Test harness, defines test cases for checking your MP solution. Please take a look at the source files and familiarize yourself with how they work. Think about how structures containing function pointers compare to classes and virtual methods in C++. If you'd like to learn more, read about the virtual function table in C++. The struct containing function pointers technique employed in this MP is also used by C GUI libraries like GTK+ and to define the operations of loadable modules, such as file systems, within the Linux kernel. 4. PROGRAMMING ============== Now you're ready to implement the core of the scheduler, including the FIFO and Round Robin scheduling algorithms. For this purpose, you should only modify sched_impl.h and sched_impl.c. Please see scheduler.h for the descriptions of what functions you must implement. You are free to put whatever you want in the thread_info and sched_queue structures. Note that the only way that the functions you implement are made available to the main program is through the sched_impl_t structures sched_fifo and sched_rr. See dummy_impl.c for a completed example of how to fill in a sched_impl_t. Suggested approach: 4.1 Create stub versions of all of the functions you will need to implement in sched_impl.c, and statically initialize sched_fifo and sched_rr. 4.2 Figure out how you will implement the scheduler queue, add the appropriate fields to struct sched_queue, and fill in the appropriate queue-related operations in the functions you created in (4.1). Remember that we provide a doubly-linked list in list.[hc]. 4.3 Implement scheduler queue admission control, so that only the requested number of threads can be in the scheduler queue at once. Create the appropriate synchronization constructs to prevent threads not in the queue from executing (look at the implementation of worker threads in scheduler.c:worker_proc()). 4.4 Implement the queue operations for selecting the next thread to execute. This will be different for FIFO vs. Round Robin scheduling. 4.5 Add in synchronization constructs to make sure only the selected thread executes at any given time. 4.6 Fill in any gaps that might remain. When you think you're done, you can test your program using the command "make test". For more thorough testing, the fifo_var and rr_var tests accept queue_size, num_workers, and num_iterations arguments just like the main program (but <num_iterations> is mandatory for the test case): ./scheduler -test fifo_var <queue_size> <num_workers> <num_iterations> ./scheduler -test rr_var <queue_size> <num_workers> <num_iterations> 5. QUESTIONS ============ Q 1 What are some pros and cons of using the struct of function pointers approach as we did in the MP to link different modules? Does it significantly affect performance? Give some examples of when you would and wouldn't use this approach, and why. Q 2 Briefly describe the synchronization constructs you needed to implement this MP--i.e., how you mediated admission of threads to the scheduler queue and how you made sure only the scheduled thread would run at any given time. Q 3 Why is the dummy scheduling implementation provided potentially unsafe (i.e. could result in invalid memory references)? How does your implementation avoid this problem? Q 4 When using the FIFO or Round Robin scheduling algorithm, can sched_proc() ever "miss" any threads and exit early before all threads have been scheduled and run to completion? If yes, give an example; if no, explain why not. Q 5 Why are the three variables in scheduler.h declared 'extern'? What would happen if they were not declared 'extern'? What would happen if they were not declared without the 'extern' in any file? Q 6 Describe the behavior of exit_error() function in scheduler.c. Why does exit_error() not use errno? Q 7 Does it matter whether the call to sched_ops->wait_for_queue(queue) in sched_proc() actually does anything? How would it affect correctness if it just returned right away? How about performance? Q 8 Explain how worker_proc() is able to call the appropriate implementation of wait_for_cpu() corresponding to the scheduling policy selected by the user on the command line. Start from main() and briefly explain each step along the way. Q 9 Is it possible that a worker thread would never proceed past the call to wa->ops->wait_for_cpu(&wa->info) when using one of the scheduling policies implemented in this MP? Q 10 Explain how you would alter the program to demonstrate the "convoy" effect, when a large compute bound job that never yields to another thread slows down all other jobs in a FIFO scheduled system? See Page 402, Stallings, the paragraph starting "Another difficulty with FCFS is that it tends to favor processor-bound processes over I/O bound processes". Why is it difficult to show the benefits of Round Robin scheduling in this case using the current implementation in the machine problem?

__MACOSX/assign3/assign3_part1/._README.txt

assign3/assign3_part1/scheduler.c

#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> #include "scheduler.h" #include "sched_impl.h" /* Used to pass thread arguments to worker_proc() */ typedef struct worker_args { pthread_t thread_id; int iterations; worker_thread_ops_t *ops; thread_info_t info; } worker_args_t; /* Used to pass thread arguments to sched_proc() */ typedef struct sched_args { sched_queue_t *queue; sched_ops_t *sched_ops; } sched_args_t; /* The scheduler uses this counter to keep track of whether it should wait * for more threads to schedule or exit. If your scheduler implementation * is written correctly, it will provide all of the synchronization * necessary to mediate access to the num_workers_remaining variable. */ static int num_workers_remaining = 0; /* Procedure implementing a worker thread. */ static void *worker_proc(void *arg) { int i; worker_args_t *wa = (worker_args_t *) arg; /* Compete with other threads to enter the scheduler queue. */ wa->ops->enter_sched_queue(&wa->info); printf("Thread %lu: in scheduler queue\n", (unsigned long) wa->thread_id); for (i = 0; i < wa->iterations; i++) { /* Don't do anything until the scheduler tells us. */ wa->ops->wait_for_cpu(&wa->info); /* Do some meaningless work... */ usleep(30000); printf("Thread %lu: loop %d\n", (unsigned long) wa->thread_id, i); /* Let another worker have a chance. */ wa->ops->release_cpu(&wa->info); } /* Leave the scheduler queue to make room for someone else * and decrement the count of remaining threads. */ wa->ops->wait_for_cpu(&wa->info); printf("Thread %lu: exiting\n", (unsigned long) wa->thread_id); wa->ops->leave_sched_queue(&wa->info); num_workers_remaining--; wa->ops->release_cpu(&wa->info); pthread_exit(0); } /* Procedure implementing the outline of the scheduler. */ static void *sched_proc(void *arg) { sched_args_t *sa = (sched_args_t *) arg; sched_queue_t *queue = sa->queue; sched_ops_t *sched_ops = sa->sched_ops; /* Wait until there's at least one worker thread to schedule. */ sched_ops->wait_for_queue(queue); /* Keep scheduling worker threads until we're done. */ while (num_workers_remaining > 0) { thread_info_t *info = sched_ops->next_worker(queue); /* next_worker() returns NULL if the scheduler queue is empty. */ if (info) { sched_ops->wake_up_worker(info); sched_ops->wait_for_worker(queue); } else { /* Wait for someone to enter the queue. */ sched_ops->wait_for_queue(queue); } } printf("Scheduler: done!\n"); return NULL; } /* Print out a message describing usage of the program. */ static void print_help(const char *progname) { printf("\nusage: %s <sched_impl> <queue_size> <num_threads> [iterations]\n", progname); printf("\tsched_impl : -fifo or -rr or -dummy\n"); printf("\tqueue_size : the number of threads that can be in the scheduler at one time\n"); printf("\tnum_threads: the number of worker threads to run\n"); printf("\titerations (optional): the number of loops each worker thread runs\n\n"); } /* Display an error and terminate. */ static void exit_error(int err_num) { fprintf(stderr, "failure: %s\n", strerror(err_num)); exit(1); } /* Create thread_count-many worker threads, each of which run for iterations-many cycles. */ static worker_args_t *create_workers(worker_thread_ops_t *ops, int thread_count, int iterations, sched_queue_t *queue) { int i = 0; /* Allocate all thread arguments in one big array (makes it easy to deallocate). */ worker_args_t *argses = (worker_args_t *) malloc(thread_count * sizeof(worker_args_t)); if (argses == NULL) exit_error(errno); /* Initialize counter used by sched_proc() to determine when to stop. * This is safe assuming sched_ops->wait_for_queue() blocks in sched_proc(). */ num_workers_remaining = thread_count; for (i = 0; i < thread_count; i++) { int err = 0; /* Set up worker thread arguments. */ argses[i].iterations = iterations; argses[i].ops = ops; ops->init_thread_info(&argses[i].info, queue); /* Create worker thread. */ err = pthread_create(&argses[i].thread_id, NULL, worker_proc, (void *) &argses[i]); if (err) exit_error(err); /* Detach worker thread. */ printf("Main: detaching worker thread %lu\n", (unsigned long) argses[i].thread_id); pthread_detach(argses[i].thread_id); } /* Arguments can only be deallocated when we're sure the worker * threads are done with them. And besides, it contains a handy * list of the thread_info_t's which need to be cleaned up at the * end. So return the arguments array. */ return argses; } /* Launch the scheduler thread. */ static int start_scheduler(pthread_t *thread_id, sched_queue_t *queue, sched_ops_t *ops) { /* Make arguments static so they're still valid after this function returns. */ static sched_args_t sa; int err; /* Set up scheduler thread arguments. */ sa.queue = queue; sa.sched_ops = ops; /* Create the scheduler thread. */ err = pthread_create(thread_id, NULL, sched_proc, &sa); if (err) exit_error(err); return err; } /* Deallocate all resources (called at end of program). */ static void clean_up(sched_impl_t *sched, int thread_count, worker_args_t *argses, sched_queue_t *queue) { int i; for (i = 0; i < thread_count; i++) sched->worker_ops.destroy_thread_info(&argses[i].info); free(argses); sched->sched_ops.destroy_sched_queue(queue); } #define WORKER_ITERATIONS 5 int smp4_main(int argc, const char **argv) { int thread_count = 0; int queue_size = 0; pthread_t sched_thread; int iterations = WORKER_ITERATIONS; worker_args_t *argses; sched_impl_t *sched; sched_queue_t queue; /* Collect command-line arguments (or exit on error). */ if (argc < 4) { print_help(argv[0]); exit(0); } if (!strcmp("-fifo", argv[1])) { sched = &sched_fifo; } else if (!strcmp("-rr", argv[1])) { sched = &sched_rr; } else if (!strcmp("-dummy", argv[1])) { sched = &sched_dummy; } else { printf("\nApologies, I'm unfamiliar with the \"%s\" scheduling policy.\n", argv[1]); print_help(argv[0]); exit(0); } queue_size = atoi(argv[2]); thread_count = atoi(argv[3]); if (argc >= 5) { iterations = atoi(argv[4]); } /* Begin execution proper. */ printf("Main: running %d workers on %d queue_size for %d iterations\n", thread_count, queue_size, iterations); /* Initialize anything that needs to be done for the scheduler queue. */ sched->sched_ops.init_sched_queue(&queue, queue_size); /* Create a thread for the scheduler. */ start_scheduler(&sched_thread, &queue, &sched->sched_ops); /* Create the worker threads and return. */ argses = create_workers(&sched->worker_ops, thread_count, iterations, &queue); printf("Main: waiting for scheduler %lu\n", (unsigned long) sched_thread); /* Wait for scheduler to finish. */ pthread_join(sched_thread, NULL); /* Clean up our resources. */ clean_up(sched, thread_count, argses, &queue); /* This will wait for all other threads. */ pthread_exit(0); }

__MACOSX/assign3/assign3_part1/._scheduler.c

assign3/assign3_part1/testrunner.c

/*************** YOU SHOULD NOT MODIFY ANYTHING IN THIS FILE ***************/ /* A simple testrunner framework Original Author: L. Angrave */ #include <sys/types.h> #include <sys/wait.h> #include <errno.h> #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <unistd.h> #include "testrunner.h" /* Constants */ #define false (0) #define true (1) #define test_killed (2) /* defaults */ static int default_timeout_seconds = 5; static int timeout_seconds; void set_testrunner_default_timeout(int s) { assert(s > 0); default_timeout_seconds = s; } void set_testrunner_timeout(int s) { assert(s > 0); timeout_seconds = s; } /* --- Helper macros and functions --- */ #define DIE(mesg) {fprintf(stderr,"\n%s(%d):%s\n",__fname__,__LINE__,mesg); exit(1);} static int eql(const char *s1, const char *s2) { return s1 && s2 && !strcmp(s1, s2); } /* Callback function for qsort on strings */ static int mystrcmp(const void *p1, const void *p2) { return eql((const char *) p1, (const char *) p2); } /* Stats of all tests run so far */ typedef struct { int ran, passed, failed; } stats_t; /* -- Signal handlers -- */ static pid_t child_pid; static int sent_child_timeout_kill_signal; static void kill_child_signal_handler(intsigno) { if (!child_pid) return; char m[] = "-Timeout(Killing test process)-"; write(0, m, sizeof(m) - 1); kill(child_pid, SIGKILL); sent_child_timeout_kill_signal = 1; } /* Internal function to run a test as a forked child. The child process is terminated if it runs for more than a few seconds */ static int invoke_test_with_timelimit(testentry_t * test, int redirect_stdouterr, int argc, const char **argv) { char fname[255]; int wait_status; pid_t wait_val; struct sigaction action; assert(!child_pid); assert(test && test->test_function && test->name); set_testrunner_timeout(default_timeout_seconds); errno = 0; child_pid = fork(); if (child_pid == -1) { fprintf(stderr, "-fork failed so running test inline-"); return test->test_function(argc, argv); } if (child_pid == 0) { if (redirect_stdouterr) { snprintf(fname, (int) sizeof(fname), "stdout-%s.txt", test->name); fname[sizeof(fname) - 1] = 0; freopen(fname, "w", stdout); memcpy(fname + 3, "err", 3); freopen(fname, "w", stderr); } exit(test->test_function(argc, argv)); } else { wait_status = -1; sigemptyset(&action.sa_mask); action.sa_handler = kill_child_signal_handler; sigaction(SIGALRM, &action, NULL); sent_child_timeout_kill_signal = 0; alarm(timeout_seconds); wait_val = waitpid(child_pid, &wait_status, 0); int child_exited_normally = WIFEXITED(wait_status); int child_exit_value = WEXITSTATUS(wait_status); int child_term_by_signal = WIFSIGNALED(wait_status); int child_term_signal = WTERMSIG(wait_status); if (child_term_by_signal) { fprintf(stderr, "testrunner:Test terminated by signal %d\n", child_term_signal); fprintf(stderr, "testrunner:waitpid returned %d (child_pid=%d,wait_status=%d)", wait_val, child_pid, wait_status); } if (child_pid != wait_val) fprintf(stderr, "testrunner: strange... wait_val != child_pid\n"); int passed = (child_pid == wait_val) && (child_exit_value == 0) && (child_exited_normally != 0); alarm(0); kill(child_pid, SIGKILL); child_pid = 0; return sent_child_timeout_kill_signal ? test_killed : passed ? 0 : 1; } } /* * run a test and update the stats. The main guts of this functionality is provided by invoke_test_with_timelimit * This outer wrapper updates thes output and statistics before and after running the test. */ static int run_one_test(stats_t * stats, testentry_t * test, int redirect_stdouterr, int argc, const char **argv) { int test_result; assert(stats && test->name && argc > 0 && argv && *argv); stats->ran++; stats->failed++; printf("%2d.%-20s:", stats->ran, test->name); fflush(stdout); test_result = invoke_test_with_timelimit(test, redirect_stdouterr, argc, argv); if (test_result == 0) { stats->failed--; stats->passed++; } printf(":%s\n", (test_result == 0 ? "pass" : test_result == 2 ? "TIMEOUT * " : "FAIL *")); return test_result != 0; } /* Help functionality to print out sorted list of test names and suite names */ static void print_targets(testentry_t tests[], int count) { const char **array; const char *previous; int i; array = (const char **) calloc(sizeof(const char *), count); /* Sort the test names and print unique entries */ for (i = 0; i < count; i++) array[i] = tests[i].name; qsort(array, count, sizeof(array[0]), mystrcmp); printf("\nValid tests : all"); for (i = 0, previous = ""; i < count; i++) if (!eql(previous, array[i])) printf(" %s", (previous = array[i])); /* Sort the suite names and print unique entries */ for (i = 0; i < count; i++) array[i] = tests[i].suite; qsort(array, count, sizeof(array[0]), mystrcmp); printf("\nValid suites:"); for (i = 0, previous = ""; i < count; i++) if (!eql(previous, array[i])) printf(" %s", (previous = array[i])); printf("\n"); } /* * Main entry point for test harness */ int run_testrunner(int argc, const char **argv, testentry_t tests[], int test_count) { const char *test_name, *target; int i; stats_t stats; int target_matched, max_errors_before_quit, redirect_stdouterr; memset(&stats, 0, sizeof(stats)); max_errors_before_quit = 1; redirect_stdouterr = 0; assert(tests != NULL); assert(test_count > 0); assert(argc > 0 && argv && *argv); while (true) { target = argc > 1 ? argv[1] : ""; assert(target); if (*target != '-') break; argc--; argv++; if (target[1] == 'f' && target[2]) max_errors_before_quit = atoi(target + 1); else if (target[1] == 'r') redirect_stdouterr = 1; } target_matched = false; for (i = 0; i < test_count && (max_errors_before_quit < 1 || stats.failed != max_errors_before_quit); i++) { test_name = tests[i].name; assert(test_name); assert(tests[i].suite); assert(tests[i].test_function); if (eql(target, test_name) || eql(target, "all") || eql(target, tests[i].suite)) { if (!target_matched) printf("Running tests...\n"); target_matched = true; run_one_test(&stats, &tests[i], redirect_stdouterr, argc - 1, argv + 1); } } if (!target_matched) { fprintf(stderr, "Test '%s' not found", (strlen(target) > 0 ? target : "(empty)")); print_targets(tests, test_count); } else { printf("\nTest Results:%d tests,%d passed,%d failed.\n", stats.ran, stats.passed, stats.failed); } return stats.passed == stats.ran && target_matched ? 0 : 1; }

__MACOSX/assign3/assign3_part1/._testrunner.c