The key thing (IMHO) to understand about device drivers in contemporary, multitasking/virtual memory operating systems (like Linux or Windows) is that, unlike in DOS, you (the .exe program) can no longer talk directly to the hardware.
In DOS, your .exe could reach right down and start poking memory addresses and reading I/O ports. In Linux and Windows, on the other hand, there is always (at *least* one!) level of indirection between you and the hardware. In both time, and space.
By analogy, it's like the difference between picking up the phone and calling a friend (or driving over to his house and talking to him), and writing a letter, dropping it in a mail box, and having the letter delivered to your friend.
In this analogy, DOS is the "telephone call" and Windows/Linux drivers are "mail the letter". Your program interacts with a driver (either by "standard" calls such as open, read or write; or by "extension" calls such as you might invent with your own ioctl); the driver interacts with the hardware (either asynchronously - when interrupted, or by polling, when your driver task happens to be scheduled by the kernel). You"post a letter" to the kernel, which delivers it to your driver. Your driver "posts a reply" back to you - again, delivered by the kernel.
My two favorite books on Linux device drivers are:
Linux Kernel Development, Robert Love:
http://www.bookpool.com/sm/0672327201
Linux Device Drivers, 3rd Edition (O'Reilly):
http://www.bookpool.com/sm/0596005903
Unfortunately, I'm not sure that either one really addresses your conceptual question.
I did a bit of googling and, despite the author's apologetic introduction, this gives a good "one page overview" of device drivers in general (and Linux device drivers in particular) that might help answer your question:
http://users.evitech.fi/~tk/rtos/wri..._device_d.html
Hopefully this response was at least *somewhat* helpful to you.
Your .. PSM