四、读写信号量(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);
}

内核同步机制-读写信号量(rw_semaphore)相关推荐

  1. Linux内核同步机制之信号量与锁

    Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率.首先,看看我们最熟悉的两种机制--信号量.锁. 一.信号量 首先还是看看内核中是怎么 ...

  2. Linux内核同步机制之(四):spin lock【转】

    转自:http://www.wowotech.net/kernel_synchronization/spinlock.html 一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享 ...

  3. 内核同步机制——信号量

    1.通用版 信号量用于对一个或多个资源进行互斥访问.基本操作如下: void sema_init(struct semaphore *sem, int val);//信号量初始化函数 静态初始化: D ...

  4. 内核同步机制之Mutex Exclusion

    为什么80%的码农都做不了架构师?>>>    S3C2440在内核版本2.6.34下开发ADC驱动时,会发现驱动程序里用了互斥通信来实现同步: <!-- lang: cpp ...

  5. Linux内核同步机制--自旋锁【转】

    本文转载自:http://www.cppblog.com/aaxron/archive/2013/04/12/199386.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已 ...

  6. linux 内核 同步机制

    原子操作   原子操作是由编译器来保证的,保证一个线程对数据的操作不会被其他线程打断.    自旋锁 原子操作只能用于临界区只有一个变量的情况,实际应用中,临界区的情况要复杂的多.对于复杂的临界区,L ...

  7. 内核同步机制-优化屏障和内存屏障

    优化屏障 编译器编译源代码时,会将源代码进行优化,将源代码的指令进行重排序,以适合于CPU的并行执行.然而,内核同步必须避免指令重新排序,优化屏障(Optimization barrier)避免编译器 ...

  8. 内核同步机制-RCU同步机制

    转自http://www.360doc.com/content/09/0805/00/36491_4675691.shtml 目录 [隐藏] 1 RCU同步机制 1.1 RCU介绍 1.2 RCU A ...

  9. 内核同步机制-信号量(semaphore)

    三.内核信号量(semaphore) Linux的信号量是一种睡眠锁,这个不同于自旋锁.如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列(具体可以参考进程的活动状态),然后 ...

最新文章

  1. SublimeLinter插件对PHP语法检测不起作用的解决办法
  2. 大型网站系统与Java中间件实践 01 认识分布式
  3. optee中的密码学算法注册模型
  4. Mongo服务器二进制文件修复,Mongodb-File-Server
  5. Qt的json对象不具备类似指针、引用的行为导致的更新不成功问题解决
  6. linux找回rm的文件夹,Linux rm 文件恢复
  7. Jsoncpp 使用方法大全
  8. Server concepts 详解
  9. 服务器硬盘 二手,分析:二手服务器配件 哪个最不该买?
  10. 现代信号处理——自适应滤波器(离散维纳滤波器)
  11. 倾斜摄影与三维实景建模技术设计书.PDF(文档可下载)
  12. 【解决方法】如何压缩网页字体文件
  13. Java:轻松一刻/程序员才懂的幽默
  14. Exchange Server 2013 运维系列——恢复已删除邮件
  15. 如何检查计算机上安装的DirectX版本?
  16. 网络七层结构(讲人话)
  17. mysql.zip版本的安装教程及环境配置
  18. 中国电信服务亚运凸显全业务运营优势
  19. VS2015配置OpenGL环境——GLUT、freeglut、glew、GLtools
  20. 职场小说:《米亚快跑》PDF版下载

热门文章

  1. STM32F405 HAL库 STM32CUBE开发
  2. 二维均匀分布的边缘密度函数_理解概率密度函数
  3. python使用BytesIO或StringIO读写文件
  4. 解决xgboost报错XGBoostError: XGBoost Library (libxgboost.dylib) could not be loaded
  5. 2750个通用停用词表整理,免费下载
  6. h3c 虚拟服务器 下一跳,H3C vLNS系列虚拟L2TP网络服务器 配置指导-E0324-5W100
  7. js 点击闭包_【JS进阶】Javascript 闭包与Promise的碰撞
  8. 深入探索android热修复技术原理_打卡活动:技术书籍书单
  9. c# npoi 公式不计算_建筑行业计算公式大全,钢筋重量计算公式,不收藏吃亏的是你自己...
  10. html签到插件,GitHub - inu1255/soulsign-chrome: 魂签,一款用于自动签到的chrome插件