Linux内核学习笔记:内核同步

        linux内核中运行的程序,时刻都要防止并发引起的竞态。这将会导致数据结构被破话,严重的时候会引起内核崩溃。所以内核同步技术对内核开发的驱动程序来说非常重要。不懂内核同步技术的人,是写不出安全健壮的内核驱动程序来的。在学习内核同步技术之前需要掌握一下几个概念。
        1 并行,并发与竞态:在SMP运行的linux内核是真正的并行运行程序,多个CPU可以同时访问同一数据结构,而在单处理器的系统上,内核程序的运行就是并发交错的运行,看起来像同时执行,一个CPU上同一时间只能执行一条指令。当正常的内核代码访问一种数据结构的时候被中断或者抢占,中断程序或者抢占后运行的内核代码也访问这种数据,在这种情况下,有两种内核控制路径访问数据结构,会发生不一致访问,这就发生了竞态。
        2 临界区:临界区是访问一种数据结构数据结构的一段代码,这段代码有可能会被不同的内核控制路径运行。内核同步保护的是数据结构而不是代码,但实际上实现的同步技术是保护访问数据结构的代码。
        3 内核抢占定义:进程在执行内核函数时(包括陷入内核的普通进程与内核线程)。即它在内核态运行时,允许发生进程切换。
        4 核控制路路径:内核执行代码所处不同的环境,主要分为三种:
         (1) 正常的系统调用与内核线程。特点是可以被中断,允许睡眠,有进程上下文,Current宏有意义,可以被内核抢占。
         (2) 可延迟函数:软中断,内核定时器,tasklet。特点是可以被中断,但是不允许睡眠,没有进程上下文,Current宏无意义,不可以被内核抢占。
           <1> 软中断的特点:在SMP系统上,相同的软中断可以在不同的CPU中同时执行,不同的软中断也可以同时执行。在单CPU的系统中,相同的软中断不能同时执行,不同的软中断也不能同时执行。
           <2> 内核定时器:是一种内核预定义的软中断,具有软中断的所有特征。
           <3> tasklet:也是一种内核预定义的软中断,但是采用了一些保护机制,使得有其他软中断没有的特点。相同的tasklet在SMP系统中也不能同时执行,不同的tasklet可以SMP系统中同时执行。
         (3) 中断处理程序:可以被其他类型的中断程序所打断。相同的中断不能嵌套发生。中断函数可以写成非可重入函数。
        5 内核运行环境分类:
         (1) 不支持内核抢占的单CPU系统
         (2) 支持内核抢占的单CPU系统
         (3) SMP系统
        在了解了这些概念后,然后再理清一下内核控制路径的关系,这对于理解不同的同步技术很重要,下面的情况只针对单CPU的可以发生内核抢占的情况:
        1 正常的内核代码,可以被中断程序打断,也可以被可延迟函数打断(本质上也是被中断打断,因为内核在每隔1ms的节拍中断处理程序中,据决定是否运行可执行函数)。如果中断处理程序中或者可延迟函数使另外的一个进程可运行,并且优先级高于被打断的进程,这时被打断的进程就会失去处理器。发生内核抢占。这样其他进程就会运行,这个进程有可能有会调用这段代码,相当于间接打断了现在的代码。所以总结一下:由于中断,正常的内核代码可以被中断处理程序打断,可以被可延迟函数间接打断,由于中断和内核抢占所以正常的内核代码必须是可重入的。(打断为强制暂停执行,非自愿放弃处理器)
        2 可延迟函数:可以被中断处理程序打断,不可以被其他可延迟函数打断,因为在同一个CPU上可延迟函数是顺序执行的。
        3 中断处理程序:可以被其他类型中断打断,不可以被同类型的中断打断。因为发生中断时,同种类型的中断被禁止。
        linux内核为了应对不同内核控制路径的代码引发的竞态的问题,应用了一下几种内核同步技术:
        1 per-CPU变量:最简单的同步技术,他是数据结构的数组,系统中的每个CPU都拥有数组中的一个元素,而且只有自己才可以操作这个元素,其他的CPU不能够访问。但是如果异步函数也要访问这个变量的话,就必须进行相关的保护。
        2 原子操作: 这种技术主要针对单变量的数据,CPU对一个变量的访问可能不是原子的,但是采用atomic_t类型的原子变量,内核可以保证原子的访问变量。注意的是,操作这种变量只能用内核提供的函数,不能单独的
操作。
        3 优化屏障与内存屏障:优化屏障:保证编译器不会混淆在屏障操作之前的汇编指令与屏障之后的汇编指令。内存屏障:保证在屏障之后的操作开始执行之前,屏障之前的操作已经完成。
        4 自旋锁: 自旋锁主要针对SMP系统,在单处理器上自旋锁退化为禁止内核抢占的开关。在SMP系统上:spin_lock首先禁止内核抢占,然后原子的测试自旋锁的装他。如果为1,那么跳出,内核控制路径获得自旋缩。如果为0,则打开内核抢占,循环测试直到为1.这就是自旋锁名字的来历。注意在自旋锁自旋的时候,可以被抢占。在单处理器系统上:spin_lock除了禁止内核抢占,什么都不做。自旋锁使用要点:在中断与可延迟函数中使用自旋锁是允许的。但一定要注意死锁。如果正常内核代码与中断处理程序使用同一把锁,在正常内核代码持有锁的时候应该禁止中断,否则就会发生死锁。可延迟函数与正常内核代码使用同一把锁的时候,在正常内核代码或得锁之时应该禁用可延迟函数。在中断处理程序与可延迟代码中使用同一把锁的时候,可延迟函数或得锁之时应该禁止中断。大体的原则是这样的:中断处理函数,可延迟函数,正常内核代码三者的打断能力由大到小。在能力小的代码中获得锁需要禁止所有能力大的代码执行。在单处理器上,虽然琐只是一个内核抢占的开关,但是用锁的方法与SMP系统上是一样的。但是目的不同,SMP系统中是为了防止死锁。单处理器上是为了防止竞态,在单处理器上的是不可能发生自旋锁死锁的。
         其他的同步技术还有:
         读/写自旋锁。这个锁是为了增加内核的并发能力。只要没有内核控制路径对数据进行修改,允许多个控制路径同时读取同一数据结构。读/写操作具有相同的优先级。
         顺序锁:与读写锁类似,但是读/写操作有不同的优先级,读操作的优先级低于写操作。也就是一个读程序持有锁的情况下可以被一个写程序打断。
         读取-复制-更新技术(RCU):为了保护大多数情况下被多个CPU读的数据结构,这种技术没有使用加锁技术
         信号量:使进程可以睡眠的锁。当进程或得不到锁的时候睡眠,当释放锁的时候,唤醒睡在锁上的进程。
        编写驱动程序中要灵活运用内核中的同步技术,这样才能编写出并发度高,稳定健壮的驱动程序。首先要确定哪些部分是需要保护的,这就需要分析访问数据的代码可能有几个内核控制路径并行或者并发执行,结合各个同步技术的特点灵活应用,一般而言能不需要同步的就不要同步,能不加锁的就不加锁,因为内核同步必然会引起一定的性能损失。
    

你可能感兴趣的:(数据结构,优化,汇编,测试,编译器,linux内核)