스핀락, 뮤텍스, 세마포어

기술/컴퓨터사이언스 2018. 11. 7. 20:40 Posted by 아는 개발자


여러 개의 프로세스가 동시에 실행 할 수 있는 멀티 코어 환경은 사용자의 시스템 전반의 성능을 향상 시켜 주었지만 개발자들에게는 '프로세스간 공유 자원 접근 관리'라는 골치아픈 숙제를 남겼다. 다수의 컴포넌트가 공유중인 자원을 동시에 읽거나 수정할 때 생기는 문제들을 포괄해서 '동기화 문제'라고 하며 대부분의 소프트웨어는 스핀락, 뮤텍스, 세마포어라는 자료구조들을 이용해 해결 한다. 이번 포스트에서는 각 자료구조들의 작동 원리와 차이점에 대해서 정리해보려고한다.


스핀락 (spinlock)


특정한 자료구조를 획득(lock) 또는 해제(unlock) 함으로서 공유 데이터에 대한 접근 권한을 관리하는 방법이다. 권한을 획득하기 전까지 CPU는 무의미한 코드를 수행하는 busy waiting 상태로 대기하고 있다가 접근 권한을 얻으면 내부 코드를 수행하고 종료후 권한을 포기한다. 상태가 획득/해제 밖에 없기 때문에 공유 영역에는 하나의 컴포넌트만 접근 할 수 있으며 획득과 해제의 주체는 동일해야한다.


// arch/arm64/kernel/debug-monitors.c
static DEFINE_SPINLOCK(step_hook_lock);

void register_step_hook(struct step_hook *hook)
{
    spin_lock(&step_hook_lock);
    list_add_rcu(&hook->node, &step_hook);
    spin_unlock(&step_hook_lock);
}

CPU를 선점하고 있는 busy waiting 상태로 대기하기 때문에 권한이 해제되는 대로 빨리 작업을 수행할 수 있는 장점이 있지만 선점 기간동안 다른 프로세스 작업이 지연될 수 있는 오버헤드도 존재한다. 그래서 짧게 수행할 수 있는 작업에 주로 사용된다.


뮤텍스 (mutex)


획득(lock), 해제(unlock) 두가지  상태가 존재해 하나의 컴포넌트만 공유영역에 접근 할 수 있고 획득과 해제의 주체가 동일해야한다는 점은 스핀락과 동일하나 권한을 획득 할 때 까지 busy waiting 상태에 머무르지 않고 sleep 상태로 들어가고 wakeup 되면 다시 권한 획득을 시도한다. 시스템 전반의 성능에 영향을 주고 싶지 않고 길게 처리해야하는 작업인 경우에 주로 사용한다. 주로 쓰레드 작업에서 많이 사용된다.


 
// arch/arm64/mm/dma-mapping.c
mutex_lock(&iommu_dma_notifier_lock);
list_for_each_entry_safe(master, tmp, &iommu_dma_masters, list) {
   if (data == master->dev && do_iommu_attach(master->dev,
        master->ops, master->dma_base, master->size)) {
    list_del(&master->list);
    kfree(master);
    break;
   }
}
mutex_unlock(&iommu_dma_notifier_lock);


세마포어 (Semaphore) 


스핀락, 뮤텍스와는 다르게 표현형이 정수형이며 이점을 살려 하나 이상의 컴포넌트가 공유자원에 접근하도록 허용할 수 있다. 예로 들면 뮤텍스와 스핀락은 옷가게에 한 개의 피팅룸만 있었던 반면 세마포어는 하나 이상의 피팅룸이존재하는 셈. 물론 세마포어를 이진수의 형태로 사용해 개념적으로 뮤텍스와 스핀락처럼 사용하는 것도 가능하다. 정수형인 만큼 획득과 해제 같은 명령이 아니라 값을 올리고 줄이는 방식으로 세마포어를 사용하다. 세마포어의 값이 0이면 기다려야 한다.


// fs/btrfs/disk-io.c
    down_read(&fs_info->cleanup_work_sem);
    if ((ret = btrfs_orphan_cleanup(fs_info->fs_root)) ||
        (ret = btrfs_orphan_cleanup(fs_info->tree_root))) {
        up_read(&fs_info->cleanup_work_sem);
        close_ctree(tree_root);
        return ret; 
    }    
    up_read(&fs_info->cleanup_work_sem);


스핀락과 뮤텍스와 달리 세마포어는 해제(Unlock)의 주체가 획득(Lock)과 같지 않아도 된다. 즉 어떤 프로세스가 세마포어의 값을 감소시켜도 다른 프로세스가 풀어줄 수 있다. 이 특징을 고려해 세마포어를 시그널(Signal) 원리로 사용 할 수도 있다.


아래의 그림처럼 TASK A가 먼저 스케줄링돼 나중에 해야할 작업(post_work)을 먼저 할 수도 있는 상황이라고 해보자. 초기 세마포어의 값을 0으로 초기화해 post_work를 호출하지 못하게 막는다. 그러고 난 후 TASK B의 pre_work가 끝난 후에 세마포어의 값을 올려서 TASK A가 post_work를 호출할 수 있도록 할 수 있다.




참고자료


- FEABAHAS 블로그


728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

멀티쓰레드 동시성  (0) 2021.06.05
프로스세와 스레드  (0) 2021.06.05
스핀락, 뮤텍스, 세마포어  (0) 2018.11.07
RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27