[关闭]
@zwh8800 2017-08-23T10:06:03.000000Z 字数 2641 阅读 190917

linux 设备驱动程序 (4) – 并发

blog 归档 linux 驱动开发


linux 设备驱动程序 (4) – 并发


进行 linux 驱动开发不得不考虑的问题就是并发问题. 因为在内核态, 代码是可抢占的, 你不知道什么时候内核会抢占你对 CPU 的使用权来执行另一段代码 (这段代码可能会修改掉你的数据). 而且现在大多使用 SMP(对称多处理器), 代码甚至可以同时执行. 性能得到了很大提升但是编程的复杂程度也高了很多. 特别是在如何防止数据被其他执行线程修改上. 幸运的是, linux 已经提供了很多设施来完成这个功能.

1. 信号量 & 互斥体

这个在多线程编程中太常见了, 就不赘述了. 另外记一下 semaphore 这个单词, 总是拼错.

列一下函数原型:

  1. #include <linux/semaphore.h> /* 不像书中所写
  2. 并没有<asm/semaphore.h> */
  3. void sema_init(struct semaphore *sem, int val);
  4. void down(struct semaphore *sem);
  5. int down_interruptible(struct semaphore *sem);
  6. int down_trylock(struct semaphore *sem);
  7. void up(struct semaphore *sem);
  8. void init_rwsem(struct rw_semaphore *sem);
  9. void down_read(struct rw_semaphore *sem);
  10. int down_read_trylock(struct rw_semaphore *sem);
  11. void up_read(struct rw_semaphore *sem);
  12. void down_write(struct rw_semaphore *sem);
  13. int down_write_trylock(struct rw_semaphore *sem);
  14. void up_write(struct rw_semaphore *sem);
  15. void downgrade_write(struct rw_semaphore *sem);

<> 上所说的 init_MUTEX 函数貌似在新版本中已经删掉了, 可以用 sema_init(&sem, 1); 来代替.
down_interruptible 函数当被中断时会返回非零值, down_trylock 当信号量不可获得时会返回非零值.

2. 自旋锁

当对信号量执行 down 函数时, 如果当前无法获取信号量, 会阻塞当前执行线程, 但是并非 CPU 空转不工作. 而是” 进入休眠”. 进入休眠是一个有明确定义的术语. 当” 进入休眠” 时, 执行线程会进入休眠状态, 这时会把 CPU 让给其他执行线程知道将来它能获取信号量为止.

但是自旋锁不一样, 当线程对自旋锁进行” 锁定” 动作时, 如果自旋锁已经被其他线程锁定, 那么当前线程将进行” 自旋”. 所谓自旋, 其实就是一个 while 循环 [它循环重复检查这个锁直到锁可用为止]. 所以说可见自旋锁当锁定时不会让出 CPU.

所以自旋锁简单, 而且也比信号量快 (因为不用设计到 CPU 调度). 但是使用却有一些限制:

  1. #include <linux/spinlock.h>
  2. spinlock_t lock = SPIN_LOCK_UNLOCKED;
  3. void spin_lock_init(spinlock_t *lock);
  4. void spin_lock(spinlock_t *lock);
  5. void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
  6. void spin_lock_irq(spinlock_t *lock);
  7. void spin_lock_bh(spinlock_t *lock);
  8. void spin_unlock(spinlock_t *lock);
  9. void spin_unlock_irqsave(spinlock_t *lock, unsigned long flags);
  10. void spin_unlock_irq(spinlock_t *lock);
  11. void spin_unlock_bh(spinlock_t *lock);
  12. int spin_trylock(spinlock_t *lock);
  13. int spin_trylock_bh(spinlock_t *lock);

irqsave 会将中断状态保存在 flags 中, 当 unlock 时必须提供同一个 flags.

irq 函数会禁止本处理器的中断.

bh 会关闭软中断.

同样, 自旋锁有 rw 版本.

3. 使用锁的一些准则与陷阱

4. 循环队列

使用循环队列是一种免锁算法. 生产者在队列的一端中写入, 消费者从另一端取走. 如果设计的好, 可以不必使用锁.

在 中有实现好的循环队列.

5. 原子变量

当对一个简单的整数进行加减的时候也加锁显得有些小题大做了. 但是很多整数运算确实不是原子的, 如 ++i;

所以 linux 内核实现了原子类型 atomic_t 来进行高效的原子的整形运算.

具体参见

6. 原子位操作

除了原子的整数变量, 内核也提供了原子的位操作类型和函数. 集体参见

7.seqlock

8. 读取 - 复制 - 更新 (RCU)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注