当前位置:首页>Linux>linux内核态锁有哪些?

linux内核态锁有哪些?

  • 2026-10-29 21:33:35
linux内核态锁有哪些?
大家好,我是王鸽,今天分享一篇文章关于Linux驱动的内核态的锁。

Linux 中的锁可以按照作用范围和实现机制分为以下几类:

1.1 按作用范围分类

进程级锁

用于协调不同进程之间的资源访问。

线程级锁

用于协调同一进程内不同线程之间的资源访问。

1.2 按实现机制分类

内核态锁

由内核直接实现,用于内核代码中的同步。

用户态锁

由用户空间程序实现,依赖内核提供的系统调用。

如下图

这篇文章主要讲的内核态的锁,内核态锁主要用于内核代码中的同步,防止多个 CPU 核心或中断上下文同时访问共享资源。

对于内核而言,任何一种锁机制都是需要解决多核通信问题的。对于除了RCU锁以外的其他锁而言,它们最低的开销也至少是一对原子访存指令,核心数越多,其原子访存开销也会跟着以O(N2)的级别增长。

在Linux内核驱动中,有多种同步机制,用于保护共享资源,防止竞态条件。以下是常见的锁类型及其区别:

一、自旋锁 (Spinlock)

工作原理:忙等待,循环检查锁状态,不会让出CPU

适用场景:

临界区很小(执行时间短)

中断上下文(不可睡眠)

多核系统

特点:

持有锁时不能睡眠

开销小,无上下文切换

可能导致CPU空转浪费

DEFINE_SPINLOCK(my_lock); spin_lock(&my_lock); // 临界区spin_unlock(&my_lock);
需要注意以下几点:
  • 自旋锁只适用于临界区代码比较短的情况,因为自旋等待的过程会占用CPU资源。
  • 自旋锁不可重入,也就是说,如果一个进程已经持有了自旋锁,那么它不能再次获取该自旋锁。
  • 在持有自旋锁的情况下,应该尽量避免调用可能会导致调度的内核函数,比如睡眠函数,因为这可能会导致死锁的发生。
  • 在使用自旋锁的时候,应该尽量避免嵌套使用不同类型的锁,比如自旋锁和读写锁,因为这可能会导致死锁的发生。
  • 当临界区代码较长或者需要睡眠时,应该使用信号量或者读写锁来代替自旋锁。
调用的函数如下:

二、互斥锁 (Mutex)

工作原理:阻塞等待,获取不到锁时任务睡眠

适用场景:

临界区可能较长

进程上下文(可睡眠)

特点:

可睡眠,会进行上下文切换

不可在中断上下文使用

支持优先级继承(防止优先级反转)

互斥锁的定义
struct  mutex { raw_spinlock_t      wait_lock; struct list_head wait_list; struct task_struct  *owner; int                 recursion;#ifdef CONFIG_DEBUG_LOCK_ALLOC struct  lockdep_mapdep_map;#endif};

互斥锁的使用非常简单,通常只需要调用两个函数即可完成:

DEFINE_MUTEX(my_mutex); mutex_lock(&my_mutex); // 临界区 mutex_unlock(&my_mutex);

三、信号量 (Semaphore)

工作原理:计数信号量,允许多个任务同时访问

适用场景:

控制对多个资源的访问

生产者-消费者问题

特点:

计数机制(非0/1二元)

可睡眠,可用于进程间同步

有up()和down()操作

DECLARE_SEMAPHORE(my_sem, 5); // 初始值5down(&my_sem);     // P操作,减1// 访问资源 up(&my_sem);       // V操作,加1

四、读写信号量 (rwsem)

工作原理:读写锁,允许多个读者或单个写者

适用场景:

读多写少的共享数据

需要区分读写操作

特点:

读者可并发,写者互斥

读者优先或写者优先(可配置)

可睡眠

DECLARE_RWSEM(my_rwsem); down_read(&my_rwsem);    // 读者 // 读操作up_read(&my_rwsem); down_write(&my_rwsem);   // 写者 // 写操作 up_write(&my_rwsem);

五、完成变量 (Completion)

工作原理:用于一个任务等待另一个任务完成

适用场景:

等待异步操作完成

线程/模块初始化同步

特点:

简单的一次性同步机制

类似信号量但更轻量

DECLARE_COMPLETION(my_comp); // 等待方 wait_for_completion(&my_comp); // 完成方complete(&my_comp);

六、原子操作 (Atomic Operations)

工作原理:硬件级别的原子指令

适用场景:

简单的计数器操作

标志位设置/清除

不需要复杂锁的简单操作

特点:

无锁机制,性能高

只能用于整数或位操作

不可睡眠

atomic_t counter = ATOMIC_INIT(0); atomic_inc(&counter);      // 原子加1 atomic_dec(&counter);      // 原子减1atomic_read(&counter);     // 原子读

七、对比总结

锁类型

睡眠能力

适用上下文

特点

性能开销

自旋锁

不可睡眠

中断/进程

忙等待,短临界区

低(无切换)

互斥锁

可睡眠

进程上下文

阻塞等待,可处理长临界区

中等(有切换)

信号量

可睡眠

进程上下文

计数机制,资源控制

中等

读写信号量

可睡眠

进程上下文

读写分离,读并发

中等

完成变量

可睡眠

进程上下文

一次性同步

低

原子操作

不可睡眠

任何上下文

无锁,硬件原子指令

最低

八、选择建议

中断上下文 → 自旋锁 或 原子操作

短临界区(<µs级) → 自旋锁 或 原子操作

长临界区或可能睡眠 → 互斥锁

读多写少 → 读写锁/读写信号量

资源计数 → 信号量

简单标志/计数器 → 原子操作

等待任务完成 → 完成变量

九、使用注意事项

避免死锁:注意锁的获取顺序

防止优先级反转:互斥锁支持优先级继承

性能考量:锁的粒度要适中,避免过度保护

可重入性:某些锁不可重入(如自旋锁)

调试支持:内核提供lockdep等工具检测锁问题

举个生活中的例子

当我们在买咖啡的时候,柜台前可能会有一个小桶,上面写着“请取走您需要的糖果,每人一颗”这样的字样。这个小桶就是一个信号量,它限制了每个人能够取走的糖果的数量,从而保证了公平性。
如果我们把这个小桶换成互斥锁,那么就可以只允许一个人在柜台前取走糖果。如果使用读写锁,那么在非高峰期的时候,多个人可以同时取走糖果,但在高峰期时只允许一个人取走。
而如果我们把这个小桶换成自旋锁,那么当有人在取走糖果时,其他人就需要一直在那里等待,直到糖果被取走为止。这样可能会造成浪费时间的情况,因为其他人可能有更紧急的事情需要处理。

区别总结:

自旋锁和互斥锁都是互斥锁,但自旋锁忙等待,互斥锁睡眠等待。

信号量可以设置计数,实现多个任务同时访问,而互斥锁只能互斥。

读写信号量是信号量的变种,优化了读多写少的场景。

完成变量用于任务间的同步,而不是互斥。

原子操作是最轻量级的,但只能用于简单的变量操作。

在选择同步机制时,需要考虑临界区的大小、是否在中断上下文、是否需要睡眠、以及并发访问的模式(读多写少等)等因素。
谢谢点赞关注收藏!

最新文章

随机文章