Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

183162
807
85251


[라즈베리파이] 동기화 - 스핀락(spinlock): spin_lock() 어셈블리 코드 분석 9장. 커널 동기화 소개

이전 시간에 인라인 어셈블리 코드를 살펴봤는데, 이번에는 어셈블리 코드를 분석하겠습니다. 실제 ARM 프로세서 입장에서 스핀락을 어떻게 실행하는지 정확히 파악하려면 어셈블리 코드를 봐야 합니다. 어셈블리 코드를 분석해야 스핀락 핵심 개념을 알 수 있다는 겁니다. 이전 절에 다룬 내용을 이해했으면 그리 어렵지 않습니다.

분석하려는 코드는 다음과 같습니다.
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}

spin_lock() 부터 __raw_spin_lock(), do_raw_spin_lock(), arch_spin_lock() 함수 들은 모두 인라인 타입 함수입니다. 따라서 컴파일을 하면 위에 언급된 함수들은 심볼이 없습니다.
spin_lock() -> _raw_spin_lock() -> __raw_spin_lock() -> do_raw_spin_lock() -> arch_spin_lock()

예를 들어 다음 get_task_exe_file() 함수 6번째 줄 코드에서 spin_lock() 함수를 호출하는 것처럼 보입니다.
[https://elixir.bootlin.com/linux/v4.14.43/source/kernel/fork.c#L1025]
1 struct file *get_task_exe_file(struct task_struct *task)
2 {
3 struct file *exe_file = NULL;
4 struct mm_struct *mm;
5
6 task_lock(task); // spin_lock(&p->alloc_lock);
7 mm = task->mm;

위 함수를 바이너리 유틸리티를 써서 어셈블리 코드를 보면 다음과 같습니다.
1 80119974 <get_task_exe_file>:
2 80119974: e1a0c00d  mov ip, sp
3 80119978: e92dd818  push {r3, r4, fp, ip, lr, pc}
4 8011997c: e24cb004  sub fp, ip, #4
5 80119980: e52de004  push {lr} ; (str lr, [sp, #-4]!)
6 80119984: ebffd2ed  bl 8010e540 <__gnu_mcount_nc>
7 80119988: e1a04000  mov r4, r0
8 8011998c: e2800c05  add r0, r0, #1280 ; 0x500
9 80119990: eb17ac72  bl 80704b60 <_raw_spin_lock>

C 코드에서는 7번째 줄 코드와 같이 spin_lock() 함수를 호출했는데 어셈블리 코드에서는 위 9번째 줄 코드와 같이 _raw_spin_lock() 함수를 호출하는 겁니다.

어셈블리 코드를 분석에 앞서 _raw_spin_lock() 함수에 전달하는 인자들의 개수와 유형을 꼼꼼히 살펴볼 필요가 있습니다.
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
__raw_spin_lock(lock);
}

_raw_spin_lock 함수로 1개 인자를 전달하며 인자의 타입은 raw_spinlock_t *lock입니다. ARM 함수 호출 규약으로 함수 인자는 r0 레지스터로 전달되는데 r0에 포인터 타입 raw_spinlock_t 구조체 변수 주소가 있습니다. r0이란 메모리 공간에 스핀락 변수가 있다는 의미입니다. 이 점을 머릿속으로 그리면서 어셈블리 코드 분석을 시작합시다.

먼저 8번째 줄 코드를 보겠습니다.
8  80704b78: e1903f9f  ldrex r3, [r0]

r0는 0xb93b4a78이고 이 메모리 공간에 0x00010001 값이 있으니 r3은 0x00010001로 변경됩니다. 0xb93b4a78 메모리 공간에 스핀락 인스턴스가 있습니다. r3에 스핀락 인스턴스 owner와 next를 모두 저장한 겁니다. r3는 스핀락 인스턴스를 의미합니다.

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

다음 수식으로 r3에 0x10000을 더합니다. 
r2 = 0x0002|0001 = 0x0001|0001 + 0x0001|0000

이는 스핀락 지역 변수 next를 +1만큼 더하는 동작입니다. ticket의 자료구조 패턴을 활용한 명령어입니다.

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

r2에는 ticket next만 +1만큼 더한 결괏값인 0x00020001을 저장하고 있습니다. 이 값을 r0 메모리 공간에 저장합니다. 스핀락 인스턴스 멤버인 next를 +1만큼 증감하는 동작입니다. 스핀락 next 값을 +1만큼 증감시키는 겁니다. 만약 r0 메모리 공간에 0x00020001이 제대로 저장됐으면 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 메모리 공간에 저장을 못하면 8번째 줄 코드인 80704b78 코드 주소로 이동해서 다시 스핀락 변수를 읽습니다.

어셈블리 코드를 C 언어 형태로 변환해서 같이 표현하면 다음과 같습니다.
8  80704b78: e1903f9f  ldrex r3, [r0]
9  80704b7c: e2832801  add r2, r3, #65536 ; 0x10000
10 80704b80: e1801f92  strex r1, r2, [r0]
11 
12 static inline void _raw_spin_lock(arch_spinlock_t *lock)
13 {
14 unsigned long tmp;
15 u32 newval;
16 arch_spinlock_t lockval;
17
18 lockval = &lock->slock;
19 newval = lockval.tickets.next + 1;
20 &(lock.tickets.next) = newval;

8~10번 줄 코드를 18~20번 줄 코드에 대응합니다. 

조금 더 알기 쉽게 표현하면 다음 그림과 같습니다.
 
어셈블리 코드를 분석할 때 C 언어로 변환하면 더 오랫동안 분석한 내용을 머릿속에 기억할 수 있습니다.

이번에는 _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만큼 증감한 0x00020001입니다. r3는 처음 스핀락 값을 저장하고 있으니 0x00010001입니다. r3 레지스터는 스핀락 구현부 코드에서 가장 중요한 역할을 하니 집중해서 볼 필요가 있습니다.

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

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

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

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

uxth이라는 낯선 어셈블리 명령어입니다. 낯선 어셈블리 코드를 보면 겁먹지 마시고 구글링으로 "uxth arm" 검색어로 키워드를 입력하면, 다음과 같이 ARM Infocenter 사이트에서 관련 정보를 얻을 수 있습니다.
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489i/CIHHJCFE.html

확인하면 uxth는 Zero extend Halfword의 약자로 16비트에서 상위 8비트를 Clear 하는 동작입니다.

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

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

0x0001 | 0001(r3)
 next     owner
  r2        r3

lsr와 uxth 단 2개 최소 어셈블리 명령어로 해당 CPU가 효율적으로 실행할 수 있도록 최적화 아이디어가 반영된 코드입니다.

다음 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번 줄 코드와 같이 두 값이 같으면 0x80704bac 주소로 이동해서 _raw_spin_lock() 함수를 빠져나옵니다.

여기서 r3의 의미를 조금 더 생각해 봅시다.
1  80704b60 <_raw_spin_lock>:
...
8  80704b78: e1903f9f  ldrex r3, [r0]

r3은 _raw_spin_lock 함수에 전달되는 스핀락 값을 저장하고 있습니다. 이때 owner와 next가 같으면 스핀락을 획득한 적이 없으니 next를 +1만큼 증감시키고 _raw_spin_lock() 함수를 빠져나오는 겁니다.

그런데 만약 ticket 값이 0x00020001이면 next가 2이고 owner가 1이므로 누군가 스핀락을 획득했다고 봐야 합니다. 만약 처음 r3가 0x00020001이면 15번 줄 코드를 실행할 때는 r3는 owner인 0x1이고 r2는 next인 0x2입니다.
15 80704b94: e1530002  cmp r3, r2
16 80704b98: 0a000003  beq 80704bac <_raw_spin_lock+0x4c>

이미 스핀락을 누군가 획득했을 경우 동작은 다음 시간에 알아보겠습니다.



#Reference 시스템 콜


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



덧글

댓글 입력 영역