Linux 2.6 内核阅读笔记 内核同步
2014年7月26日 内核抢占和内核控制路径的设计
内核抢占的一种定义:如果进程正在内核态执行内核函数时,允许发生内核切换(就是被替换的进程是内核函数所在进程),这个内核就是抢占的。
linux内核提供了内核抢占的开启和关闭功能,在current_thread_info的preempt_count字段大于0时,内核就是不能抢占的。可以通过preempt_disable和preempt_enable来增加这个字段来控制这个开关。
中断和软中断和抢占的控制主要使用到current_thread_info->preempt_count字段,这个字段被分成了4部分:0-7位为抢占计数器,8-15位为软中断计数器,16-27位为硬中断计数器,28位为PREEMPT_ACTIVE标志。
内核设计上的选择子某种程度上简化了内核控制路径的同步:
1.所有的中断处理程序相应来自于pic的中断并且禁用IRQ线。此外在中断处理程序结束前,不允许产生相同的中断事件。
2.中断处理程序、软中断和tasklet既不可以被抢占也不能被阻塞,所以它们不可能长时间处于挂起状态。在最坏的情况下,它们的执行将会有轻微的延迟,因为在执行过程中可能发生其他的中断(内核控制路径的嵌套执行)。
3.执行中断处理的内核控制路径不能被执行可延迟函数或系统调用服务例程的内核控制路径中断。
4.软中断和tasklet不能一个给定的cpu上交错执行。
5.同一个tasklet不能同时在几个cpu上执行,但是不同类型的tasklet可以在几个cpu上同时执行。
2014年7月26日 同步原语
内核使用的各种同步技术:
技术 |
说明 | 适用范围 |
每cpu变量 | 在多个cpu之间复制数据 | 所有cpu |
原子操作 | 对一个计数器原子地“读-修改-写”操作 | 所有cpu |
内存屏障 | 避免指令重新排序 | 本地cpu或者所有cpu |
自旋锁 | 加锁时忙等 | 所有cpu |
信号量 | 加锁时阻塞等待(睡眠) | 所有cpu |
顺序锁 | 基于访问计数器的锁 | 所有cpu |
本地中断的禁止 | 禁止单个cpu上的中断处理 | 本地cpu |
本地软中断的禁止 | 禁止单个cpu上的可延迟函数的执行 | 本地cpu |
读-拷贝-写 | 通过指针而不是锁来访问共享数据结构 | 所有cpu |
每cpu变量:主要做法是声明一个nr_cpus大小的变量数组,每个cpu都使用其自己的变量。要注意不要访问属于其他cpu的变量,可以使用get_cpu_var()(禁止了抢占)宏来获得这个变量,用put_cpu_var()(启动抢占)宏来结束使用这个变量。
原子操作:通过lock,inc,dec等等原子性汇编指令来实现整型值的原子操作。
内存屏障:避免编译器、cpu对指令进行重新排序,当然这样可能会失去指令重排带来的优化。
自旋锁:在进入临界区之前尝试去获得锁,如果不能获得就一直在那空等。其实在底层还是通过lock,inc,dec等等原子性的汇编指令来实现。由于自旋锁浪费cpu周期,只适合临界区比较小的同步场景。
信号量:在进入临界区之前尝试去获得信号量 ,如果不能获得就把当前进程放到信号量的等待队列里面睡眠。底层是通过自旋锁来实现的。信号量不能在中断处理程序和可延迟函数(tasklets)中使用,因为它们不允许睡眠。
顺序锁:类似于读写锁,但是不同于读写锁的读写操作拥有相同优先级,顺序锁中写操作拥有更高的优先级。
本地中断的禁止:通过cli和sti来关闭和打开本地中断。内核提供了local_irq_disable和local_irq_enable来实现这些功能。内核还提供了local_irq_save和local_irq_restore在前面两个宏的功能基础上实现eflags寄存器的保存和恢复功能,这对中断嵌套执行非常重要。
本地软中断的禁止:通过local_bh_disable和local_bh_enable来实现本地软中断的关闭和开启功能。宏展开其实也就是对current_thread_info->preempt_count的软中断部分进行加减操作。
读-拷贝-写:为了提高内核的并发度而引入的一种技术,主要场景适合于指针数据,读操作不需要同步,写操作时,先复制一份副本,再在副本上修改,最后在合适的时候(因为其他读操作可能正在进行中 )将原数据的指针指向副本的地址。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。