毛主席说过,没有调查,就没有发言权,所以一定要深入代码内部,探个究竟。
1什么是并发
并发是指多个进程”同时“对共享资源的访问,这样做势必会带来一些问题,我们称这种情况为竞态。
2并发处理方法
来看看Linux中两种常见的并发处理方法
(1)自旋锁
自旋锁定义include/linux/spinlock_types.h
20 typedef struct {
21 raw_spinlock_t raw_lock;
22 #ifdef CONFIG_GENERIC_LOCKBREAK
23 unsigned int break_lock;
24 #endif
25 #ifdef CONFIG_DEBUG_SPINLOCK
26 unsigned int magic, owner_cpu;
27 void *owner;
28 #endif
29 #ifdef CONFIG_DEBUG_LOCK_ALLOC
30 struct lockdep_map dep_map;
31#endif
32 }spinlock_t;
我们来看看arm中raw_spinlock_t是怎么定义的arch/arm/include/asm/spinlock_types.h
8 typedef struct {
9 volatile unsigned int lock;
10 }raw_spinlock_t;
自旋锁它的核心就是一个unsignedint类型的变量
使用宏DEFINE_SPINLOCK去定义一个自旋锁变量并初始化,看源代码
97 #define DEFINE_SPINLOCK(x) spinlock_t x =__SPIN_LOCK_UNLOCKED(x)
再来看前面的__SPIN_LOCK_UNLOCKED,它也是一个宏
80 #define __SPIN_LOCK_UNLOCKED(lockname) \
81 (spinlock_t) { .raw_lock = __RAW_SPIN_LOCK_UNLOCKED, \
82 SPIN_DEP_MAP_INIT(lockname) }
我们看就是给raw_lock赋了一个值,这个值是__RAW_SPIN_LOCK_UNLOCKED,它是多少呢,我们看arch/arm/include/asm/spinlock_types.h中的定义
12 #define __RAW_SPIN_LOCK_UNLOCKED { 0 }
它就是给lock这个成员初始化成0,即一开始就处于解锁状态。
当然初始化一个自旋锁还有其它方式,这里Linux内核一贯做法。例如:
spinlock_tmy_lock = SPIN_LOCK_UNLOCKED;
或者是使用spin_lock_init
/*include/linux/spinlock.h*/
104 #define spin_lock_init(lock) \
105 do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)
/*include/linux/spinlock_types.h*/
94#define SPIN_LOCK_UNLOCKED __SPIN_LOCK_UNLOCKED(old_style_spin_init)
我们看最后效果都是一样的,都是将lock这个成员初始化为0值。
既然它是一把锁,那它就有两种状态,一种是加锁,另一种是解锁。
加锁使用spin_lock,解锁使用spin_unlock。
自旋锁它的特点是忙等,怎样去理解忙等的概念呢,一种便于理解的方式如下。
while(TRUE){
while(turn!= 0); /*wait*/
critical_region();
turn= 1;
noncritical_region();
}
开始锁处于解锁状态,进程1检查到临界区处于解锁状态,进入临界区,同时给临界区加锁。如果此时进程2访问临界区,但此时临界区已加锁了,它就会一直循环检测锁的状态,这就是忙等。
(2)信号量
自旋锁会导致进程忙等,这样会浪费CPU,而信号量则不会导致这种情况。
信号量的特点是睡眠。
信号量使用structsemaphore结构描述
structsemaphore {
spinlock_t lock;
unsignedint count;
structlist_head wait_list;
};
有人将信号量比作院子的大门,而我将它比作到理发店去理发,count代表有几个理发师,如果有空的理发师,那么你就可以理发,如果没有空闲的理发师,那么你就要到长凳上休息,而信号量中的wait_list就是这里的长凳,当有空的理发师后你才可以去理发,在理发来看,都是先来先处理,要不然人家就不干了,但是在操作系统中就不一定了,各个进程有优先级,人家后来的可以先处理,现实生活中何尝没有这种情况,你去银行办理业务,人家还有vip用户呢。所以说,现实是残酷的,中国自古以来就是个等级严明的社会,以前如此,现在也是如此,谁让人家是当官的呢。说远了,还是信号量吧,这里的count值是多少,就要看我们初始化时指定它为什么值,初始化呢就使用函数sema_init,它有两个参数,第一个参数就是要初始化信号量的结构体指针,第二个参数就是count值,
32 static inline void sema_init(struct semaphore *sem, int val)
33 {
34 static struct lock_class_key __key;
35 *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
36 lockdep_init_map(&sem->lock.dep_map,"semaphore->lock", &__key, 0);
37 }
22 #define __SEMAPHORE_INITIALIZER(name, n) \
23 { \
24 .lock = __SPIN_LOCK_UNLOCKED((name).lock), \
25 .count = n, \
26 .wait_list = LIST_HEAD_INIT((name).wait_list), \
27 }
如果这个count值为1,它就是一个特殊的信号量,叫互斥信号量,可以理解为理发店只有一个理发师,不知大家看到过没有,街边一老头,一个凳子,墙上一镜子,它的工具在一个篮子里面。然后它就可以理发了。这种信号量可以使用DECLARE_MUTEX去定义一个互斥信号量
29 #define DECLARE_MUTEX(name) \
30 struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
也可以自己定义一个信号量后,使用init_MUTEX宏初始化它为互斥信号量
39 #define init_MUTEX(sem) sema_init(sem, 1)
另外一种互斥信号量它的初始值count为0
40 #define init_MUTEX_LOCKED(sem) sema_init(sem, 0)
这样的信号量大家不能理解,一个理发师都没有还怎么理发啊。这里就不能这么看了,我们前面看count也有可能为0,就是没有空闲的理发师这时count就为0,所以这里也要这么看,初始化时就没有空闲的理发师,你要等到一个人理完之后才能理发。信号量中有个p,v操作,在linux中就是down和up操作,一个人进入理发店后,要先使用down操作占用一个理发师,理完之后要使用up操作让出一个理发师出来,当然占用一个理发师之后,这个count值要减1,让出一个理发师之后这个count值要加1。那我们看看linux中的down和up操作。
42/**
43 * down - acquire the semaphore
44 * @sem: the semaphore to be acquired
45 *
46 * Acquires the semaphore. If no more tasks are allowed to acquirethe
47 * semaphore, calling this function will put the task to sleep untilthe
48 * semaphore is released.
49 *
50 * Use of this function is deprecated, please usedown_interruptible() or
51 * down_killable() instead.
52 */
53 void down(struct semaphore *sem)
54 {
55 unsigned long flags;
56
57 spin_lock_irqsave(&sem->lock, flags);
58 if (likely(sem->count > 0))
59 sem->count--;
60 else
61 __down(sem);
62 spin_unlock_irqrestore(&sem->lock, flags);
63 }
64 EXPORT_SYMBOL(down);
我们看在down之前,要先判断这个count值是否大于0,即是否有空闲的理发师,如果有空闲的理发师,count值减1,你就去理发了。如果没有空闲的理发师,那就是下面的__down操作,去看看
236 static noinline void __sched __down(struct semaphore *sem)
237 {
238 __down_common(sem, TASK_UNINTERRUPTIBLE,MAX_SCHEDULE_TIMEOUT);
239 }
199/*
200 * Because this function is inlined, the 'state' parameter will be
201 * constant, and thus optimised away by the compiler. Likewise the
202 * 'timeout' parameter for the cases without timeouts.
203 */
204 static inline int __sched __down_common(struct semaphore *sem, longstate,
205 longtimeout)
206 {
207 struct task_struct *task = current;
208 struct semaphore_waiter waiter;
209
210 list_add_tail(&waiter.list, &sem->wait_list);
211 waiter.task = task;
212 waiter.up = 0;
213
214 for (;;) {
215 if (signal_pending_state(state, task))
216 goto interrupted;
217 if (timeout <= 0)
218 goto timed_out;
219 __set_task_state(task, state);
220 spin_unlock_irq(&sem->lock);
221 timeout = schedule_timeout(timeout);
222 spin_lock_irq(&sem->lock);
223 if (waiter.up)
224 return 0;
225 }
226
227 timed_out:
228 list_del(&waiter.list);
229 return -ETIME;
230
231 interrupted:
232 list_del(&waiter.list);
233 return -EINTR;
234}
没有空闲的理发师,那就到长凳上坐着休息吧,有了再通知你。一个等待进程在信号量中叫做semaphore_waiter,然后就将这个进程加入等待的链表中,然后就休眠。当然也有等待的时间,也不可能一直等待,如果你等的不耐烦了,你也就可以先走了。
再来看up操作。
171/**
172 * up - release the semaphore
173 * @sem: the semaphore to release
174 *
175 * Release the semaphore. Unlike mutexes, up() may be called fromany
176 * context and even by tasks which have never called down().
177 */
178 void up(struct semaphore *sem)
179 {
180 unsigned long flags;
181
182 spin_lock_irqsave(&sem->lock, flags);
183 if (likely(list_empty(&sem->wait_list)))
184 sem->count++;
185 else
186 __up(sem);
187 spin_unlock_irqrestore(&sem->lock, flags);
188 }
189 EXPORT_SYMBOL(up);
判断等待链表是否为空,如果为空,count值加1。就像理发店的老板,现在已经有空的理发师了,扫一眼,看看凳子上有人没,没有人,这个空闲的理发师就先待在这里,如果有等待的人,这个人正在睡觉呢,马上叫醒他,该你理发了。
256 static noinline void __sched __up(struct semaphore *sem)
257 {
258 struct semaphore_waiter *waiter =list_first_entry(&sem->wait_list,
259 structsemaphore_waiter, list);
260 list_del(&waiter->list);
261 waiter->up = 1;
262 wake_up_process(waiter->task);
263}
我们看信号量它本身也有个自旋锁结构,这里的自旋锁是给信号量中的count加保护的,因为这个count值也很重要,可不能出任何差错。我们说自旋锁的特点是忙等,那么信号量的特点又是什么呢,它的特点就是睡眠