讀寫信號量的分析:
1) X86 彙編指令
js : jump on SF (sign flag)
xadd src, dest;
temp = src + dest;
src = dest;
dest = temp;
2)信號量定義:
struct rw_semaphore {
signed long count;
spinlock_t wait_lock;
struct list_head wait_list;
};
count: 關於讀者和寫者的計數:
高16位: Active Writer + Waiting Control Path(Read & Write);
低16位: Nonwaiting Reader + Nonwaiting Writer;
#define RWSEM_UNLOCKED_VALUE 0x00000000
#define RWSEM_ACTIVE_BIAS 0x00000001
#define RWSEM_ACTIVE_MASK 0x0000ffff
#define RWSEM_WAITING_BIAS (-0x00010000)
#define RWSEM_ACTIVE_READ_BIAS RWSEM_ACTIVE_BIAS
#define RWSEM_ACTIVE_WRITE_BIAS (RWSEM_WAITING_BIAS + RWSEM_ACTIVE_BIAS)
3) count 和sem 是分開保護的, sem 通過wait_lock保護, count 通過原子指令保護;
4)具體代碼如下
_down_read:
lock: sem->count = sem->count + (0x0000 0001)// increase active reader;
if (sem->count < 0) // has writer or waiter already
{
/*
* decrease active reader & increase waitting control path
*/
rwsem_down_failed_common(sem,
&waiter,
(-0x0001 0000 -0x0000 0001));
}
_down_write:
/* increase active writer (in high part & in low part)*/
lock: temp_sum = sem->count + (-0x0001 0000 + 0x0000 0001);
lock: tmp = sem->count;
lock: sem->count = temp_sum;
if (tmp != 0) // must be zero if the writer keeps on;
{
/*
* decrease active writer, trans active writer count
* (-0x0001 0000) to waitting control path;
*/
rwsem_down_failed_common(sem, &waiter, (-0x0000 0001));
}
rwsem_down_failed_common(sem, waiter, adjustment)
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
spin_lock(&sem->wait_lock);
add_tail(&waiter->list, &sem->wait_list);
/*
* failed to get semaphore, do some retify to mark waitting path count;
*/
lock: tmp_sum = adjustment + sem->count;
lock: tmp = sem->count;
lock: sem->count = tmp_sum;
rettemp = sem->count; /*tmp + adjustment;*/
/* no active process ,keeps on */
if ((rettemp & 0x0000 FFFF) == 0x0000 0000)
{
sem = _rwsem_do_wake(sem, 0);
}
spin_unlock(&sem->wait_lock);
for ( ; ;)
{
if (!waiter->task)
{
break;
}
schedule();
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
}
tsk->state = TASK_RUNNING;
喚醒函數:
__rwsem_do_wake(struct rw_semaphore *sem, int downgrading)
{
struct rwsem_waiter *waiter;
struct task_struct *tsk;
struct list_head *next;
signed long oldcount, woken, loop;
if (downgrading)
goto dont_wake_writers;
try_again:
/* when active count is 0x0000, increase it by 1 to prohibit others
* operation;
*/
oldcount = rwsem_atomic_update(RWSEM_ACTIVE_BIAS, sem)
- RWSEM_ACTIVE_BIAS;
/* some one already modify the count field, so undo it */
if (oldcount & RWSEM_ACTIVE_MASK)
goto undo;
waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);
/*
* if the first path in the Queue is’t a writer, we wake all reader
* in the queue;
*/
if (!(waiter->flags & RWSEM_WAITING_FOR_WRITE))
goto readers_only;
/*
* wake writer here. for a writer add 0x0000 0001 to indicat an active
* writer, the waitting path control part -0x0001 0000 automately change
* to mark an active writer in the HIGH part;
*/
list_del(&waiter->list);
tsk = waiter->task;
mb();
waiter->task = NULL;
wake_up_process(tsk);
put_task_struct(tsk);
goto out;
/* don't want to wake any writers */
dont_wake_writers:
waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);
if (waiter->flags & RWSEM_WAITING_FOR_WRITE)
goto out;
readers_only:
/*
* wake all reader here;
*/
woken = 0;
do {
woken++;
if (waiter->list.next == &sem->wait_list)
break;
waiter = list_entry(waiter->list.next,
struct rwsem_waiter, list);
} while (waiter->flags & RWSEM_WAITING_FOR_READ);
loop = woken;
woken *= RWSEM_ACTIVE_BIAS - RWSEM_WAITING_BIAS;
if (!downgrading)
/* we'd already done one increment earlier */
woken -= RWSEM_ACTIVE_BIAS;
/* + RWSEM_ACTIVE_BIAS, indicate an active reader
* - RWSEM_WAITING_BIAS because of the reader process state
* changing from waiting to active;
*/
rwsem_atomic_add(woken, sem);
next = sem->wait_list.next;
for (; loop > 0; loop--) {
waiter = list_entry(next, struct rwsem_waiter, list);
next = waiter->list.next;
tsk = waiter->task;
mb();
/* dechain the process */
waiter->task = NULL;
wake_up_process(tsk);
put_task_struct(tsk);
}
sem->wait_list.next = next;
next->prev = &sem->wait_list;
out:
rwsemtrace(sem, "Leaving __rwsem_do_wake");
return sem;
/* undo the change to count, but check for a transition 1->0 */
undo:
if (rwsem_atomic_update(-RWSEM_ACTIVE_BIAS, sem) != 0)
goto out;
goto try_again;
}