Homework 6 Solutions

22C:116, Fall 1998

Douglas W. Jones
  1. 
    Here is (untested but logically sound) C code to implement a timer
    service under our thread manager:
    
    /****
     * Package supporting timer services under thread package      *
     * Written by Douglas Jones                                    *
                                                                ****/
    #include 
    #include 
    #include 
    #define ITIMER_REAL      0    /* real time intervals */
    
    /* the representation of a timer on the timer package's internal timer lists */
    struct thread_timer {
    	struct timeval when; /* time this timer expires */
    	struct semaphore * s;  /* semaphore to signal when timer expires */
    	struct thread_timer * next; /* link for lists of timers */
    }
    
    /* the timer package's internal timer lists */
    static struct thread_timer * head; /* head of list of pending timers */
    static struct thread_timer * free; /* head of list of free timers */
    
    void at( struct timeval * t, struct semaphore * s )
    /* users call this to schedule thread_signal(s) at or soon after time t */
    {
    	struct timeval now;        /* current time of day */
    	struct itimerval diff;     /* interval from current time to time t */
    	struct thread_timer * new; /* the new thread timer to be enqueued */
    	struct thread_timer ** p;  /* pointer used to scan list of timers */
    	long oldmask;              /* used to remember blocked signals */
    
    	/* normalize t -- this does not change the abstract value of t */
    	while (t->tv_usec < 0) {
    		t->tv_usec = t->tv_usec + 1000000;
    		t->tv_sec = t->tv_sec - 1;
    	}
    	while (t->tv_usec > 1000000) {
    		t->tv_usec = t->tv_usec - 1000000;
    		t->tv_sec = t->tv_sec + 1;
    	}
    
    	gettimeofday( &now, NULL );
    
    	if (timercmp( t, &now, < )) {
    		thread_signal( s );
    		return;
    	}
    
    	/* compute how long until timer should expire */
    	diff.it_value.tv_usec = t->tv_usec - now.tv_usec;
    	diff.it_value.tv_sec = t->tv_sec - now.tv_sec;
    	if (diff.it_value.tv_usec < 0) { /* propagate borrow */
    		diff.it_value.tv_usec += 1000000;
    		diff.it_value.tv_sec -= 1;
    	}
    	/* and make it clear this is a non-periodic timer */
    	diff.it_interval.tv_usec = 0;
    	diff.it_interval.tv_sec = 0;
    
    	/* begin critical section (lock out timer interrupts) */
    	oldmask = sigblock( sigmask(SIGALRM) );
    
    	/* create timer record to enqueue, in critical section!  */
    	if (free == NULL) {
    		new = malloc( size(thread_timer) );
    	} else {
    		new = free;
    		free = new->next;
    	}
    	new->s = s;
    	memcpy( &(new->when), t, size(struct timeval) );
    
    	if (head == NULL) { /* no pre-existing timer */
    		new->next = NULL;
    		head = new;
    		setitimer( ITIMER_REAL, &diff );
    
    		/* end critical section (enable timer interrupts) */
    		(void) sigsetmask( oldmask );
    		return;
    	}
    
    	/* there exist other timers */
    
    	if (timercmp( t, &(head->when), < )) { /* new one goes before head */
    		new->next = head;
    		head = new;
    		setitimer( ITIMER_REAL, &diff );
    
    		/* end critical section (enable timer interrupts) */
    		(void) sigsetmask( oldmask );
    		return;
    	}
    
    	/* scan down list looking for place to put new timer */
    	p = &(head->next);
    	for (;;) {
    		if (timercmp( t, &((*p)->when), < )) break;
    		p = &((*p)->next);
    	}
    
    	/* stuff timer into list */
    	new->next = (*p)->next;
    	(*p)->next = new;
    
    	/* end critical section (enable timer interrupts) */
    	(void) sigsetmask( oldmask );
    	return;
    }
    
    static void timerhandler( int i )
    /* callback from UNIX kernel indicating timer expiration */
    {
    	struct thread_timer * t; /* temp handle on a timer record */
    	struct timeval now;      /* current time of day */
    	struct itimerval diff;   /* interval, current time to next */
    
    	gettimeofday( &now, NULL );
    	for (;;) { /* signal and dispose of all pending timers */
    		if (head == NULL) return;
    
    		if (timercmp( head->when, &now, > )) break; /* none pending */
    
    		/* signal head timer's semaphore */
    		thread_signal( head->s );
    
    		/* dequeue old head record, put it on freelist */
    		t = free;
    		free = head;
    		head = free->next;
    		free->next = t;
    	}
    
            /* compute how long until timer should expire */
            diff.it_value.tv_usec = head->when.tv_usec - now.tv_usec;
            diff.it_value.tv_sec = head->when.tv_sec - now.tv_sec;
            if (diff.it_value.tv_usec < 0) { /* propagate borrow */
                    diff.it_value.tv_usec += 1000000;
                    diff.it_value.tv_sec -= 1;
            }
            /* and make it clear this is a non-periodic timer */
            diff.it_interval.tv_usec = 0;
            diff.it_interval.tv_sec = 0;
    
            /* set the timer! */
    	setitimer( ITIMER_REAL, &diff );
    }
    
    void timerinit()
    /* users call this to start timer package */
    {
    	head = NULL;
    	free = NULL;
    	signal( SIGALRM, timerhandler );
    }
    

  2. The read() service, when reading from a slow device like a keyboard, may be interrupted by a signal. The man page for read says:
    The value returned [the number of bytes read from the file] may be less than nbyte if the number of bytes left in the file is less than nbyte, if the read() request was interrupted by a signal, ...
    So, each time the a timer signal is delivered to the application process, the read is interrupted and a partial line of input may be delivered to the application. This depends, of course, on the input mode (as set, for example, by IOCTL) currently in use.

  3. The difference between renaming a file (using, for example, the "mv a b" command under the UNIX shell) and copying a file to a new name and then deleting the old one (using, for example, "cp a b; rm a" under the UNIX shell) is caused by the fact that there may be multiple links to the file. renaming a file changes the name on the link found in my directory, but the link still refers to the same file. Therefore, if someone changes that file, I see the changes under the new name. If I copy the file and then delete it, what I have done is delete my link to the file and create a private copy. Others may still change the old file, but I will not see the changes they make, and they will not see the changes I make.

  4. Contiguous allocation of files leads to an external fragmentation problem, just as contiguous allocation of memory leads to external fragmentation.

    There may be an internal fragmentation problem with disk files because the unit of allocation (the disk sector) is not the same as the unit of increase in file size (the byte). This internal fragmentation problem is irrelevant to this problem because it does not depend on whether the file is allocated contiguously or in some other way, but is only related to the fact that file sizes increase in increments of one sector.

  5. Consider formatting file names as follows: "group/user/directory/file.use" where "group" is the name of the user group to which you belong, "user" is your personal name, "directory" is the logical name of a group of your files, and "file.use" is one of your files. The "/" character is just some legal character with a file name and has no special significance to the file system. Given such a naming convention, you could behave as if the files existed in a tree-structured directory, even though the system knows nothing of such structures.

  6. Assuming that we have a disk scheduler in our disk I/O subsystem, and that we are dealing with a file server for a large department and not a single user machine, compacting disk storage could have real effects on performance for a number of reasons:

    First, a compaction scheme could lead to contiguous allocation of files on the disk, where the file system naturally tends to scatter the blocks here and there. Contiguous allocation would lead to reduced seek times in sequential reads of a single file by a single user.

    Second, a compaction scheme could lead to clumping the allocated files on a disk into a single contiguous region of the disk, with available free space clumped outside that region. Without compaction, after a long series of file allocations and deallocations, the normal state of affairs would be that files are scattered over the whole disk, with free space scattered over the whole disk. The result of clumping the allocation is that seek times involving seeks from part of one file to part of another would be reduced, thus improving performance when many users are active reading and writing files.