Linux 内核同步

为什么要内核同步

防止共享资源的并发访问导致数据不一致

并发执行的来源

  • 中断
  • 进程切换
  • 多处理器

内核同步的方法

主要就是 信号量 和 自选锁 两种加锁方式

原子操作

内核中定义了原子量类型atomic_t, 可以通过下面的函数来操作原子量

原子数操作 说明
ATOMIC_INIT(int) 初始化原子量
atomic_read(atomic_t *) 读取原子量
atomic_set(atomic_t *, int) 设置原子量
atomic_add(int, atomic_t *) 原子量加
atomic_sub(int, atomic_t *) 原子量减
atomic_inc(atomic_t *) 原子量加1
atomic_dec(atomic_t *) 原子量减1
atomic_inc_and_test(atomic_t *) 原子量加1, 并判断是否为0
atomic_dec_and_test(atomic_t *) 原子量减1, 并判断是否为0
atomic_add_negative(int, atomic_t *) 原子量加, 并判断是否为负数
atomic_sub_and_test(int, atomic_t *) 原子量减, 并判断是否为0
atomic_add_return(int, atomic_t *) 原子量加, 并返回加后的值
atomic_sub_return(int, atomic_t *) 原子量减, 并返回减后的值
atomic_inc_return(atomic_t *) 原子量加1, 并返回加后的值
atomic_dec_return(atomic_t *) 原子量减1, 并返回减后的值

内核中还有对位操作的原子操作函数,这些函数可以操作任何类型的数据. 在这些函数前面加上两个__是他们的非原子版本, 在需要的时候它们可能比原子的版本更快.

原子位操作函数 说明
set_bit(int nr, void *addr) 原子的设置addr指向的地址的第nr位
clear_bit(int nr, void *addr) 原子的清除addr指向的地址的第nr位
change_bit(int nr, void *addr) 原子的改变addr指向的地址的第nr位
test_and_set_bit(int nr, void *addr) 原子的测试addr指向的地址的第nr位是否为1,如果是1,则设置为1,返回1,否则返回0
test_and_clear_bit(int nr, void *addr) 原子的测试addr指向的地址的第nr位是否为1,如果是1,则清除为0,返回1,否则返回0
test_and_change_bit(int nr, void *addr) 原子的测试addr指向的地址的第nr位是否为1,如果是1,则清除为0,返回1,否则返回0
test_bit(int nr, void *addr) 原子的返回addr指向的地址的第nr位

自旋锁

自旋锁最多被一个线程持有, 如果一个线程尝试获取一个已经被持有的自旋锁, 它会一直自旋等待, 直到自旋锁被释放.
由于等待自旋锁会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长,所以自旋锁适用于短时期的轻量级加锁.
在中断处理程序正好就是这样的场景,并且中断处理程序不能睡眠也只能用自旋锁.

死锁

两个情况:

  1. 一个锁: 一个线程获得锁了后又尝试获取锁. 或者获得了锁后睡眠,最坏的情况就是不在被唤醒然后释放锁.
  2. 多个锁: 线程要获取两个锁,但两个线程刚好持有了一个锁. 解决办法: 获取多个锁时应当一直以同样的顺序获得.

自旋锁API

自旋锁操作函数 说明
spin_lock_init(spinlock_t *) 初始化自旋锁
spin_lock(spinlock_t *) 获取自旋锁
spin_unlock(spinlock_t *) 释放自旋锁
spin_lock_irq(spinlock_t *) 获取自旋锁并关闭中断
spin_unlock_irq(spinlock_t *) 释放自旋锁并开启中断
spin_lock_irqsave(spinlock_t *, unsigned long flags) 获取自旋锁并保存中断状态
spin_unlock_irqrestore(spinlock_t *, unsigned long flags) 释放自旋锁并恢复中断状态
spin_trylock(spinlock_t *) 非阻塞尝试获取自旋锁,成功返回1,失败返回0

读着写者自旋锁

允许任意数量的读者同时进入临界区, 但是只允许一个写者进入临界区, 且写者有优先权, 当有写者等待时, 不允许读者进入临界区.

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */ 
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* Dynamic way */

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);
void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);

void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int  write_trylock(rwlock_t *lock);
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);

补充: lockmeter 工具可以用来检测自旋锁的性能.

信号量

信号量可以使等待资源线程进入休眠状态,适用于占用资源比较久的场合
信号量会引起休眠,中断不能休眠,所以信号量不能用于中断
如果共享资源的持有时间比较短,不适合使用信号量,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的优势,此时使用自旋锁。

信号量API

函数 说明
DEFINE_SEMAPHORE(sem); 定义一个信号量
sema_init(sem, int val); 初始化信号量
down(sem); 获取信号量
down_trylock(sem); 尝试获取信号量,成功返回0,失败返回非0
down_interruptible(sem); 获取信号量,可以被信号(signal,比如ctrl+c 产生的SIGINT)中断
up(sem); 释放信号量

读写信号量

函数 说明
sema_init_rwsem(struct rw_semaphore *sem) 初始化读写信号量
down_read(struct rw_semaphore *sem) 获取读信号量
down_read_trylock(struct rw_semaphore *sem) 尝试获取读信号量
up_read(struct rw_semaphore *sem) 释放读信号量
down_write(struct rw_semaphore *sem) 获取写信号量
down_write_trylock(struct rw_semaphore *sem) 尝试获取写信号量
up_write(struct rw_semaphore *sem) 释放写信号量
downgrade_write(struct rw_semaphore *sem) 降级写信号量为读信号量

互斥量

Linux 提供了专门的互斥体mutex (等效信号量为1)

函数 说明
DEFINE_MUTEX(mutex); 定义一个互斥体
mutex_init(mutex); 初始化互斥体
mutex_lock(mutex); 获取互斥体
mutex_trylock(mutex); 尝试获取互斥体,成功返回0,失败返回非0
mutex_unlock(mutex); 释放互斥体
mutex_is_locked(mutex) 判断mutex是否被获取,是返回1,不是返回0

Completions 机制

一种常见的情况: 当前线程等待某个动作结束(可能是创建一个线程,对一个进程的请求,或者基于硬件的动作). completions 是Linux内核针对这种场景提供的一种轻量级同步机制.

<linux/completion.h>

DECLARE_COMPLETION(my_completion); 
//or
struct completion my_completion;
init_completion(&my_completion);

void wait_for_completion(struct completion *c); 

void complete(struct completion *c); //多个等待者的情况只会唤醒一个
void complete_all(struct completion *c);//唤醒所有等待者, 一个等待者情况和complete一样
posted @ 2025-03-03 20:43  天刚刚破晓  阅读(33)  评论(0)    收藏  举报