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:
The other hardware devices such as disk and network are similar to console.
We study ConsoleInput only. ConsoleOutput is similar to ConsoleInput.
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.
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.
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().