Basically, input/output software organized in the following four layers:
Interrupt handlers
Device drivers
Device-independent input/output software
User-space input/output software
In every input/output software, each of the above given four layer has a well-defined function to perform and a well-defined interface to the adjacent layers.
Now let's describe briefly, all the four input/output software layers that are listed above.
Interrupt Handlers
Whenever the interrupt occurs, then the interrupt procedure does whatever it has to in order to handle the interrupt.
Device Drivers
Basically, device drivers is a device-specific code just for controlling the input/output device that are attached to the computer system.
Device-Independent Input/Output Software
In some of the input/output software is device specific, and other parts of that input/output software are device-independent.
The exact boundary between the device-independent software and drivers is device dependent, just because of that some functions that could be done in a device-independent way sometime be done in the drivers, for efficiency or any other reasons.
Here are the list of some functions that are done in the device-independent software:
Uniform interfacing for device drivers
Buffering
Error reporting
Allocating and releasing dedicated devices
Providing a device-independent block size
User-Space Input/Output Software
Generally most of the input/output software is within the operating system (OS), and some small part of that input/output software consists of libraries that are linked with the user programs and even whole programs running outside the kernel.
Goals of the I/O Software
A key concept in the design of I/O software is known as device independence. It means that I/O devices should be accessible to programs without specifying the device in advance.
Uniform Naming, simply be a string or an integer and not depend on the device in any way. In UNIX, all disks can be integrated in the file-system hierarchy in arbitrary ways so the user need not be aware of which name corresponds to which device.
Error Handling: If the controller discovers a read error, it should try to correct the error itself if it can. If it cannot, then the device driver should handle it, perhaps by just trying to read the block again. In many cases, error recovery can be done transparently at a low level without the upper levels even knowing about the error.
Synchronous (blocking) and Asynchronous (interrupt-driven) transfers: Most physical I/O is asynchronous, however, some very high-performance applications need to control all the details of the I/O, so some operating systems make asynchronous I/O available to them.
Buffering: Often data that come off a device cannot be stored directly in their final destination.
Sharable and Dedicated devices: Some I/O devices, such as disks, can be used by many users at the same time. No problems are caused by multiple users having open files on the same disk at the same time. Other devices, such as printers, have to be dedicated to a single user until that user is finished. Then another user can have the printer. Introducing dedicated (unshared) devices also introduces a variety of problems, such as deadlocks. Again, the operating system must be able to handle both shared and dedicated devices in a way that avoids problems.