Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

1148
469
422441


[라즈베리파이] 동기화 - 스핀락(spinlock): 스핀락 획득 첫 번째 시도 9. 커널 동기화(spinlock/mutex)

스핀락을 소개했을 때 그림을 다시 소개합니다.
 

위 그림이 실행할 때 실제 어셈블리 코드를 어떻게 동작하는지 알아보려는 겁니다.
다음 소절에 분석하려는 스핀락 인스턴스 owner는 0x1이고 next는 0x2입니다. 이미 스핀락을 획득한 다른 프로세스가 임계 영역을 실행 중이기 때문입니다.

이번에는 누군가 스핀락을 획득한 경우 스핀락이 어떻게 동작하는지 알아보겠습니다.

r0는 0xb93b4a78이고 이 메모리 공간에 0x00020001 값이 있습니다. r3은 0x00020001로 변경됩니다. r3에 ticket owner와 next를 모두 저장한 겁니다. next가 owner보다 1만큼 크니 누군가 스핀락을 획득한 상태입니다.

분석하려는 _raw_spin_lock() 함수를 다시 소개합니다.
1  80704b60 <_raw_spin_lock>:
2  80704b60: e1a0c00d  mov ip, sp
3  80704b64: e92dd800  push {fp, ip, lr, pc}
4  80704b68: e24cb004  sub fp, ip, #4
5  80704b6c: e52de004  push {lr} ; (str lr, [sp, #-4]!)
6  80704b70: ebe82672  bl 8010e540 <__gnu_mcount_nc>
7  80704b74: f590f000  pldw [r0]
8  80704b78: e1903f9f  ldrex r3, [r0]
9  80704b7c: e2832801  add r2, r3, #65536 ; 0x10000
10 80704b80: e1801f92  strex r1, r2, [r0]
11 80704b84: e3310000  teq r1, #0
12 80704b88: 1afffffa  bne 80704b78 <_raw_spin_lock+0x18>
13 80704b8c: e1a02823  lsr r2, r3, #16
14 80704b90: e6ff3073  uxth r3, r3
15 80704b94: e1530002  cmp r3, r2
16 80704b98: 0a000003  beq 80704bac <_raw_spin_lock+0x4c>
17 80704b9c: e320f002  wfe
18 80704ba0: e1d030b0  ldrh r3, [r0]
19 80704ba4: e1530002  cmp r3, r2
20 80704ba8: 1afffffb  bne 80704b9c <_raw_spin_lock+0x3c>
21 80704bac: f57ff05b  dmb ish
22 80704bb0: e89da800  ldm sp, {fp, sp, pc}

다음 8번째 줄 코드를 보겠습니다.
9  80704b7c: e2832801  add r2, r3, #65536 ; 0x10000

r3에 0x10000을 더합니다. 이는 ticket에 next를 +1만큼 더하는 동작입니다. ticket의 자료구조 패턴을 활용한 명령어입니다. 결과 r2는 0x00030001이 됩니다.

다음은 10번째 줄 코드입니다.
10 80704b80: e1801f92  strex r1, r2, [r0]

r2에는 ticket next만 +1만큼 더한 결괏값인 0x00030001을 저장하고 있습니다. 이 값을 r0 메모리 공간에 저장합니다. 스핀락 next 값을 +1만큼 증감시키는 겁니다. 만약 r0 메모리 공간에 0x00030001이 제대로 저장됐으면 r1은 1로 변경됩니다.

다음은 11~12번 줄 코드입니다. 
8  80704b78: e1903f9f  ldrex r3, [r0]
9  80704b7c: e2832801  add r2, r3, #65536 ; 0x10000
...
11 80704b84: e3310000  teq r1, #0
12 80704b88: 1afffffa  bne 80704b78 <_raw_spin_lock+0x18>

만약 0x00020001이 r0 메모리 공간에 저장을 못 하면 80704b78 코드 주소로 이동해서 다시 스핀락 변수를 읽습니다.

이번에는 _raw_spin_lock 함수의 후반부 코드를 보겠습니다.
13 80704b8c: e1a02823  lsr r2, r3, #16
14 80704b90: e6ff3073  uxth r3, r3
15 80704b94: e1530002  cmp r3, r2
16 80704b98: 0a000003  beq 80704bac <_raw_spin_lock+0x4c>
17 80704b9c: e320f002  wfe
18 80704ba0: e1d030b0  ldrh r3, [r0]
19 80704ba4: e1530002  cmp r3, r2
20 80704ba8: 1afffffb  bne 80704b9c <_raw_spin_lock+0x3c>

13번 줄 코드를 보면 r2와 r3 레지스터로 연산을 합니다.
8  80704b78: e1903f9f  ldrex r3, [r0]
...
13 80704b8c: e1a02823  lsr r2, r3, #16

r2는 ticket에서 next 값을 +1만큼 증감한 0x00030001입니다. r3는 처음 스핀락 값을 저장하고 있으니 0x00020001입니다. r3 레지스터는 스핀락 구현부 코드에서 가장 중요한 역할을 하니 집중해서 볼 필요가 있습니다.

lsr은 Logical Shift Right 이란 용어의 약자로 피연산자를 오른쪽으로 Shift 하는 명령어입니다. r3가 0x00010001인데 이 값을 왼쪽으로 16만큼 왼쪽으로 비트 Shift 연산을 하는 동작입니다.

연산 결과는 다음과 같습니다. 
0x00020001(r3)
+lsr
=============
0x00000001(r2)

반복하면서 설명해 드리지만, 어셈블리 코드를 볼 때는 그 의미를 파악하면서 읽을 필요가 있습니다. r3에는 next와 owner가 포함된 ticket 값이 있습니다. 0x00020001입니다. 이 중에 next 값을 r2에 저장하는 동작입니다.

다음 14번째 줄 코드를 보겠습니다.
14 80704b90: e6ff3073  uxth r3, r3

uxth명령어는 이전 시간에 배웠으니 넘어가겠습니다.

연산 결과는 다음과 같습니다. 이 어셈블리 명령어는 next와 owner 중 owner를 r3에 저장하는 임무를 수행합니다.
0x00010001(r3)
uxth
================
     0x0001

13~14줄 명령어를 같이 늘어놓고 해석하면 r3(0x00020001) 있는 next를 r2에 저장하고 owner를 r3에 저장하는 동작입니다.
13 80704b8c: e1a02823  lsr r2, r3, #16
14 80704b90: e6ff3073  uxth r3, r3

0x0002 | 0001(r3)
 next     owner
  r2        r3

다음 15번째 줄 코드를 분석할 차례입니다.
15 80704b94: e1530002  cmp r3, r2
16 80704b98: 0a000003  beq 80704bac <_raw_spin_lock+0x4c>
...
21 80704bac: f57ff05b  dmb ish

r3(owner)와 r2(next) 값을 비교해서 16번 줄 코드와 같이 두 값이 같지 않으니 18번 줄 코드를 실행합니다.
18 80704ba0: e1d030b0  ldrh r3, [r0]
19 80704ba4: e1530002  cmp r3, r2
20 80704ba8: 1afffffb  bne 80704b9c <_raw_spin_lock+0x3c>

이번에는 r0 메모리 값을 r3로 읽습니다. ldrh 명령어는 32비트 중 하위 16비트만 읽습니다. 이 명령어로 스핀락의 owner를 읽습니다. r3는 메모리 공간에 있는 스핀락 owner값이고 r2는 _raw_spin_lock 함수를 처음 실행했을 때 next인 0x2를 갖고 있습니다. 18번 줄 코드에서 r3이 0x2가 될 때까지 다음 코드17~20줄 어셈블리 코드를 무한히 수행합니다. 이를 Busy-wating이라고 합니다.
17 80704b9c: e320f002  wfe
18 80704ba0: e1d030b0  ldrh r3, [r0]
19 80704ba4: e1530002  cmp r3, r2
20 80704ba8: 1afffffb  bne 80704b9c <_raw_spin_lock+0x3c>

이 어셈블리 코드의 의미를 생각해 봅시다. _raw_spin_lock() 함수에 진입하기 전 이미 누군가 스핀락을 획득한 상태였습니다. 17~20번째 줄 코드는 스핀락을 해제할 때까지 기다리는 코드 흐름이라 볼 수 있습니다.

만약 스핀락을 해제하면 owner가 +1만큼 증가하니 18번 줄 코드에서는 r3는 0x2가 될 겁니다. 이때 스핀락 전역 변수는 0x0003|0002가 됩니다. next와 owner가 같으니 바로 스핀락을 얻게 됩니다. 

[1] 단계에서 이미 스핀락을 획득해서 임계영역을 실행 중인 상태입니다. [2] 단계에서 다른 프로세스가 스핀락을 획득하려고 시도합니다. 스핀락 인스턴스 값의 next와 owner를 확인했는데 next는 0x2, owner가 0x1이니 누군가 스핀락을 획득했으니 스핀락을 얻지 못합니다.

다음 시간에는 이 상황에서 다시 스핀락을 획득하려고 할 때 어떤 동작을 하는지 알아보겠습니다.


#Reference 시스템 콜


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


덧글

댓글 입력 영역