目录
1 并发与竞争
并发:在操作系统中,一个时间段中有几个程序同时处于就绪状态,等待调度到CPU中运行
竞争:两个或两个以上的进程同时访问一个资源
并发机制:原子变量操作、自旋锁、信号量、完成量
2 原子变量操作
绝对不会在执行结束前被任何其他任务或事件打断(不可被打断操作)
优点:编写简单;
缺点:功能太简单,只能做计数操作,保护的东西太少。
typedef struct {
volatile int counter;
} atomic_t;
linux中定义了两种操作方法:(1)原子整型操作;(2)原子位操作
2.1 原子整型操作
atomic_t count;
定义了一个atomic_t类型的变量
atomic_t类型变量只能通过Linux内核中定义的专用函数来操作
(1)定义atomic_t变量
ATOMIC_INIT的功能:定义一个atomic_t类型的变量。宏定义如下:
#define ATOMIC_INIT(i) { (i) }
定义一个名为count的atomic_t类型变量的方法:
atomic_t count = ATOMIC_INIT(0);
(2)设置atomic_t变量的值
//设置变量v的值为i
#define atomic_set(v, i) (((v)->counter) = i)
(3)读atomic_t变量的值
//返回原子类型变量v的值
#define atomic_read(v) ((v)->counter)
(4)原子变量的加减法
atomic_add()函数用来将第一个参数i的值加到第二个参数v中:
static inline void atomic_add(int i, volatile atomic_t *v)
atomic_sub()函数从原子变量v中减去i的值:
static inline void atomic_sub(int i, volatile atomic_t *v)
(5)原子变量的加减法
atomic_inc()函数用来将v指向的变量加1:
static inline void atomic_inc(volatile atomic_t *v)
atomic_dec()函数用来将v指向的变量减1:
static inline void atomic_dec(volatile atomic_t *v)
(6)加减测试
atomic_inc_and_test()函数用来将v指向的变量加1;若结果为0,返回真,若结果为非0,返回假:
static inline int atomic_inc_and_test(volatile atomic_t *v)
atomic_dec_and_test()函数用来将v指向的变量减1;若结果为0,返回真,若结果为非0,返回假:
static inline int atomic_dec_and_test(volatile atomic_t *v)
2.1 原子位操作
原子位操作不需要专门定义一个类似atomic_t类型的变量,只需要一个普通的指针就行:
// 将addr变量的第nr位置1
static inline void set_bit(int nr, volatile unsigned long *addr);
// 将addr变量的第nr位置0
static inline void clear_bit(int nr, volatile unsigned long *addr);
// 将addr变量的第nr位置为相反的数
static inline void change_bit(int nr, volatile unsigned long *addr);
//将addr变量的第nr位置1,并返回没修改的值
static inline int test_and_set_bit(int nr, volatile unsigned long *addr);
//将addr变量的第nr位置0,并返回没修改的值
static inline int test_and_clear_bit(int nr, volatile unsigned long *addr);
//将addr变量的第nr位置为相反的数,并返回没修改的值
static inline int test_and_change_bit(int nr, volatile unsigned long *addr);
3 自旋锁
Linux中提供了一些锁来避免竞争条件
自旋锁类型为:struct spinlock_t;
(1)定义和初始化
一个自旋锁必须初始化才能使用,对自旋锁的初始化可以在编译阶段通过宏来实现:
spinlock_t lock;
初始化自旋锁:
spinlock_t lock=SPIN_LOCK_UNLOCKED;
运行阶段,使用spin_lock_init()函数动态地初始化一个自旋锁:
void spin_lock_init(spinlock_t lock);
(2)锁定自旋锁
进入临界区前,需要用spin_lock宏来获得自旋锁:
// 如果能立即获得自旋锁,则立即返回,否则一直自旋在那里,直到被其他线程释放
#define spin_lock(lock) _spin_lock(lock)
(3)释放自旋锁
// 当调用宏后,锁立即被释放
#define spin_unlock(lock) _spin_unlock(lock)
(4)自旋锁使用
spinlock_t lock;
spin_lock_init(&lock);
spin_lock(&lock);
// 临界资源
spin_unlock(&lock);
注意事项:
- 自旋锁是一种忙等待,所有不应该长时间持有
- 自旋锁不能递归调用
4 信号量
与自旋锁相比,信号量只有当得到信号量的进程处于执行状态时才能够进入临界区
信号量与自旋锁的不同点:当一个进程试图去获取一个已经锁定的信号量时,进程不会像自旋锁那样在远处忙等待
当获取信号量没有释放时,进程会将自身加入一个等待队列中去睡眠,直到拥有信号量的进程释放后,睡眠才被唤醒
4.1 信号量的实现
信号量定义:
struct semaphore{
spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
(1)lock自旋锁
当count要变化时,内部锁定lock锁;当修改完成后,会释放lock锁。
(2)count变量
count==0:表示信号量被其他进程使用,现在不可用,但wait_list队列中没有进程在等待信号量
count<0:表示自身有一个进程在wait_list队列中等待信号量被释放
count>0:表示这个信号量是空闲的,可以使用
信号量根据count的值设定有多少个进程持有这个信号量
(3)等待队列
存放正在睡眠的进程链表
4.2 信号量的使用
(1)定义和初始化
定义:
struct semaphore sema;
初始化:
// 设置sem中的count值为val
static inline void sema_init(struct semaphore *sem, int val);
**初始化一个信号量的值为1 (互斥体):
#define init_MUTEX(sem) sema_init(sem, 1)
**初始化一个信号量的值为0:
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)
(2)锁定信号量
进入临界区前,使用down()获得信号量:(不能用在上下文中断)
void down(struct semaphore *sem);
down()进入睡眠之后就不能被信号唤醒
down_interruptible()进入睡眠后可以被信号唤醒
// 被信号唤醒,返回非0
int down_interruptible(struct semaphore *sem);
(3)释放信号量
void up(struct semaphore *sem);
(4)同步操作
同步:一个线程的执行需要依赖另一个线程的执行
**自旋锁与信号量的对比:
- 被包含的代码能够在短时间内执行完成,使用自旋锁;自旋锁只是忙等待,不会进入睡眠
- 信号量用来在多个进程之间互斥;在一个进程对被保护的资源占用时间比进程切换时间短时,使用信号量
5 完成量
一个线程发送一个信号通知另一个线程开始执行某个任务
5.1 完成量的实现
完成量定义:
struct completion{
unsigned int done;
wait_queue_head_t wait;
};
(1)done
用于维护一个计数。
初始化时,done=1;
done==0,拥有完成量的线程置于等待状态。done>0,等待完成量的函数立即执行。
(2)wait
所有等待该完成量的任务组成一个链表结构
5.2 完成量的使用
(1)定义和初始化
定义:
struct completion com;
初始化函数定义:
static inline void init_completion(struct completion *x)
{
x->done=0;
init_waitqueue_head(&x->wait);
}
**还可以使用宏DECLARE_COMPLETION定义和初始化完成量:
#define DECLARE_COMPLETION(work) \
struct completion = COMPLETION_INITIALIZER(work)
#define COMPLETION_INITIALIZER(work) \
{ 0, _WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }
(2)等待完成量
// 执行一个不被信号中断的等待
void __sched wait_for_completion(struct completion *X);
调用这个函数之后,若没有线程完成这个信号量,执行这个函数wait_for_completion的线程将一直等待下去
(3)释放完成量
// 唤醒一个等待进程
void complete(struct completion *x);
// 唤醒所有等待进程
void complete_all(struct completion *x);
唤醒之后,wait_for_completion函数之后的代码才可以继续执行