Blame view

lib/rwsem-spinlock.c 6.8 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /* rwsem-spinlock.c: R/W semaphores: contention handling functions for
   * generic spinlock implementation
   *
   * Copyright (c) 2001   David Howells (dhowells@redhat.com).
   * - Derived partially from idea by Andrea Arcangeli <andrea@suse.de>
   * - Derived also from comments by Linus
   */
  #include <linux/rwsem.h>
  #include <linux/sched.h>
  #include <linux/module.h>
  
  struct rwsem_waiter {
  	struct list_head list;
  	struct task_struct *task;
  	unsigned int flags;
  #define RWSEM_WAITING_FOR_READ	0x00000001
  #define RWSEM_WAITING_FOR_WRITE	0x00000002
  };
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
19
20
21
  /*
   * initialise the semaphore
   */
4ea2176df   Ingo Molnar   [PATCH] lockdep: ...
22
23
  void __init_rwsem(struct rw_semaphore *sem, const char *name,
  		  struct lock_class_key *key)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
24
  {
4ea2176df   Ingo Molnar   [PATCH] lockdep: ...
25
26
27
28
29
  #ifdef CONFIG_DEBUG_LOCK_ALLOC
  	/*
  	 * Make sure we are not reinitializing a held semaphore:
  	 */
  	debug_check_no_locks_freed((void *)sem, sizeof(*sem));
4dfbb9d8c   Peter Zijlstra   Lockdep: add lock...
30
  	lockdep_init_map(&sem->dep_map, name, key, 0);
4ea2176df   Ingo Molnar   [PATCH] lockdep: ...
31
  #endif
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
32
33
34
  	sem->activity = 0;
  	spin_lock_init(&sem->wait_lock);
  	INIT_LIST_HEAD(&sem->wait_list);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  }
  
  /*
   * handle the lock release when processes blocked on it that can now run
   * - if we come here, then:
   *   - the 'active count' _reached_ zero
   *   - the 'waiting count' is non-zero
   * - the spinlock must be held by the caller
   * - woken process blocks are discarded from the list after having task zeroed
   * - writers are only woken if wakewrite is non-zero
   */
  static inline struct rw_semaphore *
  __rwsem_do_wake(struct rw_semaphore *sem, int wakewrite)
  {
  	struct rwsem_waiter *waiter;
  	struct task_struct *tsk;
  	int woken;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
  	waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);
  
  	if (!wakewrite) {
  		if (waiter->flags & RWSEM_WAITING_FOR_WRITE)
  			goto out;
  		goto dont_wake_writers;
  	}
  
  	/* if we are allowed to wake writers try to grant a single write lock
  	 * if there's a writer at the front of the queue
  	 * - we leave the 'waiting count' incremented to signify potential
  	 *   contention
  	 */
  	if (waiter->flags & RWSEM_WAITING_FOR_WRITE) {
  		sem->activity = -1;
  		list_del(&waiter->list);
  		tsk = waiter->task;
  		/* Don't touch waiter after ->task has been NULLed */
d59dd4620   Andrew Morton   [PATCH] use smp_m...
70
  		smp_mb();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  		waiter->task = NULL;
  		wake_up_process(tsk);
  		put_task_struct(tsk);
  		goto out;
  	}
  
  	/* grant an infinite number of read locks to the front of the queue */
   dont_wake_writers:
  	woken = 0;
  	while (waiter->flags & RWSEM_WAITING_FOR_READ) {
  		struct list_head *next = waiter->list.next;
  
  		list_del(&waiter->list);
  		tsk = waiter->task;
d59dd4620   Andrew Morton   [PATCH] use smp_m...
85
  		smp_mb();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
86
87
88
89
90
91
92
93
94
95
96
97
  		waiter->task = NULL;
  		wake_up_process(tsk);
  		put_task_struct(tsk);
  		woken++;
  		if (list_empty(&sem->wait_list))
  			break;
  		waiter = list_entry(next, struct rwsem_waiter, list);
  	}
  
  	sem->activity += woken;
  
   out:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
  	return sem;
  }
  
  /*
   * wake a single writer
   */
  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;
d59dd4620   Andrew Morton   [PATCH] use smp_m...
116
  	smp_mb();
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
117
118
119
120
121
122
123
124
125
  	waiter->task = NULL;
  	wake_up_process(tsk);
  	put_task_struct(tsk);
  	return sem;
  }
  
  /*
   * get a read lock on the semaphore
   */
9f741cb8f   Harvey Harrison   lib: remove fastc...
126
  void __sched __down_read(struct rw_semaphore *sem)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
127
128
129
  {
  	struct rwsem_waiter waiter;
  	struct task_struct *tsk;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  	spin_lock_irq(&sem->wait_lock);
  
  	if (sem->activity >= 0 && list_empty(&sem->wait_list)) {
  		/* granted */
  		sem->activity++;
  		spin_unlock_irq(&sem->wait_lock);
  		goto out;
  	}
  
  	tsk = current;
  	set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  
  	/* set up my own style of waitqueue */
  	waiter.task = tsk;
  	waiter.flags = RWSEM_WAITING_FOR_READ;
  	get_task_struct(tsk);
  
  	list_add_tail(&waiter.list, &sem->wait_list);
  
  	/* we don't need to touch the semaphore struct anymore */
  	spin_unlock_irq(&sem->wait_lock);
  
  	/* wait to be given the lock */
  	for (;;) {
  		if (!waiter.task)
  			break;
  		schedule();
  		set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  	}
  
  	tsk->state = TASK_RUNNING;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
161
   out:
c4e05116a   Ingo Molnar   [PATCH] lockdep: ...
162
  	;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
163
164
165
166
167
  }
  
  /*
   * trylock for reading -- returns 1 if successful, 0 if contention
   */
9f741cb8f   Harvey Harrison   lib: remove fastc...
168
  int __down_read_trylock(struct rw_semaphore *sem)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
169
170
171
  {
  	unsigned long flags;
  	int ret = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
172
173
174
175
176
177
178
179
180
181
  
  	spin_lock_irqsave(&sem->wait_lock, flags);
  
  	if (sem->activity >= 0 && list_empty(&sem->wait_list)) {
  		/* granted */
  		sem->activity++;
  		ret = 1;
  	}
  
  	spin_unlock_irqrestore(&sem->wait_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
182
183
184
185
186
187
188
  	return ret;
  }
  
  /*
   * get a write lock on the semaphore
   * - we increment the waiting count anyway to indicate an exclusive lock
   */
9f741cb8f   Harvey Harrison   lib: remove fastc...
189
  void __sched __down_write_nested(struct rw_semaphore *sem, int subclass)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
190
191
192
  {
  	struct rwsem_waiter waiter;
  	struct task_struct *tsk;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
  	spin_lock_irq(&sem->wait_lock);
  
  	if (sem->activity == 0 && list_empty(&sem->wait_list)) {
  		/* granted */
  		sem->activity = -1;
  		spin_unlock_irq(&sem->wait_lock);
  		goto out;
  	}
  
  	tsk = current;
  	set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  
  	/* set up my own style of waitqueue */
  	waiter.task = tsk;
  	waiter.flags = RWSEM_WAITING_FOR_WRITE;
  	get_task_struct(tsk);
  
  	list_add_tail(&waiter.list, &sem->wait_list);
  
  	/* we don't need to touch the semaphore struct anymore */
  	spin_unlock_irq(&sem->wait_lock);
  
  	/* wait to be given the lock */
  	for (;;) {
  		if (!waiter.task)
  			break;
  		schedule();
  		set_task_state(tsk, TASK_UNINTERRUPTIBLE);
  	}
  
  	tsk->state = TASK_RUNNING;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
224
   out:
c4e05116a   Ingo Molnar   [PATCH] lockdep: ...
225
  	;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
226
  }
9f741cb8f   Harvey Harrison   lib: remove fastc...
227
  void __sched __down_write(struct rw_semaphore *sem)
4ea2176df   Ingo Molnar   [PATCH] lockdep: ...
228
229
230
  {
  	__down_write_nested(sem, 0);
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
231
232
233
  /*
   * trylock for writing -- returns 1 if successful, 0 if contention
   */
9f741cb8f   Harvey Harrison   lib: remove fastc...
234
  int __down_write_trylock(struct rw_semaphore *sem)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
235
236
237
  {
  	unsigned long flags;
  	int ret = 0;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
238
239
240
241
242
243
244
245
246
  	spin_lock_irqsave(&sem->wait_lock, flags);
  
  	if (sem->activity == 0 && list_empty(&sem->wait_list)) {
  		/* granted */
  		sem->activity = -1;
  		ret = 1;
  	}
  
  	spin_unlock_irqrestore(&sem->wait_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
247
248
249
250
251
252
  	return ret;
  }
  
  /*
   * release a read lock on the semaphore
   */
9f741cb8f   Harvey Harrison   lib: remove fastc...
253
  void __up_read(struct rw_semaphore *sem)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
254
255
  {
  	unsigned long flags;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
256
257
258
259
260
261
  	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);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
262
263
264
265
266
  }
  
  /*
   * release a write lock on the semaphore
   */
9f741cb8f   Harvey Harrison   lib: remove fastc...
267
  void __up_write(struct rw_semaphore *sem)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
268
269
  {
  	unsigned long flags;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
270
271
272
273
274
275
276
  	spin_lock_irqsave(&sem->wait_lock, flags);
  
  	sem->activity = 0;
  	if (!list_empty(&sem->wait_list))
  		sem = __rwsem_do_wake(sem, 1);
  
  	spin_unlock_irqrestore(&sem->wait_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
277
278
279
280
281
282
  }
  
  /*
   * downgrade a write lock into a read lock
   * - just wake up any readers at the front of the queue
   */
9f741cb8f   Harvey Harrison   lib: remove fastc...
283
  void __downgrade_write(struct rw_semaphore *sem)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
284
285
  {
  	unsigned long flags;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
286
287
288
289
290
291
292
  	spin_lock_irqsave(&sem->wait_lock, flags);
  
  	sem->activity = 1;
  	if (!list_empty(&sem->wait_list))
  		sem = __rwsem_do_wake(sem, 0);
  
  	spin_unlock_irqrestore(&sem->wait_lock, flags);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
293
  }
4ea2176df   Ingo Molnar   [PATCH] lockdep: ...
294
  EXPORT_SYMBOL(__init_rwsem);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
295
296
  EXPORT_SYMBOL(__down_read);
  EXPORT_SYMBOL(__down_read_trylock);
4ea2176df   Ingo Molnar   [PATCH] lockdep: ...
297
  EXPORT_SYMBOL(__down_write_nested);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
298
299
300
301
302
  EXPORT_SYMBOL(__down_write);
  EXPORT_SYMBOL(__down_write_trylock);
  EXPORT_SYMBOL(__up_read);
  EXPORT_SYMBOL(__up_write);
  EXPORT_SYMBOL(__downgrade_write);