操作系统实战 45 讲
彭东
网名 LMOS,Intel 傲腾项目关键开发者
65203 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 60 讲
尝尝鲜:从一个Hello到另一个Hello (2讲)
特别放送 (1讲)
操作系统实战 45 讲
15
15
1.0x
00:00/00:00
登录|注册

09 | 瞧一瞧Linux:Linux的自旋锁和信号量如何实现?

arch_write_lock
arch_read_lock
__read_lock_failed
__write_lock_failed
arch_write_unlock
arch_read_unlock
arch_rwlock_t
up
__up
down
__down_common
struct semaphore
__raw_spin_unlock函数
__raw_spin_lock函数
raw_spinlock_t数据结构
spin_unlock_string宏
spin_lock_string宏
spinlock_t数据结构
local_irq_restore
local_irq_save
local_irq_disable
local_irq_enable
raw_local_irq_restore
raw_local_irq_save
raw_local_irq_enable
raw_local_irq_disable
native_irq_enable
native_irq_disable
native_restore_fl
native_save_fl
arch_atomic_dec
arch_atomic_inc
arch_atomic_sub
arch_atomic_add
arch_atomic_set
arch_atomic_read
atomic64_t
atomic_t
读写锁操作函数
读写锁的数据结构
信号量操作函数
信号量的数据结构
Linux排队自旋锁
Linux原始自旋锁
中断控制宏
CPU中断控制函数
Linux的原子变量操作函数
Linux的原子变量定义
Linux读写锁
Linux信号量
Linux自旋锁
Linux控制中断
Linux的原子变量
Linux的自旋锁和信号量如何实现?

该思维导图由 AI 生成,仅供参考

你好,我是 LMOS。
上节课,我们学习了解决数据同步问题的思路与方法。Linux 作为成熟的操作系统内核,当然也有很多数据同步的机制,它也有原子变量、开启和关闭中断、自旋锁、信号量。
那今天我们就来探讨一下这些机制在 Linux 中的实现。看看 Linux 的实现和前面我们自己的实现有什么区别,以及 Linux 为什么要这么实现,这么实现背后的机理是什么。

Linux 的原子变量

首先,我们一起来看看 Linux 下的原子变量的实现,在 Linux 中,有许多共享的资源可能只是一个简单的整型数值。
例如在文件描述符中,需要包含一个简单的计数器。这个计数器表示有多少个应用程序打开了文件。在文件系统的 open 函数中,将这个计数器变量加 1;在 close 函数中,将这个计数器变量减 1。
如果单个进程执行打开和关闭操作,那么这个计数器变量不会出现问题,但是 Linux 是支持多进程的系统,如果有多个进程同时打开或者关闭文件,那么就可能导致这个计数器变量多加或者少加,出现错误。
为了避免这个问题,Linux 提供了一个原子类型变量 atomic_t。该变量的定义如下。
typedef struct {
int counter;
} atomic_t;//常用的32位的原子变量类型
#ifdef CONFIG_64BIT
typedef struct {
s64 counter;
} atomic64_t;//64位的原子变量类型
#endif
上述代码自然不能用普通的代码去读写加减,而是要用 Linux 专门提供的接口函数去操作,否则就不能保证原子性了,代码如下。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了Linux内核中的自旋锁和信号量,这两种机制是用来解决数据同步问题的重要工具。首先,文章详细介绍了Linux中的原子变量的实现,展示了Linux如何保证对共享资源的原子操作。其次,对中断控制函数进行了详细讲解,包括保存和恢复eflags寄存器以及开启和关闭中断的操作。最后,文章介绍了Linux中的自旋锁,包括原始自旋锁和排队自旋锁的实现原理和优化。通过这些机制,Linux内核保证了对共享资源的安全访问和操作。此外,文章还介绍了Linux中的信号量,包括单值信号量和多值信号量的使用案例,以及信号量的工作原理和对进程状态的影响。通过本文的阐述,读者可以深入了解Linux内核同步机制的实现细节和工作原理,为进一步学习和应用提供了重要参考。 文章还介绍了Linux中的读写锁,它是一种适用于读取数据频率远大于修改数据的情况的锁机制。通过对读写锁的原理和实现进行详细讲解,读者可以了解其工作方式和优势。此外,文章还总结了Linux上实现数据同步的五大利器,包括原子变量、中断控制、自旋锁、信号量和读写锁,强调了在设计算法时应尽量避免使用锁,以降低性能损失。 总的来说,本文通过深入介绍Linux内核中的同步机制和锁机制,为读者提供了全面的技术视角和实践经验,有助于读者更好地理解和应用这些关键概念。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《操作系统实战 45 讲》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(58)

  • 最新
  • 精选
  • Qfeng
    置顶
    回答思考题:Linux 的读写锁,因为每次读锁加锁计数-1,所以最多支持0x01000000个进程并发读取共享数据。 这样的读写锁的不足:读或者写锁拿不到时忙等,可以优化成trylock,拿不到可以先干其他的,等一段时间再尝试拿锁。(不知道回答的对不对) 感悟:不论是单值信号量还是多值信号量,亦或是原始自旋锁、trylock版本自旋锁还是读写锁,各种机制的设计和优化都是为了资源(CPU等)的更合理更高效的使用而优化。互斥机制有很多,理解每种锁机制重要,但是理解我们的业务更重要,这样才能因地制宜选择合适的锁。 老师简明扼要,点到即止的文风太赞了,谢谢。

    作者回复: 是的

    2022-03-29
    2
    7
  • springXu
    置顶
    同步与锁 操作系统是让应用程序认为当前有超大的内存空间和强大的cpu来工作的硬件环境,但其实硬件没有这么强大。那如何解决呢?比如在单核cpu上可以用分时技术,让进程交替执行。对于一个进程来说,我们可以把一个进程变成了多个线程来执行。但这样就产生了同一个资源可以是内存的某一具体地址,可以是鼠标可以是磁盘上的某一文件被多个线程访问和修改的问题。这两节课提供了解决思路,一个是cosmos操作系统的方案,一个是linux的方案。 1.原子性。就是硬件执行指令时不被打断。对于x86是复杂指令集。一条指令可以做读修改内存值的操作,指令集中直接支持锁定操作。对于精简指令集,就相对麻烦些,硬件会提供bitband的操作。 2.中断控制是在执行时,防止中断信号突然来了把当前执行的过程打断了。 解决方法就是关闭中断。让中断信号等到可以通知时,才发起通知。 3.自旋锁。在多核的cpu环境下,当前核心的cpu要访问的资源是有可能被其他核的cpu来访问的。如果产生这种情况,那就让其他核的cpu自己执行空转。一直到当前核心的cpu把访问资源让出后,其他核的cpu通过检测到了可以访问资源,不在空转执行相关操作恢复正常运行。而这个过程就是自旋锁。这里会有一点浪费cpu的运行效率。毕竟有个cpu在空转。当空转时间过长时,浪费的效能更大。我们需要更好的利用cpu核的方式来解决这个问题。那就是互斥。 4.信号量 对于单一资源的信号量也可以说是互斥锁。 互斥锁和自旋锁的区别就是原来那个空转的核不再空转,而是把当前运行的线程或者进程睡眠去执行其他的线程或者进程了。 当资源被适当后,去通知睡眠线程或者进程。这就是信号量。linux下新版本的信号量在被移除。

    作者回复: 你好,铁子总结 到位

    2021-05-30
    3
    19
  • 老男孩
    置顶
    这个排队自旋锁的实现方式感觉很风骚啊。关于读锁最大支持进程数是0x01000000(学友们都已经解答了)关于写饥饿的问题,既然写锁和读锁在同时获取锁状态时候写锁优先,那么就应该对读锁做一个限制,不能让读锁朝着最大数奔去。比如,系统检测到有写锁在等待,那么就限制新的读锁加入,等已经存在的读锁都释放了,写锁马上加锁更新资源。然后等待的读锁再开始加锁读取。这个等待的队列要分为读锁队列和写锁队列。优先处理写锁队列,在没有写锁的时候才能继续加读锁,如果有写锁等待,那么新的读锁不管超没超出那个最大数,都要进入读锁队列等待写锁完成后再开始自己的表演。

    作者回复: 嗯嗯见解独到

    2021-05-28
    4
    34
  • pedro
    以后我就是第一东吹了😁! 像这样的清晰明了,言简意赅的Linux内核源码解读实在是太少了,这样的文章读起来实在是太爽了,强烈小编安排一下东哥的下一个专栏叫做 《纵览Linux源码,小白也能学透》。 对于思考题答案,读并发进程的最大个数就是0x01000000,只要lock大于0都是可以共享数据的。 至于读写锁的不足,我个人觉得最不友好的点在于读写互斥上,由于读锁对写锁是互斥的,如果一直有人读,那么计数器一直小于0x01000000,加写锁时也一直小于0,写锁一直也不会成功,会陷入长时间的写饥饿状态,并且一直自旋,浪费CPU资源。 所以改进点就在于,给写进程配上一个休眠队列,待加锁失败进入队列休眠等待,待解读锁时判断计数器,决定是否唤醒队列中的写进程。 当然还有很多其它的优化点,欢迎大家集思广益~

    作者回复: 哈哈 欢迎

    2021-05-28
    2
    29
  • blentle
    回答一下思考题 1.理论上可以支持x01000000这么多进程,但实际上受限于文件句炳也就是文件描述符的限制,还有考虑多个线程的问题等等,注定最终远远小于这个值 2.读写锁造成写饥饿的情况是不是可以参考jdk的读写锁的实现,在条件等待队列中判断队列第一个元素是不是一个写进程,如果是写进程,让其直接优先获取锁.

    作者回复: 嗯嗯

    2021-05-28
    15
  • GeekYanger
    文中: “//Linux没有这样的结构,这只是为了描述方便 typedef struct raw_spinlock { union { unsigned int slock;//真正的锁值变量 u16 owner; u16 next; } }raw_spinlock_t;” 这里老师为了帮助我们理解汇编代码构造了一个这样的结构体,我觉得,这个owner和next要被包在一个struct中才是老师想要表述的意思,不然owner和next的取值是一样的,都是低16位。

    作者回复: 对 对 对 我大意了

    2021-12-13
    3
    6
  • 子青
    老师,我有两个问题想请教 1 。Linux自旋锁,如果一个进程在获取锁之后挂了怎么办,没人给owner +1了,后面排队的进程岂不是永远等不到锁释放? 2。信号量那里,down是在链表的头部插入,up是唤醒链表的头部,这样不会有饥饿问题吗,链表后面的可能永远拿不到资源?

    作者回复: 1.进程调用自旋锁,是在内核态运行的内核代码,如果在这个代码路径上挂了,那就说明内核有BUG 需要修正 2. up之后会对进程的优先级进行处理的,不会后面的进程没机会的

    2021-09-23
    2
    4
  • Geek_8c4220
    为什么这节里实现自旋锁的时候都没有关中断了呢?

    作者回复: 因为我没有讲

    2021-06-09
    4
  • doos
    感觉不学习汇编和c很多都看不懂

    编辑回复: 看你的目的,为了理解这门课,很多内容可以现搜的。汇编到初始化那里,后面都是C。具体有啥不懂之处,你可以再留言提问。

    2022-04-21
    3
  • 疯码
    请问下为什么保存和恢复eflags那段代码用push pop而不是mov呢

    作者回复: eflags寄存器不能使用mov指令访问

    2022-01-18
    3
收起评论
显示
设置
留言
58
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部