ARM Linux下spinlock 的实现

7 篇文章 0 订阅
1 篇文章 0 订阅

 

1),spin lock 结构体

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;

typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
	unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} raw_spinlock_t;


typedef struct {
    union {
        u32 slock;
        struct __raw_tickets {
#ifdef __ARMEB__
            u16 next;
            u16 owner;
#else
            u16 owner;
            u16 next;
#endif
        } tickets;
    };
} arch_spinlock_t;

暂且抛开那些CONFIG 宏定义,其实就是arch_spinlock_t这个结构体。

这个结构体是32bits大小。用了union。slock和 next owner共用这32bits。

旧版本的内核spin lock结构体只有一个u32 ,为0则可以获得锁,不为0就自旋等待。 新的自旋锁加了next和owner让线程可以按获取锁的顺序排队。

2),获取锁

static inline void spin_lock(spinlock_t *lock)
{
	raw_spin_lock(&lock->rlock);
}

没什么好说,直接到raw_spin_lock,然后中间经过数次调用,或者宏定义(其中有区分SMP和UP等)

最终到

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
	preempt_disable();
	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
	LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

1,关闭抢占,同一cpu上靠关闭抢占解决同步问题,UP上spin lock 其实就是关闭抢占

2,这个是编译时静态检查相关,保证解锁加锁成对出现

3,这个宏就最终调用了do_raw_spin_lock,再到arch_spin_lock

3)arch_spin_lock


static inline void arch_spin_lock(arch_spinlock_t *lock)
{
	unsigned long tmp;
	u32 newval;
	arch_spinlock_t lockval;

	__asm__ __volatile__(
"1:	ldrex	%0, [%3]\n"
"	add	%1, %0, %4\n"
"	strex	%2, %1, [%3]\n"
"	teq	%2, #0\n"
"	bne	1b"
	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
	: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
	: "cc");

	while (lockval.tickets.next != lockval.tickets.owner) {
		wfe();
		lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
	}

	smp_mb();
}

从第8行开始是嵌入了asm 汇编, __volatile__关键字确保下面代码不被优化改变。

根据嵌入汇编的规则,%0 是lockval ,类型为arch_spinlock_t类型,%1 %2 依次是newval 和tmp,依次类推。

汇编

第1行: 把lock->slock值保存到lock_val

第2行:newval=lockval+1<<TICKET_SHIFT

             TICKET_SHIFT 是 用来表明arch_spinlock_t中owner 和next各自所占多少bit,定义为16则,owner和next各占16bit

             加1<<TICKET_SHIFT 就是next++

第3,4行:lock->slock = newval ,判断strex返回值,确认是否成功更新锁,如果更新失败,说明有其他内核路径插入。 

               ldrex 和strex 用这种方法保证原子性,所以同一时间只会有一个线程能成功更新lock->slock的值。

               同样内核中原子操作也是用类似的方法实现。

第5行: 如果更新失败回到1行。

如果更新成功,到下面c代码

判断next是否等于owner,如果相等就获得锁,如果不相等说明还没轮到自己,调用wfe()挂起当前cpu。

释放锁操作

static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
	smp_mb();
	lock->tickets.owner++;
	dsb_sev();
}

释放锁比较简单,直接owner++,然后sev指令通知所有cpu,其他cpu会尝试获得锁。

思考一下为什么释放锁不需要用原子操作的方式操作owner,而获取锁需要用原子方式操作next?

 

另外

用smp_mb()数据内存屏障保证乱序执行时,内存操作的顺序。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值