The I/O subsystem is concerned with the work of providing the virtual interface to the hardware. This is the cod ethat has to deal with the idisyncracies of devices, converting from the OS's logical view to the messy realities of the hardware.
As we've discussed before a general purpose computer system usually has several peripherals connected to an I/O bus. Scheduling transfers across the bus and often moving data is the job of the OS.

Frequently, devices are not directly connected to the bus, but are managed by controllers. This allows multiple devices to share a single bus slot. Bus slots may be a scarce resource. Device controllers are also a frequent location for additional intelligence in the network.
Devices or controllers are frequently controlled by accessing device registers, which pass parameters from CPU to device (or controller). Such device registers are either accessible through special I/O instructions or by memory mapping the device regiaters into the processor's address space.1
The Operating System is also responsible for coordinating the interrupts generated by the devices (and controllers). Generally a priority ordering is used. Some devices can have their handlers interrupted by higher priority devices. Depending on the srevices offered by the hardware, this may result in interrupts being delayed or even lost. If a system blocks rather than delaying interrupts, the system must poll device state when a high-priority interrupt returns in case a low-priority interrupt has been lost.
In simple systems, the CPU must move each data byte to or from the bus susing a LOAD or STORE instruction, as if the data were being moved to memory. This quickly usues up much of the CPU's computational power. In order to allow systems to support high I/O utilization while the CPU is getting useful work done on the users' ehalf, devices are allowed to directly access memory. This direct access of memory by devices (or controllers is called Direct Memory Access, commonly abbreviated DMA).
The CPU is still responsible for scheduling the memory accesses made by DMA devices, but once the program has been established, the CPU has no further involvement until the transfer is complete. Typically DMA devices will issue interrupts on I/O completion.
Because this memory is not being manipulated by the CPU, and therefore addresses may not pass through an MMU, DMA devices often confuse or are confused by virtual memory. It is important to guarantee that memory intended for use by a DMA device is not manipulated by the paging system while the I/O is being performed. Such pages are usually frozen (or pinned) to avoid changes.
In some sense DMA is simply an intermediate step to general purpose programmability on devices and device controllers. Several such smart controllers exist, with features ranging from bit swapping, to digital signal processing, checksum calculations, encryption and compression and general purpose processors. Dealing with that programmibility requires synchronization and care. Moreover, in order for code to be portable, writing an interface to such smart peripherals is often a delicate balancing act between making features available and making the device unrecognizable.
The I/O software of the OS has several goals:
The Interrupt Service Routines (ISRs) are short routines designed to turn the asynchronous events from devices (and controllers) into synchronous ones that the operating system can deal with in time. While an ISR is executing, some set of interrupts is usually blocked, which is a dangerous state of affairs that should be avoided as much as possible.
ISRs generally encode the information about the interrupt into some queue that the OS checks regularly - e.g. on a context switch.
Device drivers are primarily responsible for issuing the low-level commands to the hardware that gets the hardware to do what the OS wants. As a result, much of them is hardware dependent.
Conceptually, perhaps the most important facet of device drivers is the conversion from logical to physical addressing. The OS may be coded in terms of logical block numbers for a file, but it is the device driver that converts such logical addresses to real physical addresses and encodes them in a form that the hardware can understand.
Device drivers may also be responsible for programming smart controllers, multiplexing requests and demultiplexing responses, and measuring and reporting device performance.
This is the part of the OS we've really been talking the most about. This partof the OS provides consistent device naming and interfaces to the users. It enforces protection, and does logical level caching and buffering.
In addition to providing a uniform interface, the uniform interface is sometimes pierced at this level to expose specific hardware features -- CD audio capabilities, for instance.
The device independent code also provides a consistent error mode to users, letting them know what general errors occurred when the device driver couldn't recover.
Even the OS code is relatively rough and ready. User libraries provide simpler interfaces to I/O systems. Good examples are the standard I/O library that provides a simplified interface to the filesystem. printf and fopen are easier to use than write and open. Specifically such systems handle data formatting and buffering.
Beyond that there are user level programs that specificially provide I/O services (daemons). Such programs spool data, or directly provide the services users require.