面试官让你讲讲Linux内核的竞争与并发,你该如何回答?

内核中的并发和竞争简介??在早期的 Linux内核中 , 并发的来源相对较少 。 早期内核不支持对称多处理( symmetric multi processing,SMP) , 因此 , 导致并发执行的唯一原因是对硬件中断的服务 。 这种情况处理起来较为简单 , 但并不适用于为获得更好的性能而使用更多处理器且强调快速响应事件的系统 。
??为了响应现代硬件和应用程序的需求 ,Linux内核已经发展到同时处理更多事情的时代 。 Linux系统是个多任务操作系统 , 会存在多个任务同时访问同一片内存区域的情况 , 这些任务可能会相互覆盖这段内存中的数据 , 造成内存数据混乱 。 针对这个问题必须要做处理 , 严重的话可能会导致系统崩溃 。 现在的 Linux系统并发产生的原因很复杂 , 总结一下有下面几个主要原因:

  1. 多线程并发访问 ,Linux是多任务(线程)的系统 , 所以多线程访问是最基本的原因 。
  2. 抢占式并发访问 , 内核代码是可抢占的 , 因此 , 我们的驱动程序代码可能在任何时候丢失对处理器的独占
  3. 中断程序并发访问 , 设备中断是异步事件 , 也会导致代码的并发执行 。
  4. SMP(多核)核间并发访问 , 现在ARM架构的多核SOC很常见 , 多核CPU存在核间并发访问 。 正在运行的多个用户空间进程可能以一种令人惊讶的组合方式访问我们的代码 , SMP系统甚至可在不同的处理器上同时执行我们的代码 。
??只要我们的程序在运转当中 , 就有可能发生并发和竞争 。 比如 , 当两个执行线程需要访问相同的数据结构(或硬件资源)时 , 混合的可能性就永远存在 。 因此在设计自己的驱动程序时 , 就应该避免资源的共享 。 如果没有并发的访问 , 也就不会有竞态的产生 。 因此 , 仔细编写的内核代码应该具有最少的共享 。 这种思想的最明显应用就是避免使用全局变量 。 如果我们将资源放在多个执行线程都会找到的地方(临界区) , 则必须有足够的理由 。
??如何防止我们的数据被并发访问呢?这个时候就要建立一种保护机制 , 下面介绍几种内核提供的几种并发和竞争的处理方法 。
原子操作原子操作简介??原子 , 在早接触到是在化学概念中 。 原子指化学反应不可再分的基本微粒 。 同样的 , 在内核中所说的原子操作表示这一个访问是一个步骤 , 必须一次性执行完 , 不能被打断 , 不能再进行拆分 。??例如 , 在多线程访问中 , 我们的线程一对a进行赋值操作 , a=1 , 线程二也对a进行赋值操作a=2 , 我们理想的执行顺序是线程一先执行 , 线程二再执行 。 但是很有可能在线程一执行的时候被其他操作打断 , 使得线程一最后的执行结果变为a=2 。 要解决这个问题 , 必须保证我们的线程一在对数据访问的过程中不能被其他的操作打断 , 一次性执行完成 。
整型原子操作函数
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
注:64位的整型原子操作只是将“atomic_”前缀换成“atomic64_” , 将int换成long long 。
位原子操作函数
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
原子操作例程/* 定义原子变量 , 初值为1*/static atomic_t xxx_available = ATOMIC_INIT(1); static int xxx_open(struct inode *inode, struct file *filp){ ... /* 通过判断原子变量的值来检查LED有没有被别的应用使用 */ if (!atomic_dec_and_test( /* LED被使用 , 返回忙*/ return - EBUSY;}.../* 成功 */ return 0;static int xxx_release(struct inode *inode, struct file *filp){ /* 关闭驱动文件的时候释放原子变量 */ atomic_inc(return 0;}自旋锁??上面我们介绍了原子变量 , 从它的操作函数可以看出 , 原子操作只能针对整型变量或者位 。 假如我们有一个结构体变量需要被线程A所访问 , 在线程A访问期间不能被其他线程访问 , 这怎么办呢?自旋锁就可以完成对结构体变量的保护 。