《linux内核设计与实现》--4、内核同步

  • 内核同步介绍
    • 临界区
      • 访问和操作共享数据的代码段。为避免并发访问,需保证临界区的原子性。
    • 竞争条件
      • 两个执行线程处于同一个临界区中同时执行。出现的概率很低。
    • 同步
      • 避免并发和防止竞争条件。必须认清同步的重要性。
      • 同步问题的处理:
        • 单一变量的情况,操作系统会提供指令院子地读变量、修改变量、再回写变量。
        • 复杂数据结构下的竞争情况,例如:同一队列的入队出队操作。这时候就需要使用锁了。
    • 加锁
      • 每个临界区对应一把锁,进入临界区前,需先持有锁,出来后释放锁,其他线程需先等待直至锁释放。锁的使用是自愿、非强制的。
      • 造成并发执行的原因:
        • 多处理器真并发
        • 高优先级进程抢占,同时需要进入同一临界区,伪并发
        • 中断、软中断、tasklet、内核抢占、睡眠及与用户控件的同步、对称多处理
        • 辨认出真正需要共享数据和相应的临界区,才是真正有挑战性的地方。
        • 中断处理程序中能避免并发访问的安全代码 ,称为中断安全代码(interrupt-saft)。
        • 在对称多处理的机器中能避免并发访问的安全代码,称为SMP安全代码(SMP-safe)。
        • 在 内核抢占时能避免并发访问的安全代码 ,称为抢占安全代码 (preempt-safe)。
    • 死锁
      • 一个或多个执行线程和 一个或 多个资源 ,每个线程都在等待其中一个资源,但是所有的资源 都已经被占用。所有 线程都在相互 等待,也不释放已经占有的资源。任何线程都无法继续,就产生了死锁。
      • 避免死锁的方法
        • 按顺序加锁。使用通用的锁顺序。相应用锁的代码加上注释。
        • 防止发生饥饿。
        • 避免重复请求同一个锁。
        • 设计方案力求简单。
    • 争用和扩展性
      • 锁的争用,是指当锁正在 被占用 时,有其他 线程试图获得该锁。
      • 高度争用状态,多个线程在等待获得该锁。会成为系统的瓶颈,严重降低系统的性能。(锁使用程序串行访问资源,用锁会降低系统性能)
      • 扩展性 ,是对系统可扩展程度的量度。
      • 加锁粒度,用来描述加锁保护的数据规模。
      • 加锁太粗会降低可扩展性;加锁太细会加大系统开销,带来浪费。都会 降低系统性能。设计锁应该力求简单 ,需要时才做进一步的细化加锁方案。
  • 内核同步方法
    • 原子操作
      • 原子操作可以保证指令以原子发方式执行--执行过程不被打断。
      • 内核提供了两组原子操作接口
        • 针对整数进行操作
        • 针对单独的位进行操作
      • 原子整数操作
        • 原子操作最常用于:实现计时器。
        • 原子操作通常是内联函数,往往通过内嵌汇编指令来实现。如果某个函数是原子的,那它往往会被定义成一个宏。
      • 原子位操作
        • 位操作函数是对普通的内存地址进行操作。(0-31 or 0-63)
        • 每个原子位函数都对应一个非原子位函数,它不保证原子性,名字前缀多了两个下划线__。
    • 自旋锁
      • 自旋锁最多只能被一个可执行的线程持有。防止多个执行线程同时进入临界区。
      • 如果一个执行线程试图获得一个已经被持有的自旋锁,那么该线程会一直进行忙循环--旋转等待锁重新可用(特别浪费处理器时间,因此自旋锁不应该被长时间持有)。如果锁未被争用,则线程能立刻得到它。
      • 自旋锁是不可递归的!
      • 自旋锁可以使用在中断处理程序中。中断处理程序在获取自旋锁之前,必须禁止当前处理器的本地中断,否则,中断程序会打断正持有锁的内核代码,有可能试图去争用这个已经被持有的自旋锁;开始自旋;等待锁重新可以用,但是锁的持有者在中断处理程序执行完毕前不可能运行。造成双重请求死锁。
      • 锁的大原则:
        • 需要保护的是数据,而不是代码。应该对数据加锁。
    • 读写--自旋锁
      • 一个或多个读任务可以并发地持有读者锁;用于写的锁最多只能被一个写任务 持有,而且此时不能有并发的读操作。读锁:共享锁;写锁:排斥锁。
      • 读锁有优势,读锁被持有时,写锁只能互斥等待,但是读锁可以继续持有;因此写锁只能等待所有读者释放锁,大量读锁容易使写着处于饥饿状态。
    • 信号量
      • 信号量是一种睡眠锁。如果有一个任务试图获得一个不可用(已被占用)的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时处理器能重获自由,从而去执行其他代码。当持有的信号量可用(被释放)后,处于等待队列中的任务将被唤醒,并获得该信号量。
      • 信号量和自旋锁在使用上的差异:
        • 争用信号量的进程在 等待锁重新变为可用时会睡眠,因此信号量适用于锁会被长时间持有的情况。
        • 锁呗短时间持有时,则不适合用信号量。因为睡眠、维护等待队列、唤醒所花费的开销可能比锁呗占用的全部时间还要长。
        • 执行线程在锁被争用时会睡眠,因此只能在进程上下文中才能获取信号量锁,因为在中断上下文中是不能进行调度的。
        • 持有信号量时,可以睡眠,因为其他进程试图获得同一信号量时不会因此而死锁(睡眠后依旧会执行)。
        • 占用信号量的同时,不能占用自旋锁,因为等待信号量时可能会睡眠,而持有自旋锁是不允许睡眠的。
        • 自旋锁 和信号量的选择,可以根据锁呗持有的时间长短来判断;另外,信号量不会禁止内核抢占,因此不会对调度的等待时间带来负面影响。
      • 信号量同时允许任意数量 的锁持有者,持有者的数量可以再声明信号量时指定。
      • 二值信号量
        • 在一个时刻仅允许有一个锁的持有者,这时计数等于1,称为二值信号量,或互斥信号量。PV操作。
      • 计数信号量
        • 允许在一个时刻至多有n个锁持有者,很少使用。
    • 读--写信号量
      • 所有读写信号量都是互斥信号量,引用计数值等于1;只对写着互斥,不对读者。只要没有写着,并发持有读锁的读者数不限。
      • 读--写机制只有在代码能明确界定出:读--写时才有价值。
    • 互斥体(mutex)
      • 任何可以睡眠的强制互斥锁;例如:使用计数是1的信号量。
      • 也用于一种实现互斥的特定睡眠锁,是一种互斥信号。操作更简单。
      • mutex的使用场景
        • 自旋锁、信号量、互斥体的选择:
        • 任何时刻只有一个任务可以持有mutex;
        • 在同一上下文中上锁和解锁;不能在不同上下文用,不适合内核用户空间复杂的同步场景。
        • 不允许递归上锁和解锁。
        • 进程持有mutex时,不能退出。
        • mutex只能通过官方API管理,不可拷贝、手动初始化或者重复初始化。
      • 自旋锁、信号量、互斥体的选择:
      • ​​​​​​​
    • 完成变量
      • 内核中的一个任务需要发出信号通知另一任务发生某个特定事件。完成变量是使两个任务得以同步的简单方法。
      • 一个任务要执行一些工作时,另一个任务就会在完成变量上等待;当这个任务工作完成后,会使用完成变量去唤醒正在等待的任务。
      • 例如:当子进程执行或者退出时,vfork()系统调用使用完成变量唤醒父进程。
    • 顺序锁
      • 用于读写共享数据,主要依靠一个序列计数器。当有疑义的数据被写入时,会得到一个锁,并且序列值会增加。在读取数据之前和之后,序列号都被读取。如果读取的序列号值相同,说明在读操作进行的过程中没有写操作打断过。如果读取的值是偶数,那么就表明写操作没有发生(因为锁初始为0,写锁会使值变成奇数,释放时则变成偶数)。
      • 顺序锁对写着更有利,只要没有其他写着,写锁总是能够被成功获得。读者不会影响写锁,挂起的写着会不断使得读者操作循环,直到不再有任何写着持有锁为止。
      • 适用场景:
        • 数据存在很多读者
        • 数据写着很少
        • 虽然写者很少,但是希望写优先于读,且不允许读者让写者饥饿。
        • 数据很简单,简单结构,简单整型(不能使用原子量的时候)。
        • 如:jiffies(linux机器启动到当前的时间),get_jiffies_64使用了顺序锁。
      • 禁止抢占
        • 自旋锁是抢占安全的,但某些情况并不需要自旋锁,仍然需要关闭内核抢占。
        • 例如:单独处理器中的多进程伪并发访问变量。这个时候并不需要加锁,可以通过禁止内核抢占的方式解决。
    • ​​​​​​​顺序和屏障
      • 在多处理器上,可能需要按写数据的顺序读数据,但编译器和处理器为了提高效率,可能对读和写重新排序;因此,需要有机制来确保顺序要求;确保顺序的指令称为屏障。
      • 保证变量读写顺序的屏障函数如下:

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值