diff --git a/kernel/arch/x86_64/asm/cmpxchg.h b/kernel/arch/x86_64/asm/cmpxchg.h new file mode 100644 index 00000000..f63ad259 --- /dev/null +++ b/kernel/arch/x86_64/asm/cmpxchg.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include + +/** + * @brief 通过extern不存在的函数,来让编译器报错。以防止不符合要求的代码的产生。 + */ +extern void __cmpxchg_wrong_size(void) __compiletime_error("Bad argument size for cmpxchg"); + +// 定义常量:操作符涉及到的字节数 +#define __X86_CASE_B 1 +#define __X86_CASE_W 2 +#define __X86_CASE_L 4 +#define __X86_CASE_Q 8 + +/** + * @brief lock cmpxchg指令的包装。 + * 将_ptr指向的值与old_ptr指向的值做比较,如果相等,则将_new指向的值,加载到_ptr指向的值中。 + */ +#define __raw_try_cmpxchg(_ptr, _old_ptr, _new, size) \ + ({ \ + bool is_success = false; \ + typeof(_ptr) _old = (typeof(_ptr))(_old_ptr); \ + typeof(*(_ptr)) __old = *_old; \ + typeof(*(_ptr)) __new = (_new); \ + switch (size) \ + { \ + case __X86_CASE_B: \ + { \ + volatile uint8_t *__ptr = (volatile uint8_t *)(_ptr); \ + asm volatile("lock cmpxchgb %[new], %[ptr]\n\t" \ + : CC_OUT(z)(is_success), \ + [ptr] "+m"(*__ptr), \ + [old] "+a"(__old) \ + : [new] "q"(__new) \ + : "memory"); \ + break; \ + } \ + case __X86_CASE_W: \ + { \ + volatile uint16_t *__ptr = (volatile uint16_t *)(_ptr); \ + asm volatile("lock cmpxchgw %[new], %[ptr]\n\t" \ + : CC_OUT(z)(is_success), \ + [ptr] "+m"(*__ptr), \ + [old] "+a"(__old) \ + : [new] "q"(__new) \ + : "memory"); \ + break; \ + } \ + case __X86_CASE_L: \ + { \ + volatile uint32_t *__ptr = (volatile uint32_t *)(_ptr); \ + asm volatile("lock cmpxchgl %[new], %[ptr]\n\t" \ + : CC_OUT(z)(is_success), \ + [ptr] "+m"(*__ptr), \ + [old] "+a"(__old) \ + : [new] "q"(__new) \ + : "memory"); \ + break; \ + } \ + case __X86_CASE_Q: \ + { \ + volatile uint64_t *__ptr = (volatile uint64_t *)(_ptr); \ + asm volatile("lock cmpxchgq %[new], %[ptr]\n\t" \ + : CC_OUT(z)(is_success), \ + [ptr] "+m"(*__ptr), \ + [old] "+a"(__old) \ + : [new] "q"(__new) \ + : "memory"); \ + break; \ + } \ + default: \ + __cmpxchg_wrong_size(); \ + } \ + if (unlikely(is_success == false)) \ + *_old = __old; \ + likely(is_success); \ + }) + +#define arch_try_cmpxchg(ptr, old_ptr, new_ptr) \ + __raw_try_cmpxchg((ptr), (old_ptr), (new_ptr), sizeof(*ptr)) \ No newline at end of file diff --git a/kernel/common/lockref.h b/kernel/common/lockref.h new file mode 100644 index 00000000..c700c8f1 --- /dev/null +++ b/kernel/common/lockref.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +#if ARCH(X86_64) +// 仅在x64架构下启用cmpxchg +#define __LOCKREF_ENABLE_CMPXCHG__ +#endif +struct lockref +{ + union + { +#ifdef __LOCKREF_ENABLE_CMPXCHG__ + aligned_u64 lock_count; // 通过该变量的声明,使得整个lockref按照8字节对齐 +#endif + struct + { + spinlock_t lock; + int count; + }; + }; +}; + +/** + * @brief 原子的将引用计数加1 + * + * @param lock_ref 要被操作的lockref变量 + */ +void lockref_inc(struct lockref *lock_ref); + +/** + * @brief 原子地将引用计数加1.如果原来的count≤0,则操作失败。 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return int 操作成功=>true + * 操作失败=>false + */ +bool lockref_inc_not_zero(struct lockref *lock_ref); + +/** + * @brief 原子地减少引用计数。如果已处于count≤0的状态,则返回-1 + * + * 本函数与lockref_dec_return()的区别在于,当在cmpxchg()中检测到count<=0或已加锁,本函数会再次尝试通过加锁来执行操作 + * 而后者会直接返回错误 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return int 操作成功 => 返回新的引用变量值 + * lockref处于count≤0的状态 => 返回-1 + */ +int lockref_dec(struct lockref *lock_ref); + +/** + * @brief 原子地减少引用计数。如果处于已加锁或count≤0的状态,则返回-1 + * + * 本函数与lockref_dec()的区别在于,当在cmpxchg()中检测到count<=0或已加锁,本函数会直接返回错误 + * 而后者会再次尝试通过加锁来执行操作 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return int 操作成功 => 返回新的引用变量值 + * lockref处于已加锁或count≤0的状态 => 返回-1 + */ +int lockref_dec_return(struct lockref *lock_ref); + + +/** + * @brief 原子地减少引用计数。若当前的引用计数≤1,则操作失败 + * + * 该函数与lockref_dec_or_lock_not_zero()的区别在于,当cmpxchg()时发现old.count≤1时,该函数会直接返回false. + * 而后者在这种情况下,会尝试加锁来进行操作。 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return true 成功将引用计数减1 + * @return false 如果当前的引用计数≤1,操作失败 + */ +bool lockref_dec_not_zero(struct lockref *lock_ref); + +/** + * @brief 原子地减少引用计数。若当前的引用计数≤1,则操作失败 + * + * 该函数与lockref_dec_not_zero()的区别在于,当cmpxchg()时发现old.count≤1时,该函数会尝试加锁来进行操作。 + * 而后者在这种情况下,会直接返回false. + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return true 成功将引用计数减1 + * @return false 如果当前的引用计数≤1,操作失败 + */ +bool lockref_dec_or_lock_not_zero(struct lockref *lock_ref); + +/** + * @brief 将lockref变量标记为已经死亡(将count设置为负值) + * + * @param lock_ref 指向要被操作的lockref变量的指针 + */ +void lockref_mark_dead(struct lockref * lock_ref); + +/** + * @brief 自增引用计数。(除非该lockref已经死亡) + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return true 操作成功 + * @return false 操作失败,lockref已死亡 + */ +bool lockref_inc_not_dead(struct lockref *lock_ref); \ No newline at end of file diff --git a/kernel/common/spinlock.h b/kernel/common/spinlock.h index 64a6ab3d..ebf2a38f 100644 --- a/kernel/common/spinlock.h +++ b/kernel/common/spinlock.h @@ -11,6 +11,7 @@ #pragma once #include #include +#include /** * @brief 定义自旋锁结构体 @@ -19,8 +20,7 @@ typedef struct { int8_t lock; // 1:unlocked 0:locked -}spinlock_t; - +} spinlock_t; /** * @brief 自旋锁加锁 @@ -167,3 +167,18 @@ long spin_trylock(spinlock_t *lock) spin_unlock(lock); \ local_irq_enable(); \ } while (0) + +/** + * @brief 判断自旋锁是否已经加锁 + * + * @param lock 待判断的自旋锁 + * @return true 已经加锁 + * @return false 尚未加锁 + */ +static inline bool spin_is_locked(const spinlock_t *lock) +{ + int x = READ_ONCE(lock->lock); + return (x == 0) ? true : false; +} + +#define assert_spin_locked(lock) BUG_ON(!spin_is_locked(lock)) \ No newline at end of file diff --git a/kernel/lib/lockref.c b/kernel/lib/lockref.c new file mode 100644 index 00000000..67c375ba --- /dev/null +++ b/kernel/lib/lockref.c @@ -0,0 +1,224 @@ +#include +#include + +#ifdef __LOCKREF_ENABLE_CMPXCHG__ +#include + +#define CMPXCHG_LOOP(__lock_ref, CODE, SUCCESS) \ + { \ + int retry = 100; \ + struct lockref old; \ + BUILD_BUG_ON(sizeof(old) != sizeof(uint64_t)); \ + old.lock_count = READ_ONCE(__lock_ref->lock_count); \ + while (likely(!spin_is_locked(&old.lock))) \ + { \ + struct lockref new = old; \ + CODE; \ + if (likely(arch_try_cmpxchg(&__lock_ref->lock_count, &old.lock_count, new.lock_count))) \ + { \ + SUCCESS; \ + } \ + if (!--retry) \ + break; \ + pause(); \ + } \ + } +#else + +#define CMPXCHG_LOOP(__lock_ref, CODE, SUCCESS) \ + do \ + { \ + } while (0) + +#endif + +/** + * @brief 原子的将引用计数加1 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + */ +void lockref_inc(struct lockref *lock_ref) +{ + // 先尝试使用cmpxchg进行无锁操作,若成功则返回 + CMPXCHG_LOOP(lock_ref, ++new.count;, return;); + + // 无锁操作超时,或当前是上锁的状态,则退化为有锁操作 + spin_lock(&lock_ref->lock); + ++lock_ref->count; + spin_unlock(&lock_ref->lock); +} + +/** + * @brief 原子地将引用计数加1.如果原来的count≤0,则操作失败。 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return int 操作成功=>true + * 操作失败=>false + */ +bool lockref_inc_not_zero(struct lockref *lock_ref) +{ + CMPXCHG_LOOP(lock_ref, + if (old.count <= 0) return false; + ++new.count; + , + return true;) + bool retval; + spin_lock(&lock_ref->lock); + retval = false; + if (lock_ref->count > 0) + { + ++lock_ref->count; + retval = true; + } + spin_unlock(&lock_ref->lock); + return retval; +} + +/** + * @brief 原子地减少引用计数。如果已处于count≤0的状态,则返回-1 + * + * 本函数与lockref_dec_return()的区别在于,当在cmpxchg()中检测到count<=0或已加锁,本函数会再次尝试通过加锁来执行操作 + * 而后者会直接返回错误 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return int 操作成功 => 返回新的引用变量值 + * lockref处于count≤0的状态 => 返回-1 + */ +int lockref_dec(struct lockref *lock_ref) +{ + CMPXCHG_LOOP(lock_ref, + if (old.count <= 0) break; + --new.count; + , + return new.count;); + + // 如果xchg时,处于已加锁的状态或者检测到old.count <= 0,则采取加锁处理 + int retval = -1; + spin_lock(&lock_ref->lock); + if (lock_ref->count > 0) + { + --lock_ref->count; + retval = lock_ref->count; + } + spin_unlock(&lock_ref->lock); + + return retval; +} + +/** + * @brief 原子地减少引用计数。如果处于已加锁或count≤0的状态,则返回-1 + * + * 本函数与lockref_dec()的区别在于,当在cmpxchg()中检测到count<=0或已加锁,本函数会直接返回错误 + * 而后者会再次尝试通过加锁来执行操作 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return int 操作成功 => 返回新的引用变量值 + * lockref处于已加锁或count≤0的状态 => 返回-1 + */ +int lockref_dec_return(struct lockref *lock_ref) +{ + CMPXCHG_LOOP(lock_ref, + if (old.count <= 0) return -1; + --new.count; + , + return new.count;); + + return -1; +} + +/** + * @brief 原子地减少引用计数。若当前的引用计数≤1,则操作失败 + * + * 该函数与lockref_dec_or_lock_not_zero()的区别在于,当cmpxchg()时发现old.count≤1时,该函数会直接返回false. + * 而后者在这种情况下,会尝试加锁来进行操作。 + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return true 成功将引用计数减1 + * @return false 如果当前的引用计数≤1,操作失败 + */ +bool lockref_dec_not_zero(struct lockref *lock_ref) +{ + CMPXCHG_LOOP(lock_ref, + if (old.count <= 1) return false; + --new.count; + , + return true;) + + bool retval = false; + spin_lock(&lock_ref->lock); + if (lock_ref->count > 1) + { + --lock_ref->count; + retval = true; + } + spin_unlock(&lock_ref->lock); + return retval; +} + +/** + * @brief 原子地减少引用计数。若当前的引用计数≤1,则操作失败 + * + * 该函数与lockref_dec_not_zero()的区别在于,当cmpxchg()时发现old.count≤1时,该函数会尝试加锁来进行操作。 + * 而后者在这种情况下,会直接返回false. + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return true 成功将引用计数减1 + * @return false 如果当前的引用计数≤1,操作失败 + */ +bool lockref_dec_or_lock_not_zero(struct lockref *lock_ref) +{ + CMPXCHG_LOOP(lock_ref, + if (old.count <= 1) break; + --new.count; + , + return true;); + + bool retval = false; + spin_lock(&lock_ref->lock); + if (lock_ref->count > 1) + { + --lock_ref->count; + retval = true; + } + spin_unlock(&lock_ref->lock); + return retval; +} + +/** + * @brief 将lockref变量标记为已经死亡(将count设置为负值) + * + * @param lock_ref 指向要被操作的lockref变量的指针 + */ +void lockref_mark_dead(struct lockref *lock_ref) +{ + // 需要自旋锁先被加锁,若没有被加锁,则会抛出错误信息 + assert_spin_locked(&lock_ref->lock); + lock_ref->count = -128; +} + +/** + * @brief 自增引用计数。(除非该lockref已经死亡) + * + * @param lock_ref 指向要被操作的lockref变量的指针 + * @return true 操作成功 + * @return false 操作失败,lockref已死亡 + */ +bool lockref_inc_not_dead(struct lockref *lock_ref) +{ + CMPXCHG_LOOP(lock_ref, + if (old.count < 0) return false; + ++new.count; + , + return true;) + + bool retval = false; + // 快捷路径操作失败,尝试加锁 + spin_lock(&lock_ref->lock); + if (lock_ref->count >= 0) + { + ++lock_ref->count; + retval = true; + } + spin_unlock(&lock_ref->lock); + return retval; +}