字段sleepers: 睡眠进程, 其实应该不等于真实的睡眠进程数
字段count:信号量计数(up 加, down 减)
字段wait(down时使用, 保存睡眠进程, 提供spinlock锁)
57
fastcall
void __sched
__down
(struct semaphore
* sem
)
58
{
59
struct task_struct
*tsk
= current
;
60
DECLARE_WAITQUEUE
(wait
, tsk
);
61
unsigned long flags
;
62
63
tsk
->state
= TASK_UNINTERRUPTIBLE
;
64
spin_lock_irqsave
(&sem
->wait
.lock
, flags
);
65
add_wait_queue_exclusive_locked
(&sem
->wait
, &wait
);
66
67
sem
->sleepers
++;
68
for (;;) {
69
int sleepers
= sem
->sleepers
;
70
71
72
73
74
75
76
if (!atomic_add_negative
(sleepers
- 1, &sem
->count
)) {
77
sem
->sleepers
= 0;
78
break;
79
}
80
sem
->sleepers
= 1;
81
spin_unlock_irqrestore
(&sem
->wait
.lock
, flags
);
82
83
schedule
();
84
85
spin_lock_irqsave
(&sem
->wait
.lock
, flags
);
86
tsk
->state
= TASK_UNINTERRUPTIBLE
;
87
}
88
remove_wait_queue_locked
(&sem
->wait
, &wait
);
89
wake_up_locked
(&sem
->wait
);
90
spin_unlock_irqrestore
(&sem
->wait
.lock
, flags
);
91
tsk
->state
= TASK_RUNNING
;
92
}
考虑已经有一个进程占用信号量(down0, up0), 随后调用两次down(down1(up1), down2(up1))获取信号量的case:
对于down1:
1. sem count减1, count为-2
2. count为负, 调用__down
3. 设当前进程状态为TASK_UNINTERRUPTIBLE(line 63)
4. 获取该信号量的spin lock并且关本地中断(意味着无其它代码打扰, down2进不来)(line 64)
5. 把该进程加入该信号量等待队列(line65)
6. 将sleepers加1(line 67)
7. 进入循环,(line 68)
8. 将sleeper-1的值加入count, 并检查count是否为负(line 76)
9. 如果在这个检查之前up0被call, count不为负, 设sleepers为0,跳出循环,获得信号量,从等待队列删除本进程,唤起另一个等待队列上的进程(fter line88)
10. 如果为负, 设sleepers为1, 释放自旋锁,调度其它进程,这个时候down2可能会进来, 并获取自旋锁,(line 80-83)
11.down2把他的进程加到等待队列,将sleepers加1(=2), count = -2
12. down2此时把sleepers-1加到count上, count(-1)依然为负,sleepers又被设为1, 自旋锁又被释放, 其它进程又被调度,(line 80-83)
13.此时up0将被call,count变成0
14.down1进程可能被唤醒(up0会call__up去wake_up等待队列),获得spinlock, (line85)
15. 此时把sleepers-1=0加入到count, count不变,等于0,非负, 获取自旋锁,(line76-78)
16. 退出循环
17. 把down1进程从等待队列删除(line88)
18.唤醒等待队列上的其它进程,但是因为down1进程还没有释放spinlock,故其它想获取该信号量的进程被阻塞, 此时sleepers = 0, count = 0,(line89, line85)
19. 释放spinlock(line90)
20.改down1进程的状态为TASK_RUNNING,(line91)
21. down2获得spinlock,(line 85)
22. 把sleepers-1 = -1 加到count, count=-1(line 76)
23.count为负, 设sleepers =1, 释放自旋锁,调度其它进程(line80-83)
24. down1进程继续运行,会call up1释放信号量,此时count = 0, sleepers =1, 唤醒其它等待进程
25. 把sleepers -1 = 0加到count, count 等于0, 非负,down2获得自旋锁(line76)
26. 跳出循环, 把down2进程从等待队列删除,(line88)
27. 唤醒其它等待进程(line89)
28. 释放自旋锁, 该down2进程状态为TASK_RUNNING.(line90-91)
这里分析了一种路径, 也有可能down2先获得信号量
可见信号量对多cpu也是有效的
up相对简单一些
1. 增加count
2. 如果count非正, 调用__up去call wake_up去唤醒等待队列上的其它进程
读写信号量:读写互斥, 读读不斥, 写写斥
内核以FIFO方式处理所有等待读写信号量的进程。
包括:
count字段
wait_list字段
wait_lock自旋锁字段