Homework 4 Solutions

22C:116, Spring 1999

Douglas W. Jones
  1. The following version of the dining philosopher's problem as solved in Tannenbaum has been tested:
    /* following only needed for getpid() call used for srandom() */
    #include 
    
    /* following only needed for random() and srandom() */
    #include 
    
    /* following needed for most of program */
    #include 
    #include "threads.h"
    
    /****************************/
    /* Philosophical Activities */
    /****************************/
    
    void think()
    {
        do {
            thread_relinquish();
        } while (random() & 7);
    }
    
    #define eat think
    
    /*************************************************/
    /* Philosophical Code, rewritten from Tannenbaum */
    /*************************************************/
    
    #define N	 5 		/* number of philosophers */
    #define THINKING 0		/* philosopher is thinking */
    #define HUNGRY	 1		/* philosopher is hungry */
    #define EATING	 2		/* philosopher is eating */
    
    int left( int i )
    /* return number of i's left neighbor */
    {
       if (i <= 0) return N-1;
       return i-1;
    }
    
    int right( int i )
    /* return number of i's right neighbor */
    {
       if (i >= N-1) return 0;
       return i+1;
    }
    
    int state[N];			/* array to keep track of everyone's state */
    struct thread_semaphore mutex;	/* mutual exclusion for critical regions */
    struct thread_semaphore s[N];	/* one semaphore per philosopher */
    
    void initforks()
    {
        int i;
        thread_semaphore_init( &mutex, 1 );
        for (i = 0; i < N; i++) {
            state[i] = THINKING; /* start all philosophers thinking */
            thread_semaphore_init( &s[i], 0 );
        }
    }
    
    void test( int i )
    {
        if ( (state[i] == HUNGRY)
        &&   ( state[left( i )] != EATING)
        &&   ( state[right( i )] != EATING) ) {
            state[i] = EATING;
            thread_signal( &s[i] );
        }
    }
    
    void take_forks( int i )
    {
        thread_wait( &mutex );
        state[i] = HUNGRY;
        test( i );
        thread_signal( &mutex );
        thread_wait( &s[i] );
    }
    
    void put_forks( int i )
    {
        thread_wait( &mutex );
        state[i] = THINKING;
        test( left( i ) );
        test( right( i ) );
        thread_signal( &mutex );
    }
    
    void philosopher( int i )
    {
        putchar( '0'+i ); puts(" start");
        for (;;) {
    	/* philosopher starts out thinking */
            take_forks(i);
            putchar( '0'+i ); puts(" eat");
            eat();
            put_forks(i);
            putchar( '0'+i ); puts(" think");
            think();
        }
    }
    
    /*************************************/
    /* Main program, starts philosophers */
    /*************************************/
    
    int main()
    {
        srandom( (unsigned) getpid() ); /* randomize the random numbers */
        thread_manager_init();
        thread_startup_report();
        initforks();
        {
            int i;
            for (i = 0; i < N; i++) {
                thread_launch( 4000, philosopher, i );
            }
        }
        thread_manager_start();
    }
    
    Furthermore, adding random calls to thread_relinquish() within the code does not change its correctness -- this experiment is equivalent to running it under a preemptive scheduler.

  2. Part A: Here is a complete solution:
    /* following only needed for getpid() call used for srandom() */
    #include 
    
    /* following only needed for random() and srandom() */
    #include 
    
    /* following needed for most of program */
    #include 
    #include "threads.h"
    
    /***************************/
    /* FIFO DISK REQUEST QUEUE */
    /***************************/
    
    struct request {
        int cl;
        struct thread_semaphore * dn;
        struct request * next;
    };
    
    struct request* head;
    struct request* tail;
    struct request* freelist;
    struct thread_semaphore data;
    
    void initqueue()
    {
        head = NULL;
        tail = NULL;
        freelist = NULL;
        thread_semaphore_init( &data, 0 );
    }
    
    void enqueue( int cyl, struct thread_semaphore * done )
    {
        /* get a new disk request record */
        struct request* req;
        if (freelist == NULL) {
            req = (struct request *)malloc(sizeof(struct request));
        } else {
            req = freelist;
            freelist = req->next;
        }
    
        /* load the disk request record with data */
        req->cl = cyl;
        req->dn = done;
        req->next = NULL;
        
        /* enqueue the disk request */
        if (head == NULL) { /* the queue is empty */
            head = req;
        } else {
            tail->next = req;
        }
        tail = req;
    
        /* synchronize */
        thread_signal( &data );
    }
    
    void dequeue( int * cyl, struct thread_semaphore ** done )
    {
        /* synchronize */
        thread_wait( &data );
    
        /* dequeue the disk request (the queue is non-empty) */
        struct request * req;
        req = head;
        head = req->next;
    
        /* unload the disk request data */
        *cyl = req->cl;
        *done = req->dn;
    
        /* dispose of the request record */
        req->next = freelist;
        freelist = req;
    }
    
    /************************************/
    /* DISK INTERRUPT SERVICE SIMULATOR */
    /************************************/
    
    void disk( int n )
    {
        int cyl = 0; /* cylinder number */
        int ocyl;    /* old cylinder number */
        int diff;    /* difference cylinder numbers */
        int i,j;     /* loop counters */
        int time = 0;/* a measure of the total time taken */
        struct thread_semaphore * done;
    
        do (i = 0; i < n; i++) {
    	ocyl = cyl;
    	dequeue( &cyl, &done );
    	diff = cyl - ocyl;
    	if (diff < 0) diff = -diff;
    	for (j = 0; j <= diff; j++) {
    	    thread_relinquish();
    	    time++;
    	}
    	thread_signal( done );
        }
        print( "time = &d", time );
    }
    
    /***********************/
    /* DISK USER SIMULATOR */
    /***********************/
    
    void user( int n )
    {
        int i;	/* count of disk operations */
        int cyl;	/* selected cylinder */
        struct thread_semaphore done;
        thread_semaphore_init( &done, 0 );
        cyl = random();
        do (i = 1; i <= n; i++) { /* do n disk operations */
    	while (random() & 3) thread_relinquish(); /* take some time */
    	if ((random() & 3) == 0)) cyl = random();
    	cyl = cyl & 63;
    	enqueue( cyl, &done ); /* post one disk request */
        	thread_wait( &done );
        }
    }
    
    /***********************************/
    /* Main program, starts simulation */
    /***********************************/
    
    int main()
    {
        srandom( (unsigned) getpid() ); /* randomize the random numbers */
        thread_manager_init();
        thread_startup_report();
        initqueue();
        thread_launch( 4000, disk, 100 );
        {
            int i;
            for (i = 0; i < 5; i++) {
                thread_launch( 4000, user, 20 );
            }
        }
        thread_manager_start();
    }
    

    Part B: The required modifications are confined to the queue module:

    /*******************************/
    /* ELEVATOR DISK REQUEST QUEUE */
    /*******************************/
    
    struct request {
        int cl;
        struct thread_semaphore * dn;
        struct request * next;
    };
    
    #define CYLS 64
    struct request* heads[CYLS];
    struct request* tails[CYLS];
    int cylinder;
    int direction;
    struct request* freelist;
    struct thread_semaphore data;
    
    void initqueue()
    {
        int i;
        for (i = 0; i < CYLS; i++) {
            heads[i] = NULL;
            tails[i] = NULL;
        }
        freelist = NULL;
        cylinder = 0;
        direction = 1; /* start up from the bottom */
        thread_semaphore_init( &data, 0 );
    }
    
    void enqueue( int cyl, struct thread_semaphore * done )
    {
        /* get a new disk request record */
        struct request* req;
        if (freelist == NULL) {
            req = (struct request *)malloc(sizeof(struct request));
        } else {
            req = freelist;
            freelist = req->next;
        }
    
        /* load the disk request record with data */
        req->cl = cyl;
        req->dn = done;
        req->next = NULL;
    
        /* enqueue the disk request in queue for appropriate cylinder */
        if (heads[cyl] == NULL) { /* the queue is empty */
            heads[cyl] = req;
        } else {
            tails[cyl]->next = req;
        }
        tails[cyl] = req;
    
        /* synchronize */
        thread_signal( &data );
    }
    
    void dequeue( int * cyl, struct thread_semaphore ** done )
    {
        struct request * req;
    
        /* synchronize */
        thread_wait( &data );
    
        /* find a cylinder where there is work to do */
        while (heads[cylinder] == NULL) {
            cylinder = cylinder + direction;
            if ((cylinder == 0) || (cylinder == (CYLS-1))) {
                direction = -direction;
            }
        }
    
        /* dequeue the disk request (the queue is non-empty) */
        req = heads[cylinder];
        heads[cylinder] = req->next;
    
        /* unload the disk request data */
        *cyl = req->cl;
        *done = req->dn;
    
        /* dispose of the request record */
        req->next = freelist;
        freelist = req;
    }
    
    This gives an improvement of about 40%, on the average over a number of experimental runs comparing it with the FIFO version.