Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] 동기화 - 스핀락(spinlock): spin_lock_irq() 소개 [라즈베리파이]커널 동기화

이해를 돕기 위해 다시 화장실을 예를 들겠습니다. 

어떤 영업 사원이 있다고 가정하겠습니다. 영업 사원은 언제 전화를 받는 것이 회사 규칙입니다. 그런데 이 영업 사원은 화장실에 들어갈 때 전화를 안 받아도 된다고 회사와 약속을 했습니다. 화장실에서 중요한 용무를 봐야 하기 때문입니다. 그 조건으로 화장실 문에 들어갈 때는 비행기 모드로 바꾸고 화장실에서 나올 때는 반드시 비행기 모드를 풀어야 합니다.

여기서 전화를 인터럽트 그리고 화장실 문에 들어간 상태를 임계 영역이라고 바꿔서 생각해봅시다. 이 영업 사원은 화장실에서 용무를 볼 때 전화가 오지 않으니 방해을 안 받습니다. 스핀락을 얻어 임계 영역을 실행 중에 인터럽트를 실행을 막는 상태입니다.

임계영역이 다음과 같은 코드 흐름일 때 임계 영역에서 잠시 인터럽트 발생을 막아야 합니다.
1. 정확한 순서로 데이터 시트에 언급된 순서로 특정 메모리 구간에 어떤 값을 써야 할 경우
2. 각 디바이스 드라이버가 Suspend/Resume와 같은 슬립에 진입하거나 깨어나는 동작일 경우
3. 코드 실행 순서를 반드시 지켜야 하는 코드인 경우

이번에는 spin_lock_irq() 함수를 써서 임계영역을 보호하는 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/drivers/mfd/rtsx_pcr.c#L1306]
1 static void rtsx_pci_remove(struct pci_dev *pcidev)
2 {
3 struct pcr_handle *handle = pci_get_drvdata(pcidev);
4 struct rtsx_pcr *pcr = handle->pcr;
5
6 pcr->remove_pci = true;
7
8 /* Disable interrupts at the pcr level */
9 spin_lock_irq(&pcr->lock);
10 rtsx_pci_writel(pcr, RTSX_BIER, 0);
11 pcr->bier = 0;
12 spin_unlock_irq(&pcr->lock);

10~11번째 줄 코드가 임계영역인데 이 코드 구간에서 인터럽트 발생을 막는 겁니다. spin_lock() 함수와 마찬가지로 10~11번째 함수 전후로 spin_lock_irq()와 spin_unlock_irq() 함수로 임계 영역을 보호합니다.
spin_lock_irq() 함수 인자와 반환 값을 확인합시다.
static __always_inline void spin_lock_irq(spinlock_t *lock);
static __always_inline void spin_unlock_irq(spinlock_t *lock);

인터럽트 발생을 막으며 스핀락을 획득하는 spin_lock_irq() 와 spin_unlock_irq() 함수 모두 반환 값은 void 타입이며 모두 인자는 spinlock_t 구조체입니다.
static __always_inline void spin_lock(spinlock_t *lock);
static __always_inline void spin_unlock(spinlock_t *lock);

spin_lock()와 spin_unlock() 함수에 전달하는 인자와 같은 구조체입니다. spin_lock_irq() 함수는 spin_lock() 함수에 비해 어떤 차이점이 있을까요? 스핀락 관점으로 보면 spin_lock_irq() 함수는 spin_lock() 함수와 같은 동작입니다. 다만 spin_lock_irq() 함수에서 임계영역에서 인터럽트를 중지하는 추가됐을 뿐입니다.

spin_lock() 함수와 차이점을 알아보기 위해 spin_lock_irq() 함수 구현부 코드를 보겠습니다. 이전 절에서 마찬가지로 이번에도 전처리 코드를 열어서 spin_lock_irq() 함수 코드를 활용합니다.
[fs/.tmp_fs_struct.i]
1 static inline void spin_lock_irq(spinlock_t *lock)
2 {
3 _raw_spin_lock_irq(&lock->rlock);
4}
5
6  static inline  void __raw_spin_lock_irq(raw_spinlock_t *lock)
7  {
8  do { arch_local_irq_disable(); do { } while (0); } while (0);
9  do { __preempt_count_add(1); __asm__ __volatile__("": : :"memory"); } while (0);
10 do { } while (0);
11 do_raw_spin_lock(lock);
12 }
13
14 static inline void do_raw_spin_lock(raw_spinlock_t *lock)
15 {
16 (void)0;
17 arch_spin_lock(&lock->raw_lock);
18 }
19
20 static inline void arch_local_irq_disable(void)
21 {
22 asm volatile(
23  " cpsid i @ arch_local_irq_disable"
24  :
25 :
26  : "memory", "cc");
27 }

spin_lock_irq()는 인라인 타입 함수며 _raw_spin_lock_irq() 함수로 치환됩니다. __raw_spin_lock_irq() 함수를 눈여겨보면 다음과 같이 8번째 줄을 볼 수 있습니다.
8  do { arch_local_irq_disable(); do { } while (0); } while (0);

spin_lock() 함수를 호출하면 치환되는 코드 흐름을 보면 __raw_spin_lock() 함수를 호출됩니다.
static inline  void __raw_spin_lock(raw_spinlock_t *lock)
{
 do { preempt_count_add(1); __asm__ __volatile__("": : :"memory"); } while (0);
 do { } while (0);
 do_raw_spin_lock(lock);
}

spin_lock() 함수와 비교해서 spin_lock_irq() 함수는 arch_local_irq_disable() 함수를 호출하는 코드가 추가됐습니다. 이를 제외하고 다른 동작이 같습니다.

arch_local_irq_disable() 함수 구현부를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.43/source/arch/arm/include/asm/irqflags.h#]
static inline void arch_local_irq_disable(void)
{
asm volatile(
" cpsid i @ arch_local_irq_disable"
:
:
: "memory", "cc");
}

ARM 어셈블리 명령어로 “cpsid i” 를 실행합니다.

이 “cpsid i" 명령어가 어떤 동작을 하는지 알아보기 위해 다음 ARM 사이트에 접속했습니다. 
[http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0203ik/ch02s08s01.html]
CPSID i  ; Disable interrupts and configurable fault handlers (set PRIMASK)

내용을 확인하니 인터럽트를 잠시 Disable하는 동작입니다.

해당하는 어셈블리 코드를 보면 정말로 명령어를 확인할 수 있습니다.
80704ee8 <_raw_spin_lock_irq>:
80704ee8: e1a0c00d  mov ip, sp
80704eec: e92dd818  push {r3, r4, fp, ip, lr, pc}
80704ef0: e24cb004  sub fp, ip, #4
80704ef4: e52de004  push {lr} ; (str lr, [sp, #-4]!)
80704ef8: ebe82590  bl 8010e540 <__gnu_mcount_nc>
80704efc: e1a04000  mov r4, r0
80704f00: f10c0080  cpsid i

스핀락을 걸고 임계영역 코드를 실행할 때 인터럽트 발생을 막는 역할을 수행합니다.

이번에는 spin_lock_irq() 코드 구현부를 보겠습니다.
1 static inline void spin_lock_irq(spinlock_t *lock)
2 {
3  raw_spin_lock_irq(&lock->rlock);
4}
5
6 void __lockfunc _raw_spin_lock_irq(raw_spinlock_t *lock)
7 {
8 __raw_spin_lock_irq(lock);
9 }
10
11 static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
12 {
13 do { arch_local_irq_disable(); trace_hardirqs_off(); } while (0);
14 do { preempt_count_add(1); __asm__ __volatile__("": : :"memory"); } while (0);
15 do { } while (0);
16 do_raw_spin_lock(lock);
17 }

spin_lock() 함수 흐름과 비교하면 13번째 줄 코드와 같이 __raw_spin_lock_irq() 함수에서 arch_local_irq_disable() 함수를 호출하는 동작 이외에 구현부가 같습니다.

arch_local_irq_disable() 함수 구현부를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.43/source/arch/arm/include/asm/irqflags.h#]
static inline void arch_local_irq_disable(void)
{
asm volatile(
" cpsid i @ arch_local_irq_disable"
:
:
: "memory", "cc");
}

ARM 어셈블리 명령어로 “cpsid i” 를 실행합니다. 이 “cpsid i" 명령어가 어떤 동작을 하는지 알아보기 위해 다음 ARM 사이트에 접속합시다.


[http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0203ik/ch02s08s01.html]
CPSID i  ; Disable interrupts and configurable fault handlers (set PRIMASK)

“cpsid i” 이란 명령어는 인터럽트를 잠시 Disable하는 동작입니다.

임계영역 접근 전에 인터럽트를 막는 “cpsid i” 명령어를 실행합니다.

이번에는 CPU가 실행하는 어셈블리 코드로 _raw_spin_lock_irq() 함수에서 정말 “cpsid i” 명령어를 쓰는지 확인합시다.
1 80704ee8 <_raw_spin_lock_irq>:
2 80704ee8: e1a0c00d  mov ip, sp
3 80704eec: e92dd818  push {r3, r4, fp, ip, lr, pc}
4 80704ef0: e24cb004  sub fp, ip, #4
5 80704ef4: e52de004  push {lr} ; (str lr, [sp, #-4]!)
6 80704ef8: ebe82590  bl 8010e540 <__gnu_mcount_nc>
7 80704efc: e1a04000  mov r4, r0
8 80704f00: f10c0080  cpsid i

해당하는 어셈블리 코드를 보면 8번째 줄 코드와 같이 정말로 “cpsid i” 명령어를 확인할 수 있습니다.
8 80704f00: f10c0080  cpsid i

정리하면 spin_lock_irq() 함수는 스핀락을 걸고 임계영역 코드를 실행할 때 인터럽트 발생을 막는 역할을 수행합니다.


#Reference 시스템 콜


Reference(워크큐)
워크큐(Workqueue) Overview


덧글

댓글 입력 영역