Roadmap 5: Console

In this roadmap, we study the following issues associated with console:

  • Data structures of console;
  • Understand the interface between the console (a real-time hardware devices) and the system;
  • Synchronization of console and system.

  • Start by looking at the file main.cc (threads/main.cc). Here are a few lines of code from this routine.

    int
    main(int argc, char **argv)
    {
        int argCount;                       // the number of arguments
                                            // for a particular command
    
        ...
    #ifdef USER_PROGRAM
            ...
            else if (!strcmp(*argv, "-c")) {      // test the console
                if (argc == 1)
                    ConsoleTest(NULL, NULL);
                else {
                    ASSERT(argc > 2);
                    ConsoleTest(*(argv + 1), *(argv + 2));
                    argCount = 3;
                }
                interrupt->Halt();          // once we start the console, then
                                            // Nachos will loop forever waiting
                                            // for console input
            }
    #endif // USER_PROGRAM
       ...
    }
    
    NOTE

    The function ConsoleTest() is called for testing.

     

     

    userprog/progtest.cc

     

    void
    ConsoleTest (char *in, char *out)
    {
        char ch;
    
        console = new Console(in, out, ReadAvail, WriteDone, 0);
        readAvail = new Semaphore("read avail", 0);
        writeDone = new Semaphore("write done", 0);
    
        for (;;) {
            readAvail->P();         // wait for character to arrive
            ch = console->GetChar();
            console->PutChar(ch);   // echo it!
            writeDone->P() ;        // wait for write to finish
            if (ch == 'q') return;  // if q, quit
        }
    }
    
    
    NOTE

    It is a simple console. It echos what you type and quits when you type q .

    - It creates a console by calling the constructor Console()
    - It initializes two semaphores readAvail and writeDone for synchronization
    - It waits for a character to arrive by calling readAvail->P()
    - When it wakes up (a chacter arrives), it reads the character by calling GetChar()
    - It echos the character PutChar(ch) by sending the character to write
    - It waits for the output device until the write is done

     

     

     

    machine/console.h
    class Console {
      public:
        ...
      private:
        int readFileNo;                     // UNIX file emulating the keyboard
        int writeFileNo;                    // UNIX file emulating the display
        VoidFunctionPtr writeHandler;       // Interrupt handler to call when
                                            // the PutChar I/O completes
        VoidFunctionPtr readHandler;        // Interrupt handler to call when
                                            // a character arrives from the keyboard
        int handlerArg;                     // argument to be passed to the
                                            // interrupt handlers
        bool putBusy;                       // Is a PutChar operation in progress?
                                            // If so, you can't do another one!
        char incoming;                      // Contains the character to be read,
                                            // if there is one available.
                                            // Otherwise contains EOF.
    };
    
    NOTE

    The mental picture of the console data structure should look something as follows.

            |------------------|
            | readFileNo       |    read file descriptor
            |------------------|
            | writeFileNo      |    write file descriptor
            |------------------|
            | writeHandler     |    pointer to the interrupt handler when a write
            |                  |    is done
            |------------------|
            | readHandler      |    pointer to the interrupt handler when a
            |                  |    a character arrives
            |------------------|
            | handlerArg       |    argument to be passed to the interrupt handlers
            |------------------|
            | putBusy          |    a flag indicating a PutChar is in progress
            |------------------|
            | incoming         |    buffer for an incoming character
            |------------------|
    

     

     

    machine/console.cc
    
    Console::Console(char *readFile, char *writeFile, VoidFunctionPtr readAvail,
                    VoidFunctionPtr writeDone, int callArg)
    {
        if (readFile == NULL)
            readFileNo = 0;                                 // keyboard = stdin
        else
            readFileNo = OpenForReadWrite(readFile, TRUE);  // should be read-only
        if (writeFile == NULL)
            writeFileNo = 1;                                // display = stdout
        else
            writeFileNo = OpenForWrite(writeFile);
    
        // set up the stuff to emulate asynchronous interrupts
        writeHandler = writeDone;
        readHandler = readAvail;
        handlerArg = callArg;
        putBusy = FALSE;
        incoming = EOF;
    
        // start polling for incoming packets
        interrupt->Schedule(ConsoleReadPoll, (int)this, ConsoleTime
    , ConsoleReadInt)
    ;
    }
    
    
    NOTE

    This function initializes the simulation of a hardware console device. The first two arguments are in and out files, if NULL, they are stdin (keyboard) and stdout (terminal). The third and fourth arguments are interrupt handlers. These provide interfaces between the hardware device console and the system. Through the interrupt handler (a function) ReadAvail, the system can control what to do when a char arrives. Similarly, writDone defines what to do when a write is done. In the case of ConsoleTest(), they are ReadAvail and WriteDone, which V() the semaphores readAvail and writeDone respectively. They wake up threads waiting on the semaphores for a character to arrive or a display to finish. The last argument is the argument for the interrupt handlers. Then we start polling for incoming characters. ConsoleReadPoll() is an interrupt handler. ConsoleTime is the thread interrupt time (defined in machine/stats.h). ConsoleReadInt indicates the interrupt type (defined in machine/interrupt.h).

     

     

     

    machine/console.cc
    char
    Console::GetChar()
    {
       char ch = incoming;
    
       incoming = EOF;
       return ch;
    }
    
    NOTE

    This function reads a character from the input buffer, resets the buffer to EOF, and returns the charater.

     

     

     

    machine/console.cc
    void
    Console::PutChar(char ch)
    {
        ASSERT(putBusy == FALSE);
        WriteFile(writeFileNo, &ch, sizeof(char));
        putBusy = TRUE;
        interrupt->Schedule(ConsoleWriteDone, (int)this, ConsoleTime,
                                            ConsoleWriteInt);
    }
    
    NOTE

    It writes a character to the simulated display, schedules an interrupt to occur in the future, and returns. This is asynchronous in the sense that this function may return before (or after) the write is done (when the interrupt ConsoleWriteDone occurs). In ConsoleTest(), the synchronization is done by using a semaphor. After a thread requests a write, it is blocked on the semaphor until the interrupt ConsoleWriteDone occurs, which signals the sleeping thread. The synchronization in reading is similar. But, ConsoleTest works only for one thread and it simply echos inputs.

     

     

     

    machine/console.cc
    static void ConsoleReadPoll(int c)
    { Console *console = (Console *)c; console->CheckCharAvail(); }
    
    NOTE

    It is a dummy function. It is really Console::CheckCharAvail().

     

     

     

    machine/console.cc
    void
    Console::CheckCharAvail()
    {
        char c;
    
        // schedule the next time to poll for a packet
        interrupt->Schedule(ConsoleReadPoll, (int)this, ConsoleTime,
                            ConsoleReadInt);
    
        // do nothing if character is already buffered, or none to be read
        if ((incoming != EOF) || !PollFile(readFileNo))
            return;
    
        // otherwise, read character and tell user about it
        Read(readFileNo, &c, sizeof(char));
        incoming = c ;
        stats->numConsoleCharsRead++;
        (*readHandler)(handlerArg);
    }
    
    NOTE

    - This function schedules the next interrupt so we periodically check if a character is available.
    - If nothing available, it does nothing. Otherwise, it reads a character and puts it in the buffer for incoming character.
    - It updates stats.
    - Run the readHandler. In our case, it is ReadAvail which V() the semaphore readAvail to wake up the waiting reader.