As an example of how multiprogramming handles I/O requests we will study how to wait for the user to input data from the keyboard.
Key-presses are asynchronous and external
A user pressing a key on a keyboard is not an internal event, nor is a key-press synchronous, a keypress can happen at any time independent of the executing program.
Normally the operating system is responsible for handling user input and output. When a user program requests service from the operating system this is implemented as system calls.
System calls forms an interface between user programs and the operating system.
We will start by to study how to implement a system call that lets the user input a single character from the keyboard. Next we will study how to implement a system call that lets the user input a string from the keyboard.
Let’s sketch the design for a system call similar to the C
getc that allows a program to read a single single character typed by a human user on
The below figure shows an example where job 1 calls the custom
call and job 2 executes while job 1 waits for the user to press a key on
In the above figure important events are marked with a number inside a yellow circle.
getcsystem call to read a character from the keyboard.
getcsystem call to complete.
The result of the
getcsystem call can not be obtained immediately, the kernel
must wait for the user to press a key on the keyboard. When handling the
system call, the kernel blocks the caller and make another job run until the
user presses a key on the keyboard.
We have already studied how interrupts can be used to wait for a single keypress from a human user. How can we design a system call for inputting a string?
Where should the input buffer be allocated? In user space or in kernel space?
To enforce memory safety user programs are not allowed to access (read or write) kernel data but the kernel is allowed to read and write data in user space.
The kernel must know the address (pointer) to the input buffer in user space.
The kernel must know the size of the input buffer and decide what to do when the buffer is full.
To better understand the details of how the read string system call can be implemented we will study a small example with two jobs. In this example, job 1 allocates a 3 element input buffer and uses a system call to request the operating system to wait (using interrupts) for the user to fill this buffer with two characters and terminate the buffer with null. Until the input buffer is full the operating system (kernel) let job 2 execute between keyboard interrupts.
A common pattern for system calls and other library functions is for the caller to allocate a struct or array in user space. The caller pass a pointer to the struct or array as an argument to the system call or library function call. The system call or library function writes result data to the struct or array in user space using the provided pointer.
An alternative to the above patterns would be to return data (structure or array elements) on the stack. So why are pointers (call by reference) used instead of returning on the stack?