스핀락의 주요 특징

스핀락의 특징

  • 뮤텍스 비해 구현 복잡도가 낮음
  • 스핀락 구현부는 아키텍처에 의존적으로 CPU 아키텍처(Armv7, Armv8)
    에 따라 스핀락 구현부가 다름
  • 접근 단위 : cpu 코어

스핀락의 세부 동작 원리

  • 이미 누군가가 스핀락을 획득했으면 스핀락을 획득할 때까지 계속 기다림

  • 다른 프로세스가 스핀락을 해제하면 바로 스핀락을 획득하고 Critical
    Section을 실행

  • slow path : 스핀락을 획득하기 위해서 기다리는데 실행구간이 짧아야한다.

스핀락 사용 시 주의 사항

  • 스핀락을 획득한 Critical Section에서 실행 시간이 오래 걸리면 시스템 성
    능에 악영향
  • 인터럽트 컨텍스트에서 사용 가능
  • Watchdog Reset

스핀락 관련 디버그 컨피그 (Feat. 디버그 버전에서 사용됨)

  • CONFIG_DEBUG_SPINLOCK // 컨피그를 키는게 좋음

__kthread_create_on_node() 함수

스핀락을 써서 Critical Section을 보호하는 루틴 1
https://elixir.bootlin.com/linux/v5.4.130/source/kernel/kthread.c
01 struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
02 void *data, int node,
03 const char namefmt[],
04 va_list args)
05 {
06 DECLARE_COMPLETION_ONSTACK(done);
07 struct task_struct *task;
08 struct kthread_create_info *create = kmalloc(sizeof(*create),
09 GFP_KERNEL);
...
10 spin_lock(&kthread_create_lock); // 스핀락을 획득
11 list_add_tail(&create->list, &kthread_create_list); // Critical Section
12 spin_unlock(&kthread_create_lock); // 스핀락을 해제
13
14 wake_up_process(kthreadd_task);
스핀락을 써서 Critical Section을 보호하는 루틴 2
https://elixir.bootlin.com/linux/v5.4.130/source/drivers/rpmsg/qcom_glink_native.c
static int qcom_glink_rx_open_ack(struct qcom_glink *glink, unsigned int lcid)
{
struct glink_channel *channel;
spin_lock(&glink->idr_lock);
channel = idr_find(&glink->lcids, lcid); // 크리티컬 섹샨
spin_unlock(&glink->idr_lock);
if (!channel) {
dev_err(glink->dev, "Invalid open ack packet\n");
return -EINVAL;
}

스핀락의 종류

티켓 스핀락

  • v4.14까지 사용
  • 스핀락 Holder에게 티켓 부여

큐드 스핀락

  • v4.19부터 Armv8(Aarch64)에 적용
  • 티겟 스핀락에 비해 성능이 뛰어남

ticket 스핀락

  • waiter 에게 ticket(번호)을 부여하여 write 순서를 정함
  • 스핀락을 획득한 holder는 next를 +1만큼 증가
  • 스핀락을 해제하는 holder는 owner를 +1만큼 증가
  • 단일 lock 구조체에 대해 spinning을 하고 있으므로 cache-line bouncing
    문제가 있음
  • 코어별로 캐시가 있는데 캐시의 사이즈만큼 로딩을 하고 성능 측면에 문제가 있다.

qspinlock(큐드 스핀락)

  • 2개 CPU까지 스핀락 변수에 엑세스(1st CPU 코어 스핀락 획득, 2nd CPU코어 스핀락 Busy Waiting)
  • 3번째 CPU부터 mcs lock에 큐잉을 해 cache inbalance 현상을 개선

스핀락 구조체

  • spinlock_t(struct spinlock): 리눅스 커널 공통
  • raw_spinlock_t(struct raw_spinlock): 아키텍처 인터페이스 자료구조
  • arch_spinlock_t: 아키텍처 내 자료구조
스핀락 자료구조
https://elixir.bootlin.com/linux/v5.15.30/source/include/linux/spinlock_types.h
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;

스핀락 구조체 디버깅: raw_spinlock 선언부

스핀락 자료구조
crash64> struct spinlock_t
typedef struct spinlock {
union {
struct raw_spinlock rlock;
};
} spinlock_t;
SIZE: 4
crash64> struct raw_spinlock
struct raw_spinlock {
arch_spinlock_t raw_lock;
}
crash64> struct arch_spinlock_t
typedef struct qspinlock {
union {
atomic_t val;
struct {
u8 locked;
u8 pending;
};
struct {
u16 locked_pending;
u16 tail;
};
};
} arch_spinlock_t;

struct qspinlock 구조체

- locked: 스핀락을 획득하면 1로 변경됨
- pending: 스핀락을 획득하기 위해 기다리는 상황에서 1로 변경됨
- tail: mcs_lock의 CPU 번호를 저장함
스핀락 자료구조
https://elixir.bootlin.com/linux/v5.15.30/source/include/asm-generic/qspinlock_types.h
typedef struct qspinlock {
union {
atomic_t val;
struct {
u16 tail;
u16 locked_pending;
};
struct {
u8 reserved[2];
u8 pending; 
u8 locked; // 획득을 해제하면 0으로 바뀜,
};
};
} arch_spinlock_t;

struct qspinlock 구조체 디버깅

스핀락을 걸었을 때 필드
(spinlock_t) bdi_lock = (
(struct raw_spinlock) rlock = (
(arch_spinlock_t) raw_lock = (
(atomic_t) val = (
(int) counter = 0x1),
(u8) locked = 0x1, // 1
(u8) pending = 0x0,
(u16) locked_pending = 0x1,
(u16) tail = 0x0)))
------------------------------- 해제했을 때 필드
(spinlock_t) bdi_lock = (
(struct raw_spinlock) rlock = (
(arch_spinlock_t) raw_lock = (
(atomic_t) val = (
(int) counter = 0x0),
(u8) locked = 0x0, // 0
(u8) pending = 0x0,
(u16) locked_pending = 0x0,
(u16) tail = 0x0)))

queued_spin_lock() 함수

스핀락 코드 분석
https://elixir.bootlin.com/linux/v5.15.30/source/include/asm-generic/qspinlock.h
static __always_inline void queued_spin_lock(struct qspinlock *lock)
{
int val = 0;
if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL)))
return; // 이미 다른 cpu코어가 스핀락읗 획득한 적이 없다면 fast path 루틴으로 이 코드만 실행, locked를 1로 바꿈 , 있다면 busy wait 동작을 무한히
queued_spin_lock_slowpath(lock, val);
}

queued_spin_unlock() 함수

스핀락 코드 분석
https://elixir.bootlin.com/linux/v5.15.30/source/include/asm-generic/qspinlock.h
static __always_inline void queued_spin_unlock(struct qspinlock *lock)
{
/*
* unlock() needs release semantics:
*/
smp_store_release(&lock->locked, 0);
}

spin_lock_irq()/spin_unlock_irq() 함수

  • spin_lock()/spin_unlock() 함수에서 스핀락을 걸 때 인터럽트를 비활성화
    하는 기능을 추가

spin_lock_irq()/spin_unlock_irq() 함수를 사용해야 하는

상황

  • 스핀락으로 Critical Section을 보호할 때 인터럽트가 발생하면 안됨
  • 스핀락의 기존 기능은 그대로 사용

spin_lock_irq() 함수의 구현 방식

  • spin_lock() 함수에서 호출한 함수를 그대로 사용
  • local_irq_disable() 함수를 호출해 로컬 인터럽트를 비활성화

spin_unlock_irq() 함수의 구현 방식

  • spin_unlock() 함수에서 호출한 함수를 그대로 사용
  • local_irq_enable() 함수를 호출해 로컬 인터럽트를 활성화

spin_lock_irqsave()/spin_unlock_irqrestore() 함수 소개

  • spin_lock_irq()/spin_unlock_irq() 함수에서 스핀락을 획득하면
    서 인터럽트를 비활성화할 때 인터럽트를 상태(활성화/비활성화)
    를 확인하는 기능 추가

spin_lock_irqsave()/spin_unlock_irqrestore() 함수를 사용해야 하는 상황

  • 스핀락으로 Critical Section을 보호할 때 인터럽트가 발생하면 안됨
  • 함수 호출 깊이가 깊어지면 인터럽트의 상태(활성화/비활성화)를 확인하기
    어려움
  • 스핀락의 기존 기능은 그대로 사용

스핀락 업데이트 패치

- dmaengine: sf-pdma: apply proper spinlock flags in sf_pdma_prep_dma_memcpy()

<출처>
https://git.kernel.org/pub/scm/linux/kernel/git/next/linuxnext.git/commit/?id=94b4cd7c5fc0dd6858a046b00ca729fb0512b9ba
dmaengine: sf-pdma: apply proper spinlock flags in sf_pdma_prep_dma_memcpy()
The second parameter of spinlock_irq[save/restore] function is flags,
which is the last input parameter of sf_pdma_prep_dma_memcpy().
So declare local variable 'iflags' to be used as the second parameter of
spinlock_irq[save/restore] function.
Signed-off-by: Austin Kim <austin.kim@lge.com>
Link: https://lore.kernel.org/r/20210611065336.GA1121@raspberrypi
Signed-off-by: Vinod Koul <vkoul@kernel.org>
패치
스핀락 업데이트 패치
diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c
index c4c4e85757643..f12606aeff87c 100644
--- a/drivers/dma/sf-pdma/sf-pdma.c
+++ b/drivers/dma/sf-pdma/sf-pdma.c
@@ -94,6 +94,7 @@ sf_pdma_prep_dma_memcpy(struct dma_chan *dchan, dma_addr_t dest, dma_addr_t src,
{
struct sf_pdma_chan *chan = to_sf_pdma_chan(dchan);
struct sf_pdma_desc *desc;
+ unsigned long iflags;
if (chan && (!len || !dest || !src)) {
dev_err(chan->pdma->dma_dev.dev,
@@ -109,10 +110,10 @@ sf_pdma_prep_dma_memcpy(struct dma_chan *dchan, dma_addr_t dest, dma_addr_t src,
desc->dirn = DMA_MEM_TO_MEM;
desc->async_tx = vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
- spin_lock_irqsave(&chan->vchan.lock,flags); // 원래 코드
+ spin_lock_irqsave(&chan->vchan.lock, iflags); // 
chan->desc = desc;
sf_pdma_fill_desc(desc, dest, src, len);
- spin_unlock_irqrestore(&chan->vchan.lock,flags);
+ spin_unlock_irqrestore(&chan->vchan.lock, iflags);
return desc->async_tx;
}

Ftrace: 스핀락 디버깅

spinlock_fastpa-985 [002] .... 471.920711: _raw_spin_lock+0x4/0x78 <-ksys_dup3+0x60/0x140 // 스핀락이 컴파일되면 raw spin lock이라는 심볼로 보임
spinlock_fastpa-985 [002] .... 471.920715: <stack trace>
=> _raw_spin_lock+0x8/0x78
=> ksys_dup3+0x60/0x140
=> __arm64_sys_dup3+0x28/0x38
=> el0_svc_common.constprop.2+0x9c/0x1a0
=> do_el0_svc+0x2c/0x98
=> el0_svc+0x20/0x30
=> el0_sync_handler+0x90/0xb8
=> el0_sync+0x160/0x180
spinlock_fastpa-985 [002] ...1 471.920719: _raw_spin_unlock+0x4/0x48 <-do_dup2+0xe8/0x148
spinlock_fastpa-985 [002] ...1 471.920720: <stack trace>
=> _raw_spin_unlock+0x8/0x48
=> do_dup2+0xe8/0x148
=> ksys_dup3+0xbc/0x140
=> __arm64_sys_dup3+0x28/0x38
=> el0_svc_common.constprop.2+0x9c/0x1a0
=> do_el0_svc+0x2c/0x98
=> el0_svc+0x20/0x30
=> el0_sync_handler+0x90/0xb8
=> el0_sync+0x160/0x180

0개의 댓글