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

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

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

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

Jonathan Corbet, Greg Kroah-Hartman, Alessandro Rubini

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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








5.4. Completions


A common pattern in


kernel programming involves initiating some activity outside of the
current thread, then waiting for that activity to complete. This
activity can be the creation of a new kernel thread or user-space
process, a request to an existing process, or some sort of
hardware-based action. It such cases, it can be tempting to use a
semaphore for
synchronization
of the two tasks, with code such as:

struct semaphore sem;
init_MUTEX_LOCKED(&sem);
start_external_task(&sem);
down(&sem);

The external task can then call up(&sem) when
its work is done.

As is turns out, semaphores are not the best tool to use in this
situation. In normal use, code attempting to lock a semaphore finds
that semaphore available almost all the time; if there is significant
contention for the semaphore, performance suffers and the locking
scheme needs to be reviewed. So semaphores have been heavily
optimized for the "available" case.
When used to communicate task completion in the way shown above,
however, the thread calling down will almost
always have to wait; performance will suffer accordingly. Semaphores
can also be subject to a (difficult) race condition when used in this
way if they are declared as automatic variables. In some cases, the
semaphore could vanish before the process calling
up is finished with it.

These concerns inspired the addition of the
"completion" interface in the 2.4.7
kernel. Completions are a lightweight mechanism with one task:
allowing one thread to tell another that the job is done. To use
completions, your code must include
<linux/completion.h>. A completion can be
created with:

DECLARE_COMPLETION(my_completion);

Or, if the completion must be
created and initialized dynamically:

struct completion my_completion;
/* ... */
init_completion(&my_completion);

Waiting for the completion is a simple matter of calling:

void wait_for_completion(struct completion *c);

Note that this function performs an uninterruptible wait. If your
code calls wait_for_completion and nobody ever
completes the task, the result will be an unkillable
process.[2]

[2] As of this writing, patches adding
interruptible versions were in circulation but had not been merged
into the mainline.


On the other side, the actual completion event may be signalled by
calling one of the following:

void complete(struct completion *c);
void complete_all(struct completion *c);

The two functions behave differently if more than one thread is
waiting for the same completion event. complete
wakes up only one of the waiting threads while
complete_all allows all of them to proceed. In
most cases, there is only one waiter, and the two functions will
produce an identical result.

A completion is normally a one-shot device; it is used once then
discarded. It is possible, however, to reuse completion structures if
proper care is taken. If complete_all is not
used, a completion structure can be reused without any problems as
long as there is no ambiguity about what event is being signalled. If
you use complete_all, however, you must
reinitialize the completion structure before reusing it.
The
macro:

INIT_COMPLETION(struct completion c);

can be used to quickly perform this reinitialization.

As an example of how completions may be used, consider the
complete

module, which is included in the example source. This module defines
a device with simple semantics: any process trying to read from the
device will wait (using wait_for_completion)
until some other process writes to the device. The code which
implements this behavior is:

DECLARE_COMPLETION(comp);
ssize_t complete_read (struct file *filp, char _ _user *buf, size_t count, loff_t *pos)
{
printk(KERN_DEBUG "process %i (%s) going to sleep\n",
current->pid, current->comm);
wait_for_completion(&comp);
printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
return 0; /* EOF */
}
ssize_t complete_write (struct file *filp, const char _ _user *buf, size_t count,
loff_t *pos)
{
printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",
current->pid, current->comm);
complete(&comp);
return count; /* succeed, to avoid retrial */
}

It is possible to have multiple processes
"reading" from this device at the
same time. Each write to the device will cause exactly one read
operation to complete, but there is no way to know which one it will
be.

A typical use of the completion mechanism is with kernel thread
termination at module exit time. In the prototypical case, some of
the driver internal workings is performed by a kernel thread in a
while (1) loop. When the module
is ready to be cleaned up, the exit function tells the thread to exit
and then waits for completion. To this aim, the kernel includes a
specific function to be used by the thread:

void complete_and_exit(struct completion *c, long retval);


    / 202