- 实现内核同步的意义是什么?
目前内核支持SMP,所以共享资源一定要防止并发访问,如果多个执行线程同时访问和操作数据,就可能发生各线程之间相互覆盖共享数据情况,造成被访问数据处于不一致状态,因此我们要了解Linux内核如何解决同步问题和防止产生竞争条件。
1、临界区和竞争条件
- 什么是临界区?
访问和操作共享数据的代码段。 - 什么是竞争条件?
多个执行线程处于同一个临界区。 - 什么是执行线程
指任何正在执行的代码实例。比如,一个在内核执行的进程、一个中断处理程序或一个内核线程等… - 什么是同步?
安排进程/线程执行的先后顺序 - 同步的目的是什么?
避免并发和防止竞争条件
2、 加锁
- 关于锁的基本认识?
- 为了防止竞争条件,我们需要能确保一次有且仅有一个线程对数据结构进行操作,或者当另一个线程在对临界区标记时,就禁止(锁定)其他的访问。
- 我们可以利用锁机制(在其他很多地方都用到这种机制,比如数据库,只要是访问数据都可能用到)。
- 锁的使用是非强制的,程序员自愿的,但是最好在对共享数据访问时使用锁。
- 锁采用原子操作实现,即不会被打断。
- 内核中造成并发执行的原因是什么?
- 中断:几乎在任何时刻异步发生,打断当前代码
- 软中断和tasklet:内核能在任何时刻唤醒和调度软中断和tasklet
- 内核抢占:内核具有抢占性,内核中的任务可能会被另一个任务抢占
- 睡眠及与用户空间同步:内核执行的进程可能休眠,这会唤醒调度程序,导致调度一个新的用户进程执行
- SMP:多处理器同时执行代码
- 有哪些安全代码?
- 中断安全代码(interrupt-safe):在中断处理程序中能避免并发访问的代码。
- SMP安全代码(SMP-safe):在对称多处理器中能避免并发访问的代码。
- 抢占安全代码(preempt-safe):在内核抢占时能避免并发访问的代码。
- 在编写内核代码时,如何辨别需要被保护的数据?
- 此数据是不是全局,其他线程能不能访问?
- 此数据会在进程上下文中、中断上下文中共享吗?会在两个不同的中断处理程序中共享?
- 进程在访问时此数据时可不可能被抢占,被调度的新进程会不会访问此数据?
- 当前进程会不会阻塞在此数据上,会让此数据处于何种状态?
- 怎样防止数据失控?
- 此函数在另一个处理器上被调度会发生什么?
- 处理并发的配置选项
Linux
内核可在编译时配置,所以可以针对指定机器进行裁剪。CONFIG_SMP
选项控制内核是否支持SMP
, 很多加锁问题在单处理器上不存在,可以避免自旋锁的开销。CONFIG_PREEMPT
运行内核抢占的配置选项。
3、死锁
- 产生死锁的原因是什么?
有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有资源都已经被占用了。所有线程都在相互等待,但它们永远不释放资源。 - 举个简单的例子?
- 自死锁:一个执行线程试图去获取自己已持有的锁,最终的结果就是死锁。
- ABBA死锁
- 自死锁:一个执行线程试图去获取自己已持有的锁,最终的结果就是死锁。
- 避免死锁的方法是什么?
- 按顺序加锁——使用嵌套锁时保证以相同顺序获取锁。(当然,释放的时候最好以相反的顺序操作)
- 防止饥饿发生——假设本代码不会结束,加跳出的代码。
- 不重复请求同一锁
- 设计越简单越好——加锁越复杂,死锁越容易出现
4、争用和扩展性
- 锁的争用
当锁被占用时,有其他线程试图获得该锁。 - 加锁粒度
用来描述加锁保护的数据规模(小到数据元素,大到内核所有的数据结构) - 扩展性
对系统可扩展程度的一个量度。理想情况下,处理器的数量加倍应该会使系统处理性能翻倍。而实际上,这是不可能达到的。 - 三者直接的联系?
- 当锁争用严重时,加锁太粗会降低可扩展性;
- 当锁争用不明显时,加锁过细会加大系统无谓的开销。