Linux并发处理

毛主席说过,没有调查,就没有发言权,所以一定要深入代码内部,探个究竟。

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;

我们来看看armraw_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)

另外一种互斥信号量它的初始值count0

40 #define init_MUTEX_LOCKED(sem) sema_init(sem, 0)

这样的信号量大家不能理解,一个理发师都没有还怎么理发啊。这里就不能这么看了,我们前面看count也有可能为0,就是没有空闲的理发师这时count就为0,所以这里也要这么看,初始化时就没有空闲的理发师,你要等到一个人理完之后才能理发。信号量中有个p,v操作,在linux中就是downup操作,一个人进入理发店后,要先使用down操作占用一个理发师,理完之后要使用up操作让出一个理发师出来,当然占用一个理发师之后,这个count值要减1,让出一个理发师之后这个count值要加1。那我们看看linux中的downup操作。

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值也很重要,可不能出任何差错。我们说自旋锁的特点是忙等,那么信号量的特点又是什么呢,它的特点就是睡眠

你可能感兴趣的:(Linux并发处理)