前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >liunx内核中的互斥自旋锁和读写自旋锁的实现详解

liunx内核中的互斥自旋锁和读写自旋锁的实现详解

作者头像
gzq大数据
发布2022-05-11 11:43:22
9890
发布2022-05-11 11:43:22
举报
文章被收录于专栏:大数据那些事大数据那些事

今天把这两个锁的内核实现源码重新捋了一遍,基于liunx2,6.0,直接粘注释版: 核心文件,x86下实现的spinlock

#ifndef __ASM_SPINLOCK_H
#define __ASM_SPINLOCK_H

#include <asm/atomic.h>
#include <asm/rwlock.h>
#include <asm/page.h>
#include <linux/config.h>

extern int printk(const char * fmt, ...)
	__attribute__ ((format (printf, 1, 2)));

/*
 * Your basic SMP spinlocks, allowing only a single CPU anywhere
 */

typedef struct {
    //自旋锁为无符号的整型变量 volatile保证变量都从内存中获取,不要缓存在寄存器里
	volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned magic;
#endif
} spinlock_t;
//定义一个spinlock的魔数,用来调试用
#define SPINLOCK_MAGIC	0xdead4ead

#ifdef CONFIG_DEBUG_SPINLOCK
#define SPINLOCK_MAGIC_INIT	, SPINLOCK_MAGIC
#else
#define SPINLOCK_MAGIC_INIT	/*如果没有开启调试状态将什么也没有 */
#endif
//创建一个值为1的自旋锁
#define SPIN_LOCK_UNLOCKED (spinlock_t) { 1 SPINLOCK_MAGIC_INIT }
//初始化自旋锁 x是一根指针 所以解引用
#define spin_lock_init(x)	do { *(x) = SPIN_LOCK_UNLOCKED; } while(0)

/*
 * Simple spin lock operations.  There are two variants, one clears IRQ's
 * on the local processor, one does not.
 * 简单的自旋锁操作。有两种变体,一种清除本地处理器上的IRQ,另一种不清除。
 *
 * We make no fairness assumptions. They have a cost.
 * 我们没有做出公平的假设(非公平)。它们是有代价的
 */
//判断自旋锁是否被锁定 先通过取地址拿到spinlock里的lock 再转为字符,再解引用判断是否小于0
#define spin_is_locked(x)	(*(volatile signed char *)(&(x)->lock) <= 0)
//等待自旋锁释放,barrier()保证禁止编译器任意排序
#define spin_unlock_wait(x)	do { barrier(); } while(spin_is_locked(x))
//获取自旋锁内联汇编代码,这里只是code部分,剩下在用的时候肯定是有输出数和输入数的
#define spin_lock_string \
	"1:" \ //1位置
	"lock ; decb %0" \ //对lock变量的值进行自减,如果lock变量是0或者小于0,再减岂不是直接就小于0了吗,所以底下js判断是否符号位为1,也就是小于0
	"js 2f" \  //f是forward,往后跳,因为2:确实在后面,如果小于0,跳转到前面的2处 js判断的是符号位是否为1,为1当然就小于0啦
	LOCK_SECTION_START("") \ //涉及到ELF的知识
	"2:" \
	"rep;nop" \ //repeat空操作
	"cmpb $0,%0" \ //比较lock的值是否为0,%0可以从后面的代码看出,是输出参数,是lock变量 cmpb的b代表比一个字节,l代表4字节
	"jle 2b" \ //b是backward,往前跳,jle代表小于或等于0,继续2处
	"jmp 1b" \//jmp:无条件转移到指定内存地址,否则跳回到1处去进行减一操作,这也不一定还能拿到哦,还得判断,如果成功就拿到了锁!!!!!
	LOCK_SECTION_END //".previous\n\t"

/*
 * This works. Despite all the confusion.
 * 这很有效。尽管如此混乱。
 * (except on PPro SMP or if we are using OOSTORE)
 * (PPro errata 66, 92)
 */
 
#if !defined(CONFIG_X86_OOSTORE) && !defined(CONFIG_X86_PPRO_FENCE)

#define spin_unlock_string \ //这里和lock_string是不一样的,直接就把输入输出和clobber都填好了
	"movb $1,%0" \ //把1设置为lock的值
		:"=m" (lock->lock) : : "memory" //static spinlock_t lock;这里的lock是一个spinlock_t,所以指针再指向spinlock里的lock


static inline void _raw_spin_unlock(spinlock_t *lock)
{
#ifdef CONFIG_DEBUG_SPINLOCK
	if (lock->magic != SPINLOCK_MAGIC)
		BUG();
	if (!spin_is_locked(lock))
		BUG();
#endif
	__asm__ __volatile__(
		spin_unlock_string
	);
}

#else

#define spin_unlock_string \
	"xchgb %b0, %1" \
		:"=q" (oldval), "=m" (lock->lock) \
		:"0" (oldval) : "memory"

static inline void _raw_spin_unlock(spinlock_t *lock)
{
	char oldval = 1;
#ifdef CONFIG_DEBUG_SPINLOCK
	if (lock->magic != SPINLOCK_MAGIC)
		BUG();
	if (!spin_is_locked(lock))
		BUG();
#endif
	__asm__ __volatile__(
		spin_unlock_string
	);
}

#endif

static inline int _raw_spin_trylock(spinlock_t *lock)
{
	char oldval;
	__asm__ __volatile__(
		"xchgb %b0,%1" //交换lock的值(%1)和oldval
		:"=q" (oldval), "=m" (lock->lock) //q:将输入变量放入eax,ebx,ecx,edx中的一个 
		:"0" (0) : "memory"); //0:表示用它限制的操作数与某个指定的操作数(这里就是0)匹配
	return oldval > 0; //大于0说明lock变量为1,那说明就没有人拿到这个锁,那么久尝试获得到了锁
}

static inline void _raw_spin_lock(spinlock_t *lock)
{
#ifdef CONFIG_DEBUG_SPINLOCK
	__label__ here;
here:
	if (lock->magic != SPINLOCK_MAGIC) {
printk("eip: %p\n", &&here);
		BUG();
	}
#endif
	__asm__ __volatile__(
		spin_lock_string
		:"=m" (lock->lock)//输出操作数列表为lock : : "memory");
}


/*
 * Read-write spinlocks, allowing multiple readers
 * but only one writer.
 *
 * NOTE! it is quite common to have readers in interrupts
 * but no interrupt writers. For those circumstances we
 * can "mix" irq-safe locks - any writer needs to get a
 * irq-safe write-lock, but readers can get non-irqsafe
 * read-locks.
 */
typedef struct {
	volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned magic;
#endif
} rwlock_t;//多个读者共享,写者互斥,和互斥自旋锁机构一模一样

#define RWLOCK_MAGIC	0xdeaf1eed

#ifdef CONFIG_DEBUG_SPINLOCK
#define RWLOCK_MAGIC_INIT	, RWLOCK_MAGIC
#else
#define RWLOCK_MAGIC_INIT	/* */
#endif

#define RW_LOCK_UNLOCKED (rwlock_t) { RW_LOCK_BIAS RWLOCK_MAGIC_INIT }//RW_LOCK_BIAS->0x0100 0000第七位

//初始化读写自旋锁
#define rwlock_init(x)	do { *(x) = RW_LOCK_UNLOCKED; } while(0)

#define rwlock_is_locked(x) ((x)->lock != RW_LOCK_BIAS)//RW_LOCK_BIAS代表没上锁(既没有读锁也没有写锁)

/*
 * On x86, we implement read-write locks as a 32-bit counter
 * with the high bit (sign) being the "contended" bit.
 * 在x86上,我们将读写锁实现为32位计数器,高位(符号)为“争用”位。
 *
 * The inline assembly is non-obvious. Think about it.
 *
 * Changed to use the same technique as rw semaphores.  See
 * semaphore.h for details.  -ben
 */
#endif
/* the spinlock helpers are in arch/i386/kernel/semaphore.c */
//helper__write_lock_failed和的实现去这个地方(arch/i386/kernel/semaphore.c)找,否则找不到
//获取读锁或者写锁失败后的helper实现
static inline void _raw_read_lock(rwlock_t *rw)
{
#ifdef CONFIG_DEBUG_SPINLOCK
	if (rw->magic != RWLOCK_MAGIC)
		BUG();
#endif
	__build_read_lock(rw, "__read_lock_failed");//在读写锁文件rwlock.h里有相应实现
}

static inline void _raw_write_lock(rwlock_t *rw)
{
#ifdef CONFIG_DEBUG_SPINLOCK
	if (rw->magic != RWLOCK_MAGIC)
		BUG();
#endif
	__build_write_lock(rw, "__write_lock_failed");
}

//读锁和写锁的释放也很简单,原子加1或者原子加0x0100 0000
#define _raw_read_unlock(rw)		asm volatile("lock ; incl %0" :"=m" ((rw)->lock) : : "memory")
#define _raw_write_unlock(rw)	asm volatile("lock ; addl $" RW_LOCK_BIAS_STR ",%0":"=m" ((rw)->lock) : : "memory")

static inline int _raw_write_trylock(rwlock_t *lock)
{
	atomic_t *count = (atomic_t *)lock;
	if (atomic_sub_and_test(RW_LOCK_BIAS, count))
		return 1;
	atomic_add(RW_LOCK_BIAS, count);
	return 0;
}

#endif /* __ASM_SPINLOCK_H */

rwlock.h核心文件,x86实现:

/* include/asm-x86_64/rwlock.h
 *
 *	Helpers used by both rw spinlocks and rw semaphores.
 *
 *	Based in part on code from semaphore.h and
 *	spinlock.h Copyright 1996 Linus Torvalds.
 *
 *	Copyright 1999 Red Hat, Inc.
 *	Copyright 2001,2002 SuSE labs 
 *
 *	Written by Benjamin LaHaise.
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 */
#ifndef _ASM_X86_64_RWLOCK_H
#define _ASM_X86_64_RWLOCK_H

#include <linux/stringify.h>

#define RW_LOCK_BIAS		 0x01000000
#define RW_LOCK_BIAS_STR	"0x01000000"

#define __build_read_lock_ptr(rw, helper)   \
	asm volatile(LOCK "subl $1,(%0)" \ //获取读锁就是尝试在lock上减1,因为RW_LOCK_BIAS是非常大的一个数
	// 只有有写锁的时候会直接减去0x01000000变为0,其他时候是不可能小于0的,所以没有写锁的情况下,去抢占这个读锁是没有问题的
		     "js 2f" \ //判断符号位是否为1,也即是否为负数,如果是负数,那么说明已经有写锁啦,那你只能去2位置了
		     "1:" \
		    LOCK_SECTION_START("") \
		     "2: call " helper "" \ //调用helper方法,helper即为__read_lock_failed,下面把这段实现摘抄出来了,我没有获取到锁,只好调用这个方法
		     "jmp 1b" \
		    LOCK_SECTION_END \
		     ::"a" (rw) : "memory") //a:将输入变量放入eax

//这段代码是从i386的semaphore下摘出来的
	asm(
	"__read_lock_failed:"
		LOCK "incl	(%eax)"//原子性增加eax寄存器中的值(也就是lock变量的值)
	"1: rep; nop" //进行空操作,耗掉一点点时间
		"cmpl	$1,(%eax)"// cmp影响符号位,lock变量和1相减如果是负数,符号位为1
		"js 1b" //如果是负数那么就去1重新比较,直到可以获得读锁
		LOCK "decl	(%eax)"//说明eax也就是lock大于等于1,进行相减再次判断是否为负数,因为可能两个线程同时走这一步,严谨!!!!!
		"js __read_lock_failed"//负数说明又没抢到,继续循环吧
		"ret"//抢到了,返回
	);



#define __build_read_lock_const(rw, helper)   \
	asm volatile(LOCK "subl $1,%0\n\t" \
		     "js 2f\n" \
		     "1:\n" \
		    LOCK_SECTION_START("") \
		     "2:\tpushq %%rax\n\t" \
		     "leaq %0,%%rax\n\t" \
		     "call " helper "\n\t" \
		     "popq %%rax\n\t" \
		     "jmp 1b\n" \
		    LOCK_SECTION_END \
		     :"=m" (*((volatile int *)rw))::"memory")

#define __build_read_lock(rw, helper)	do { \
						if (__builtin_constant_p(rw)) \
							__build_read_lock_const(rw, helper); \
						else \
							__build_read_lock_ptr(rw, helper); \//走这里
					} while (0)

#define __build_write_lock_ptr(rw, helper) \
	asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)" \ //核心,写锁只能有一个,上来直接把那个大数减完了
		     "jnz 2f" \ //看是否为0,不为0说明绝对有读锁在占着,直接失败去2处forward
		     "1:" \
		     LOCK_SECTION_START("") \
		     "2: call " helper "" \同读锁逻辑,截取helper如下
		     "jmp 1b" \
		     LOCK_SECTION_END \
		     ::"a" (rw) : "memory")

//这段代码是从i386的semaphore下摘出来的
	asm(
	"__write_lock_failed:"
		LOCK "addl	$" RW_LOCK_BIAS_STR ",(%eax)" //大同小异,先加后减
	"1: rep; nop\n\t"
		"cmpl	$" RW_LOCK_BIAS_STR ",(%eax)"
		"jne	1b"
		LOCK "subl	$" RW_LOCK_BIAS_STR ",(%eax)"
		"jnz	__write_lock_failed"
		"ret"
	);


#define __build_write_lock_const(rw, helper) \
	asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)\n\t" \
		     "jnz 2f\n" \
		     "1:\n" \
		    LOCK_SECTION_START("") \
		     "2:\tpushq %%rax\n\t" \
		     "leaq %0,%%rax\n\t" \
		     "call " helper "\n\t" \
		     "popq %%rax\n\t" \
		     "jmp 1b\n" \
		    LOCK_SECTION_END \
		     :"=m" (*((volatile long *)rw))::"memory")

#define __build_write_lock(rw, helper)	do { \
						if (__builtin_constant_p(rw)) \
							__build_write_lock_const(rw, helper); \
						else \
							__build_write_lock_ptr(rw, helper); \ //上读锁走这里
					} while (0)

#endif

看注释可以很容易理解。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-01-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档