Kernel Implementation: Keyboard Input Buffer

Now that you have code to handle key press events (you finished the last implementation task, right?) we need to create an interface that allows programs to wait for keyboard input. Instead of displaying characters every time a user presses a key, we’re going to store input characters in a circular buffer. We’ll then implement a kgetc function that reads one character at a time from this buffer, or blocks if a key has not been pressed yet.

The keyboard buffer

If you haven’t already, you should read about circular buffers so you understand the general approach. We will use a circular buffer to store key presses because we (a) do not have the ability to dynamically allocate memory in our kernel yet, and (b) dynamically allocating memory during an in terrupt handler is unsafe because that interrupt could have arrived in the middle of an allocation from the kernel’s normal control flow. The downside of a circular buffer is its limited capacity, but if we make the circular buffer large enough we should get reasonable behavior from the interface.

You will need to add a character to the circular buffer each time there is a character key press, but other keys like shift and key-up events do not add to the circular buffer. If you receive a character keypress event and the buffer is full you can simply ignore the key press. you might have encountered this behavior before if you held down a key on a Linux machine when no programs are asking for input; on many machines the OS will generate a beep to inform you that the keypress is being ignored.

Implementaiton details

Some key presses like a or 1 will add characters to the buffer. Others like shift change keyboard state but do not add to the buffer. How do you think we should handle keys like enter and backspace? Think about both of these keys in the context of the kgetc function you will implement in the next part, and be prepared to explain why you chose a particular approach (perhaps in your status update next week).

The kgetc function

The kgetc function will read one character at a time from the circular buffer. Please implement kgetc with the following signature:

/**
 * Read one character from the keyboard buffer. If the keyboard buffer is empty this function will
 * block until a key is pressed.
 *
 * \returns the next character input from the keyboard
 */
char kgetc();

Critically, kgetc should block if there are no buffered key presses. To block, just loop until there is a value in the keyboard buffer. It may seem odd to write what appears to be an infinite loop, but remember that a keyboard event will generate an interrupt, which pulls the kernel away from this loop to run your keyboard interrupt handler. This is a bit like writing parallel code with threads, although we don’t need synchronization in this case because the interrupt handler doesn’t actually run concurrently with the kgetc code.

Using kgetc

Finally, add code to your kernel’s _start function to test kgetc. You might end up duplicating the behavior you had before this implementation task, but you now have the ability to request input at any point while the kernel is running instead of just displaying keypresses.

If you have extra time

If you finish this task early you should begin implementing a kgets function to read a line of characters all at once instead of one character at a time. Unlike the unsafe gets function, kgets will take in an array where it will store characters and a capacity for the array. The kgets function should read characters until it either fills the array or reads a newline character. For convenience, kgets should always add a null terminator to the array so make sure you reserve a space for that character. Here is the signature and documentation for kgets:

/**
 * Read a line of characters from the keyboard. Read characters until the buffer fills or a newline
 * character is read. If input ends with a newline, the newline character is stored in output. The
 * string written to output is always null terminated unless the function fails for some reason.
 *
 * \param output A pointer to the beginning of an array where this function should store characters.
 *               This function will write a null terminator into the output array unless it fails.
 * \param capacity The number of characters that can safely be written to the output array
 *                 including the final null termiantor.
 * \returns The number of characters read, or zero if no characters were read due to an error.
 */
size_t kgets(char* output, size_t capacity);