一、为什么要有同步机制
在操作系统引入了进程概念,进程成为调度实体后,系统就具备了并发执行多个进程的能力,但也导致了系统中各个进程之间的资源竞争和共享。对于这些交错路径执行的内核路径,如不采取必要的同步措施,将会对一些关键数据结构进行交错访问和修改,从而导致这些数据结构状态的不一致,进而导致系统崩溃。因此,为了确保系统高效稳定有序地运行,linux必须要采用同步机制。
二、内核同步机制的手段
信号量;原子操作;自旋锁;异步通知
三、信号量
3.1 信号量的作用
信号量本质上就是一个可以操作的数,这个数可以进行加或者是减的操作,加的时候相当于信号的释放,减的时候相当于信号的消耗,当信号量消耗到0的时候 表示资源已经被消耗完,除非当别的程序释放了信号量。
在IPC通讯里边,主要用于进程间的同步与互斥;
在多线程中,主要用于线程间的同步与互斥;
在内核中,主要用于内核的同步与互斥;
3.2 信号量相关的API
信号量的创建:
静态创建:
#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
name:信号量的核心结构体的名字
静态创建的时候会默认的将信号量的值设置成1
动态创建:
函数的功能:动态的创建一个信号量
函数的头文件:<linux/semaphore.h>
函数的原型:void sema_init(struct semaphore *sem, int val)
函数的参数:
struct semaphore *sem:信号量的核心结构体
int val:信号量的初值
函数的返回值:无
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
信号量的核心结构体
struct semaphore {
raw_spinlock_t lock; //自旋锁
unsigned int count; //信号量的值,当信号量的值为正数的时候 ,表示信号量可用
当信号量的值为0的时候表示信号量不可以用
struct list_head wait_list;
};
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
信号量的操作
头文件:<inux/semaphore.h>
信号量的消耗 (P操作)
void down(struct semaphore *sem);
阻塞的消耗一个信号量,当信号量消耗不成功,就会陷入阻塞,直到有其他的程序释放了信号
int down_interruptible(struct semaphore *sem);
阻塞的请求一个信号量,当成功请求到信号后马上返回,
当请求失败时会陷入阻塞,直到有其他的程序释放了信号或者是产生了中断
int down_trylock(struct semaphore *sem);
非阻塞的消耗一个信号量,假如信号量消耗成功 返回0,假如信号量消耗失败 返回1
信号量的释放 (V操作)
void up(struct semaphore *sem);
释放信号量,对信号量的成员count进行加1
四、原子操作
4.1 什么叫做原子操作
原子操作---在linux操作系统里,叫做不可分割操作;
原子操作:就是赋值,这个赋值跟我们之前所学的赋值又有些不一样,原子操作的赋值是不可被打断的,也就是说用原子操作进行赋值,必然会赋值成功,不会被打断。
4.2 原子操作相关的结构体
typedef struct {
int counter; //原子操作的对象
} atomic_t;
4.3 原子操作相关的API
头文件:#include <asm/types.h>
设置原子的值: atomic_set(v, i):
参数
v:原子的结构体的指针
i:你要给原子赋的值
++++++++++++++++++++++++++++++++++++++++++++++
读取原子的值:int atomic_read(const atomic_t *v);
const atomic_t *v:原子的核心结构体指针
+++++++++++++++++++++++++++++++++++++++++++++++
原子值进行加操作:atomic_add(int i, atomic_t * v);
参数:
i:要给原子值加上的数值
v:原子的核心结构体指针
++++++++++++++++++++++++++++++++++++++++++++++++
原子值的减操作:atomic_sub(int i, atomic_t * v);
参数:
i:要减去的值
v:原子的核心结构体指针
+++++++++++++++++++++++++++++++++++++++++++++++++
原子的自加: atomic_inc(atomic_t * v);
+++++++++++++++++++++++++++++++++++++++++++++++++
原子的自减:atomic_dec(atomic_t * v);
+++++++++++++++++++++++++++++++++++++++++++++++++
判断原子值是否为1:atomic_dec_and_test(atomic_t * v)
返回值:
如果为1,则返回真
否则返回0
五、自旋锁
5.1 自旋锁、互斥锁
在内核里也有互斥锁
初始化锁:mutex_init();
上锁:mutex_lock();
mutex_trylock();
解锁:mutex_unlock();
在内核里互斥锁的用法跟在多线程编程里使用的方法是一样的
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
自旋锁是嵌入里比较常用的一种底层锁,下面是优缺点:
缺点:占用的资源比较多,类似于while(1)会不停的询问条件是否能满足;
优点:反应比互斥锁要及时;
5.2 自旋锁相关的API
函数功能:初始化一个自旋锁的核心结构体
函数的头文件:linux/spinlock.h
函数的原型:spin_lock_init(spinlock_t *lock)
函数的参数:
spinlock_t *lock:自旋锁的核心结构体的指针,需要声明一个这样的结构体
函数的返回值:无
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
函数的功能:阻塞的申请一个自旋锁
函数的头文件:linux/spinlock.h
函数的原型:void spin_lock(spinlock_t *lock)
函数的参数:spinlock_t *lock:自旋锁的核心结构体的指针
函数的返回值:无
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
函数的功能:非阻塞的请求一个自旋锁
函数的头文件:linux/spinlock.h
函数的原型:int spin_trylock(spinlock_t *lock)
函数的参数:spinlock_t *lock:自旋锁的核心结构体的指针
函数的返回值:
成功 返回0
失败 返回非0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
函数的功能:释放一个自旋锁
函数的头文件:linux/spinlock.h
函数的原型:void spin_unlock(spinlock_t *lock);
函数的参数:spinlock_t *lock:自旋锁的核心结构体的指针
函数的返回值:无