Semaphores

Until early versions of 2.6 kernel releases, semaphores were the primary form of sleep locks. A typical semaphore implementation comprises a counter, wait queue, and set of operations that can increment/decrement the counter atomically.

When a semaphore is used to protect a shared resource, its counter is initialized to a number greater than zero, which is considered to be unlocked state. A task seeking access to a shared resource begins by invoking the decrement operation on the semaphore. This call checks the semaphore counter; if it is found to be greater than zero, the counter is decremented and the function returns success. However, if the counter is found to be zero, the decrement operation puts the caller task to sleep until the counter is found to have increased to a number greater than zero.

This simple design offers great flexibility, which allows adaptability and application of semaphores for different situations. For instance, for cases where a resource needs to be accessible to a specific number of tasks at any point in time, the semaphore count can be initialized to the number of tasks that require access, say 10, which allows a maximum of 10 tasks access to shared resource at any time. For yet other cases, such as a number of tasks that require mutually exclusive access to a shared resource, the semaphore count can be initialized to 1, resulting in a maximum of one task to access the resource at any given point in time.

Semaphore structure and its interface operations are declared in the kernel header <include/linux/semaphore.h>:

struct semaphore {
        raw_spinlock_t     lock;
        unsigned int       count;
        struct list_head   wait_list;
};

Spinlock (the lock field) serves as a protection for count, that is, semaphore operations (inc/dec) are programmed to acquire lock before manipulating count. wait_list is used to queue tasks to sleep while they wait for the semaphore count to increase beyond zero.

Semaphores can be declared and initialized to 1 through a macro: DEFINE_SEMAPHORE(s).

A semaphore can also be initialized dynamically to any positive number through the following:

void sema_init(struct semaphore *sem, int val)

Following is a list of operation interfaces with a brief description of each. Routines with naming convention down_xxx() attempt to decrement the semaphore, and are possible blocking calls (except down_trylock()), while routine up() increments the semaphore and always succeeds:

/**
 * down_interruptible - acquire the semaphore unless interrupted
 * @sem: the semaphore to be acquired
 *
 * Attempts to acquire the semaphore.  If no more tasks are allowed to
 * acquire the semaphore, calling this function will put the task to sleep.
 * If the sleep is interrupted by a signal, this function will return -EINTR.
 * If the semaphore is successfully acquired, this function returns 0.
 */
   int down_interruptible(struct semaphore *sem);

/** * down_killable - acquire the semaphore unless killed * @sem: the semaphore to be acquired * * Attempts to acquire the semaphore. If no more tasks are allowed to * acquire the semaphore, calling this function will put the task to sleep. * If the sleep is interrupted by a fatal signal, this function will return * -EINTR. If the semaphore is successfully acquired, this function returns * 0. */ int down_killable(struct semaphore *sem);

/** * down_trylock - try to acquire the semaphore, without waiting * @sem: the semaphore to be acquired * * Try to acquire the semaphore atomically. Returns 0 if the semaphore has * been acquired successfully or 1 if it it cannot be acquired. * */ int down_trylock(struct semaphore *sem);

/** * down_timeout - acquire the semaphore within a specified time * @sem: the semaphore to be acquired * @timeout: how long to wait before failing * * Attempts to acquire the semaphore. If no more tasks are allowed to * acquire the semaphore, calling this function will put the task to sleep. * If the semaphore is not released within the specified number of jiffies, * this function returns -ETIME. It returns 0 if the semaphore was acquired. */ int down_timeout(struct semaphore *sem, long timeout);

/** * up - release the semaphore * @sem: the semaphore to release * * Release the semaphore. Unlike mutexes, up() may be called from any * context and even by tasks which have never called down(). */ void up(struct semaphore *sem);

Unlike mutex implementation, semaphore operations do not support debug checks or validations; this constraint is due to their inherent generic design which allows them to be used as exclusion locks, event notification counters, and so on. Ever since mutexes made their way into the kernel (2.6.16), semaphores are no longer the preferred choice for exclusion, and the use of semaphores as locks has considerably reduced, and for other purposes, the kernel has alternate interfaces. Most of the kernel code using semaphores has be converted into mutexes with a few minor exceptions. Yet semaphores still exist and are likely to remain at least until all of the kernel code using them is converted to mutex or other suitable interfaces.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.72.224