内核中的锁机制

内核锁

多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理

分类

(1)原子操作

atomic_t数据类型,atomic_inc(atomic_t *v)将v加1
原子操作比普通操作效率要低,因此必要时才使用,且不能与普通操作混合使用
如果是单核处理器,则原子操作与普通操作相同

(2)信号量与互斥量

struct semaphore数据类型,down(struct semaphore * sem)和up(struct semaphore * sem)是占用和释放
struct mutex数据类型,mutex_lock(struct mutex *lock)和mutex_unlock(struct mutex *lock)是加锁和解锁
竞争信号量与互斥量时需要进行进程睡眠和唤醒,代价较高,所以不适于短期代码保护,适用于保护较长的临界区,并且内核中越来越倾向于互斥量的使用。

(3)读取者/写入者信号量

一个rwsem可允许一个写入者或无限多个读取者拥有该信号量,写入者具有更高的获取优先级;当某个写入者进入临界区时,在所有写入者完成写入工作之前,不会允许读取者获得访问。如果有大量的写入者竞争该信号量,则这种实现会导致读取者“饿死”。
因此,最好在很少需要写入访问且写入者只会短期拥有信号量的时候使用rwsem。
struct rw_semaphore;
void init_rwsem(struct rw_semaphore *sem);

void down_read(struct rw_semaphore *sem);
void down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);

void down_write(struct rw_semaphore *sem);
void down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);

(4)completion

内核编程中常见的一种模式,在当前线程之外进行某个活动,然后等待该活动的结束。这个活动可能是,创建一个新的内核线程或者新的用户空间进程、对一个已有进程的某个请求,或者某个类型的硬件操作等等。
在这种情况下,我们可以使用信号量来同步这两个任务,示例如下:
struct semaphore sem;
sema_init(&sem,0);//locked
start_external_task(&sem);
down(&sem);
由于信号量的up和down操作可以分开在两个线程中执行,所以在start_external_task中完成任务后,调用up(&sem)。从而会唤醒等待的原线程。(注:对于这种信号量的同步方式,在应用层也可以按照类似的方式进行处理。不过在应用层线程同步使用的是条件变量,可以对条件变量进行封装成为sem的形式,即可按照类似方式使用。)
在内核中使用信号量可以达到同步的目的,不过还有另外一个机制性能更好,那就是completion接口。
DECLARE_COMPLETION(done);
struct completion done;
init_completion(&done);
等待:
void wait_for_completion(struct completion *c);
唤醒:
void complete(struct completion *c);
void complete_all(struct completion *c);

(5)自旋锁

等待解锁的进程将反复检查锁是否释放,而不会进入睡眠状态(忙等待),所以常用于短期保护某段代码。同时,持有自旋锁的进程也不允许睡眠,不然会造成死锁——因为睡眠可能造成持有锁的进程被重新调度,而再次申请自己已持有的锁。
自旋锁最初是为了在多处理器系统上使用而设计的,只要考虑到并发问题,单处理器工作站在运行可抢占内核时其行为就类似SMP。如果非抢占式的单处理器进入某个锁的自旋状态,则会永远自旋下去,因为没有任何其他线程能够获得CPU来释放这个锁。因此,非抢占式的单处理器系统上的自旋锁被优化为不做任何事情,但是改变IRQ掩码状态的例程是个例外。
使用自旋锁的核心规则是:任何拥有自旋锁的代码都必须是原子的,它不能休眠,不能因为任何原因而放弃处理器,除了中断服务程序以外(某些情况此时也不能放弃处理器)。在拥有自旋锁的时候避免休眠有时候很难做到,休眠可能发生在许多无法预期的地方,当我们编写需要在自旋锁下执行的代码时,必须注意每一个所调用的函数。
API:
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
void spin_lock_init(spinlock_t *lock);

void spin_lock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);

由于spinlock可以在中断例程中使用,所以为了防止死锁,会有一些跟中断相关的操作函数api:
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_irq_bh(spinlock_t *lock);

void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_irq_bh(spinlock_t *lock);

void spin_trylock(spinlock_t *lock);
void spin_trylock_bh(spinlock_t *lock);

irqsave版本是在获得锁之前禁止当前CPU的中断,中断状态保存在flags中。irq版本是在获得锁之前禁止当前CPU的中断,不对状态进行保存。bh版本是在获得锁之前禁止软件中断,但是会让硬件中断打开。bh版主要用在硬件中断不访问,软件中断中访问到的锁。

(6)读取者/写入者自旋锁

类似于读取者/写入者信号量,内核也提供了自旋锁的读取者/写入者形式。这种锁允许任意数量的读取者同时进入临界区,但写入者必须互斥访问。
rwlock_t my_rwlock = RW_LOCK_UNLOCKED;
rwlock_t my_rwlock;
rwlock_init(&rwlock);

… …api略过

互斥量与信号量的区别?

(1)互斥量用于线程的互斥,信号量主要用于线程的同步
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
(2)互斥量值只能为0/1,信号量值可以为非负整数
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问
(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程得到,而另一个线程释放,这是它可以用于同步的原因。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值