linux 内核信号量

字段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

* Add "everybody else" into it. They aren't


73

* playing, because we own the spinlock in


74

* the wait_queue_head.


75

*/


76

if (!atomic_add_negative
(sleepers
- 1, &sem
->count
)) {

77

sem
->sleepers
= 0;

78

break;

79

}

80

sem
->sleepers
= 1; /* us - see -1 above */


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自旋锁字段

你可能感兴趣的:(linux,list,struct,Semaphore,UP)