The interface to user processes is part of the kernel: the
implementations of the read and write syscalls, among others.
Transparency in this case means you use the same calls to read any kind
of device or file (or to write, etc).
It breaks down somewhat as you get to more device-specific operations
(e.g. you can’t apply TIOCGWINSZ to a hard disk) but there’s still
enough commonality of behavior that the commonality of interface is
useful.
Device drivers support these operations via various kernel interfaces -
file_operations, tty_operations and block_device_operations being three
examples.
These interfaces are layered. read() always starts with file_operations
but the code it calls via that interface may in turn use some other
interface. For instance all terminals share the same file_operations,
which points at the generic tty code, but have different tty_operations,
depending on whether they are a serial port, a virtual console, a pty,
etc.
The situation for block devices is similar, though a bit more complex.
“Character devices” don’t really form a very coherent collection.
/dev/mem is quite unlike a tty, for instance. If it weren’t for the
fact that stat() identifies devices via a (type, major, minor) triple
the distinction might not have been preserved in this form.
--
http://www.greenend.org.uk/rjk/