Linux 并发和竞争

  • Linux内核提供的原子整形操作API
  1. 借助多核Cache一致性协议可以很方便实现原子操作
  2. 在x86架构下,处理器提供了在指令执行期间对总线加锁的手段。通过在需要原子操作的指令前附加lock指令前缀将总线锁定,保证了指令执行时不会受到其它处理器的影响。
  3. ARM架构下的原子操作不使用总线锁定的方式,而是采用独占访问机制。ARM提供了一对独占内存加载和存储的指令,用于支持原子操作实现:

    ldxr指令:内存独占加载指令。从内存中以独占的方式加载内存地址的值到通用寄存器里;
    stxr指令:内存独占存储指令。以独占的方式把新的数据存储到内存中。

  4. #inlcude "include/linux/types.h"
    
    typedef struct {
        int counter;
    } atomic_t;
    
    typedef struct {
        long long counter;
    } atomic64_t;
    
    ATOMIC_INIT(int i)                          //定义原子变量的时候对其初始化 
    int atomic_read(atomic_t *v)                //读取v的值,并且返回。
    void atomic_set(atomic_t *v, int i)         //向v写入i值
    void atomic_add(int i, atomic_t *v)         //给v加上i值
    void atomic_sub(int i, atomic_t *v)         //从v减去i值
    void atomic_inc(atomic_t *v)                //给v加1,也就是自增
    void atomic_dec(atomic_t *v)                //给v减1,也就是自减
    int atomic_dec_return(atomic_t *v)          //给v减1,并且返回v的值
    int atomic_inc_return(atomic_t *v)          //给v加1,并且返回v的值
    int atomic_sub_and_test(int i, atomic_t *v) //从v减i,如果结果为0就是返回真,否则返回假
    int atomic_dec_and_test(atomic_t *v)        //从v减1,如果结果为0就是返回真,否则返回假
    int atomic_inc_and_test(atomic_t *v)        //给v加1,如果结果为0就是返回真,否则返回假
    int atomic_add_negative(int i, atomic_t *v) //给v加i,如果结果为负就返回真,否则返回假
    
  • 原子位操作API
  1. #include "include/linux/types.h"
    
    void set_bit(int nr, void *p)            //将p地址的第nr位置1
    void clear_bit(int nr, void *p)          //将p地址的第nr位清零
    void change_bit(int nr, void *p)         //将p地址的第nr位翻转
    int test_bit(int nr, void *p)            //获取p地址的第nr位的值
    int test_and_set_bit(int nr, void *p)    //将p地址的第nr位置1,并且返回nr位原来的值
    int test_and_clear_bit(int nr, void *p)  //将p地址的第nr位清零,并且返回nr位原来的值
    int test_and_change_bit(int nr, void *p) //将p地址的第nr位翻转,并且返回nr位原来的值
  • 自旋锁
  1. ①、因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要
    短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处
    理方式,比如稍后要讲的信号量和互斥体。
    ②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能
    导致死锁。
    ③、不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就
    必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己
    把自己锁死了!
    ④、在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还
    是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。
  2. #include "include/linux/types.h"
    
    typedef struct spinlock {
      union {
        struct raw_spinlock rlock;
    
    #ifdef CONFIG_DEBUG_LOCK_ALLOC
    #define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
        struct {
          u8 __padding[LOCK_PADSIZE];
          struct lockdep_map dep_map;
        };
    #endif
      };
    } spinlock_t;
    
    DEFINE_SPINLOCK(spinlock_t lock)     //定义并初始化一个自选变量
    int spin_lock_init(spinlock_t *lock) //初始化自旋锁
    void spin_lock(spinlock_t *lock)     //获取指定的自旋锁,也叫做加锁
    void spin_unlock(spinlock_t *lock)   //释放指定的自旋锁
    int spin_trylock(spinlock_t *lock)   //尝试获取指定的自旋锁,如果没有获取到就返回0
    int spin_is_locked(spinlock_t *lock) //检查指定的自旋锁是否被获取,如果没有被获取就返回非0,否则返回0
    
    /*中断自旋锁*/
    void spin_lock_irq(spinlock_t *lock) //禁止本地中断,并获取自旋锁
    void spin_unlock_irq(spinlock_t *lock) //激活本地中断,并释放自旋锁
    void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) //保存中断状态,禁止本地中断,并获取自旋锁
    void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) //将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁
    
    /*下半部/底半部(BH),*/
    void spin_lock_bh(spinlock_t *lock)    //关闭下半部,并获取自旋锁
    void spin_unlock_bh(spinlock_t *lock)  //打开下半部,并释放自旋锁
  • 读写自旋锁
  1. 读写自旋锁为读和写操作提供了不同的锁,一次只能允许一个写操作,也就是只能一个线
    程持有写锁,而且不能进行读操作。但是当没有写操作的时候允许一个或多个线程持有读锁,
    可以进行并发的读操作。
  2. 
    typedef struct {
      arch_rwlock_t raw_lock;
    } rwlock_t;
    
    DEFINE_RWLOCK(rwlock_t lock)         //定义并初始化读写锁
    void rwlock_init(rwlock_t *lock)     //初始化读写锁
    
    /*读锁*/
    void read_lock(rwlock_t *lock)       //获取读锁
    void read_unlock(rwlock_t *lock)     //释放读锁
    void read_lock_irq(rwlock_t *lock)   //禁止本地中断,并且获取读锁
    void read_unlock_irq(rwlock_t *lock) //打开本地中断,并且释放读锁
    void read_lock_irqsave(rwlock_t *lock, unsigned long flags) //保存中断状态,禁止本地中断,并获取读锁
    void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags) //将中断状态恢复到以前的状态,并且激活本地中断,释放读锁
    void read_lock_bh(rwlock_t *lock)    //关闭下半部,并获取读锁
    void read_unlock_bh(rwlock_t *lock)  //打开下半部,并释放读锁
    /*写锁*/
    void write_lock(rwlock_t *lock)       //获取写锁
    void write_unlock(rwlock_t *lock)     //释放写锁
    void write_lock_irq(rwlock_t *lock)   //禁止本地中断,并且获取写锁
    void write_unlock_irq(rwlock_t *lock) //打开本地中断,并且释放写锁
    void write_lock_irqsave(rwlock_t *lock, unsigned long flags) //保存中断状态,禁止本地中断,并获取写锁
    void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags) //将中断状态恢复到以前的状态,并且激活本地中断,释放写锁
    void write_lock_bh(rwlock_t *lock)    //关闭下半部,并获取写锁
    void write_unlock_bh(rwlock_t *lock)  //打开下半部,并释放写锁
  • 顺序锁
  1. 顺序锁在读写锁的基础上衍生而来的,使用读写锁的时候读操作和写操作不能同时进行。
    使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行
    并发的写操作。虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,
    最好重新进行读取,保证数据完整性。
  2. 顺序锁保护的资源不能是指针,因为如果在写操作的时
    候可能会导致指针无效,而这个时候恰巧有读操作访问指针的话就可能导致意外发生,比如读
    取野指针导致系统崩溃。
  3. typedef struct {
      struct seqcount seqcount;
      spinlock_t lock;
    } seqlock_t;
    
    DEFINE_SEQLOCK(seqlock_t sl)            //定义并初始化顺序锁
    void seqlock_init(seqlock_t *sl)        //初始化顺序锁
    /*顺序锁写操作*/
    void write_seqlock(seqlock_t *sl)       //获取写顺序锁
    void write_sequnlock(seqlock_t *sl)     //释放写顺序锁
    void write_seqlock_irq(seqlock_t *sl)   //禁止本地中断,并且获取写顺序锁
    void write_sequnlock_irq(seqlock_t *sl) //打开中断状态,禁止本地中断,并获取写顺序锁
    void write_seqlock_irqsave(seqlock_t *sl, unsigned long flags)
    void write_sequnlock_irqstore(seqlock_t *sl, unsigned log flags)
    void write_seqlock_bh(seqlock_t *sl)
    void write_sequnlock_bh(seqlock_t *sl)
    /*顺序锁读操作*/
    unsigned read_seqbegin(const seqlock_t *sl) //读单元访问共享资源的时候调用此函数,此函数会返回顺序锁的顺序号
    unsigned read_seqretry(const_seqlock_t *sl) //读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读
    
  • 信号量
  1. ①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场
    合。
    ②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
    ③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换
    线程引起的开销要远大于信号量带来的那点优势。
  2. struct semaphore {
      raw_spinlock_t   lock;
      unsigned int     count;
      struct list_head wait_list;
    };
    
    DEFINE_SEMAPHORE(name) //定义一个信号量,并且设置信号量的值为1
    void sema_init(struct semaphore *sem, int val) //初始化信号量sem,设置信号量值为val
    void down(struct semaphore *sem) //获取信号量,因为会导致休眠,因此不能在中断中使用
    int down_trylock(struct semaphore *sem);  //尝试获得信号量,如果能获取到信号量就获取,并且返回0,如果不能就返回非0,并且不会进入休眠
    int down_interruptible(struct semaphore *sem) //获取信号量,和down类似,只是使用down进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
    void up(struct semaphore *sem)  //释放信号量
  • 互斥体
  1. ①、mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
    ②、和信号量一样,mutex 保护的临界区可以调用引起阻塞的 API 函数。
    ③、因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并
    且 mutex 不能递归上锁和解锁。
  2. struct mutex {
      /*1: unlocked, 0: locked, negative: locked, possible waiters*/
      atomic_t   count;
      spinlock_t wait_lock;
    };
    
    DEFINE_MUTEX(name) //定义并初始化一个mutex变量
    void mutex_init(mutex *lock) //初始化mutex
    void mutex_lock(struct mutex *lock)   //获取mutex,也就是给mutex上锁。如果获取不到就休眠
    void mutex_unlock(struct mutex *lock) //释放mutex
    int mutex_trylock(struct mutex *lock) //尝试获取mutex,如果成功就返回1,如果失败就返回0
    int mutex_is_locked(struct mutex *lock) //判断mutex是否被获取,如果是就返回1,否则返回0
    int mutex_lock_interruptible(struct mutex *lock) //使用此函数获取信号量失败进入休眠以后可以被信号打断
  • Completion (用于同步)
    struct completion done;
    
    static int arm11mcu_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
    {
    
        init_completion(&done);
        ...
        wait_for_completion(&done);
    }
    
    static irqreturn_t arm11mcu__spi_irq(int irq, void *dev)
    {
        ...
        complete(&hw->done);
        return IRQ_HANDLED;
    }
  •  等待队列:代表一切睡眠进程的集合
  • 阻塞与非阻塞使用(用等待队列)
    
    DECLARE_WAITQUEUE(wait, current); //定义等待队列
    add_wait_queue(&xxx_wait, &wait); //添加等待队列
    ...
    
    __set_current_state(TASK_INTERRUPTIBLE); //改变进程状态(浅度睡眠)
    
    ...
    
    shchedule(); //调度其他进程执行
    if (signal_pending(current)) //如果是因为信号唤醒
    {
        if (!ret)
            ret = -ERESTARTSYS;
        goto out;
    }
    
    out:
    remove_wait_queue(&xxx_wait, &wait); //将等待队列移出等待队列头
    set_current_state(TASK_RUNNING); //设置进程状态为TASK_RUNNING

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值