本文kernel代码分析基于以下
1.linux-4.14.159
2.64bit代码处理逻辑
学习目的:1.写代码涉及锁的实现时能更好的选择不同的锁 2 死锁问题涉及信号量时能更熟练的debug。
我们知道linux内核中存在进程、中断、软中断等相互之间的同步问题,在SMP下这些使用场景会变的尤为复杂,因此linux内核提供了semaphore、spinlock、mutex,rcu、atomic等锁机制来解决这些问题。然而每种锁机制都有不同的实现原理以使用场景,在性能方面也是会存在一定差异,因此对于不同场景下的问题我们能选择最适用的机制才是最好的。
此篇我们重点看下semaphore以及rw_semaphore 这些锁机制的内核源码实现。
一. semaphore
信号量常用于进程间的同步问题
信号量允许计数值大于1,也就是说允许多个进程进入临界区访问共享资源
当计数值等于1,只允许一个进程进入,此时信号量类似mutex
信号量不允许在中断处理中使用(中断不允许睡眠),因为其获取失败后会进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE)
1.1 semaphore使用首先需要初始化,看下面初始化相关的结构体:
struct semaphore {
raw_spinlock_t lock; //@1
unsigned int count; //@2
struct list_head wait_list; //@3
};
struct semaphore_waiter {
//@4
struct list_head list;
struct task_struct *task;
bool up;
};
@1. lock为自旋锁用于count的互斥访问。自旋锁用于很短时间就能完成的操作,不允许睡眠和进程切换
@2. count为计数值,为最大同时可访问临界区的进程数量
@3. 信号量申请不成功后,其申请者会放到wait_list等待队列中
@4. 等待队列定义,list用于关联到信号量的wait_list,task为等待的进程,up为从等待队列释放时会设为true
1.2 初始化后我们看使用场景。
down(sem);
...临界区资源...
up(sem);
down用于获取信号量,up用于释放信号量,我们重点看下源码实现
down
void down(struct semaphore *sem) //@1
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags); //@2
if (likely(sem->count > 0))
sem->count--; //@3
else
__down(sem);//@4
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
@1. 获取信号量,如果获取失败会休眠直到信号量被释放。官方建议使用down_interruptible、down_killable,这几个接口的唯一差异是调用时传入参数不一样。
down为TASK_UNINTERRUPTIBLE,表示睡眠过程不接受信号打断,
down_interruptible为TASK_INTERRUPTIBLE,表示睡眠过程可接受信号打断,返回值表明是信号量申请成功还是信号打断
down_killable为TASK_KILLABLE,表明睡眠过程中可因致命信号被中断,几个都是类似的。
@2.使用自旋锁关中断、抢占保护,目的是保证对count的原子访问
@3.可用计数大于0,申请成功进入临界区资源,可用计数需要减1
@4 可用计数不满足大于0,调用__down->__down_common进一步处理;
static inline int __sched __down_common(struct semaphore *sem, long state, long timeout)
{
struct semaphore_waiter waiter;
list_add_tail(&waiter.list, &sem->wait_list); //@5
waiter.task = current;
waiter.up = false;
for (;;) {
if (signal_pending_state(state, current)) //@6
goto interrupted;
if (unlikely(timeout <= 0)) //@7
goto timed_out;
__set_current_state(state); //@8
raw_spin_unlock_irq(&sem->lock); //@9
timeout = schedule_timeout(timeout); //@10
raw_spin_lock_irq(&sem->lock);
if (waiter.up) //@11
return 0;
}
timed_out:
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;