Linux Device Drivers 2nd Edition (779877), страница 68
Текст из файла (страница 68)
If that happens, thingsbecome hairy, and you can choose among different possible solutions. The shortimplementation just loses data; there’s no check for overflow, and if head goesbeyond tail, a whole buffer of data is lost. Some alternative implementations areto drop the last item; to overwrite the buffer tail, as printk does (see “How Messages Get Logged” in Chapter 4); to hold up the producer, as scullpipe does; or toallocate a temporary extra buffer to back up the main buffer. The best solutiondepends on the importance of your data and other situation-specific questions, sowe won’t cover it here.Although the circular buffer appears to solve the problem of concurrent access,there is still the possibility of a race condition when the read function goes tosleep.
This code shows where the problem appears in short:while (short_head == short_tail) {interruptible_sleep_on(&short_queue);/* ... */}When executing this statement, it is possible that new data will arrive after thewhile condition is evaluated as true and befor e the process goes to sleep. Information carried in by the interrupt won’t be read by the process; the process goesto sleep even though head != tail, and it isn’t awakened until the next dataitem arrives.We didn’t implement correct locking for short because the source of short_r ead isincluded in “A Sample Driver” in Chapter 8, and at that point this discussion wasnot worth introducing.
Also, the data involved is not worth the effort.Although the data that short collects is not vital, and the likelihood of getting aninterrupt in the time lapse between two successive instructions is often negligible,sometimes you just can’t take the risk of going to sleep when data is pending. Thisproblem is general enough to deserve special treatment and is delayed to ‘‘Goingto Sleep Without Races’’ later in this chapter, where we’ll discuss it in detail.28022 June 2001 16:39http://openlib.org.uaRace ConditionsIt’s interesting to note that only a producer-and-consumer situation can beaddressed with a circular buffer. A programmer must often deal with more complex data structures to solve the concurrent-access problem.
The producer/consumer situation is actually the simplest class of these problems; other structures,such as linked lists, simply don’t lend themselves to a circular buffer implementation.Using SpinlocksWe have seen spinlocks before, for example, in the scull driver. The discussionthus far has looked only at a few uses of spinlocks; in this section we cover themin rather more detail.A spinlock, remember, works through a shared variable. A function may acquirethe lock by setting the variable to a specific value.
Any other function needing thelock will query it and, seeing that it is not available, will ‘‘spin’’ in a busy-wait loopuntil it is available. Spinlocks thus need to be used with care. A function that holdsa spinlock for too long can waste much time because other CPUs are forced towait.Spinlocks are represented by the type spinlock_t, which, along with the various spinlock functions, is declared in <asm/spinlock.h>. Normally, a spinlockis declared and initialized to the unlocked state with a line like:spinlock_t my_lock = SPIN_LOCK_UNLOCKED;If, instead, it is necessary to initialize a spinlock at runtime, use spin_lock_init:spin_lock_init(&my_lock);There are a number of functions (actually macros) that work with spinlocks:spin_lock(spinlock_t *lock);Acquire the given lock, spinning if necessary until it is available.
On returnfrom spin_lock, the calling function owns the lock.spin_lock_irqsave(spinlock_t *lock, unsigned long flags);This version also acquires the lock; in addition, it disables interrupts on thelocal processor and stores the current interrupt state in flags. Note that all ofthe spinlock primitives are defined as macros, and that the flags argument ispassed directly, not as a pointer.spin_lock_irq(spinlock_t *lock);This function acts like spin_lock_irqsave, except that it does not save the current interrupt state. This version is slightly more efficient thanspin_lock_irqsave, but it should only be used in situations in which you knowthat interrupts will not have already been disabled.28122 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt Handlingspin_lock_bh(spinlock_t *lock);Obtains the given lock and prevents the execution of bottom halves.spin_unlock(spinlock_t *lock);spin_unlock_irqrestore(spinlock_t *lock, unsigned longflags);spin_unlock_irq(spinlock_t *lock);spin_unlock_bh(spinlock_t *lock);These functions are the counterparts of the various locking primitivesdescribed previously.
spin_unlock unlocks the given lock and nothing else.spin_unlock_irqrestor e possibly enables interrupts, depending on the flagsvalue (which should have come from spin_lock_irqsave). spin_unlock_irqenables interrupts unconditionally, and spin_unlock_bh reenables bottom-halfprocessing. In each case, your function should be in possession of the lockbefore calling one of the unlocking primitives, or serious disorder will result.spin_is_locked(spinlock_t *lock);spin_trylock(spinlock_t *lock)spin_unlock_wait(spinlock_t *lock);spin_is_locked queries the state of a spinlock without changing it. It returnsnonzero if the lock is currently busy.
To attempt to acquire a lock withoutwaiting, use spin_trylock, which returns nonzero if the operation failed (thelock was busy). spin_unlock_wait waits until the lock becomes free, but doesnot take possession of it.Many users of spinlocks stick to spin_lock and spin_unlock. If you are using spinlocks in interrupt handlers, however, you must use the IRQ-disabling versions(usually spin_lock_irqsave and spin_unlock_irqsave) in the noninterrupt code. Todo otherwise is to invite a deadlock situation.It is worth considering an example here. Assume that your driver is running in itsread method, and it obtains a lock with spin_lock. While the read method is holding the lock, your device interrupts, and your interrupt handler is executed on thesame processor. If it attempts to use the same lock, it will go into a busy-waitloop, since your read method already holds the lock. But, since the interrupt routine has preempted that method, the lock will never be released and the processordeadlocks, which is probably not what you wanted.This problem can be avoided by using spin_lock_irqsave to disable interrupts onthe local processor while the lock is held.
When in doubt, use the _irqsave versions of the primitives and you will not need to worry about deadlocks. Remember, though, that the flags value from spin_lock_irqsave must not be passed toother functions.Regular spinlocks work well for most situations encountered by device driver writers. In some cases, however, there is a particular pattern of access to critical data28222 June 2001 16:39http://openlib.org.uaRace Conditionsthat is worth treating specially. If you have a situation in which numerous threads(processes, interrupt handlers, bottom-half routines) need to access critical data ina read-only mode, you may be worried about the overhead of using spinlocks.Numerous readers cannot interfere with each other; only a writer can create problems. In such situations, it is far more efficient to allow all readers to access thedata simultaneously.Linux has a different type of spinlock, called a reader-writer spinlock for this case.These locks have a type of rwlock_t and should be initialized toRW_LOCK_UNLOCKED.
Any number of threads can hold the lock for reading at thesame time. When a writer comes along, however, it waits until it can get exclusiveaccess.The functions for working with reader-writer locks are as follows:read_lock(rwlock_t *lock);read_lock_irqsave(rwlock_t *lock, unsigned long flags);read_lock_irq(rwlock_t *lock);read_lock_bh(rwlock_t *lock);function in the same way as regular spinlocks.read_unlock(rwlock_t *lock);read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);read_unlock_irq(rwlock_t *lock);read_unlock_bh(rwlock_t *lock);These are the various ways of releasing a read lock.write_lock(rwlock_t *lock);write_lock_irqsave(rwlock_t *lock, unsigned long flags);write_lock_irq(rwlock_t *lock);write_lock_bh(rwlock_t *lock);Acquire a lock as a writer.write_unlock(rwlock_t *lock);write_unlock_irqrestore(rwlock_t *lock, unsigned longflags);write_unlock_irq(rwlock_t *lock);write_unlock_bh(rwlock_t *lock);Release a lock that was acquired as a writer.If your interrupt handler uses read locks only, then all of your code may acquireread locks with read_lock and not disable interrupts.
Any write locks must beacquired with write_lock_irqsave, however, to avoid deadlocks.It is worth noting that in kernels built for uniprocessor systems, the spinlock functions expand to nothing. They thus have no overhead (other than possiblydisabling interrupts) on those systems, where they are not needed.28322 June 2001 16:39http://openlib.org.uaChapter 9: Interrupt HandlingUsing Lock VariablesThe kernel provides a set of functions that may be used to provide atomic (noninterruptible) access to variables.
Use of these functions can occasionally eliminatethe need for a more complicated locking scheme, when the operations to be performed are very simple. The atomic operations may also be used to provide a sortof ‘‘poor person’s spinlock’’ by manually testing and looping. It is usually better,however, to use spinlocks directly, since they have been optimized for this purpose.The Linux kernel exports two sets of functions to deal with locks: bit operationsand access to the ‘‘atomic’’ data type.Bit operationsIt’s quite common to have single-bit lock variables or to update device status flagsat interrupt time—while a process may be accessing them. The kernel offers a setof functions that modify or test single bits atomically.
Because the whole operationhappens in a single step, no interrupt (or other processor) can interfere.Atomic bit operations are very fast, since they perform the operation using a singlemachine instruction without disabling interrupts whenever the underlying platformcan do that. The functions are architecture dependent and are declared in<asm/bitops.h>. They are guaranteed to be atomic even on SMP computersand are useful to keep coherence across processors.Unfortunately, data typing in these functions is architecture dependent as well.The nr argument is mostly defined as int but is unsigned long for a fewarchitectures.
Here is the list of bit operations as they appear in 2.1.37 and later:void set_bit(nr, void *addr);This function sets bit number nr in the data item pointed to by addr. Thefunction acts on an unsigned long, even though addr is a pointer tovoid.void clear_bit(nr, void *addr);The function clears the specified bit in the unsigned long datum that livesat addr. Its semantics are otherwise the same as set_bit.void change_bit(nr, void *addr);This function toggles the bit.test_bit(nr, void *addr);This function is the only bit operation that doesn’t need to be atomic; it simplyreturns the current value of the bit.28422 June 2001 16:39http://openlib.org.uaRace Conditionsint test_and_set_bit(nr, void *addr);int test_and_clear_bit(nr, void *addr);int test_and_change_bit(nr, void *addr);These functions behave atomically like those listed previously, except thatthey also return the previous value of the bit.When these functions are used to access and modify a shared flag, you don’t haveto do anything except call them.
Using bit operations to manage a lock variablethat controls access to a shared variable, on the other hand, is more complicatedand deserves an example. Most modern code will not use bit operations in thisway, but code like the following still exists in the kernel.A code segment that needs to access a shared data item tries to atomically acquirea lock using either test_and_set_bit or test_and_clear_bit. The usual implementation is shown here; it assumes that the lock lives at bit nr of address addr.