뮤텍스란

  • 뮤텍스는 상호 배제(mutual exclusion)의 약자로서 Critical Section에 한 개
    의 프로세스만 접근하는 동기화 기법
  • 뮤텍스는 운영체제에서 쓰는 용어로 ‘Critical Section에 2개의 프로세스가 동
    시에 접근하지 못하도록 방지하는 기법’
  • 각 운영체제 커널마다 다르게 구현됨

리눅스 커널에서 뮤텍스란

  • 뮤텍스는 스핀락과 더불어 리눅스 커널 및 리눅스 커널 드라이버에 가장 많
    이 활용되는 커널 동기화 기법
  • 뮤텍스는 슬립을 지원하며 프로세스 컨텍스트에서 주로 쓰는 커널 동기화 기
  • 인터럽트 컨텍스트에서 사용 금지
  • 뮤텍스를 획득하는 주인공 : 프로세스 (Owner 필드)

dbs_work_handler() 함수

뮤텍스를 써서 Critical Section을 보호하는 루틴 1
https://elixir.bootlin.com/linux/v5.15.30/source/drivers/cpufreq/cpufreq_governor.c
static void dbs_work_handler(struct work_struct *work)
{
struct policy_dbs_info *policy_dbs;
struct cpufreq_policy *policy;
struct dbs_governor *gov;
policy_dbs = container_of(work, struct policy_dbs_info, work);
policy = policy_dbs->policy;
gov = dbs_governor_of(policy);
mutex_lock(&policy_dbs->update_mutex);
gov_update_sample_delay(policy_dbs, gov->gov_dbs_update(policy)); // 이 함수는 하나의 프로세스만 실행이 된다. 
mutex_unlock(&policy_dbs->update_mutex);
...
}

iommu_probe_device() 함수

뮤텍스를 써서 Critical Section을 보호하는 루틴 2
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 5419c4b9f27ad..80c5a1c572168 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c // arm에서 제공하는 ip
@@ -273,7 +273,9 @@ int iommu_probe_device(struct device *dev)
* support default domains, so the return value is not yet
* checked.
*/
+ mutex_lock(&group->mutex);
iommu_alloc_default_domain(group, dev);
+ mutex_unlock(&group->mutex);
if (group->default_domain) {
ret = __iommu_attach_device(group->default_domain, dev);

뮤텍스 구조체

- CONFIG_MUTEX_SPIN_ON_OWNER, CONFIG_DEBUG_MUTEXES,
CONFIG_DEBUG_LOCK_ALLOC은 추가 디버깅 피쳐
뮤텍스 자료 구조
https://elixir.bootlin.com/linux/v5.15.30/source/include/linux/mutex.h
struct mutex {
atomic_long_t owner; // 뮤텍스를 획득한 프로세스의 태스크 디스크립터 주소 오너가 0면 뮤텍스를 획득한 프로세스가 없다. 오너에 주소가 보이면 이 뮤텍스를 어떤 프로세스가 획득했다.
raw_spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct list_head wait_list; // 뮤텍스를 기다리는 프로세스의 정보, 자신을 등록하고 슬립에 돌입
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map; 
#endif
};

뮤텍스 구조체 디버깅

뮤텍스 자료 구조
crash64> struct mutex
struct mutex {
atomic_long_t owner;
raw_spinlock_t wait_lock;
struct optimistic_spin_queue osq;
struct list_head wait_list;
}
SIZE: 32

mutex_waiter 구조체

  • struct list_head list: 뮤텍스 획득을 시도하다 잠든 프로세스의 연결 리스트
  • struct task_struct *task: 뮤텍스를 기다리는 프로세스의 태스크 디스크립터 주소 저
뮤텍스 자료 구조
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/locking/mutex.h
struct mutex_waiter {
struct list_head list;
struct task_struct *task;
struct ww_acquire_ctx *ww_ctx;
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
};

뮤텍스의 구현 방식

fastpath

  • 뮤텍스는 다른 프로세스가 이미 획득하지 않은 상태면 바로 획득
  • fastpath로 빨리 뮤텍스를 획득하고 해제

slowpath: fastpath 흐름으로 뮤텍스 획득을 시도했는데 다른 프로세스가 이

미 뮤텍스를 획득한 경우 실행되는 동작

  • 뮤텍스를 획득하지 못한 프로세스는 대기열에 자신을 등록하고 휴면 상태에 들어감
  • 뮤텍스를 해제한 프로세스는 뮤텍스 대기열에 등록(뮤텍스 획득을 이미 시도)한 다른 프
    로세스를 깨움

mutex_lock() 함수

뮤텍스의 구현 방식: fastpath
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/locking/mutex.c
void __sched mutex_lock(struct mutex *lock)
{
might_sleep();
if (!__mutex_trylock_fast(lock)) // 뮤텍스의 구조체 체크 , 오너필드 0이면 뮤텍스 오너에 자신의 프로세스의 속성정보가 담긴 태스크디스크립터 주소를 저장하고 크리티컬 섹션
__mutex_lock_slowpath(lock);
}
EXPORT_SYMBOL(mutex_lock);

__mutex_trylock_fast() 함수

뮤텍스의 구현 방식: fastpath
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/locking/mutex.c
static __always_inline bool __mutex_trylock_fast(struct mutex *lock)
{
unsigned long curr = (unsigned long)current; // current : 현재 실행중인 프로세스의 태스크 디스크립터의 시작주소 curr이라는 로컬변수에 저장
unsigned long zero = 0UL;
if (atomic_long_try_cmpxchg_acquire(&lock->owner, &zero, curr)) // 0이면 curr 오너에 저장
return true;
return false;
}

뮤텍스 획득 과정

  • mutex 구조체의 owner 필드 점검
  • owner가 0x0이니 뮤텍스를 다른 프로세스가 획득하지 않은 상태로 판단
  • 뮤텍스 자료구조인 mutex 구조체의 owner 필드는 뮤텍스를 획득한 프로세스의 태스크
    디스크립터를 저장

__mutex_trylock_fast() 함수 분석

  • atomic_long_cmpxchg_acquire() 함수를 호출해서 &lock->owner에 저장된 값이 0
    이면 curr(현재 뮤텍스를 획득하는 프로세스의 태스크 디스크립터 주소)를 저장
  • &lock->owner가 0이면 뮤텍스를 다른 프로세스가 획득하지 않았다는 의미

mutex_unlock() 함수

뮤텍스의 구현 방식: fastpath
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/locking/mutex.c
void __sched mutex_unlock(struct mutex *lock)
{
#ifndef CONFIG_DEBUG_LOCK_ALLOC
if (__mutex_unlock_fast(lock)) // 현재 이코드를 실행하고 있는 
return;
#endif
__mutex_unlock_slowpath(lock, _RET_IP_);
}
EXPORT_SYMBOL(mutex_unlock);

__mutex_unlock_fast() 함수

뮤텍스의 구현 방식: fastpath
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/locking/mutex.c
static __always_inline bool __mutex_unlock_fast(struct mutex *lock)
{
unsigned long curr = (unsigned long)current; // 프로세스의 태스크 디스크립터의 현재주소를 curr에 저장, 락 오너의 필드를 0으로 바꿔줌
return atomic_long_try_cmpxchg_release(&lock->owner, &curr, 0UL);
}

뮤텍스 해제 과정

  • mutex 구조체의 owner 필드 점검
  • owner가 현재 프로세스의 태스크 디스크립터와 비교
  • 뮤텍스 자료구조인 mutex 구조체의 owner 필드를 0x0으로 변경

__mutex_unlock_fast() 함수 분석

  • current라는 매크로를 써서 현재 실행 중인 프로세스의 태스크 디스크립터 주소를 curr
    지역변수에 저장
  • &lock->owner 주소가 curr와 같으면 &lock->owner를 0으로 변경. 뮤텍스를 획득한
    프로세스가 뮤텍스를 해제

slowpath

slowpath: 뮤텍스 획득 시도 과정에서 슬립

  • __mutex_lock_slowpath() 함수
  • __mutex_lock() 함수
  • __mutex_lock_common() 함수 // kernel/locking/mutex.c

slowpath: 뮤텍스 릴리즈 후 다른 프로세스를 깨움

  • __mutex_unlock_slowpath() 함수

__mutex_lock_common () 함수

뮤텍스의 구현 방식: slowpath
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/locking/mutex.c
static __always_inline int __sched
__mutex_lock_common(struct mutex *lock, unsigned int state, unsigned int subclass,
struct lockdep_map *nest_lock, unsigned long ip,
struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)
{
...
waiter.task = current; // 뮤텍스획득을 시도하다가 슬립에 진입하는 프로세스의 태스크 주소를 이 필드에 저장
...
set_current_state(state) // task 언인터럽터블 상태, 프로세스가 깨어날 조건을 설정하고 슬립에 진입할 때, 아래의 함수를 호출하고 슬립에 진입. 
...
raw_spin_unlock(&lock->wait_lock);
schedule_preempt_disabled(); // 슬립에 진입, 뮤텍스를 해제할 때 아래의 루틴 실행
...
acquired:
__set_current_state(TASK_RUNNING);
...
}

__mutex_unlock_slowpath() 함수

뮤텍스의 구현 방식: slowpath
static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip)
{
struct task_struct *next = NULL;
...
if (!list_empty(&lock->wait_list)) {
/* get the first entry from the wait-list: */
struct mutex_waiter *waiter =
list_first_entry(&lock->wait_list,
struct mutex_waiter, list);
next = waiter->task;
debug_mutex_wake_waiter(lock, waiter);
wake_q_add(&wake_q, next); // 깨움
}
...
wake_up_q(&wake_q);
}

kworker/u8:0-1063 프로세스는 fastpath로 뮤텍스를 획득하고 해제

Ftrace: 뮤텍스 디버깅
kworker/u8:0-1063 [003] .... 3223.346749: mutex_lock+0x4/0x58 <- // 워커스레드, 
flush_to_ldisc+0x48/0x140
kworker/u8:0-1063 [003] .... 3223.346751: <stack trace>
=> mutex_lock+0x8/0x58 // 뮤텍스락
=> flush_to_ldisc+0x48/0x140 // 워크핸들러
=> process_one_work+0x1f4/0x4d8
=> worker_thread+0x50/0x480
=> kthread+0x148/0x158
=> ret_from_fork+0x10/0x30
kworker/u8:0-1063 [003] .... 3223.346760: mutex_unlock+0x4/0x60 <- // 해제할때 스택 트레이스
flush_to_ldisc+0x78/0x140
kworker/u8:0-1063 [003] .... 3223.346761: <stack trace>
=> mutex_unlock+0x8/0x60
=> flush_to_ldisc+0x78/0x140
=> process_one_work+0x1f4/0x4d8
=> worker_thread+0x50/0x480
=> kthread+0x148/0x158
=> ret_from_fork+0x10/0x30

0개의 댓글