驱动编程核心理论之并发控制

驱动编程核心理论之并发控制
并发,竟态的理解:也就是Linux的东西在时间和空间被同时使用导致的问题
Linux系统提供的解决方案:
    中继屏蔽
    原子操作
    自旋锁
    信号量
   
1,中断屏蔽
    首先这个依赖于CPU,CPU一般提供屏蔽中断的打开中断的功能,中断屏蔽使得中断和进程之间的并发
    不再发生,同时进程调试也依赖于中断,内核抢占的进程间并发也就可以避免
   
    使用方法
    local_irq_disable()//屏蔽中断
    ...
    critical section()//临界区
    ...
    local_irq_enable()//打开中断
    这种方法只能解决单个CPU的问题
   
    注意 : 长时间中断屏蔽很危险,比较适合和自旋锁联合使用
    local_irq_save(flags)    禁止中断同时保存目前CPU的中断信息位
    local_irq_restore(flags) 打开中断同时恢复中断信息位
    底半部操作
    local_bh_disable()
    local_bh_enable()
   
2,原子操作
    原子操作是指在执行过程中不会被的代码路径所中断的操作。待理解
    分为两类,针对位的操作和整型变量的操作
   
    针对整型变量的原子操作的相关函数
    void atomic_set(atomic_t *v, int i);//设置原子变量的值为i
    atomic_t v = ATOMIC_INIT(0);//定义原子变量v并初始化为0
    atomic_read(atomic_t *v);//返回原子变量的值
    void atomic_add(int i, atomic_t *v);//原子变量增加i
    void atomic_sub(int i, atomic_t *v);//原子变量减少i
    void atomic_inc(atomic_t *v);//原子变量自增加1
    void atomic_dec(atomic_t *v);//原子变量自减少1
    操作并测试函数
    int atomic_inc_and_test(atomic_t *v);
    int atomic_dec_and_test(atomic_t *v);
    int atomic_sub_and_test(atomic_t *v);
    //在进行自增自减和减操作后,测试是否为0,如果是则返回true,否则返回为false
   
    操作关返回函数,操作后返回新的值
    int atomic_add_return(int i, atomic_t *v);
    int atomic_sub_return(int i, atomic_t *v);
    int atomic_inc_return(int i, atomic_t *v);
    int atomic_dec_return(int i, atomic_t *v);
   
    针对位的原子操作
    void set_bit(nr, void *addr);
    //设置位操作,设置addr地址的第nr位,设置后该位立即写为1
    void clear_bit(nr,void *addr);
    //清除为操作,同上清除该位,值变为0
    void change_bit(nr,void *addr);
    //改变位操作,nr位反置
    test_bit(nr, void *addr);
    //测试位操作,返回addr地址的第nr位
    int test_and_set_bit(nr, void *addr);
    int test_and_clear_bit(nr, void *addr);
    int test_and_change_bit(nr, void *addr);
    以上三个操作是先进行测试后再进行相应的操作
   
    例程:使用原子变量使设备只能被一个进程打开
    xxx代表设备名
    static atomic_t xxx_available = ATOMIC_INIT(1);
    static int xxx_open(struct inode *inode, struct file *flip)//open操作函数
    {
    ...
    if(!atomic_dec_and_test(&xxx_available))
        {
            atomic_inc(&xxx_available);
            return - EBUSY;//设备已被打开
        }
    ...
    return 0;//原子操作成功,解决竞争问题
   
   
    static int xxx_release(struct inode *inode, struct file *flip)
        {
            atomic_inc(&xxx_available);
            return 0;//减少1,释放设备
        }
     }  
    
3,自旋锁
    这是一种对临界资源进行互斥访问的典型手段
    是要和原子操作进行配合使用的
    理解方法,将其看成一个变量,该变量把一个临界区标记成“我正在运行,请稍等一会”或者标记
    为“我当前不运行,可以被使用”
    相关函数
    定义自旋锁
    spinlock_t lock;
    初始化自旋锁
    spin_lock_init(&lock);//动态的初始化
    获得自旋锁
    spin_lock(lock);//如果能够立即获得锁就马上返回,否则就自旋保持在那里,直到锁被释放
    spin_trylock(&lock);//尝试能否获得,如果能返回真,否则返回假,并不会在那时保持
    释放自旋锁
    spin_lock(&lock)//与spin_lock或spin_trylock配合使用
    一般使用流程
    spinlock_t lock;
    spin_lock_init(&lock);
    spin_lock(&lock);
    ...//临界区
    spin_unlock(&lock);
   
    注意:
    1,自旋锁主要针对SMP或单CPU但内核可抢占的情况,不是这两种情况的退化成空操作
    2,临界区的操作还可以受到底半部的和中断的影响,还需要用到自旋锁的衍生,以下会给出几个函数
    3,自旋锁实际是忙等待,只有在锁占有极短的时间才能用,否则会降低系统的性能
    4,自旋锁可能会导致系统死锁,特别是在递规使用自旋锁,还有就是如果获得锁之后再阻塞也会死锁
   
    典型结构
   
    int xxx_count = 0;
   
    static int xxx_open(struct inode *inode, struct file *flip)
    {
        ...//定义锁这里应该
        spinlock(&xxx_lock);//??这个函数?
        if (xxx_count)
        {
            spin_unlock(&xxx_lock);
            return - EBUSY;
           
        }
        xxx_count++;
        spin_unlock(&xxx_lock);
        ...
        return 0;
    }
   
    static int xxx_release(struct inode *inode, struct file *flip)
    {
        ....
        spinlock(&lock);
        xxx_count--;
        spin_unlock(&xxx_lock);
        return 0;
    }
   
   
    衍生锁--读写自旋锁
   
    写操作时最多只能有一个写进程,但同时可以有多个读执行单元,但读和写不能同时进行
    1,定义和初始化读写自旋锁
    rwlock_t my_rwlock = RW_LOCK_UNLOCKED;//静态初始化
    rwlock_t my_rwlock;
    rwlock_init(&my_rwlock);//动态初始化
    2,读锁定
    void read_lock(rwlock_t *lock);
    void read_lock_irqsave(rwlock_t *lock, unsigned long flag);
    void read_lock_irq(rwlock_t *lock);
    void read_lock_bh(rwlock_t *lock);
    3,读写锁
    void read_unlock(rwlock_t *lock);
    void read_unlock_irqsave(rwlock_t *lock, unsinged long flag);
    void read_unlock_irq(rwlock_t *lock);
    void read_unlock_bh(rwlock_t *lock);
    在对共享资源进行读取时,先调用读锁定,读操作完成后再进行解锁
    4,写锁定
    void write_lock(rwlock_t *lock);
    void write_lock_irqsave(rwlock_t *lock, unsigned long flag);
    void write_lock_irq(rwlock_t *lock);
    void write_lock_bh(rwlock_t *lock);
    void write_lock_trylock(rwlock_t *lock);
    5,写解锁
    void write_unlock(rwlock_t *lock);
    void write_unlock_irqsave(rwlock_t *lock, unsigned long flag);
    void write_unlock_irq(rwlock_t *lock);
    void write_unlock_bh(rwlock_t *lock);
    同读锁一样,也是先上锁再解锁
   
    应用框架
    rwlock_t lock;
    rwlock_init(&lock);
   
    read_lock(&lock);
    ....//临界资源
    read_unlock(&lock);
   
   
    write_lock_irqsave(&lock,flags);
    ...//临界资源
    write_unlock_irqretore(&lock,flags);
   
    衍生锁--顺序锁
   
    顺序锁是对读写锁的一种优化,读写锁可能会引起阻塞,有顺序锁了就不会
    说明:使用顺序锁,写时,被保护的资源依然可以被读,而不必等写完,同样,读时,被保护的资源
    仍可被写
    但是写与写之间仍然是互斥的
    可能情况,读时,由于写入的数据使资源发生了变化,读要重新执行,但概率较小
    另外,使用顺序锁时被保护的共享资源不能含有指针
    1,获得顺序锁
    void write_seqlock(seqlock_t *sl);
    int write_tryseqlock(seqlock_t *sl);
    write_seqlock_irqsave(lock, flags);
    write_seqlock_bh(lock);
    write_seqlock_irq(lock);
    2,释放顺序锁
    void write_sequnlock(seqlock_t *sl);
    write_sequnlock_irqrestore(lock, flags);
    write_sequnlock_irq(lock);
    write_sequnlock_bh(lock);
   
    3,读开始
    unsinged read_seqbegin(const seqlock_t *sl);
    read_seqbegin_irqsave(lock, flags);
    读执行单元在对被顺序锁保护的共享资源进行访问时要调用该函数,返回sl的顺序号
    4,重读
    int read_seqretry(const seqlock_t *sl, unsigned iv);
    read_seqretry_irqrestore(lock,iv,flags);
    读执行单元在访问完被顺序锁sl保护的共享资源后要调用这个函数检查是否同时有写操作,如果有
    要重读。
   
    框架
    do {
        seqnum =  read_seqbegin(&seqlock_a);
        ...
    } while (read_seqretry(&seqlock_a, seqnum));
   
    衍生锁--RCU(读-拷贝-更新)//有待于进一步的学习,包括链表
    RCU也是对数据保护的一种方式,其保护的数据,读写不需要的任何锁就可以访问它,不会产生竟态
    死锁
    过程:写之前先对保护的数据复制一个副本,修改的只是副本,然后通过回调重新指向被修改的数据
    优点:是高性能的读写锁,充许多个读写执行单元访问被保护的数据
    不足:多个写执行单元的同步开销很大,可能延时数据结构的释放
   
    函数:
    读锁定:
    rcu_read_lock()
    rcu_read_lock_bh()
    读解锁:
    rcu_read_unlock()
    rcu_reac_unlock_bh()
    使用模型:
    rcu_read_lock()
    ...//读写临界区
    rcu_read_unlock()
    其实质是使能和禁止内核抢占
   
    同步RCU
    synchronize_rcu()
    该函数由写执行单元调用,同时阻塞写执行单元
   
    挂接回调
    void fastcall call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu);
    由RCU写执行单元调用,但不是使写阻塞。可以在中断上下或软中断中使用。

4,信号量
    同锁一样,也是保护临界区的一种方法,只有得到信号量的进程才能访问临界区的代码
    但与自旋锁不同的是当得不到信号量时,会进入休眠,而不会原地打转
    函数:
    定义信号量
    struct semaphore sem;
    初始化信号量
    void sema_init(struct semaphore *sem,int val);//初始化为val
    初始化互斥信号量
    void init_MUTEX(struct semaphore *sem);//初始化为1
    相当于sema_init(struct semaphore *sem, 1);
    void init_MUTEX_LOCKED (struct semaphore *sem);//初始化为0
    相当于sema_init(struct semaphore *sema, 0);
   
    快速定义:
    DECLEAR_MUTEX(name);//初始化为1
    DECLEAR_NUTEX_LOCKED(name);//初始化为0
   
    获取信号量
    void down(struct semaphore *sem);//会导致睡眠,不适合在中断上下文中使用
    int down_interruptible(struct semphore *sem);//使用该函数后进程进入睡眠状态可以被信号打断
    信号也会导致该函数返回,返回值为非0
    int down_trylock(struct semphore *sem);//该函数会尝试信号量sem,如果能立刻获得,获得并
    返回0,否则返回非0,不会导致调用者睡眠,可以在中断上下文中使用
    if (down_interuptible(&sem)
    {
        return - ERESTARTSYS;
    }
   
    释放信号量
    void up(struct semaphore * sem);//释放信号量,唤醒等待者
   
    信号量的一使用框架
    DECLEAR_MUTEX(mount_sem)
    down(&mount_sem)
    ...
    critical section//临界区
    ...
    up(&mount_sem);
    例程:信号量实现一个设备只能被一个进程打开
    static DECLEAR_MUTEX(xxx_lock);
    static int xxx_open(struct inode *inode, struct file *flip)
    {
        ...
        if(down_trylock(&xxx_lock)
            return - EBUSY;
        ...
        return 0;
    }
   
    static int xxx_release(struct inode *inode, struct file *flip)
    {
        up(&xxx_lock);
        return 0;
    }
   
    信号量用于同步
    但是更好的方法是用完成量,completion
    与completion有关的函数
    定义完成量
    struct completion my_completion;
    初始化completion
    init_completion(&my_completion);
    同时完成定义和初始化
    DECLEAR_COMPLETION(my_completion);
    等待完成量
    void wait_for_completion(struct completion *c);
    唤醒完成量
    void completion(stuct completion *c)//只唤醒一个等待的执行单元
    void completion_all(struct completion *c);//释放所有等待同一完成量的执行单元
   
    读写信号量
    类似于自旋锁与读写自旋锁关系一样,读写信号量可能引起进程阻塞地,它可充许多个读执行单元
    同时访问共享资源,而最多只能有一个写执行单元。读写信号量是比信号量的粒度更大
    函数:
    定义和初始化读写信号量
    struct rw_semaphore my_rw;
    void init_rwsem(struct rw_semaphore *sem);
    读信号量获取:
    void down_read(struct rw_semaphore *sem);
    int down_read_trylock(struct rw_semphore *sem);
    读信号量释放
    void up_read(struct rw_semaphore *sem);
    写信号量获取
    void down_write(stuct rw_semaphore *sem);
    int down_write_trylock(struct rw_semaphore *sem);
    写信号量释放
    void up_write(struct rw_semaphore *sem);
   
    使用框架:
    rw_semaphore rw_sem;
    init_rwsem(&rw_sem);
    down_read(&rw_sem);
    ...//临界区
    up_read(&rw_sem);
   
    down_write(&rw_sem);
    ...//临界区
    up_write(&rw_sem);
   
    互斥体
    定义初始化
    struct mutex my_mutex;
    mutex_init(&my_mutex);
    获取互斥体
    void fastcall mutex_lock(struct mutex *lock);
    int fastcall mutex_lock_interuptible(stuct mutex *lock);
    int fastcall mutex_trylock(stuct mutex *lock);
    和信号量相比,互斥会引起睡眠,并不被信号打断
    释放互斥体
    void fastcall mutex_unlock(struct mutex *lock);
    互斥体使用和信号量用于互斥场合完全一样
   
    小结:中继屏蔽很小单独使用,常和自旋锁一起配合
          原子操作只能针对整型和位操作
          自旋锁和信号量应用最为广泛
            自旋锁会导致死循环,锁定期间不能有阻塞,要求临界区小
            信号量则可以适用于临界区较大的情况

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值