rw_semaphoree

设计思想

基本

  • 读与读不互斥;
  • 读与写互斥;
  • 写与写互斥;

等待机制

  • 读只有睡眠等待
  • 写有忙等待,与睡眠等待;

唤醒

  • 读:唤醒链表上的读进程,直到遇到一个写进程为止;
  • 写:一次只能唤醒一个写者

count计数:(不保证完全正确)

概述

通过count标识锁的状态;

  • 空闲状态:(0,0)
  • 等待队列存在waiter:#define RWSEM_WAITING_BIAS (-RWSEM_ACTIVE_MASK-1)
  • 只通过RWSEM_WAITING_BIAS标识存在waiter,不标识有多少个waiter;
  • 标识存在写或读的:#define RWSEM_ACTIVE_BIAS 0x00000001L
    • 标识存在读:(0, 1)
    • 标识存在写:(-1, 1)

设计思想

  • 可以看作一个二元组(x,y),(0,0)标识空闲;
  • 1(RWSEM_ACTIVE_BIAS)标识存在读或写,为了区分读或者写通过二元组的x标识;

注释中的count:

  • 0x00000000:锁空闲
  • 0x0000000X:只有x个读
  • 0xffff000X:(-1,x)模型,-1表示有写,x表示有读;
    • 所以可能是有x-1个读获取锁,有1个写;
    • 1个写获取锁,x-1个读在等待;
  • 0xffff0001:(-1,1)
    • 只有一个写;
  • 0xfffe0001:(-2,1);有一个写,又进入一个写waiter,太巧妙了这值;(-65535 - 65536,背后的数学逻辑?)

代码解读:

获取读锁:__down_read

主要路径:

  • 尝试获取锁:atomic_long_inc_return_acquire
  • 获取锁失败进入失败路径:rwsem_down_read_failed
  • 读锁只有加入等待队列睡眠路径,所以有以下代码:
    • 加入等待队列:list_add_tail(&waiter.list, &sem->wait_list);
    • 判断锁是否获取没:!waiter.task
    • 睡眠等待:schedule();

一个特殊路径:

可以看见主要路径很符合预期,就是:获取锁,获取锁失败,加入等待队列睡眠等待获取锁;
猜测是作者实验测试的过程中,发现了一个可以优化的点,在正式睡眠之前判断了一次当前锁是否空闲,如果空闲就要唤醒等待队列中睡眠的进程,于是就有了如下代码:

	if (count == RWSEM_WAITING_BIAS ||	/* 只有RWSEM_WAITING_BIAS,表示写锁已经释放了 */
	    (count > RWSEM_WAITING_BIAS &&	/* -65536 */	/* 存在的情况是:有一个写着,一个读者代码走到这里的过程中,如果又有锁进来了,并且其他的锁还没有走到atomic_long_add_return把之前加的值减回去 */
	     adjustment != -RWSEM_ACTIVE_READ_BIAS))	/* != -1 */	/* 理解是限制个数,比如又65535个锁,这里值就刚好是-1了,不知道理解对不对 */
		__rwsem_mark_wake(sem, RWSEM_WAKE_ANY, &wake_q);    /* 把waiter加入唤醒队列 */

获取写锁:__down_write

主要路径

  • 获取锁:atomic_long_add_return_acquire(RWSEM_ACTIVE_WRITE_BIAS,
  • 获取锁失败进入失败路径:rwsem_down_write_failed
  • 乐观自旋获取锁:rwsem_optimistic_spin
  • 加入等待队列睡眠等待:
    • 尝试获取锁:rwsem_try_write_lock(count, sem)
    • 睡眠等待:schedule;

特殊路径:

估计也是作者实验的过程中发现的,也加了一些特殊处理:
正式睡眠之前检测等待队列是否为空,检测此时写锁是否释放了,如果写锁释放了唤醒读者,代码如下:

		if (count > RWSEM_WAITING_BIAS) {	/* 如果此时的计数大于0,证明写释放锁了, */
			__rwsem_mark_wake(sem, RWSEM_WAKE_READERS, &wake_q);	/* 唤醒读者 */

为什么只对读进行了判断,如果此时的wait是写呢?
代码里面没有对写进行特殊处理,估计让写走正常路径获取把

释放锁:rwsem_wake

无论是读还是写都会调用到该函数;

  • 唤醒等待队列中的wait:__rwsem_mark_wake(sem, RWSEM_WAKE_ANY, &wake_q);
    做了一个特殊处理:
	if (rwsem_has_spinner(sem)) {
		if (!raw_spin_trylock_irqsave(&sem->wait_lock, flags))	/* 获取锁 */
			return sem;	/* 没有获取到锁直接返回 */
		goto locked;	/* 获取到锁 */
	}

认为存在自旋获取锁时,wait_lock获取很难,为了不浪费这点时间,就直接返回了??没太看懂

posted @ 2024-12-19 21:33  _xingxing  阅读(11)  评论(0)    收藏  举报