- 中断和中断处理.
- 中断: 由于处理器和外设速度上的差异, 在两者协同工作时, 让硬件在需要时再向内核发出信号.
- 特殊的电信号, 处理器接收到后, 交予OS来处理.
- 随时可以发生.
- 特定的中断与特定的设备相关联, 且内核知道这些信息.
- 异常是同步中断, 产生时必须考虑与CPU 时钟同步.
- 中断处理程序.
- 特定的类型声明的C函数.
- 上半部与下半部. 既要快速运行,又要完成尽可能多的工作量
- 上半部: 在所有中断被禁止时, 只做严格优先的工作.
- 下半部: 能被允许稍后完成的工作.
- 使用中断的设备, 其相应的驱动程序需要注册中断处理程序.
- 当一个中断处理程序在执行时,相应的中断线在所有处理器上都被屏蔽掉. 所以不会重入.
- 中断上下文.
- 由于没有后备进程, 所以不可以睡眠, 同时也不能调用可能睡眠的函数.
- 中断: 由于处理器和外设速度上的差异, 在两者协同工作时, 让硬件在需要时再向内核发出信号.
- 内核同步方法
- 原子操作.
- 两个原子操作不可能并发地访问同一变量.
- 自旋锁.
- 在短时间内进行轻量级加锁.
- 线程在等待它重新可用时自旋, 这点特别浪费CPU时间, 所以自旋锁不应该被长时间持有.
- 需要禁止本地中断, 来防止中断自旋.
- 锁的是数据而不是代码, 保护的是临界区中的数据.
- 读写自旋锁
- 对数据的操作可以划分为读/写或消费者/生产者类别时.
-
不能把读锁升级为写锁.
- 语句:
read_lock; write_lock.
- 会导致死锁: 写锁不断自旋,等到读锁被释放,包括自己.
- 当需要写操作时, 要在一开始就直接申请写锁.
- 语句:
- 即使一个线程递归地获得同一读锁也是安全的.
- 照顾读的锁.
- 当读锁被持有时, 写操作为了互斥只能等待.
- 多个读者很容易造成写者饥饿.
- 信号量.
- 睡眠锁, 会导致两次上下文切换.
- 由于可能会睡眠, 需要维护等待队列以及唤醒的开销. 它只适合于锁会被长时间持有的情况.
- 持有信号量时可以睡眠, 而自旋锁是不允许睡眠的.
- 由于锁被争用时会睡眠, 所以只能在进程上下文中获取信号量. 因为在中断上下文种不能进行调度.
- 持有信号量的代码可以被抢占.
- 信号量可以同时允许任意数量的锁持有者.
- 互斥信号量(只允许一个持有者), 计数信号量(>1).
- 两个原子操作 P,V. down/up.
- 读写信号量. 都是互斥信号量. 只对写者互斥.
-
downgrade_write
: 将写锁降级为读锁.
-
- 互斥体.
- mutex. 更简单的睡眠锁.
- 更多的限制: 在同一上下文上锁和解锁. 不允许递归地上锁和解锁. 当持有一个mutex时,进程不能退出.
- 首选互斥体, 当不满足其某个限制时, 再选择信号量.
- 中断上下文中只能使用自旋锁, 而在任务睡眠时只能使用互斥体.
- 完成变量.
- 简单的信号量的替代.
- 过程:
wait_for_completion + complete
- 顺序锁.
- 用于读写共享数据, 如序列计数器.
- 锁的初值为0, 写锁使值变为奇数, 释放时变为偶数.
- 在读取数据的前后, 序列号都会被读取, 若相同, 则说明读没有被写打断过, 或者读取值为偶数时, 也没有发生过写操作.
- 对写者更有利: 只要没有写者, 就能获得写锁.
- 禁止抢占.
- 如果一个自旋锁被持有, 内核便不能进行抢占. 这是为了保持内核的抢占安全.
- 抢占计数器 = 被持有锁的数量(preempt_enable的次数) - preempt_disable的调用次数.
- 顺序和屏障.
- CPU为了优化其传输管道,打乱了分配和提交指令的顺序.
- 屏障: 指示编译器不要对给定点周围的指令序列进行重新排序.
- 原子操作.