一、并发
并发就是多个进程同时、并行执行。在但处理器下,并发只是宏观上的并发,用户感觉是多个进程共同执行,其实只是多个进程轮流占用处理器运行,只有在多处理器的情况下才会实现真正的同时执行。
二、临界区
临界区就是访问和操作共享数据的代码段。进程并发访问临界区的数据和操作共享资源是不安全的。如果两个进程同时访问临界区,那就会发生资源抢夺,这种情况就叫做竞争条件。避免并发和防止竞争条件被称为同步。
三、内核造成并发执行的原因
(1)中断:中断是随时可能产生,内核一旦接收到中断,就会优先执行中断。如果中断代码中修改了之前运行进程的共享资源,就会出现bug。
(2)内核抢占:支持内核抢占的情况下,正在执行的进程随时可能被抢占。
(3)睡眠:当在内核中执行的进程睡眠时,就会唤醒调度程序,调度新的进程执行。
(4)多处理器:多个处理器就能同时执行多个进程。
四、单CPU下
1、(1)在单处理器不支持抢占的情况下,运行在内核的两个内核进程,并不会产生并发。(2)中断上下文和普通进程之间,会产生并发。内核进程在执行时,随时可能被中断打断,所以临界区的代码,可以通过关闭中断来避免并发。
2、(1)单处理器支持抢占的情况下,运行在内核的两个进程,会产生并发。访问临界区时需要关抢占。
(2)中断上下文与普通内核进程之间,会产生并发。在临界区代码,可以通过关闭中断来避免并发。
五、SMP下
1、多处理器抢占式内核的内核同步需要防:
(1)防内核抢占
(2)防中断打断
(3)防其他处理器
接下来主要介绍以下如何防其他处理器。
2、自旋锁
代码在进入临界区前加上锁,在进程还没出临界区之前,别的进程(包括自身处理器和别的处理器的进程)都不能进入临界区。
自旋锁可理解为,每个进程进入上锁的临界区前,必须先获得锁,否则在获得锁这边代码上查询(忙等待),直到临界区里面的进程走出临界区,别的进程获得锁后进入临界区。有且只有一个获得锁的进程进入临界区。
(1)使用自旋锁需要先定义并初始化自旋锁:
1)静态定义并初始化
spinlock_t lock = SPIN_LOCK_UNLOCKED;
2)也可以动态定义并初始化
spinlock_t lock;
spin_lock(&lock);
(2)在进入临界区前,必须先获得锁,使用函数:
spin_lock(&lock);
(3)在退出临界区后,须释放锁,使用函数:
spin_unlock(&lock);
3、信号量
自旋锁不能睡眠,要求临界区执行时间尽可能的短。信号量是一种睡眠锁,当进程试图获取已经别占用的信号量,它就会被放到等待队列中,直到信号量释放后被唤醒。信号量有两种:互斥信号量和计数信号量。互斥信号量就是说同一时间只能有一个进程获得锁并进入临界区。
4、互斥量
互斥量是互斥信号量的升级板。使用方法如下:
(1)定义并初始化(两种)
1)静态定义并初始化:
DEFINE_MUTEX(name)
2)动态定义并初始化
struct mutex mutex;
mutex_init(&mutex);
(2)获得互斥量
void inline __sched mutex_lock(struct mutex *lock) //不能获得锁是进入不可中断睡眠
int __sched mutex_lock_interruptible(struct mutex *lock) //进入可中断睡眠
int __sched mutex_trylock(struct mutex *lock) //尝试获得锁
(3)释放信号量
void __sched mutex_unlock(struct *lock)
5、原子操作(atomic_t)
原子操作就是这段代码不会被其他进程打断,加上自旋锁之后的操作也算是原子操作。