面试官让你讲讲Linux内核的竞争与并发,你该如何回答?( 三 )


??使用了自旋锁之后可以保证临界区不受别的CPU和本CPU内的抢占进程的打扰 , 但是得到锁的代码在执行临界区的时候 , 还可能受到中断和底半部的影响 , 为了防止这种影响 , 建议使用以下列表中的函数:
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
在多核编程的时候 , 如果进程和中断可能访问同一片临界资源 , 我们一般需要在进程上下文中调用spin_ lock irqsave() spin_unlock_irqrestore() , 在中断上下文中调用 spin_lock() spin _unlock() 。 这样 , 在CPU上 , 无论是进程上下文 , 还是中断上下文获得了自旋锁 , 此后 , 如果CPU1无论是进程上下文 , 还是中断上下文 , 想获得同一自旋锁 , 都必须忙等待 , 这避免一切核间并发的可能性 。 同时 , 由于每个核的进程上下文持有锁的时候用的是 spin_lock_irgsave() , 所以该核上的中断是不可能进入的 , 这避免了核内并发的可能性 。
DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */ /* 线程A */void functionA (){ unsigned long flags; /* 中断状态 */ spin_lock_irqsave( if (xxx_count) {/* 已经打开*/ spin_unlock( return -EBUSY; }xxx_count++;/* 增加使用计数*/spin_unlock( ...return 0;/* 成功 */}static int xxx_release(struct inode *inode, struct file *filp){...spinlock(xxx_count--;/* 减少使用计数*/spin_unlock(return 0;}读写自旋锁??当临界区的一个文件可以被同时读取 , 但是并不能被同时读和写 。 如果一个线程在读 , 另一个线程在写 , 那么很可能会读取到错误的不完整的数据 。 读写自旋锁是可以允许对临界区的共享资源进行并发读操作的 。 但是并不允许多个线程并发读写操作 。 如果想要并发读写 , 就要用到了顺序锁 。??读写自旋锁的读操作函数如下所示:
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
读写自旋锁的写操作函数如下所示:
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
读写锁例程rwlock_t lock; /* 定义rwlock */rwlock_init( /* 初始化rwlock *//* 读时获取锁*/read_lock(... /* 临界资源 */read_unlock(/* 写时获取锁*/write_lock_irqsave(... /* 临界资源 */write_unlock_irqrestore(顺序锁??顺序锁是读写锁的优化版本 , 读写锁不允许同时读写 , 而使用顺序锁可以完成同时进行读和写的操作 , 但并不允许同时的写 。 虽然顺序锁可以同时进行读写操作 , 但并不建议这样 , 读取的过程并不能保证数据的完整性 。
顺序锁操作函数??顺序锁的读操作函数如下所示:
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
?顺序锁的写操作函数如下所示:
面试官让你讲讲Linux内核的竞争与并发,你该如何回答?文章插图
自旋锁使用注意事项

  1. 因为在等待自旋锁的时候处于“自旋”状态 , 因此锁的持有时间不能太长 , 一定要短 , 否则的话会降低系统性能 。 如果临界区比较大 , 运行时间比较长的话要选择其他的并发处理方式 , 比如稍后要讲的信号量和互斥体 。
  2. 自旋锁保护的临界区内不能调用任何可能导致线程休眠的API函数 , 比如copy_from_user()、copy_to_user()、kmalloc()和msleep()等函数 , 否则的话可能导致死锁 。
  3. 不能递归申请自旋锁 , 因为一旦通过递归的方式申请一个你正在持有的锁 , 那么你就必须“自旋” , 等待锁被释放 , 然而你正处于“自旋”状态 , 根本没法释放锁 。 结果就是自己把自己锁死了