同步机制
在使用共享内存时,必须注意保护共享资源,多线程并发访问共享资源,在内核中也要特别留意。
1. 临界区和竞争条件
临界区(Critical section)就是访问和操作共享数据的代码段。竞争条件(race conditions)指多个执行线程可能同时执行同一个临界区,出现竞争情况。避免并发和防止竞争条件称为同步(Synchronization)。
2. 加锁
加锁是针对数据而不是代码
简单的算术运算和比较提供原子指令还行,复杂的数据结构我们就需要一种方法确保一次有且只有一个线程对它进行操作,加锁就是这种机制:将临界区锁起来。
Linux实现了几种不同的锁策略,它们之间的主要区别在于:当锁已经被其他线程持有时采取不同的行为表现。
用户空间之所以要同步,是因为用户程序会被调度程序抢占和重新调度。
内核可能造成并发执行的原因:
- 中断——中断几乎可以在任何时刻异步执行,所以随时可能打断当前执行的代码
- 软中断和tasklet——内核能在任何时刻唤醒或调度软中断和tasklet
- 内核抢占——内核具有抢占性,当前任务可能会被另一个任务抢占
- 睡眠及与用户空间同步——在内核执行的进程可能睡眠,这会唤醒调度程序,从而导致调度一个新的用户进程执行
- 对称多处理——多个处理器能真正同时执行代码
安全代码
- 中断安全代码interrupt-safe:在中断处理程序中能避免并发访问的安全代码
- SMP安全代码SMP-safe:在对称多处理机器中能避免并发访问的安全代码
- 抢占安全代码preempt-safe:在内核抢占时能避免并发访问的安全代码
编写内核代码注意
- 这个数据是不是全局的?除了当前线程,其他线程能不能访问?
- 这个数据会不会在进程上下文和中断上下文中共享?它是不是要在两个不同的中断处理程序中共享?
- 进程在访问数据时可不可能被抢占?被调度的新程序会不会访问同一个数据?
- 当前进程是不是会睡眠(阻塞)在某个资源上,如果是,它会让共享数据处于何种状态?
- 怎样防止数据失控?
- 如果这个函数又在另一个处理器上被执行会发生什么?
- 如何确保代码原理并发威胁?
3. 死锁
预防死锁:
- 按顺序加锁、按相应逆序解锁
- 防止发生饥饿
- 不要重复请求同一把锁
- 设计力求简单