九。内核同步问题

来源:互联网 发布:python 递归深度 编辑:程序博客网 时间:2024/06/05 09:01
如有错误请指出,大家一起学习:

1、
    访问共享资源的的代码都是临界区
    用户态的锁都是睡眠性质的
    内核中有些锁是死等,有些锁是睡眠

2、内核抢占
    2.6 以后的内核都支持抢占    
    内核代码可被打断
    默认情况下,内核不支持抢占

    服务器是不会支持抢占的,自己玩的桌面版的一般支持

    make menuconfig
         Kernel Features  --->
            Preemption Model (No Forced Preemption (Server))  --->
                  ( ) No Forced Preemption (Server)               //不支持抢占
                   ( ) Voluntary Kernel Preemption (Desktop)       //介于上下两者之间
                   (X) Preemptible Kernel (Low-Latency Desktop)   //抢占式内核
                                                   
    原子变量是可被打断的,eg:ldrex、strex 内核中所有的锁都是基于这两条指令的

    抢占式内核中才有用的函数,在非抢占式内核中都是空函数:
    禁止内核抢占:
        preempt_disable()
    开启内核抢占:
        preempt_enable()

3、原子变量
    只有指令不能被打断,原子变量也会被打断,只是会保证即使被打断结果也是正确的,
    原子变量的实现必须有硬件支持
        ldrex strex slrex

    通过 ldrex 命令从内存中把地址放到指定寄存器的,在经过总线的时候会标记某个标志位(硬件支持);别的代码也是可以取这个地址的数据,也会标记这个标志位;当操作完这个数据通过 strex 放回的时候会检测这个标志位(strex r0, r1, [r2], r0 寄存器就是放标志位的),检测通过会将数据放回那个地址并清除标志位;别的程序再往回放的时候检测不到标志位,就会自动返回去,从拿数据开始重新操作
    
    核心头文件 <arch/arm/include/asm/atomic.h>

########################################################################
# 汇编语言:
# Qo:相当于C中的 volatile
# cc:保护 CPSR 寄存器
# smp_mb():封装后的 dmb
# Ir:所代表的变量是立即数
# bne 1b
# b:before;往前面的 1 标号跳,这样一个标号可以被使用两次,bne 1a(a:after)
########################################################################

################################################################
# arm 会根据自己的实现机制,将代码乱序执行
# 为了防止代码被乱序执行,指令之间需要加上屏障,这就需要 DMB、DSB、ISB
# DMB 只对操作内存的指令有影响,DMB 上下的指令还是可以颠倒的
# DSB 必须上面的执行完了,下面的指令才能执行
# ISB 会刷新上下文,只有在异常或异常返回的时候才会刷新上下文
################################################################

    相关函数:    
    atomic_read
    atomic_set    //获得原子变量?
    atomic_add
    atomic_add_return
    atomic_sub
    atomic_sub_return
    atomic_cmpxchg(atomic, old, new)
        如果要设置的原子变量的值等于 old,才会把 new 设置为新的值;否则原子变量的值不变
    atomic_clear_mask(mask, addr)
        *addr &= ~mask;就是被 bic,经过一系列操作包装成原子变量

    相关宏:
    #define atomic_inc(v)       atomic_add(1, v)        //加1
    #define atomic_dec(v)       atomic_sub(1, v)        //减1

    #define atomic_inc_and_test(v)  (atomic_add_return(1, v) == 0)    //查看 +1后返回值是否为 0
    #define atomic_dec_and_test(v)  (atomic_sub_return(1, v) == 0)    //查看 -1后返回值是否为 0
    #define atomic_inc_return(v)    (atomic_add_return(1, v))        //+1 后的返回值
    #define atomic_dec_return(v)    (atomic_sub_return(1, v))        //-1 后的返回值
    #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)    //查看 -i后返回值是否为 0

4、原子位操作
    p:代表指针
    nr:代表第几位,从 0 位开始
    可以使用大于 32 位的位操作

    相关宏:
    set_bit(nr, p)            把 p 指向的数的 nr 位 置1
    clear_bit(nr, p)        把 p 指向的数的 nr 位 置0
    change_bit(nr, p)        把 p 指向的数的 nr 位 反转
    test_set_bit(nr, p)        先返回原来的值,把 p 指向的数的 nr 位 置1
    test_clear_bit(nr, p)    先返回原来的值,把 p 指向的数的 nr 位 置0
    test_change_bit(nr, p)    先返回原来的值,把 p 指向的数的 nr 位 反转

5、自旋锁
    多核CPU使用
    在不支持抢占的单核系统中,自旋锁的函数都是空函数;在支持抢占的单核系统中,自旋锁锁上相当于禁止抢占,释放锁相当于可以抢占
    死等的锁叫自旋锁,占着 CPU 不放,死等着
    比如,cpu0 获得锁后,cpu1的代码也想获得这把锁,cpu1的代码就会占着cpu1不放,死等着,直到cpu0释放锁
    特点:死等

    死锁:
        ABBA死锁:两个锁都需要对方的锁,都等待对方释放
        自死锁:自己请求自己的锁,等待自己释放
        <-****** 带着锁一定不能去睡眠 ******->
            带锁的时候如果睡眠了,其它的程序如果想获得这把锁,就会占着cpu不放,死等着,此时调度器就起不来,
    spinlock_t(结构体):自旋锁类型
    核心头文件:<include/linux/spinlock.h>

    函数:
    初始化锁:
    获得锁:(一把锁同时只能一个人持有)
        spin_lock
    试图获得锁,如果获得返回 1,否则 0;不会去死等
        spin_trylock
    获得锁并禁止中断下半部分:
        spin_lock_bh
    获得锁并禁止中断:
        spin_lock_irq
    获得锁并禁止中断且保存中断状态:
        spin_lock_irqsave

    释放锁:
        spin_unlock
    释放锁并允许中断下半部分:    
        spin_unlock_bh
    释放锁并允许中断:
        spin_unlock_irq
    释放锁并允许中断且恢复中断状态
        spin_unlock_irqrestore

6、X86下写模块
    内核地址:/lib/modules/3.8.0-32-generic/build/
    修改Makefile 为:
        LINUX_PATH = /lib/modules/`uname -r`/

7、信号量
    特点:睡眠等待

    计数信号量
    二值信号量/互斥信号量

    死锁:
        获得了自旋锁,又要获得信号量;相当于带着锁睡眠了
        在中断上下文中不恩功能用信号量
        自死锁

    核心头文件: <include/linux/semaphore.h>
    
    struct semaphore {
        raw_spinlock_t      lock;    //这个锁是用于保护链表的,不是说信号量是基于自旋锁的
        unsigned int        count;    //计数信号量,count为几,就几个同时拥有;二值信号量count初始化为1
        struct list_head    wait_list;    //内核用链表维护信号量
    };

    函数:
    初始化一个信号量
        sema_init(sem, val)        //val就是上面count的值
    获得信号量,如果获取不到信号量会睡眠,睡眠不可打断
        down()
    获得信号量,如果获取不到信号量会睡眠,睡眠可打断;任何信号能唤醒
        down_interruptible()
    获得信号量,如果获取不到信号量会睡眠,睡眠可打断;如果杀死进程的信号可以唤醒它
        down_killable()
    试图获得信号量,如果获取返回 0,否则 1
        down_trylock()
    获得信号量,如果获取不到信号量会睡眠,睡眠不可打断;超时自己醒来,jiffies设定时间
        down_timeout(sem, jiffies)
    释放信号量
        up()

8、加锁也可以被正常的中断打断,因为锁就是一个普通的代码,想不被中断打断,使用禁止中断的锁函数