sleeping tasks

Let us imagine some processes running on an offshore Web server hosting dubious images. Each process is serving a different user, and both users request a copy of the same file. The file is initially on disk, and it will take several milliseconds for the hardware to read the bits off the medium, decode them, and present them to the device driver. What do the two processes do in the mean time?

The system call 'read()' could periodically check to see whether the filesystem has loaded in the first chunk of the file:

read(int fileID, char *userBuffer, int bufferLength){
    while (dataNotAvailable()) /* do nothing */;
    // Copy bytes to process's buffer, from system buffer.
    memcpy(userBuffer, device->buffer, bufferLength);
}
The system call, and hence the process, sits in a tight loop, continually checking whether the first few bytes of the file has been read. This is not a good solution. It means that a process is not doing any useful work, but it is consuming a big chunk of the CPU resources. If two processes are doing the same thing, the situation is worse. It is likely that the very fact of the two processes waiting on the file actually slow down the speed of the disk transfer on which they are waiting.

Much better solution if the processes waiting on a file (or any other process) put themselves to sleep, to be awakened when the data is available. This is known as a blocking file read---it may take some time for the call to complete and in the mean time, the process is blocked. Typically the device would keep a list of waiting processes. (In Linux, a flavour of UNIX, this is known as a 'wait queue'.)

The system call would now look like:

read(int fileID, char *userBuffer, int bufferLength){
    if (dataNotAvailable()){
        linkProcessToWaitQueue(device->queue, thisProcess);
        thisProcess->status = BLOCKED;
        // Call the scheduler and dispatcher to give up the CPU.
        scheduler();
    }
    // By the time we get here, data is available...
    // ... and thisProcess->status is once again RUNNING.
    // Copy bytes to process's buffer, from system buffer.
    memcpy(userBuffer, device->buffer, bufferLength);
}

The call, instead of 'busy-waiting', puts itself on a wait queue, marks itself as 'BLOCKED', and calls the scheduler, which picks another process to dispatch.

When the data has at last been read, the device-driver process removes all the processes from its wait queue, sets all of their states to 'RUNNABLE', and calls the scheduler and dispatcher again. Eventually, one of the RUNNABLE tasks gets the processor, and is able to continue from where it left off in the system call---and in this example, return the bytes to the user code which called it.


last updated 6 February 1997