您的位置:首页 > 运维架构 > Linux

内核同步机制-读写信号量(rw_semaphore)

2012-10-06 16:02 309 查看
四、读写信号量(rw_semaphore)

读/写信号量适于在读多写少的情况下使用。如果一个任务需要读和写操作时,它将被看作写者,在不需要写操作的情况下可降级为读者。任意多个读者可同时拥有一个读/写信号量,对临界区代码进行操作。

在没有写者操作时,任何读者都可成功获得读/写信号量进行读操作。如果有写者在操作时,读者必须被挂起等待直到写者释放该信号量。在没有写者或读者操作时,写者必须等待前面的写者或读者释放该信号量后,才能访问临界区。写者独占临界区,排斥其他的写者和读者,而读者只排斥写者。

读/写信号量可通过依赖硬件架构或纯软件代码两种方式实现。下面只说明纯软件代码实现方式。

 

如果一个读写信号量当前没有被写者拥有并且也没有写者等待读者释放信号量,那么任何读者都可以成功获得该读写信号量;否则,读者必须被挂起直到写者释放该信号量。如果一个读写信号量当前没有被读者或写者拥有并且也没有写者等待该信号量,那么一个写者可以成功获得该读写信号量,否则写者将被挂起,直到没有任何访问者。因此,写者是排他性的,独占性的。

读写信号量有两种实现,一种是通用的,不依赖于硬件架构,因此,增加新的架构不需要重新实现它,但缺点是性能低,获得和释放读写信号量的开销大;另一种是架构相关的,因此性能高,获取和释放读写信号量的开销小,但增加新的架构需要重新实现。在内核配置时,可以通过选项去控制使用哪一种实现。

读写信号量的相关API有:

(1)API说明

用户可通过调用读/写信号量API实现读/写操作的同步。读/写信号量API说明如表1。

API函数定义功能说明
DECLARE_RWSEM(name)声明名为name的读写信号量,并初始化它。
void init_rwsem(struct rw_semaphore *sem);对读写信号量sem进行初始化。
void down_read(struct rw_semaphore *sem);读者用来获取sem,若没获得时,则调用者睡眠等待。
void up_read(struct rw_semaphore *sem);读者释放sem。
int down_read_trylock(struct rw_semaphore *sem);读者尝试获取sem,如果获得返回1,如果没有获得返回0。可在中断上下文使用。
void down_write(struct rw_semaphore *sem);写者用来获取sem,若没获得时,则调用者睡眠等待。
int down_write_trylock(struct rw_semaphore *sem);写者尝试获取sem,如果获得返回1,如果没有获得返回0。可在中断上下文使用
void up_write(struct rw_semaphore *sem);写者释放sem。
void downgrade_write(struct rw_semaphore *sem);把写者降级为读者。
 

DECLARE_RWSEM(name)

该宏声明一个读写信号量name并对其进行初始化。

void init_rwsem(struct rw_semaphore *sem);

该函数对读写信号量sem进行初始化。

void down_read(struct rw_semaphore *sem);

读者调用该函数来得到读写信号量sem。该函数会导致调用者睡眠,因此只能在进程上下文使用。

int down_read_trylock(struct rw_semaphore *sem);

该函数类似于down_read,只是它不会导致调用者睡眠。它尽力得到读写信号量sem,如果能够立即得到,它就得到该读写信号量,并且返回1,否则表示不能立刻得到该信号量,返回0。因此,它也可以在中断上下文使用。

void down_write(struct rw_semaphore *sem);

写者使用该函数来得到读写信号量sem,它也会导致调用者睡眠,因此只能在进程上下文使用。

int down_write_trylock(struct rw_semaphore *sem);

该函数类似于down_write,只是它不会导致调用者睡眠。该函数尽力得到读写信号量,如果能够立刻获得,就获得该读写信号量并且返回1,否则表示无法立刻获得,返回0。它可以在中断上下文使用。

void up_read(struct rw_semaphore *sem);

读者使用该函数释放读写信号量sem。它与down_read或down_read_trylock配对使用。如果down_read_trylock返回0,不需要调用up_read来释放读写信号量,因为根本就没有获得信号量。

void up_write(struct rw_semaphore *sem);

写者调用该函数释放信号量sem。它与down_write或down_write_trylock配对使用。如果down_write_trylock返回0,不需要调用up_write,因为返回0表示没有获得该读写信号量。

void downgrade_write(struct rw_semaphore *sem);

该函数用于把写者降级为读者,这有时是必要的。因为写者是排他性的,因此在写者保持读写信号量期间,任何读者或写者都将无法访问该读写信号量保护的共享资源,对于那些当前条件下不需要写访问的写者,降级为读者将,使得等待访问的读者能够立刻访问,从而增加了并发性,提高了效率。

读写信号量适于在读多写少的情况下使用,在linux内核中对进程的内存映像描述结构的访问就使用了读写信号量进行保护。

在Linux中,每一个进程都用一个类型为task_t或struct task_struct的结构来描述,该结构的类型为struct mm_struct的字段mm描述了进程的内存映像,特别是mm_struct结构的mmap字段维护了整个进程的内存块列表,该列表将在进程生存期间被大量地遍利或修改。

因此mm_struct结构就有一个字段mmap_sem来对mmap的访问进行保护,mmap_sem就是一个读写信号量,在proc文件系统里有很多进程内存使用情况的接口,通过它们能够查看某一进程的内存使用情况,命令free、ps和top都是通过proc来得到内存使用信息的,proc接口就使用down_read和up_read来读取进程的mmap信息。

当进程动态地分配或释放内存时,需要修改mmap来反映分配或释放后的内存映像,因此动态内存分配或释放操作需要以写者身份获得读写信号量mmap_sem来对mmap进行更新。系统调用brk和munmap就使用了down_write和up_write来保护对mmap的访问。

(2)读/写信号量结构rw_semaphore

读/写信号量结构rw_semaphore描述了读/写信号量的值和等待队列,其列出如下(在include/linux/rwsem-spinlock.h中):

struct rw_semaphore {
/*读/写信号量定义:
* - 如果activity为0,那么没有激活的读者或写者。
* - 如果activity为+ve,那么将有ve个激活的读者。
* - 如果activity为-1,那么将有1个激活的写者。 */
__s32			activity;   /*信号量值*/
spinlock_t		wait_lock;   /*用于锁等待队列wait_list*/
struct list_head	wait_list;    /*如果非空,表示有进程等待该信号量*/
#ifdef CONFIG_DEBUG_LOCK_ALLOC  /*用于锁调试*/
struct lockdep_map dep_map;
#endif
};


(3)读者加锁/解锁操作实现分析

加读者锁操作

读者加锁函数down_read用于加读者锁,如果没有写者操作时,等待队列为空,读者可以加读者锁,将信号量的读者计数加1。如果有写在操作时,等待队列非空,读者需要等待写者操作完成。函数down_read列出如下(在kernel/rwsem.c中):

void __sched down_read(struct rw_semaphore *sem)
{
might_sleep();     /*用于调试自旋锁睡眠*/
rwsem_acquire_read(&sem->dep_map, 0, 0, _RET_IP_); /*确认获得锁,用于调试*/
/*跟踪锁状态信息(如:锁深度),用于调试*/
LOCK_CONTENDED(sem, __down_read_trylock, __down_read);
}


函数__down_read 完成加读者的具体操作,其列出如下(在lib/rwsem-spinlock.c中):

void __sched __down_read(struct rw_semaphore *sem)
{
struct rwsem_waiter waiter;
struct task_struct *tsk;
 
spin_lock_irq(&sem->wait_lock);
/*如果有0或多个读者,并且等待队列为空,就可以获取sem*/
if (sem->activity >= 0 && list_empty(&sem->wait_list)) {
/* 获得sem */
sem->activity++;  /*读者计数加1*/
spin_unlock_irq(&sem->wait_lock);
goto out;
}
 
/*运行到这里,说明不能获取sem,将当前进程加入等待队列进行等待*/
tsk = current;
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
 
/* 建立等待队列成员*/
waiter.task = tsk;
waiter.flags = RWSEM_WAITING_FOR_READ;  /*表示等待读操作*/
get_task_struct(tsk);   /*进程使用计数加1*/
 
list_add_tail(&waiter.list, &sem->wait_list);  /*将等待成员加到等待队列尾*/
 
/* 不再需要访问等待队列,因此,这里解锁*/
spin_unlock_irq(&sem->wait_lock);
 
/* 读者等待获取sem */
for (;;) {
if (!waiter.task)
break;
schedule();
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
}
/*运行这里,退出等待,说明可以获取sem了*/
tsk->state = TASK_RUNNING;
out:
;
}


解读者锁操作

函数up_read释放读者锁,如果等待队列非空,说明有写者在等待,就从等待队列唤醒一个写者。其列出如下(在kernel/rwsem.c中):

void up_read(struct rw_semaphore *sem)
{
rwsem_release(&sem->dep_map, 1, _RET_IP_);  /*获取解锁信息,用于调试*/
 
__up_read(sem);
}


函数__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;
 
sem->activity = -1;  /*表示有一个写者正在写操作*/
 
/*获取一个等待者*/
waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);
list_del(&waiter->list);  /*将该等待者从等待队列删除*/
 
tsk = waiter->task;
smp_mb();   /*加内存屏障,确保完成上面的指针引用操作*/
waiter->task = NULL;
wake_up_process(tsk);  /*唤醒进程*/
put_task_struct(tsk);    /*进程上下文使用计数减1*/
return sem;
}


(3)写者加锁/解锁操作实现分析

加写者锁操作

函数down_write完成加写者锁操作,其列出如下:

void __sched down_write(struct rw_semaphore *sem)
{
might_sleep();
rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_);
 
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
}
 
void __sched __down_write(struct rw_semaphore *sem)
{
__down_write_nested(sem, 0);
}


函数__down_write_nested完成加写者锁的具体操作。当没有读者或写者操作时,写者才可以获取写者锁。写者锁是独占的。如果有其他写者或读者操作时,写者必须等待。其列出如下:

void __sched __down_write_nested(struct rw_semaphore *sem, int subclass)
{
struct rwsem_waiter waiter;
struct task_struct *tsk;
 
spin_lock_irq(&sem->wait_lock);
/*如果没有读者,并且等待队列为空(说明没有写者)时,写者才能获取写者锁*/
if (sem->activity == 0 && list_empty(&sem->wait_list)) {
/* 获取写者锁*/
sem->activity = -1;
spin_unlock_irq(&sem->wait_lock);
goto out;
}
 
/*运行到这里,说明有读者或写者在操作,需要等待*/
tsk = current;
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
 
/* 建立等待队列成员*/
waiter.task = tsk;
waiter.flags = RWSEM_WAITING_FOR_WRITE; /*标识为等待写操作*/
get_task_struct(tsk);    /*进程上下文使用计数加1*/
 
list_add_tail(&waiter.list, &sem->wait_list);  /*加到等待队列尾*/
spin_unlock_irq(&sem->wait_lock);
 
/* 进行等待*/
for (;;) {
if (!waiter.task)
break;
schedule();
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
}
/*被唤醒*/
tsk->state = TASK_RUNNING;
out:
;
}


解写者锁操作

函数up_write释放写者锁,将读者计数设置为0,其列出如下:

void up_write(struct rw_semaphore *sem)
{
rwsem_release(&sem->dep_map, 1, _RET_IP_);
 
__up_write(sem);
}
 
void __up_write(struct rw_semaphore *sem)
{
unsigned long flags;
 
spin_lock_irqsave(&sem->wait_lock, flags);
 
sem->activity = 0;  /*表示有0个读者*/
if (!list_empty(&sem->wait_list))
sem = __rwsem_do_wake(sem, 1); /*唤醒等待者*/
 
spin_unlock_irqrestore(&sem->wait_lock, flags);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息