Roadmap 5: Console

In Interrupt part, we studied the timer hardware device and its interface with the system. In this roadmap, we study I/O devices and the following issues:

  • Data structures of console;
  • Console interrupts;
  • Interface between the console (a real-time hardware device) and the system;
  • Synchronization of console and system.
  • The other hardware devices such as disk and network are similar to console.


    We study ConsoleInput only. ConsoleOutput is similar to ConsoleInput.

     

     

    machine/console.h

    class ConsoleInput : public CallBackObj {
      public:
        ...
        void CallBack();                    // interrupt handler
      private:
        int readFileNo;                     // UNIX file emulating the keyboard
        CallBackObj *callWhenAvail;         // its CallBack() is invoked when
                                            // there is a char to be read
        char incoming;                      // Contains the character to be read,
                                            // if there is one available.
                                            // Otherwise contains EOF.
    };
    

    ConsoleInput is a class derived from the base class CallBackObj. It has an implementation of CallBack(), which is invoked when a console read interrupt occurs. An object callWhenAvail is passed as a parameter. This is an interface between the raw hardware ConsoleInput and the software system. The CallBack() function of callWhenAvail notifies the system when there is a char to be read.

    The mental picture of the ConsoleInput data structures looks something as follows.

    
            |---------------|
            | readFileNo    |    read file descriptor
            |---------------|
            | callWhenAvail |    its CallBack() is invoked when
            |               |    a character arrives
            |---------------|
            | incoming      |    buffer for an incoming character
            |---------------|
    

     

     

    machine/console.cc

    
    ConsoleInput::ConsoleInput(char *readFile, CallBackObj *toCall)
    {
        if (readFile == NULL)
            readFileNo = 0;                                 // keyboard = stdin
        else
            readFileNo = OpenForReadWrite(readFile, TRUE);  // should be read-only
        // set up the stuff to emulate asynchronous interrupts
        callWhenAvail = toCall;
        incoming = EOF;
    
        // start polling for incoming keystrokes
        kernel->interrupt->Schedule(this, ConsoleTime, ConsoleReadInt);
    }
    

    If readFile is NULL, UNIX stdin is used to emulate the keyboard, otherwise a UNIX file is open for the keyboard. The CallBack() function of the object callWhenAvail or toCall is called when there is a char available to be read. Then the first console read interrupt is scheduled. When a console read interrupt occurs (machine/interrupt.cc/CheckIfDue()), the CallBack() of this (ConsoleInput) object is invoked.

     

     

    machine/console.cc

    
    void
    ConsoleInput::CallBack()
    {
        char c;
    
        ASSERT(incoming == EOF);
        if (!PoolFile(readFileNo)) { // nothing to be read
            // schedule the next time to poll for incoming keystrokes
            kernel->interrupt->Schedule(this, ConsoleTime, ConsoleReadInt);
        } else {
            // otherwise, read a character and tell user about it
            Read(readFileNo, &c, sizeof(char));
            incoming = c;
            kernel->stats->numConsoleCharsRead++;
            callWhenAvail->CallBack();
        }
    }
    

    What is the object toCall passed to ConsoleInput constructor?

     

     

    userprog/synchconsole.cc

    
    SynchConsoleInput::SynchConsoleInput(char *inputFile)
    {
        consolInput = new ConsolInput(inputFile, this);
        lock = new Lock("console in");
        waitFor = new Semaphore("console in", 0);
    }
    

    SynchConsoleInput constructs the hardware device ConsoleInput and passes this (SynchConsoleInput) object as a parameter. Now, we can see that when a character is available to be read, the CallBack() of this object is invoked.

     

     

    userprog/synchconsole.cc

    
    void
    SynchConsoleInput::CallBack()
    {
        waitFor->V();
    }
    

    It simply wakes up a thread blocked on the semaphore waitFor, waiting for a character to be read. To understand the synchronization between the hardware device ConsoleInput and the system, we examine how the user reads a character from the keyboard. The user calls:

     

     

    
    char
    SynchConsoleInput::GetChar()
    {
        char ch;
    
        lock->Acquire();
        while ((ch = consoleInput->GetChar()) == EOF) {
            waitFor->P();
        }
        lock->Release();
        return ch;
    }
    

    A lock is used for mutual exclusion to make sure that the keyboard is accessed one at a time. If no character is available, the calling thread sleeps on the semaphore waitFor, whose value is initialized to zero.
    How is the thread waken up?
    When a console read interrupt occurs, the CallBack() function of ConsoleInput is invoked. If there are keystrokes, it reads a character into incoming and wakes up the thread by calling CallBack() of SynchConsoleInput. Then the thread goes back in the while loop and calls:

     

     

    machine/console.cc

    
    char
    ConsoleInput::GetChar()
    {
        char ch = incoming;
        if (incoming != EOF) {       // schedule when next char will arrive
            kernel->interrupt->Schedule(this, ConsoleTime, ConsoleReadInt);
        }
        incoming = EOF;
        return ch;
    }
    

    It checks incoming. If there is a character available in incoming, it schedules the next console read interrupt, reset incoming and return the character. Note that this is an asynchronous hardware, it returns immediately, whether there is a character available or not.

    Now, we can see that the synchronization is achieved by the semaphore waitFor. The user keeps waiting until the hardware puts a char in incoming and notifies the user. Then the user gets the character returned by ConsoleInput::GetChar().