Roadmap 5: Console
In this roadmap, we study the following issues associated with console:
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.