Linux Device Drivers (3rd Edition) [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Linux Device Drivers (3rd Edition) [Electronic resources] - نسخه متنی

Jonathan Corbet, Greg Kroah-Hartman, Alessandro Rubini

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








5.2. Concurrency and Its Management


In a modern Linux system,


there
are numerous sources of concurrency and, therefore, possible race
conditions. Multiple user-space processes are running, and they can
access your code in surprising combinations of ways. SMP systems can
be executing your code simultaneously on different processors. Kernel
code is preemptible; your driver's code can lose the
processor at any time, and the process that replaces it could also be
running in your driver. Device interrupts are asynchronous events
that can cause concurrent execution of your code. The kernel also
provides various mechanisms for delayed code execution, such as
workqueues, tasklets, and timers, which can cause your code to run at
any time in ways unrelated to what the current process is doing. In
the modern, hot-pluggable world, your device could simply disappear
while you are in the middle of working with it.

Avoidance of race conditions can be an intimidating task. In a world
where anything can happen at any time, how does a driver programmer
avoid the creation of absolute chaos? As it turns out, most race
conditions can be avoided through some thought, the
kernel's concurrency control primitives, and the
application of a few basic principles. We'll start
with the principles first, then get into the specifics of how to
apply them.

Race conditions come about as a result of shared access to resources.
When two threads of execution[1] have a reason to work with the same data
structures (or hardware resources), the potential for mixups always
exists. So the first rule of thumb to keep in mind as you design your
driver is to avoid shared resources whenever possible. If there is no
concurrent access, there can be no race conditions. So
carefully-written kernel code should have a minimum of
sharing.
The most obvious application of this idea is to avoid the use of
global variables. If you put a resource in a place where more than
one thread of execution can find it, there should be a strong reason
for doing so.

[1] For the purposes of
this chapter, a "thread" of
execution is any context that is running code. Each process is
clearly a thread of execution, but so is an interrupt handler or
other code running in response to an asynchronous kernel
event.


The fact of the matter is, however, that such sharing is often
required. Hardware resources are, by their nature, shared, and
software resources also must often be available to more than one
thread. Bear in mind as well that global variables are far from the
only way to share data; any time your code passes a pointer to some
other part of the kernel, it is potentially creating a new sharing
situation. Sharing is a fact of life.

Here is the hard rule of resource sharing: any time that a hardware
or software resource is shared beyond a single thread of execution,
and the possibility exists that one thread could encounter an
inconsistent view of that resource, you must explicitly manage access
to that resource. In the scull example above,
process B's view of the situation is inconsistent;
unaware that process A has already allocated memory for the (shared)
device, it performs its own allocation and overwrites
A's work. In this case, we must control access to
the scull data structure. We need to arrange
things so that the code either sees memory that has been allocated or
knows that no memory has been or will be
allocated by anybody else. The usual technique for



access
management is called locking or
mutual exclusionmaking sure that only
one thread of execution can manipulate a shared resource at any time.
Much of the rest of this chapter will be devoted to locking.

First, however, we must briefly consider one other important rule.
When kernel code creates an object that will be shared with any other
part of the kernel, that object must continue to exist (and function
properly) until it is known that no outside references to it exist.
The instant that scull makes its devices
available, it must be prepared to handle requests on those devices.
And scull must continue to be able to handle
requests on its devices until it knows that no reference (such as
open user-space files) to those devices exists. Two requirements

come
out of this rule: no object can be made available to the kernel until
it is in a state where it can function properly, and references to
such objects must be tracked. In most cases, you'll
find that the kernel handles reference counting for you, but there
are always exceptions.

Following the above rules requires planning and careful attention to
detail. It is easy to be surprised by concurrent access to resources
you hadn't realized were shared. With some effort,
however, most race conditions can be headed
off
before they bite youor your users.


    / 202