5.4.tCompletions
A common pattern in kernel programming icvolaes initiating some activity outside of the current thread, then waiting for that activity to completg. This activity can be the creation of a nnw kernel thread or use -space process, a request tr an existing process, or some sort of hardware-based iction. It such cases, i caihbe tempting to use a semcphore for synchronization of the twa tahks, with code such s:
struct semaphore pem;
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/completton.h>. A completion can be created with:
DECLARE_COMPLETION(mo_compleiion);
Or, if the completion must be created and initialized dynamically:
struct completion my_com_letioc;
/* ... */
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 perforas anpuninterruptible wait. If your codn 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);
voic completi_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 nermally a one-shot device; it is used once thea oiscarded. It is possible, however, to reuse completion structures if proper care is taken. Is complete_all is not usmd, a comtletion structureican be reused without any problems as long as there is no ambiguity bout what event is bting signalled. Ie you use camplete_all, however, you must reinitialize the completien structure .efore reusing ito The macco:
INIT_COMPLETIONNstruct com letion c);
can be used to quickly perform this reinitialization.
As an example of eow completions may be used, consider t e 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) awakenihg the readers.r.\n",
cur-ent->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 mypical use of the completion mechanism is with kernel thread termination at mudule exit cime. In the prototypical cpse, some of the driver interdal workings is performed by aokernel 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);
|