linux内核顺序锁的代码分析

顺序锁的特点

  • 允许任意多个读操作同时进入临界区
  • 只允许1个写操作同时进入临界区 ,其他写操作会忙等待
  • 如果当前只有读操作,则写操作可以随时进入临界区,不用理会读操作
seqlock_init(lock)//初始化锁
read_seqbegin(lock)//读者进入临界区
read_deqretry(lock) //读者退出临界区
write_seqlock(lock)//写者加锁
write_sequnlock(lock) //写者解锁

一般使用方法:

seqlock_t lock;//定义顺序锁
seqlock_init(lock);//初始化顺序锁

read_fun
{
	 write_seqlock(&lock)//写者加锁
	 /* 写数据 */
	 write_sequnlock(&lock);//写者解锁
}

write_fun
{
	do {
		seq = read_seqbegin(&lock);//读者进入临界区
		/* 需要完成的工作 */
	} while read_deqretry(&lock, seq);//读者退出临界区
}

使用方法也比较简单,只有read_deqretry可能有点疑问,就是读者退出临界区的时候会检测在读操作的过程中有没有写操作,如果有写操作则返回1,说明需要重新读,再跑一次循环吧;如果没有写操作,则返回0,说明不需要重新读,可以退出循环。

三、顺序锁

1.锁的结构体

typedef struct {
	struct seqcount seqcount;
	spinlock_t lock;
} seqlock_t;

typedef struct seqcount {
	unsigned sequence;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} seqcount_t;

顺序锁的结构体很简单,只有两个成员,一个成员是struct seqcount结构体,这个结构体其实就是一个unsigned 的数据类型而已,另一个成员就是一个自旋锁。

2.锁的初始化

我们是使用seqlock_init函数来初始化一个顺序锁的:

#define seqlock_init(x)					\
	do {						\
		seqcount_init(&(x)->seqcount);		\
		spin_lock_init(&(x)->lock);		\
	} while (0)

看这个宏,我们很清楚的知道,他就是初始化seqcount结构体和自旋锁。spin_lock_init前面已经讲过了,不过还是可以看看怎么初始化seqcount结构体的:

# define seqcount_init(s)				\
	do {						\
		static struct lock_class_key __key;	\
		__seqcount_init((s), #s, &__key);	\
	} while (0)

static inline void __seqcount_init(seqcount_t *s, const char *name,
					  struct lock_class_key *key)
{
	/*
	 * Make sure we are not reinitializing a held lock:
	 */
	lockdep_init_map(&s->dep_map, name, key, 0);
	s->sequence = 0;
}

看了也很简单就是把seqcount结构的sequence 成员初始化为0。

3.读者进入临界区

我们是使用read_seqbegin函数来进行读者进入临界区:

static inline unsigned read_seqbegin(const seqlock_t *sl)
{
	return read_seqcount_begin(&sl->seqcount);
}

static inline unsigned read_seqcount_begin(const seqcount_t *s)
{
	seqcount_lockdep_reader_access(s);//空操作
	return raw_read_seqcount_begin(s);
}

static inline unsigned raw_read_seqcount_begin(const seqcount_t *s)
{
	unsigned ret = __read_seqcount_begin(s);
	smp_rmb();//读内存屏障
	return ret;
}

static inline unsigned __read_seqcount_begin(const seqcount_t *s)
{
	unsigned ret;

repeat:
	ret = READ_ONCE(s->sequence);
	if (unlikely(ret & 1)) {
		cpu_relax();
		goto repeat;
	}
	return ret;
}

#define READ_ONCE(x) __READ_ONCE(x, 1)
#define __READ_ONCE(x, check)						\
({									\
	union { typeof(x) __val; char __c[1]; } __u;			\
	if (check)							\
		__read_once_size(&(x), __u.__c, sizeof(x));		\
	else								\
		__read_once_size_nocheck(&(x), __u.__c, sizeof(x));	\
	smp_read_barrier_depends(); /* Enforce dependency ordering from x */ \
	__u.__val;							\
})

static __always_inline
void __read_once_size(const volatile void *p, void *res, int size)
{
	__READ_ONCE_SIZE;
}

#define __READ_ONCE_SIZE						\
({									\
	switch (size) {							\
	case 1: *(__u8 *)res = *(volatile __u8 *)p; break;		\
	case 2: *(__u16 *)res = *(volatile __u16 *)p; break;		\
	case 4: *(__u32 *)res = *(volatile __u32 *)p; break;		\
	case 8: *(__u64 *)res = *(volatile __u64 *)p; break;		\
	default:							\
		barrier();						\
		__builtin_memcpy((void *)res, (const void *)p, size);	\
		barrier();						\
	}								\
})

一步步追,我们最后才发现,read_seqbegin啥也没做,只是把seqlock_t 结构体的seqcount 结构体的sequence成员的值返回而已,好像真的没有做啥。

4.读者退出临界区

我们是使用read_seqretry函数来进行读者退出临界区:

static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
{
	return read_seqcount_retry(&sl->seqcount, start);
}

static inline int read_seqcount_retry(const seqcount_t *s, unsigned start)
{
	smp_rmb();//读内存屏障
	return __read_seqcount_retry(s, start);
}

static inline int __read_seqcount_retry(const seqcount_t *s, unsigned start)
{
	return unlikely(s->sequence != start);
}

读者退出临界区的函数更简单,就是比较seqlock_t 结构体的seqcount 结构体的sequence成员的值和start而已,start就是读者进入临界区的的时候返回的值。如果读者在进入临界区和退出临界区的时候,他们的sequence值是一样的,说明顺序锁没有写入过东西。如果他们的sequence值是不一样的,说明在读者进入和退出临界区的期间,有写者获取过锁。

5.写者加锁

我们是使用write_seqlock函数来进行写者加锁操作的:

static inline void write_seqlock(seqlock_t *sl)
{
	spin_lock(&sl->lock);//自旋锁加锁
	write_seqcount_begin(&sl->seqcount);
}

static inline void write_seqcount_begin(seqcount_t *s)
{
	write_seqcount_begin_nested(s, 0);
}

static inline void write_seqcount_begin_nested(seqcount_t *s, int subclass)
{
	raw_write_seqcount_begin(s);
	seqcount_acquire(&s->dep_map, subclass, 0, _RET_IP_);//空操作
}

static inline void raw_write_seqcount_begin(seqcount_t *s)
{
	s->sequence++;
	smp_wmb();//写内存屏障
}

write_seqlock也很简单,首先给自旋锁加锁,然后修改sequence的值,让sequence加一,最后使用写内存屏障保证代码的执行顺序。

6.写者解锁

我们是使用write_sequnlock函数来进行写者解锁操作的:

static inline void write_sequnlock(seqlock_t *sl)
{
	write_seqcount_end(&sl->seqcount);
	spin_unlock(&sl->lock);//自旋锁解锁
}

static inline void write_seqcount_end(seqcount_t *s)
{
	seqcount_release(&s->dep_map, 1, _RET_IP_);//空操作
	raw_write_seqcount_end(s);
}

static inline void raw_write_seqcount_end(seqcount_t *s)
{
	smp_wmb();//写内存屏障
	s->sequence++;
}

write_sequnlock也很简单,首先使用写内存屏障保证代码的执行顺序,然后修改sequence的值,让sequence加一,最后给自旋锁解锁。

顺序锁就是这样子实现的,主要是通过自旋锁保护写者的临界区,然后通过sequence的值表示是否锁保护的临界区有没有遭遇到写者的修改。如果读者在进入临界区和退出临界区的时候,他们的sequence值是一样的,说明顺序锁没有写入过东西。如果他们的sequence值是不一样的,说明在读者进入和退出临界区的期间,有写者获取过锁。
不过,我这里有个疑问,就是当我们在写者加锁和写者解锁期间,进行了读者进入临界区和退出临界区的操作,读者是不会意识到目前的是在写者写入过程中的,所以我们应该尽量快速的完成写入操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小坚学Linux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值