分类: LINUX
2014-08-09 23:21:39
法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友()发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。
1.1 读写信号量
读写信号量类似于信号量,但是它允许同时存在多个读者。在不能获得信号量时,当前进程会调度出去。
读写自旋锁用于不允许调度的上下文(如中断上下文),而读写信号量用于允许调度的上下文。
通常情况下(配置了CONFIG_RWSEM_GENERIC_SPINLOCK时),读写信号量的数据结构是rw_semaphore:
/**
* 读写信号量数据结构
*/
struct rw_semaphore {
/**
* 如果为0表示没有读者,也没有写者
* 如果大于0表示当前的并发读者数。
* 如果为-1,表示有一个写者。
*/
__s32 activity;
/**
* 保护本数据结构的自旋锁。
*/
spinlock_t wait_lock;
/**
* 等待信号量的等待队列。
*/
struct list_head wait_list;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/**
* 用于调试。
*/
struct lockdep_map dep_map;
#endif
};
1.1.1 获取和释放读信号量获取读信号量的函数是down_read:
/**
* 获取读信号量
*/
void __sched __down_read(struct rw_semaphore *sem)
{
struct rwsem_waiter waiter;
struct task_struct *tsk;
unsigned long flags;
/**
* 获得信号量的自旋锁。并关中断。关中断的原因是系统提供了可用于中断中的原语__down_read_trylock
*/
spin_lock_irqsave(&sem->wait_lock, flags);
/**
* 如果以下两个条件满足,则可以顺利申请到读锁:
* 当前没有写者获取到写锁。
* 也没有进程在申请写锁
*/
if (sem->activity >= 0 && list_empty(&sem->wait_list)) {
/* granted */
sem->activity++;/* 递增读者计数。 */
spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */
goto out;/* ??有必要?? */
}
/**
* 下面将进程挂到等待队列中
*/
tsk = current;
/**
* 设置进程状态为TASK_UNINTERRUPTIBLE,请注意这里有一个内存屏障和编译屏障
*/
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
/* set up my own style of waitqueue */
waiter.task = tsk;
/**
* 此标志表示当前任务正在等待读锁。唤醒时需要判断此标志。
*/
waiter.flags = RWSEM_WAITING_FOR_READ;
get_task_struct(tsk);/* ??暂时不清楚为什么要增加进程的引用计数?? */
list_add_tail(&waiter.list, &sem->wait_list);/* 将进程加入信号量的等待队列 */
/* we don't need to touch the semaphore struct anymore */
spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */
/* wait to be given the lock */
for (;;) {/* 这里的死循环最多执行两次 */
/**
* 如果进程还没有调用schedule切换出去,就被信号量唤醒,那么就退出循环
* 其实不判断似乎也没有问题,也许是出于效率的原因。
*/
if (!waiter.task)
break;
schedule();/* 这里调度出去,并等待被唤醒 */
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
}
tsk->state = TASK_RUNNING;/* 恢复任务状态 */
out:
;
}
释放读锁的函数是up_read:
/**
* 释放读信号量。
*/
void __up_read(struct rw_semaphore *sem)
{
unsigned long flags;
/**
* 获得自旋锁并关中断。
*/
spin_lock_irqsave(&sem->wait_lock, flags);
/**
* 当满足以下两个条件时,就唤醒等待的写者
* 已经没有读者持有信号量。
* 等待队列上有写者
*/
if (--sem->activity == 0 && !list_empty(&sem->wait_list))
sem = __rwsem_wake_one_writer(sem);/* 唤醒等待队列中的写者(只唤醒第一个写者) */
spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */
}
static inline struct rw_semaphore *
__rwsem_wake_one_writer(struct rw_semaphore *sem)
{
struct rwsem_waiter *waiter;
struct task_struct *tsk;
/**
* 将activity置为-1表示当前信号量被写者持有。
*/
sem->activity = -1;
/**
* 取出等待队列中的第一个对象,表示第一个等待信号量的写者
*/
waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);
list_del(&waiter->list);/* 将等待的写者从队列中摘除。 */
tsk = waiter->task;
/**
* 这里的屏障应该是为了确保被唤醒的写者在看到waiter->task = NULL前,看到sem->activity = -1;
* 虽然wake_up_process将进程唤醒后,进程会看到wake_up_process之前的所有修改,但是等待任务在调度前就在判断waiter->task
* 这时wake_up_process函数的屏障还没有起作用,因此需要在这里加一个屏障
*/
smp_mb();
waiter->task = NULL;
wake_up_process(tsk);/* 唤醒等待的任务并递减任务引用计数 */
put_task_struct(tsk);
return sem;
}
1.1.2 获取和释放写信号量获取写锁的函数是down_write:
/**
* 获取写锁
*/
void __sched __down_write_nested(struct rw_semaphore *sem, int subclass)
{
struct rwsem_waiter waiter;
struct task_struct *tsk;
unsigned long flags;
spin_lock_irqsave(&sem->wait_lock, flags);/* 获取自旋锁并关中断 */
/**
* 没有读者和写者,并且没有其他处于等待状态的读者和写者
*/
if (sem->activity == 0 && list_empty(&sem->wait_list)) {
/* granted */
sem->activity = -1;/* activity置为-1表示被写者拥有 */
spin_unlock_irqrestore(&sem->wait_lock, flags);
goto out;
}
/**
* 运行到这里,说明有读者或者写者,将当前任务挂起
*/
tsk = current;
set_task_state(tsk, TASK_UNINTERRUPTIBLE);/* 设置线程状态 */
/* set up my own style of waitqueue */
waiter.task = tsk;
/* 这个标志表示等待者是一个写者 */
waiter.flags = RWSEM_WAITING_FOR_WRITE;
get_task_struct(tsk);/* 递增进程的引用计数。在被唤醒时,由释放信号量的进程递减其引用计数 */
/**
* 将进程添加到等待队列中。
*/
list_add_tail(&waiter.list, &sem->wait_list);
/* we don't need to touch the semaphore struct anymore */
spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */
/* wait to be given the lock */
for (;;) {
if (!waiter.task)/* 如果信号量被释放并且唤醒了本线程,则退出 */
break;
schedule();/* 没有释放信号量,必须调度出去 */
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
}
/* 成功获取了信号量,退出 */
tsk->state = TASK_RUNNING;
out:
;
}
写者释放读写信号量的函数是up_write,实质上是调用__up_write:
/**
*
写者释放读写信号量
*/
void __up_write(struct rw_semaphore *sem)
{
unsigned long flags;
spin_lock_irqsave(&sem->wait_lock, flags);/* 获取自旋锁并关中断 */
sem->activity = 0;/* 因为仅仅只能有一个写者,也不能有读者能够与写者同在,因此写者释放信号量时,可以将activity设置为0,表示没有写者和读者了 */
if (!list_empty(&sem->wait_list))/* 有读者或者写者在等待获得信号量,需要唤醒一个或者多个读者,或者唤醒一个写者 */
sem = __rwsem_do_wake(sem, 1);
spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */
}
与读者释放读锁不同,读者仅仅会唤醒队列中第一个等待者,这个等待者必然是一个写者。写者释放写锁时,可能唤醒一个或者多个等待的进程:
/**
* 写者释放信号量时,唤醒等待的读者和写者
*/
static inline struct rw_semaphore *
__rwsem_do_wake(struct rw_semaphore *sem, int wakewrite)
{
struct rwsem_waiter *waiter;
struct task_struct *tsk;
int woken;
/**
* 取得等待队列上的第一个等待进程。
*/
waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);
if (!wakewrite) {/* 调用者不希望唤醒写者 */
if (waiter->flags & RWSEM_WAITING_FOR_WRITE)/* 第一个等待进程是等待写锁 */
goto out;/* 此时不能成功唤醒写者,退出 */
goto dont_wake_writers;/* 跳转到dont_wake_writers,开始唤醒等待的读者 */
}
/* if we are allowed to wake writers try to grant a single write lock
* if there's a writer at the front of the queue
* - we leave the 'waiting count' incremented to signify potential
* contention
*/
if (waiter->flags & RWSEM_WAITING_FOR_WRITE) {/* 第一个等待者是在等待写锁,只唤醒这个进程就行了。 */
sem->activity = -1;/* 将标志设置为-1,表示锁被写者持有了 */
list_del(&waiter->list);/* 将等待者从队列中删除 */
tsk = waiter->task;
/* Don't touch waiter after ->task has been NULLed */
smp_mb();
waiter->task = NULL;/* 将task字段设置为空,这样等待者被唤醒后,就知道是被信号量唤醒的 */
wake_up_process(tsk);/* 唤醒等待者 */
put_task_struct(tsk);/* 释放等待者的进程结构的引用计数。这个计数在等待者将自己添加到等待队列时增加。 */
goto out;
}
/* grant an infinite number of read locks to the front of the queue */
/**
* 运行到这里,说明第一个等待者是在等待读锁,需要唤醒所有读者,直到遇到一个写者。
*/
dont_wake_writers:
woken = 0;/* 被唤醒的读者计数 */
while (waiter->flags & RWSEM_WAITING_FOR_READ) {/* 只要等待队列中的等待者是读者,就继续处理,将所有读者全部唤醒。 */
struct list_head *next = waiter->list.next;/* waiter是当前待唤醒任务,这里取得下一个任务 */
/**
* 将等待者从队列中摘除。
*/
list_del(&waiter->list);
tsk = waiter->task;
smp_mb();
/**
* 将等待者唤醒。被唤醒任务通过waiter->task字段即能够判断出自己是被正常唤醒的。
*/
waiter->task = NULL;
wake_up_process(tsk);
/**
* 释放等待进程的任务结构的引用计数。
*/
put_task_struct(tsk);
woken++;/* 将唤醒的读者数量加1 */
if (list_empty(&sem->wait_list))/* 没有更多的等待者了,退出 */
break;
/**
* 处理下一个等待者,如果它不是写者,则继续将它唤醒。
*/
waiter = list_entry(next, struct rwsem_waiter, list);
}
/**
* 将activity设置为当前活动的读者数量。
*/
sem->activity += woken;
out:
return sem;
}
1.1.3 关于读写信号量的疑问