自旋锁操作
自旋锁概述
自旋锁 是为实现保护共享资源而提出的一种轻量级的锁机制。
自旋锁在任何时刻最多只能有一个拥有者,也就是自旋锁最多只能被一个线程持有,如果一个线程试图请求一个已经被持有的自旋锁,那么这个任务就会一直在那里循环判断该自旋锁的拥有者是否已经释放了该锁,直到锁重新可用。要是锁未被争用,请求它的线程便能立刻得到它,继续运行。
自旋锁的使用
SylixOS 自旋锁的类型为 spinlock_t。使用时需定义一个 spinlock_t 类型的变量,如:
spinlock_t spin;
一个自旋锁必须要调用 LW_SPIN_INIT 函数创建之后才能使用。
程序可以调用 LW_SPIN_LOCK 函数来等待一个自旋锁,调用 LW_SPIN_UNLOCK 函数来释放一个自旋锁。
中断服务程序不能调用任何 SylixOS 自旋锁 API。
自旋锁的初始化
#include <SylixOS.h>
VOID LW_SPIN_INIT (spinlock_t *psl);
函数 LW_SPIN_INIT 原型分析:
- 参数 psl 是需要操作的自旋锁。
自旋锁的加锁与解锁
#include <SylixOS.h>
VOID LW_SPIN_LOCK (spinlock_t *psl);
VOID LW_SPIN_TRYLOCK (spinlock_t *psl);
以上两个函数原型分析:
- 参数 psl 是需要加锁的自旋锁的指针。
LW_SPIN_TRYLOCK 是 LW_SPIN_LOCK 的“尝试等待”版本,在自旋锁已经被占有时,LW_SPIN_LOCK 将“自旋”,而 LW_SPIN_TRYLOCK 将立即返回。
#include <SylixOS.h>
INT LW_SPIN_UNLOCK (spinlock_t *psl);
函数 LW_SPIN_UNLOCK 原型分析:
- 此函数返回调度器的返回值。
- 参数 psl 是需要解锁的自旋锁的指针。
使用 LW_SPIN_LOCK 函数或者 LW_SPIN_TRYLOCK 函数加锁的自旋锁的解锁操作只能调用 LW_SPIN_UNLOCK 函数。
#include <SylixOS.h>
VOID LW_SPIN_LOCK_IGNIRQ (spinlock_t *psl);
VOID LW_SPIN_TRYLOCK_IGNIRQ (spinlock_t *psl);
以上两个函数原型分析:
- 参数 psl 是需要加锁的自旋锁的指针。
LW_SPIN_TRYLOCK_IGNIRQ 是 LW_SPIN_LOCK_IGNIRQ 的“尝试等待”版本,在自旋锁已经被占有时,LW_SPIN_LOCK_IGNIRQ 将“自旋”,而 LW_SPIN_TRYLOCK_IGNIRQ 将立即返回。
#include <SylixOS.h>
VOID LW_SPIN_UNLOCK_IGNIRQ (spinlock_t *psl);
函数 LW_SPIN_UNLOCK_IGNIRQ 原型分析:
- 参数 psl 是需要解锁的自旋锁的指针。
以上三个函数必须在中断关闭的情况下被调用。
使用 LW_SPIN_LOCK_IGNIRQ 函数或者 LW_SPIN_TRYLOCK_IGNIRQ 函数加锁的自旋锁的解锁操作只能调用 LW_SPIN_UNLOCK_IGNIRQ 函数。
尽管用了自旋锁可以保证临界区不受别的进程抢占打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断的影响,为了防止这种影响,就需要用到带中断屏蔽功能的自旋锁。
#include <SylixOS.h>
VOID LW_SPIN_LOCK_IRQ (spinlock_t *psl, INTREG *piregInterLevel);
BOOL LW_SPIN_TRYLOCK_IRQ (spinlock_t *psl, INTREG *piregInterLevel);
以上两个函数原型分析:
- 参数 psl 是需要加锁的自旋锁的指针。
- 参数 piregInterLevel 是中断锁定信息。
LW_SPIN_TRYLOCK_IRQ 是 LW_SPIN_LOCK_IRQ 的“尝试等待”版本,在自旋锁已经被占有时,LW_SPIN_LOCK_IRQ 将“自旋”,而 LW_SPIN_TRYLOCK_IRQ 将立即返回。
#include <SylixOS.h>
INT LW_SPIN_UNLOCK_IRQ (spinlock_t *psl, INTREG iregInterLevel);
函数 LW_SPIN_UNLOCK_IRQ 原型分析:
- 参数 psl 是需要解锁的自旋锁的指针。
- 参数 iregInterLevel 是中断锁定信息。
使用 LW_SPIN_LOCK_IRQ 函数或者 LW_SPIN_TRYLOCK_IRQ 函数加锁的自旋锁的解锁操作只能调用 LW_SPIN_UNLOCK_IRQ 函数。
#include <SylixOS.h>
VOID LW_SPIN_LOCK_QUICK (spinlock_t *psl, INTREG *piregInterLevel);
函数 LW_SPIN_LOCK_QUICK 原型分析:
- 参数 psl 是需要加锁的自旋锁的指针。
- 参数 piregInterLevel 是中断锁定信息。
此函数不提供“尝试等待”的版本。
#include <SylixOS.h>
VOID LW_SPIN_UNLOCK_QUICK (spinlock_t *psl, INTREG iregInterLevel);
函数 LW_SPIN_UNLOCK_QUICK 原型分析:
- 参数 psl 是需要解锁的自旋锁的指针。
- 参数 iregInterLevel 是中断锁定信息。
使用 LW_SPIN_LOCK_QUICK 函数加锁的自旋锁的解锁操作只能调用 LW_SPIN_UNLOCK_QUICK 函数。
#include <SylixOS.h>
VOID LW_SPIN_LOCK_TASK (spinlock_t *psl);
VOID LW_SPIN_TRYLOCK_TASK (spinlock_t *psl);
以上两个函数原型分析:
- 参数 psl 是需要加锁的自旋锁的指针。
LW_SPIN_TRYLOCK_TASK 是 LW_SPIN_LOCK_TASK 的“尝试等待”版本,在自旋锁已经被占有时,LW_SPIN_LOCK_TASK 将“自旋”,而 LW_SPIN_TRYLOCK_TASK 将立即返回。
#include <SylixOS.h>
INT LW_SPIN_UNLOCK_TASK (spinlock_t *psl);
函数 LW_SPIN_UNLOCK_TASK 原型分析:
- 参数 psl 是需要解锁的自旋锁的指针。
以上三个函数不锁定中断,同时允许加锁后调用可能产生阻塞的操作。
使用 LW_SPIN_LOCK_TASK 函数或者 LW_SPIN_TRYLOCK_TASK 函数加锁的自旋锁的解锁操作只能调用 LW_SPIN_UNLOCK_TASK 函数。
#include <SylixOS.h>
VOID LW_SPIN_LOCK_RAW (spinlock_t *psl, INTREG *piregInterLevel);
函数 LW_SPIN_LOCK_RAW 原型分析:
- 参数 psl 是需要操作的自旋锁的指针。
- 参数 piregInterLevel 是中断锁定信息。
#include <SylixOS.h>
BOOL LW_SPIN_TRYLOCK_RAW (spinlock_t *psl, INTREG *piregInterLevel);
函数 LW_SPIN_TRYLOCK_RAW 原型分析:
- 此函数成功时返回 LW_TRUE,失败时返回 LW_FALSE。
- 参数 psl 是需要操作的自旋锁的指针。
- 参数 piregInterLevel 是中断锁定信息。
LW_SPIN_TRYLOCK_RAW 是 LW_SPIN_LOCK_RAW 的“尝试等待”版本,在自旋锁已经被占有时,LW_SPIN_LOCK_RAW 将“自旋”,而 LW_SPIN_TRYLOCK_RAW 将立即返回。
#include <SylixOS.h>
VOID LW_SPIN_UNLOCK_RAW (spinlock_t *psl, INTREG iregInterLevel);
函数 LW_SPIN_UNLOCK_RAW 原型分析:
- 参数 psl 是需要操作的自旋锁的指针。
- 参数 piregInterLevel 是中断锁定信息。
使用 LW_SPIN_LOCK_RAW 函数或者 LW_SPIN_TRYLOCK_RAW 函数加锁的自旋锁的解锁操作只能调用 LW_SPIN_UNLOCK_RAW 函数。
以下程序展示了 SylixOS 自旋锁的使用,内核模块在装载时创建两个线程和一个 SylixOS 自旋锁,两个线程分别对变量 _G_iCount 进行自增操作和打印,使用 SylixOS 自旋锁作为访问变量 _G_iCount 的互斥手段,内核模块在卸载时删除两个线程。
#define __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <module.h>
LW_HANDLE _G_hThreadAId = LW_OBJECT_HANDLE_INVALID;
LW_HANDLE _G_hThreadBId = LW_OBJECT_HANDLE_INVALID;
static INT _G_iCount = 0;
static spinlock_t _G_spinlock;
static PVOID tTestA (PVOID pvArg)
{
INT iValue;
while (1) {
LW_SPIN_LOCK(&_G_spinlock);
iValue = _G_iCount;
LW_SPIN_UNLOCK(&_G_spinlock);
printk("tTestA(): count = %d\n", iValue);
API_TimeSSleep(1);
}
return (LW_NULL);
}
static PVOID tTestB (PVOID pvArg)
{
while (1) {
LW_SPIN_LOCK(&_G_spinlock);
_G_iCount++;
LW_SPIN_UNLOCK(&_G_spinlock);
API_TimeSSleep(1);
}
return (LW_NULL);
}
VOID module_init (VOID)
{
printk("spinlock_module init!\n");
LW_SPIN_INIT(&_G_spinlock);
_G_hThreadAId = API_ThreadCreate("t_testa", tTestA, LW_NULL, LW_NULL);
if (_G_hThreadAId == LW_OBJECT_HANDLE_INVALID) {
printk("t_testa create failed.\n");
return;
}
_G_hThreadBId = API_ThreadCreate("t_testb", tTestB, LW_NULL, LW_NULL);
if (_G_hThreadBId == LW_OBJECT_HANDLE_INVALID) {
printk("t_testb create failed.\n");
return;
}
}
VOID module_exit (VOID)
{
if (_G_hThreadAId != LW_OBJECT_HANDLE_INVALID) {
API_ThreadDelete(&_G_hThreadAId, LW_NULL);
}
if (_G_hThreadBId != LW_OBJECT_HANDLE_INVALID) {
API_ThreadDelete(&_G_hThreadBId, LW_NULL);
}
printk("spinlock_module exit!\n");
}
在 SylixOS Shell 下装载模块:
#insmod ./spinlock.ko
spinlock_module init!
module spinlock.ko register ok, handle: 0x13338f0
tTestA(): count = 0
tTestA(): count = 1
tTestA(): count = 2
tTestA(): count = 3
tTestA(): count = 4
在 SylixOS Shell 下卸载模块:
#rmmod spinlock.ko
spinlock_module exit!
module /lib/modules/spinlock.ko unregister ok.
自旋锁使用注意事项
自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁机制可能存在以下两个问题:
- 死锁:申请者试图递归地获得自旋锁必然会引发死锁。
- 消耗过多 CPU 资源:如果申请不成功,申请者将一直循环判断,这无疑降低了 CPU 的使用率。
因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的,当临界区很大或有共享设备的时候,需要较长时间占用锁,使用自旋锁会降低系统性能。
需要注意的是,被自旋保护的临界区代码执行时不能因为任何原因放弃 CPU,因此在自旋锁保护的区域内不能调用任何可能引发系统任务调度的 API。