内核使用内存屏障的场景
和自旋锁相比,信号量有哪些特点,信号量如何实现
自旋锁机制和信号量
自旋锁同一时刻只能被一个内核代码持有,如果有另一个内核代码视图获得一个已经被持有的自旋锁,那么次内核代码需要一只等待,直到只有锁释放锁
自旋锁的特点
- 可以在中断上下文中使用;
- 如果要求,自旋锁需要尽快完成临界区的执行任务
- 忙等待的锁机制
- 统一时间它只能有一个内核代码可以获得锁
typedef struct spinlock {
union{
struct raw_spinlock rlock;
#ifdef config_debug_lock_alloc
#define lock_padsize(offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[lock_padsize];
struct lockdep_map dep_map;
}
#endif
};
}spinlock_t;
中断处理程序要求:“快”或“短“,持有锁的会因为被中断打断页不能尽快释放锁,而中断处理程序一直在忙等待锁,导致死锁发生。
linux 内核自旋锁的变重,在获得自旋锁的时候关闭本地cpu中断就可以解决此问题
```c
static __always_inline void spin_lock_irq(spinlock_t *lock)
{
raw_spin_lock_irq(&lock->rlock)
}
```
local_irq_disable()函数:用于关闭本地处理器中断,这样做的目的在获取自旋锁的时候可以确保不会发生中断,从而避免死锁的问题,spin_lock_irq()
防止本地中断处理程序和持有锁之间存在所的争用。
自旋锁的核心原则: 拥有自旋锁的临界区代码必须是原子执行,不能休眠和主动调度,工作中会出现调用分配内存函数kmalloc()的错误,如果要调用,需要 指定使用‘GFP_ATOMIC',在原子上下文中分配,避免出现kmalloc 阻塞,但是如果使用了也不要在持有自旋锁的时候频繁调用它
信号量
信号量:为操作系统最常用的同步源于,自旋锁是实现一种忙等待的所,信号量允许进程进入睡眠状态,常见的生产者和消费者问题
struct semaphore {
raw_spinlock_t lock;
unisgned int count; //根据 count 统计互斥信号量,
struct list_head wait_list;
}
互斥体
在 Linux内核中,类似信号量的实现称为互斥体,信号量实在并行处理环境中对多个处理器访问某个公共资源进行保护机制,互斥则用于互斥操作 类似count 计数等于1 ,不复用的选择重写的原因
- 性能优化 内核态和用户态的差异,内核需要的是性能,自旋锁和睡眠锁的选择额,内核中的mutex可以智能的选择哪种锁机制
- 特殊功能和特性 优先级继承,内核中支持优先级继承机制,中断上下文支持,内核需要处理中断上下文的锁操作,要求锁的实现能够在禁用中断的情况下工作。
- 可移植性 多架构支持,每种架构有不同的内存模型和同步原语,内核实现需要高度可抑制
- 调试和诊断的能力 内核中的 mutex 实现可以包含丰富的调试信息和诊断工具,死锁检测,内核可以实现复杂的死锁检测机制,帮助开发者发现和解决死锁问题
- 内存管理 内存分配控制内核中的内存管理与用户态的内存管理有显著不同。内核中的 mutex 实现可以更好地控制内存分配和释放,优化内存使用。标准库的 mutex 实现依赖于用户态的内存管理,不适用于内核态
互斥锁的初始化方式: 静态使用DEFINE_MUTEX 宏;在内核代码中动态使用mutex_init()函数 互斥锁的API函数接口:mutex_lock mutex_unlock 互斥锁最先实现自选等待机制 互斥锁在睡眠之前尝试获取锁 互斥锁比信号量实现高效一些 互斥锁实现MCS锁来避免多个cpu急用锁而导致cpu 高速缓存行颠簸现象
struct mutex {
atomic_long_t owner; //用于指向锁持有者的task_struct 数据结构
spinlock_t wait_lock;//等待获得互斥锁中使用的自旋锁,用于保护wait_list睡眠等待队列
#ifdef config_mutex_spin_on_owner
struct optimistic_spin_queue osq;//用户实现mcs锁机制
#endif
struct list_head wait_list
#ifdef config_debug_mutexes
void *magic;
#endif
#ifdef config_debug_lock_alloc
strcut lockdep_map dep_map;
#endif
};
在实战开发当中如何选择信号量,互斥体和自旋锁
如果有中断上下文肯定选择自旋锁,如果临界区有睡眠,避免选择自旋锁,在信号量和互斥体中选择, 其他优先选择互斥体
读写锁(自旋锁和信号量)
信号量的缺点是没有区分临界区的读写属性,读写锁:允许多个线程并发读访问临界区,但是写访问只限制于一个线程,读写锁能够有效提高并发性,在多处理器中允许 同时多个读者访问共享资源,但写是只有一个
- 统一时刻只允许一个写者进入进阶区
- 读者和写着不能同时进入临界区
- 允许多个读者同时进入临界区,但是同一时刻写着不能进入。
常用的API 函数如下: rwlock_init(lock) //初始化rwlock 申请写者锁,释放写者锁,申请读者锁,释放读者锁 关闭中断并且申请读者锁,关闭中断并且申请写者锁,打开中断并且释放写者锁。
读写信号量
struct rw_semaphore{
atomic_long_t count; //用于表示读写信号量的计数
struct list_head wait_list;//链表用于管理所有在该信号量上睡眠的进程,没有成功获取锁的进程睡眠在这个链表上
raw_spinlock_t wait_lock; //自旋锁变量
struct optimistic_spin_queue osq ; mcs 锁
struct task_struct *owner //当写者成功获取锁,owner 指向锁持有者的task_struct数据结构
}
RCU
成为read-copy-update 是linux内核重要同步机制,linux 内核锁机制:原子操作,自旋锁,读写锁,读写信号量,互斥锁等等。 在RCU 中 rcu_read_lock()/rcu_read_unlock()组成一个RCU 读临界。