Linux设备驱动中的并发控制

7.1 并发与竞态

       解决竞态问题的途径是保证对共享资源的互斥访问。

       访问共享资源的代码区域称为临界区,临界区需要以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁和信号量等是 Linux 设备驱动中可采用的互斥途径。

7.2 中断屏蔽

       中断屏蔽将使得中断与进程之间的并发不再发生,而且,由于 Linux 内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也就得以避免了。

       local_irq_disable()local_irq_enable() 都只能禁止和使能本 CPU 内的中断,因此,并不能解决 SMPCPU 引发的竞态。

       local_irq_save(flags) 除了进行禁止中断操作以外,还保存目前 CPU 的中断位信息, local_irq_restore(flags) 进行相反的操作。

       如果只想禁止中断的底半部,应使用 local_bh_disable(), 使能底半部使用 local_bh_enable()

7.3 原子操作

       原子操作指的是在执行过程中不会被别的代码路径所中断的操作。

       Linux 内核提供了一系列函数来实现内核中的原子操作,这些函数又分为两类,分别针对位和整型变量进行原子操作。

7.3.1 整型原子操作

1. 设置值 void atomic_set(atomic_t *v,int i); Atomic_t v = ATOMIC_INT(0);

2. 获取值 atomic_read(atomic_t *v);

3. 加减   void atomic_add(int i,atomic_t *v); void atomic_sub(int i,atomic_t *v);

4. 自增自减   void atomic_inc(atomic_t *v); void atomic_dec(atomic_t *v);

5. 操作测试 int atomic_inc_and_test(atomic_t *v); int atomic_dec_and_test(atomic_t *v); int atomic_sub_and_test(int i, atomic_t *v);

       操作后测试其值是否为 0 ,为 0 返回 true ,否则返回 false

6. 操作返回 int atomic_inc_and_return(atomic_t *v); int atomic_dec_and_return(atomic_t *v); int atomic_sub_and_return(int i, atomic_t *v); int atomic_add_and_return(int i, atomic_t *v);

       操作后返回新值。

7.3.2 位原子操作

1. 设置位 void set_bit(nr,void *addr); 设置 addr 地址的第 nr 位,即将位写 1

2. 清除位 void clear_bit(nr,void *addr); 将位写为 0

3. 改变位 void change_bit(nr,void *addr); 将位进行反置。

4. 测试位 test_bit(nr,void *addr); 返回第 nr 位。

5. 测试操作 int test_and_set_bit(nr,void *addr); int test_and_clear_bit(nr,void *addr); int test_and_change_bit(nr,void *addr);

       先返回,后操作。

7.4 自旋锁

7.4.1 自旋锁的使用

       自旋锁是一种对临界资源进行互斥访问的典型手段。

操作: 1. 定义 spinlock_t lock; 2. 初始化 spin_lock_init(&lock); 3 获得 spin_lock(&lock); 自旋等待 spin_trylock(&lock); 非阻塞,立即返回。 4. 释放 spin_unlock(&lock);

注意:自旋锁实际是忙等锁;自旋锁可能导致系统死锁。

7.4.2 读写自旋锁

       读写自旋锁可允许读的并发。

操作: 1. 定义初始化 rwlock_t my_rwlock = RW_LOCK_UNLOCKED;

rwlock_t my_rwlock;

rwlock_init(&my_rwlock);

2. 读锁定 void read_lock(rw_lock_t *lock);

3. 读解锁 void read_unlock(rw_lock_t *lock);

4. 写锁定 void write_lock(rw_lock_t *lock);

void write_trylock(rw_lock_t *lock);

5. 写解锁 void write_unlock(rw_lock_t *lock);

7.4.3 顺序锁

       顺序锁中读或写单元都不会被对方阻塞,但是写写仍然互斥。

       顺序锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写单元可能使得指针失效。

操作: 1. 获得锁 void write_seqlock(seqlock_t *sl);

void write_tryseqlock(seqlock_t *sl);

2. 释放锁 void write_sequnlock(seqlock_t *sl);

       读完后需要进行检查在读期间是否有写操作,如果有则需要重新进行读操作。

7.4.4 - 拷贝 - 更新

       对于被 RCU 保护的共享数据结构,读执行单元不需要获得任何锁就可以访问它,不需要锁也使得使用更容易,因为死锁问题就不需要考虑。

       使用 RCU 写执行单元在访问它前需首先复制一个副本,然后对副本进行修改,最后使用一个回调机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的 CPU 都退出对共享数据的操作的时候。

操作: 1. 读锁定 rcu_read_lock()    rcu_read_lock_bh()

2. 读解锁 rcu_read_unlock()   rcu_read_unlock_bh()

3. 同步 RCU  synchronize_rcu()

4 挂接回调 void fastcall call_rcu(struct rcu_head *head, void (*func)(struct_rcu_head *rcu) );

RCU 还增加了链表操作函数的 RCU 版本。

7.5 信号量

7.5.1 信号量的使用

       与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。

操作: 1. 定义 struct semaphore sem;

2. 初始化 void sema_init(struct semaphore *sem, int val);

void init_MUTEX(struct semaphore *sem); // 信号量 sem 的值设置为 1

void init_MUTEX_LOCKED(struct semaphore *sem); // 信号量 sem 的值设置为 0

DECLARE_MUTEX(name)  //name 的信号量初始化为 1

DECLARE_MUTEX_LOCKED(name)  //name 的信号量初始化为 0

3. 获取 void down(struct semaphore *sem); // 休眠

int down_interruptible(struct semaphore *sem);

Int down_trylock(struct semaphore *sem); // 不会休眠

4. 释放 void up(struct semaphore *sem);

7.5.2 信号量用于同步

       如果信号量被初始化为 0 ,则它可以用于同步,同步意味着一个执行单元的继续执行需等待另一执行单元完成某事,保证执行的先后顺序。

7.5.3 完成量用于同步

       Linux 提供了一种比信号量更好的同步机制,即完成量 (completion)

操作: 1. 定义   struct completion my_completion;

2. 初始化 init_completion(&my_completion);

DECLARE_COMPLETION(my_completion);

3. 等待 void wait_for_completion(struct completion *c);

4. 唤醒 void complete(struct completion *c);

void complete_all(struct completion *c);

7.5.4 自旋锁 vs 信号量

       严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。

       信号量是进程级的,用于多个进程之间对资源的互斥。

       信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程间的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。

       如果被保护的共享资源需要在中断或软中断情况下使用,则只能选择自旋锁。如果一定要使用信号量,则只能通过 down_trylock() 进行,以避免阻塞。

7.5.5 读写信号量

       它可允许 N 个读执行单元同时访问共享资源,而最多只能有一个写执行单元。

操作: 1. 定义初始化 struct rw_semaphore my_rws;

void init_rwsem(struct rw_semaphore *sem);

2. 读信号量获取 void down_read(struct rw_semaphore *sem);

int down_read_trylock(struct rw_semaphore *sem);

3. 读信号量释放 void up_read(struct rw_semaphore *sem);

4. 写信号量获取 void down_write(struct rw_semaphore *sem);

int down_write_trylock(struct rw_semaphore *sem);

3. 写信号量释放 void up_write(struct rw_semaphore *sem);

7.6 互斥体

操作: 1. 定义初始化 struct mutex my_mutex;

Mutex_init (&my_mutex);

2. 获取 void fastcall mutex_lock(struct mutex *lock);

int fastcall mutex_lock_interruptible(struct mutex *lock);

int fastcall mutex_trylock (struct mutex *lock);

3. 释放 void fastcall mutex_unlock(struct mutex *lock);

7.7 增加并发控制后的 globalmem 驱动

       由于调用 copy_from_user()copy_to_user() 可能导致阻塞,因此不能使用自旋锁,宜使用信号量。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值