【linux学习笔记】内核同步

临界区就是访问、操作共享资源的代码段。

为什么需要进行同步?

是因为存在任务的抢占和重新调度,或者多核处理器的并发导致多个进程可以同时进入临界区修改共享资源。

可能造成并发的原因:
1)中断;
2)软中断和tasklet,内核可以在任何时候环形或者调度软中断和tasklet打断当前正在执行的代码;
3)内核抢占;
4)进程进入睡眠状态;
5)多个处理器同时执行代码。

同步过程中常见的几个BUG:
1)内核在临界区休眠;
2)中断发生,被中断进程正处于临界区,而中断服务程序正要访问该临界区;
3)进程正在临界区的时候发生抢占;
4)多核并发访问临界区。
总结:进程在临界区的执行过程不能被打断。

死锁问题:
我理解的死锁就是一个进程尝试去获取一个不可能施放的锁,进而陷入拥塞状态。比如自锁:进程想要获取自己已经获取的一个锁。互锁(ABBA):进程1有锁A想要获取锁B,进程2持有锁B而想要获取锁A。
如何解决死锁:
1.按顺序获取锁,并按照相反顺序施放锁;
2.在进程开始就尝试获取它所需要的全部锁,如果没有办法获取那么久不执行;
3.防止饥饿:如果一个进程阻塞,就隐性的施放他所持有的所有锁;
4.锁的设计应当力求简单,越是复杂的锁越是容易出现问题。

常用同步手段

因为比较熟悉了,下面对比的来说三种锁:

  • 自旋锁:开销小;适合短时间持有;因为不会休眠所以适用于不可休眠的代码段,比如中断上下文。
  • 互斥体与信号量:互斥体可以理解为互斥信号量/二值信号量(不同的是添加了比较多限制,下面会分析),相对于自旋锁,开销相对较大,但是如果想要长期持有可以使用互斥量;同时,由于互斥体可能会产生休眠,所以就不适用那些不能睡眠的代码。信号量与互斥体的不同除了一些限制,信号量还可以是多值信号量,因此,内核抢占过程中,如果一个进程处于受信号量保护的临界区,抢占可以发生,因为还有别的信号量可以使用,但是因为互斥体是单值的,因此它和自旋锁一样此时不能发生抢占。
  • 对比信号量和互斥体的结构:

信号量的结构体:

struct semaphore {
	atomic_t count;
	wait_queue_head_t wait;
};
-----------------------------------------------------------
struct __wait_queue_head {
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

转化一下:

struct semaphore {
	atomic_t count;
	spinlock_t lock;
	struct list_head task_list;
};

互斥体:

struct mutex {
	/* 1: unlocked, 0: locked, negative: locked, possible waiters */
	atomic_t		count;
	spinlock_t		wait_lock;
	struct list_head	wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
	struct thread_info	*owner;
	const char 		*name;
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
};

CONFIG_DEBUG_MUTEXESCONFIG_DEBUG_LOCK_ALLOC为编译选项,他们置位之后就会对mutex产生很多约束:包括mutex只能为单值;在该线程中获取的mutex必须在mutex中施放;禁止递归上锁;禁止在中断服务程序中上锁等。其他的可以发现,信号量与互斥体的结构基本是一样的。

你可能感兴趣的:(linux学习)