Concurrency and Race Conditions

<asm/semaphore.h>

create a semaphore directly, then set it up with sema_init:
void sema_init(struct semaphore *sem, int val);

where val is the initial value to assign to a semaphore.

Usually, however, semaphores are used in a mutex mode

DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);

Here, the result is a semaphore variable (called name) that is initialized to 1 (with
DECLARE_MUTEX) or 0 (with DECLARE_MUTEX_LOCKED).

If the mutex must be initialized at runtime (which is the case if it is allocated dynamically,
for example), use one of the following:
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);

void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_trylock(struct semaphore *sem);

down decrements the value of the semaphore and waits as long as need be. down_
interruptible does the same, but the operation is interruptible. The interruptible version
is almost always the one you will want; it allows a user-space process that is
waiting on a semaphore to be interrupted by the user. You do not, as a general rule,
want to use noninterruptible operations unless there truly is no alternative. Noninterruptible
operations are a good way to create unkillable processes (the dreaded
“D state” seen in ps), and annoy your users. Using down_interruptible requires some
extra care, however, if the operation is interrupted, the function returns a nonzero
value, and the caller does not hold the semaphore. Proper use of down_interruptible
requires always checking the return value and responding accordingly.
The final version (down_trylock) never sleeps; if the semaphore is not available at the
time of the call, down_trylock returns immediately with a nonzero return value.

void up(struct semaphore *sem);

The Linux kernel provides a special type of semaphore called a rwsem (or “reader/writer
semaphore”) for this situation. The use of rwsems in drivers is relatively rare, but they
are occasionally useful.

<linux/rwsem.h>

void init_rwsem(struct rw_semaphore *sem);

void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);

A call to down_read provides read-only access to the protected resources, possibly
concurrently with other readers. Note that down_read may put the calling process into an uninterruptible sleep. down_read_trylock will not wait if read access is
unavailable; it returns nonzero if access was granted, 0 otherwise. Note that the convention
for down_read_trylock differs from that of most kernel functions, where success
is indicated by a return value of 0. A rwsem obtained with down_read must
eventually be freed with up_read.

void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);

down_write, down_write_trylock, and up_write all behave just like their reader counterparts,
except, of course, that they provide write access. If you have a situation
where a writer lock is needed for a quick change, followed by a longer period of readonly
access, you can use downgrade_write to allow other readers in once you have
finished making changes.

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.

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.*

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);

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.

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 */
}

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);

<linux/spinlock.h>.

spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
or at runtime with:
void spin_lock_init(spinlock_t *lock);

void spin_lock(spinlock_t *lock);

void spin_unlock(spinlock_t *lock);

void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock)

void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);

int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);

<linux/spinlock.h>

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* Dynamic way */

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);

void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);

atomic_t, defined in <asm/atomic.h>.

void atomic_set(atomic_t *v, int i);
atomic_t v = ATOMIC_INIT(0);
Set the atomic variable v to the integer value i. You can also initialize atomic values
at compile time with the ATOMIC_INIT macro.
int atomic_read(atomic_t *v);
Return the current value of v.
void atomic_add(int i, atomic_t *v);
Add i to the atomic variable pointed to by v. The return value is void, because
there is an extra cost to returning the new value, and most of the time there’s no
need to know it.
void atomic_sub(int i, atomic_t *v);
Subtract i from *v.
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
Increment or decrement an atomic variable.
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
Perform the specified operation and test the result; if, after the operation, the
atomic value is 0, then the return value is true; otherwise, it is false. Note that
there is no atomic_add_and_test.
int atomic_add_negative(int i, atomic_t *v);
Add the integer variable i to v. The return value is true if the result is negative,
false otherwise.
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
Behave just like atomic_add and friends, with the exception that they return the
new value of the atomic variable to the caller.

atomic_sub(amount, &first_atomic);
atomic_add(amount, &second_atomic);

The available bit operations are:
void set_bit(nr, void *addr);
Sets bit number nr in the data item pointed to by addr.
void clear_bit(nr, void *addr);
Clears the specified bit in the unsigned long datum that lives at addr. Its semantics
are otherwise the same as set_bit.
void change_bit(nr, void *addr);
Toggles the bit.

test_bit(nr, void *addr);
This function is the only bit operation that doesn’t need to be atomic; it simply
returns the current value of the bit.
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
Behave atomically like those listed previously, except that they also return the
previous value of the bit.

/* try to set lock */
while (test_and_set_bit(nr, addr) != 0)
wait_for_a_while( );
/* do your work */
/* release lock, and check... */
if (test_and_clear_bit(nr, addr) = = 0)
something_went_wrong( ); /* already released: error */

Seqlocks are defined in <linux/seqlock.h>. There are the two usual methods for initializing
a seqlock (which has type seqlock_t):
seqlock_t lock1 = SEQLOCK_UNLOCKED;
seqlock_t lock2;
seqlock_init(&lock2);

Read access works by obtaining an (unsigned) integer sequence value on entry into
the critical section. On exit, that sequence value is compared with the current value;
if there is a mismatch, the read access must be retried. As a result, reader code has a
form like the following:

unsigned int seq;
do {
seq = read_seqbegin(&the_lock);
/* Do what you need to do */
} while read_seqretry(&the_lock, seq);

If your seqlock might be accessed from an interrupt handler, you should use the
IRQ-safe versions instead:
unsigned int read_seqbegin_irqsave(seqlock_t *lock,
unsigned long flags);
int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq,
unsigned long flags);

Writers must obtain an exclusive lock to enter the critical section protected by a
seqlock. To do so, call:
void write_seqlock(seqlock_t *lock);
The write lock is implemented with a spinlock, so all the usual constraints apply.
Make a call to:
void write_sequnlock(seqlock_t *lock);
to release the lock. Since spinlocks are used to control write access, all of the usual
variants are available:

void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
void write_seqlock_irq(seqlock_t *lock);
void write_seqlock_bh(seqlock_t *lock);
void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
void write_sequnlock_irq(seqlock_t *lock);
void write_sequnlock_bh(seqlock_t *lock);
There is also a write_tryseqlock that returns nonzero if it was able to obtain the lock

Code using RCU should include <linux/rcupdate.h>.

On the read side, code using an RCU-protected data structure should bracket its references
with calls to rcu_read_lock and rcu_read_unlock. As a result, RCU code
tends to look like:
struct my_stuff *stuff;
rcu_read_lock( );
stuff = find_the_stuff(args...);
do_something_with(stuff);
rcu_read_unlock( );

void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);
The given func is called when it is safe to free the resource; it is passed to the same
arg that was passed to call_rcu. Usually, the only thing func needs to do is to call
kfree.

int ioctl(int fd, unsigned long cmd, ...);

The ioctl driver method has a prototype that differs somewhat from the user-space
version:
int (*ioctl) (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);

Here is how some ioctl commands are defined in scull. In particular, these commands
set and get the driver’s configurable parameters.
/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)

#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
#define SCULL_IOC_MAXNR 14


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值