If you have never thought about this kind of thing before, you may be thinking, "Gee, my printer is 250 CPS. That means there can't be more than 1/250th of a second between characters. Not enough time to do anything useful." But of course it is enough time. In fact, to the CPU, it's a very long time. The 486/33 that I am writing this on will process 100,000 instructions or more in that 250th of a second.
Even if you have thought about it, you still might think "So what's the big deal? Send a character, let the CPU do something else until the interrupt, and loop back. Simple.". Well, the driver could do that, and it would work, but it wouldn't be a real good way to do it. Consider that the data that's going to be printed is coming from Somewhere. The driver could just read a character of that data and shove it out the port as described. But that leaves the original source of those characters all tied up until the printer has printed every last byte. Wouldn't it be better to copy Somewhere into a local buffer and print it out from that, thereby freeing the original source?
Of course it would. So the driver for this printer allocates a local buffer (which is a CLIST, by the way- in case you've seen that term before and wondered what it was) and copies the Somewhere bytes into that. The clist buffer is only so big, of course, so the driver has to keep trying to print some characters while it's filling the clist, and if Somewhere is big enough it may end up tying up the process that's trying to get that stuff printed, but the general effect is that it will get released sooner than it would otherwise.
When the driver can't do anything else right now (the clist is full and the printer isn't ready for another character), it goes to sleep. That lets the kernel give some time to something else that needs attention. But, until the clist is full, the driver should keep on transferring characters.
So when that interrupt that says the printer is ready again does come, our driver program could be doing almost anything. It could be filling the clist, or it could be sleeping because the clist is full. The interrupt says "I'm ready now", so it's time to send another character. This is where it gets complicated.
It's a bit beyond the scope if this article to show exactly how this can happen, but if the writer of this device driver is not very careful, the driver can end up sleeping for ever. The problem is trying to do two things: set a flag that says " I'm sleeping" and then putting itself to sleep. An interrupt that sneaks in between those two instructions is what will do the damage: it will clear the flag that says "I'm sleeping", and then issue a "wakeup" call to somebody that's already awake and working. No harm done there, but then the interrupted process gets control again, does go to sleep, but nobody knows that because the flag has been cleared!
If that's totally confusing, don't feel bad. People who write this kind of code get confused too. The important thing to grasp is that the interrupt can come at any time, and the driver has to be prepared to deal with it. The way it does this is by saying "Don't send me any more interrupts for a while". Why not just say that until the driver is nicely prepared to handle them again? Well, there are two problems with that. One is that doing so may prevent other devices from issuing interrupts, which tends to slow thing down more than a bit.
The second problem doesn't apply to the printer we are considering here, but what about a serial port that is receiving data which the driver is responsible for sending along somewhere else? That serial port usually has a buffer of it's own, but it's pretty small. When it wants attention ("take some of these characters from me!") it pretty much wants it NOW. The driver can't go off on an extended vacation and come back to work on this side of things when it feels up to the task. If a driver tried that, the serial port would overflow and data would be lost.
The driver also has to be prepared to handle both extra and totally missing interrupts. An interrupt can arrive out of nowhere for no reason whatsoever. Worse, the device might never issue any interrupts at all. That apparently happens frequently with parallel printer ports. To handle it, the driver writer has to make some guesses about how long is a safe enough time to wait before proceeding.
And that brings us to the other difficulty in writing driver code: the finicky hardware itself. As a general fuzzy sort of thing, most hardware works by the CPU writing something Somewhere and perhaps reading a status from Somewhere or Somewhere else.
All very nice, but the particular piece of hardware can be extremely fussy about how you write it and how fast you attempt to read back that status. Often you have to write to several different places and then send a "strobe" to say "go ahead, work on what I just gave you".
Let's take that parallel printer port as an example. One book suggests that on initialization, the printer port should send back a status of hexadecimal "e0". My printer port reports back "e2". This kind of unexpected behaviour is what gives device driver writers headaches.
But, you probably never will have to write a device driver. Whatever equipment you are adding undoubtedly comes with it's own driver and it's probably a lot better than you or I could write anyway.
Next: SCO Device Drivers Part III
© 1998 Anthony Lawrence. All rights reserved.
Got something to add? Send me email.
More Articles by Tony Lawrence © 2012-07-14 Tony Lawrence