Assignment 6, Solved

Part of the homework for 22C:50, Summer 2003
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

  1. Do problem 1 on page 140 of the text.

    The first problem we face is to figure out what the baud-rate divisor means. We know that 115,200 baud translates to a divisor of 1, while 57,600 baud translates to a divisor of 2 -- half the baud rate translates to twice the divisor! We know that the divisor is 3 for 38,400 baud, and again, we discover that 1/3 the baud rate translates to 3 times the divisor.

    So, the problem asks about the archaic baud rates 45.5 baud and 75 baud. All we need to do is divide 115,200 by this to get the divisor! So:

    115200/45.5 = 253210 = 9E416 115200/75 = 153610 = 60016

    The divisor for 45.5 baud is approximate, while the divisor for 75 baud is exact.

  2. Modify the code for comreadline from Figure 9.8 so it handles tabs.
    	void comreadline( struct filevariable * f, char buf[], int len );
    	{
    	     int p = 0;
    /*a*/	     int col = 1; /* count columns in the output */
    
    	     do {
    		  char ch = f->read(f);
    		  if (ch >= ' ') { /* character is printable */
    		       if (p < len) { /* space is available in buffer */
    			    buf[p] = ch;      /* put it in the buffer */
    			    p = p + 1;
    /*a*/	     		    col = col + 1;    /* record current column */
    			    f->write(f, ch);  /* echo it */
    		       }
    		  } else if (ch == '\b') { /* character is backspace */
    		       if (p > 0) then begin
    			    p = p - 1;
    /*a*/	     		    col = col - 1;      /* record current column */
    			    f->write(f, '\b');  /* erase a character */
    			    f->write(f, ' ');
    			    f->write(f, '\b');
    		       }
    		  } else if (ch == '\t') { /* character is tab */
    /*a*/		       while (((col % 8) != 0) && (p < len)) {
    /*a*/			    buf[p] = ' ';       /* put blank in the buffer */
    /*a*/	     		    p = p + 1;
    /*a*/	     		    col = col + 1;      /* record current column */
    /*a*/	     		    f->write(f, ' ');   /* echo the blank */
    /*a*/		       }
    		  }
    	     } while ((ch != '\n') && (ch != '\r'));
    	     if (p < len) { /* space is available in buffer */
    		  buf[p] = '\0';    /* put terminator in buffer */
    	     }
    	     f->write(f, '\r');
    	     f->write(f, '\n');
    	}
    

    Changes to the code from the figure are marked with a marginal comment. Note that this code cheats, a bit, by putting the right number of blanks in the buffer when the user hits a tab, and as a result, the problem of erasing tabs is greatly simplified because erasure always moves the cursor back one column. If I had recorded the tab in the buffer, I'd have had to do quite a bit more work to erase them properly!

  3. Consider f, a pointer to an open-file object (a structure), where f->put is the pointer to the function that implements the put method for that particular file. In an object oriented programming language like C++ or Java, a call to this method would be written as f.put(c). In C, this is written (*f->put)(f,c) Why is it necessary to pass the pointer f to the put method in addition to using f to find the pointer that was needed to call put?

    The broad general answer: Methods of the object need access to the variables of that object. In the text (*f->put)(f,c), the first use of f is only concerned with transfer of control to the function that implements the desired method, while the second use of f gives that function access to the variables local to the object f.

    The specific answer: In the text (*f->put)(f,c), the first use of f is only concerned with transfer of control to the put function in the appropriate I/O driver, while the second use of f gives that function access to the state information in f that is specific to that particular file or device.

  4. The Unix device independent I/O model is based on two basic system functions, read() and write().

    Suppose you are writing to a block-sequential DMA device such as a high-speed tape backup on a UNIX system. The block size on the device is 512 bytes, but your software uses a buffer of 1000 bytes. Explain why the operating system cannot do output directly from the user's bufer, but must copy from the user's buffer to a system buffer before doing output to the device. Make careful use of the relevant terminology from Chapter 9.

    Whenever a block is written to tape where that block lies entirely within the user's buffer, it could be written without the need for an intermediate buffer. So, from the first 1000 word buffer written by the user, we may write the first block to tape from bytes 0 to 511 of the user's buffer. However, the second block to tape will be composed of bytes 512 to 999 from the user's first buffer followed by bytes 0 to 23 of the user's second buffer, and these must be assembled into a single buffer, provided by the operating system before being written to tape.

    The third block to tape can again be written directly from the user's second buffer, bytes 24 to 535, but the fourth block will need to be assembled in a system buffer again.

    This is called blocking (or reblocking) the user's data stream into blocks that match the device's requirements.