Chapter 13 -- I/O

all about I/O

computers aren't useful unless we can put data into them,
and get results out.

   input --   data to computer
   output --  data from computer

   computer model:
       -----        --------
       |CPU| <----> |memory|
       -----   ^    --------
	      \ /


examples of input devices:
   keyboard, mouse, network, disk,  ??

examples of output devices:
   printer, (terminal) display, network, ??

simulator has only 2 I/O devices,
  keyboard (for input)
  display  (for output)


programmer interface -- tools give
 get_ch, put_ch, put_str

 these are actually OS implemented procedures.
 (The OS is the program that interfaces between the programmer
  or application user and the actual hardware.  For us, it is NT.)

protection issues --
  in a real system, there could be more than one terminal
  (terminal is a keyboard and display together)

  Should one user be able to display characters on another's
  display?  Lock up another's keyboard?  Send a file of
  infinite length to the printer, effectively shutting
  all others out?

  In practice, the OS's job is "resource management,"
  allocating all portions of the processor.  Examples
  of resources are the CPU and all I/O devices.

physical issues --
  A computer today (1998) can complete an instruction at
  the rate of about 1 each nsec.
  Unfortunately, typical I/O devices are much slower, often
  requiring 10s of milliseconds to deal with a single
  character. That is approx. 1 million times slower!
  This situation is dubbed the "access gap."

disk - a real, live, phisical device

Vocabulary, to form a picture of a disk (ch 13, p260)

  PLATTER -- sort of like a phonograph record or CD.

  data is stored on a SURFACE of a platter.

  all platters are tied together and rotate around the SPINDLE
    at a fixed speed.

  each surface has one or more READ/WRITE HEADS.

  Platters are broken down into TRACKS.  A single track is
    one of many concentric circles on the platter.

  All the corresponding tracks on all surfaces, taken together,
    form a CYLINDER.

  Each track is broken down into SECTORS.

How we read/write to a sector.
  Given:  the sector position on the cylinder. (looked up in a
  table, or calculated from the disk address).

  -- the disk is spinning.

  -- the read/write head is moving to the correct cylinder (track).
     the physical movement, acceleration, etc. comes into play.
     This is SEEK time.

  -- once the read/write head is over the correct cylinder, there
     is bound to be some time to wait until the correct sector
     is under the head.  This is ROTATIONAL LATENCY.

  -- Even at the correct sector, it still takes some time for
     the data to be read/written.  This is the READ or WRITE

     time to read a sector =   seek time + rotate time + read time.

So, the nitty gritty issue is:  how does the OS accomplish
  I/O requests?  There are 2 possibilities.

  1.  have special I/O instructions
      --  input
	  need to know  which device, how much data, where the
	  data is to go
      --  output
	  need to know  which device, how much data, where the
	  data currently is

      How does the processor know that the instruction has completed?
	(Is there any need to wait?)
      What happens if the device encounters an error?
        (Does this halt the computer?)

  2.  the solution of choice
      overload memory locations to use as communication channels.

      for example,
       0x0000 0000  -|
       .             |   real memory
       .             |
       .             |
       0xffff 0000  -|

       0xffff 0008  - data from keyboard (Keyboard_Data)

       0xffff 0010  - data to display (Display_Data)

   then, by reading (loading) from location 0xffff0008, data
   is requested from the keyboard
   then, by writing (storing) to location 0xffff0010, data
   is sent to the display

     the syscall code in the OS must be (in essence)

     mov  eax, Keyboard_Data   # get_ch syscall
     return from syscall


     mov  Display_Data, eax    # put_ch syscall
     return from syscall

     This method of I/O is called MEMORY-MAPPED I/O.

Problems with memory-mapped I/O as currently given:
 -- get_ch presumably returns once a character has been typed.
    What happens if the user does not type a character?
    Types it on the wrong keyboard?  Goes to get a drink
    of water?

    What happens to the data if the user types 2 characters
    before get_ch has been called?

    How does the computer know if a character has been typed?

 -- put_ch and put_str:  how does the computer know that the device
    is ready to print out a second character?  What if the
    printer jams?  (printers and terminals are SLOW!)

 What is needed is a way to convey information about the
 STATUS of I/O devices.  This status information is used
 to coordinate and SYNCHRONIZE the useage of devices.

       0x0000 0000  -|
       .             |   real memory
       .             |
       .             |
       0xffff 0000  -|

       0xffff 0008  - data from keyboard (Keyboard_Data)
       0xffff 000c  - STATUS from keyboard (Keyboard_Status)

       0xffff 0010  - data to display (Display_Data)
       0xffff 0014  - STATUS from display (Display_Status)

  assume that the MSB is used to tell the status of a device.
    MSB = 1 means device ready
    MSB = 0 means device is busy
  note that we can check for device ready/busy by looking to see
    if the Status word is negative (2's comp) or not.

    for the keyboard, a 1 means that a character has been typed
		      a 0 means that no character is available

    for the display,  a 1 means that a new character may be sent
		      a 0 means that the device is still disposing
		         of a previous character

Then, the code in the OS must be more like

     keyboard_wait:            ; for get_ch
	   test  Keyboard_Status, 80000000h
	   jz    keyboard_wait
	   mov   eax, Keyboard_Data


     display_wait:             ; for put_ch
	   test  Display_Status, 80000000h
	   jz    display_wait
	   mov   Display_Data, eax

   This scheme is known as BUSY WAITING, or SPIN WAITING.
   The little loop is called a SPIN WAIT LOOP.

Something that is not well explained (at this level) is how
these status bits get set and cleared.  The spin wait loop
reads the status word, but does not change it.

  The device (its CONTROLLER) sets and clears the bit.
  An implied fuction is that the device sets the bit
  when it becomes ready to work on another character.

  AND, a mov from Keyboard_Data also clears the MSB of Keyboard_Status
  AND, a mov to Display_Data also clears the MSB of Display_Status

PROBLEMS with this programmed I/O approach:

-- much time is wasted spin waiting.

   if it takes 100 instructions to program this, and each
   instruction takes 20ns to execute, then it takes

     100 * 20nsec = 2000nsec = 2 usec    to execute this code

     if a device takes 2msec (=2000usec) to deal with one character,
        then the percent of time spent waiting is

	time waiting       2000us
	------------ = --------------- =  .999 = 99.9%
	total time      2000us + 2usec

   We'd like a solution that spent less time "doing nothing"

-- if (somehow) a second key is pressed before the program does
   a get_ch, the first key pressed is lost.  There is only one
   character's worth of storage.

   This problem is actually a "Catch-22."  The keyboard_wait code has
   to be run often enough that no characters are lost, but
   executing this code spin waits until a character is pressed.
   The system could do nothing but wait around for characters!

Some problems are solved by the use of queues (buffers).
   The check for device ready is separated from the sending
   and receiving of characters.  Code for this is in the
   text, pages 265 and 266.

   putnextchar:  print a character if there is one in the
		 queue, and the device is ready
		 (done by OS periodically)
   printstring:  put character(s) in queue and return
		 (called by user program)

   getnextchar:  get a character and place in a queue, if one
		 is waiting at the keyboard
		 (done by OS periodically)
   getstring:    get character from queue (if available) and return;
		 if queue is empty, spin wait until there is
		 a character.
		 (called by user program)

Some difficulties are caused by this situation:
 -- OS must call getnextchar regularly and often so as not
    to lose characters.

 -- What happens if the queue(s) become full?  Are characters

 -- OS must call putnextchar regularly to empty out the queue.