Where there's an operating system involved, programs don't talk to device drivers, at least not directly. Programs talk to abstractions that, unbeknownst to them, eventually end up talking to device drivers by way of one or more layers of abstraction.
I'm going to skip the complexities of modern operating systems and use CP/M, a microcomputer operating system developed 45 years ago, as an example. CP/M was a layer cake with three layers:
Program. The top layer is a program that does something useful (word processing, playing Space Invaders) by doing computation and I/O. Let's say that at some point the program wants to display the letter 'A' for the user to see. CP/M provides an abstraction known as the console, which is where the user interacting with the program should be looking. The conventional way to do send a character there is with a few assembly instructions:
LD C,2 ; Load 2 into register C
LD E,65 ; Load the ASCII code for 'A' into register E
CALL 5 ; Call CP/M's routine for getting things done
(If you're not familiar with them, registers can be thought of as variables that live in the processor.) We'll get to what the magic numbers
5 are all about in a minute. The takeaway here is that all the program knows is that there's a console and there's a way to write to it. It doesn't know or care about anything beyond that. This is the first of two abstractions that CP/M uses for I/O.
BDOS. The address
5 the program called is the entry point for the next layer, the Basic Disk Operating System or BDOS. The BDOS provides a whole raft of numbered functions that are like ordering by number from a restaurant menu. You tell it you'd like console output by loading the
C register with the function number (
2 for console output) and the
E register with the character to be sent. Console output is a very simple operation, and the BDOS doesn't really have to do much with it other than call the next layer.
BIOS. The BIOS, or Basic Input/Output System is the layer where all of the hardware-specific code lives. In modern systems, this would be considered a set of device drivers. Like the BDOS, the BIOS provides calls for a standard set of very primitive operations that the BDOS uses to do its business. One of those operations is called
CONOUT, which takes care of getting the character the program asked to write two layers above through to whatever hardware does it. (Unlike PCs, things weren't homogeneous back then. Everybody's system had different ways of making it happen.) Console output is a simple pass-through for the BDOS, but doing something more complex like creating a file on a disk might require many BIOS calls to manipulate the media. Again, because the BIOS has a standard, abstract interface, the BDOS always knows how to get what it wants and doesn't care how the BIOS does it.
You're probably wondering why there are two abstractions (program-to-BDOS and BDOS-to-BIOS) instead of just one. The answer is that CP/M and its BDOS could be provided in binary form to computer manufacturers, they'd write a custom BIOS with device drivers for their hardware, bolt the two together and ship it as the OS for their systems. This was a big deal because the BDOS was maintained by one organization and therefore always was a known quantity to user programs, making it possible to run the same applications on a very wide (for the time) variety of hardware. This is why operating systems exist and we don't just write programs that twiddle the hardware directly.
Everything I've described here applies to modern operating systems, too. Unix, for example, abstracts everything as files. It gives programs the same set of system calls (
close(), etc.) to communicate whether it's a disk drive or serial port. The set of decisions and abstractions is much more complex, but it still eventually boils down to picking out what device driver code at the bottom layer needs to be run to make the operation happen.