Linux Device Drivers 2nd Edition (779877), страница 36
Текст из файла (страница 36)
The benefit of implementingdevice control this way is that the user can control the device just by writing data,without needing to use (or sometimes write) programs built just for configuringthe device.For example, the setter m program acts on the console (or another terminal) configuration by printing escape sequences. This behavior has the advantage of permitting the remote control of devices. The controlling program can live on adifferent computer than the controlled device, because a simple redirection of thedata stream does the configuration job.
You’re already used to this with ttys, butthe technique is more general.The drawback of controlling by printing is that it adds policy constraints to thedevice; for example, it is viable only if you are sure that the control sequence can’tappear in the data being written to the device during normal operation. This isonly partly true for ttys.
Although a text display is meant to display only ASCIIcharacters, sometimes control characters can slip through in the data being writtenand can thus affect the console setup. This can happen, for example, when youissue gr ep on a binary file; the extracted lines can contain anything, and you oftenend up with the wrong font on your console.*Controlling by write is definitely the way to go for those devices that don’t transferdata but just respond to commands, such as robotic devices.For instance, a driver written for fun by one of your authors moves a camera ontwo axes.
In this driver, the ‘‘device’’ is simply a pair of old stepper motors, whichcan’t really be read from or written to. The concept of ‘‘sending a data stream’’ to astepper motor makes little or no sense. In this case, the driver interprets what isbeing written as ASCII commands and converts the requests to sequences ofimpulses that manipulate the stepper motors. The idea is similar, somewhat, to theAT commands you send to the modem in order to set up communication, themain difference being that the serial port used to communicate with the modemmust transfer real data as well. The advantage of direct device control is that youcan use cat to move the camera without writing and compiling special code toissue the ioctl calls.* CTRL-N sets the alternate font, which is made up of graphic symbols and thus isn’t afriendly font for typing input to your shell; if you encounter this problem, echo a CTRL-Ocharacter to restore the primary font.14022 June 2001 16:36http://openlib.org.uaBlocking I/OWhen writing command-oriented drivers, there’s no reason to implement the ioctlmethod.
An additional command in the interpreter is easier to implement and use.Sometimes, though, you might choose to act the other way around: instead ofmaking write into an interpreter and avoiding ioctl, you might choose to avoidwrite altogether and use ioctl commands exclusively, while accompanying thedriver with a specific command-line tool to send those commands to the driver.This approach moves the complexity from kernel space to user space, where itmay be easier to deal with, and helps keep the driver small while denying use ofsimple cat or echo commands.Blocking I/OOne problem that might arise with read is what to do when there’s no data yet,but we’re not at end-of-file.The default answer is ‘‘go to sleep waiting for data.’’ This section shows how aprocess is put to sleep, how it is awakened, and how an application can ask ifthere is data without just blindly issuing a read call and blocking.
We then applythe same concepts to write.As usual, before we show actual code, we’ll explain a few concepts.Going to Sleep and AwakeningWhenever a process must wait for an event (such as the arrival of data or the termination of a process), it should go to sleep. Sleeping causes the process to suspend execution, freeing the processor for other uses.
At some future time, whenthe event being waited for occurs, the process will be woken up and will continuewith its job. This section discusses the 2.4 machinery for putting a process to sleepand waking it up. Earlier versions are discussed in “Backward Compatibility” laterin this chapter.There are several ways of handling sleeping and waking up in Linux, each suitedto different needs. All, however, work with the same basic data type, a wait queue(wait_queue_head_t). A wait queue is exactly that—a queue of processes thatare waiting for an event. Wait queues are declared and initialized as follows:wait_queue_head_t my_queue;init_waitqueue_head (&my_queue);When a wait queue is declared statically (i.e., not as an automatic variable of aprocedure or as part of a dynamically-allocated data structure), it is also possibleto initialize the queue at compile time:DECLARE_WAIT_QUEUE_HEAD (my_queue);14122 June 2001 16:36http://openlib.org.uaChapter 5: Enhanced Char Driver OperationsIt is a common mistake to neglect to initialize a wait queue (especially since earlierversions of the kernel did not require this initialization); if you forget, the resultswill usually not be what you intended.Once the wait queue is declared and initialized, a process may use it to go tosleep.
Sleeping is accomplished by calling one of the variants of sleep_on, depending on how deep a sleep is called for.sleep_on(wait_queue_head_t *queue);Puts the process to sleep on this queue. sleep_on has the disadvantage of notbeing interruptible; as a result, the process can end up being stuck (and unkillable) if the event it’s waiting for never happens.interruptible_sleep_on(wait_queue_head_t *queue);The interruptible variant works just like sleep_on, except that the sleep can beinterrupted by a signal. This is the form that device driver writers have beenusing for a long time, before wait_event_interruptible (described later)appeared.sleep_on_timeout(wait_queue_head_t *queue, long timeout);interruptible_sleep_on_timeout(wait_queue_head_t *queue,long timeout);These two functions behave like the previous two, with the exception that thesleep will last no longer than the given timeout period.
The timeout is specified in ‘‘jiffies,’’ which are covered in Chapter 6.void wait_event(wait_queue_head_t queue, int condition);int wait_event_interruptible(wait_queue_head_t queue, intcondition);These macros are the preferred way to sleep on an event. They combine waiting for an event and testing for its arrival in a way that avoids race conditions.They will sleep until the condition, which may be any boolean C expression,evaluates true. The macros expand to a while loop, and the condition isreevaluated over time—the behavior is different from that of a function call ora simple macro, where the arguments are evaluated only at call time. The latter macro is implemented as an expression that evaluates to 0 in case of success and -ERESTARTSYS if the loop is interrupted by a signal.It is worth repeating that driver writers should almost always use the interruptibleinstances of these functions/macros.
The noninterruptible version exists for thesmall number of situations in which signals cannot be dealt with, for example,when waiting for a data page to be retrieved from swap space. Most drivers do notpresent such special situations.Of course, sleeping is only half of the problem; something, somewhere will haveto wake the process up again. When a device driver sleeps directly, there is14222 June 2001 16:36http://openlib.org.uaBlocking I/Ousually code in another part of the driver that performs the wakeup, once itknows that the event has occurred.
Typically a driver will wake up sleepers in itsinterrupt handler once new data has arrived. Other scenarios are possible, however.Just as there is more than one way to sleep, so there is also more than one way towake up. The high-level functions provided by the kernel to wake up processesare as follows:wake_up(wait_queue_head_t *queue);This function will wake up all processes that are waiting on this event queue.wake_up_interruptible(wait_queue_head_t *queue);wake_up_interruptible wakes up only the processes that are in interruptiblesleeps.
Any process that sleeps on the wait queue using a noninterruptiblefunction or macro will continue to sleep.wake_up_sync(wait_queue_head_t *queue);wake_up_interruptible_sync(wait_queue_head_t *queue);Normally, a wake_up call can cause an immediate reschedule to happen,meaning that other processes might run before wake_up returns. The “synchronous” variants instead make any awakened processes runnable, but donot reschedule the CPU. This is used to avoid rescheduling when the currentprocess is known to be going to sleep, thus forcing a reschedule anyway.Note that awakened processes could run immediately on a different processor,so these functions should not be expected to provide mutual exclusion.If your driver is using interruptible_sleep_on, there is little difference betweenwake_up and wake_up_interruptible.
Calling the latter is a common convention,however, to preserve consistency between the two calls.As an example of wait queue usage, imagine you want to put a process to sleepwhen it reads your device and awaken it when someone else writes to the device.The following code does just that:DECLARE_WAIT_QUEUE_HEAD(wq);ssize_t sleepy_read (struct file *filp, char *buf, size_t count,loff_t *pos){printk(KERN_DEBUG "process %i (%s) going to sleep\n",current->pid, current->comm);interruptible_sleep_on(&wq);printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);return 0; /* EOF */}14322 June 2001 16:36http://openlib.org.uaChapter 5: Enhanced Char Driver Operationsssize_t sleepy_write (struct file *filp, const char *buf, size_t count,loff_t *pos){printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",current->pid, current->comm);wake_up_interruptible(&wq);return count; /* succeed, to avoid retrial */}The code for this device is available as sleepy in the example programs and canbe tested using cat and input/output redirection, as usual.An important thing to remember with wait queues is that being woken up doesnot guarantee that the event you were waiting for has occurred; a process can bewoken for other reasons, mainly because it received a signal.