ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

192239
1625
172589


[리눅스커널] 프로세스: current_thread_info() 매크로 함수 분석 4. 프로세스(Process) 관리

current_thread_info() 매크로 함수는 프로세스가 실행 중일 때 thread_info 구조체가 있는 스택의 최상단 주소를 계산해 반환합니다.

current_thread_info() 매크로 함수의 사용 예시

current_thread_info() 매크로 함수의 구현부를 분석하기에 앞서 매크로 함수를 어느 코드에서 쓰는지 알아봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/asm-generic/preempt.h
1  static __always_inline int preempt_count(void)
2  {
return READ_ONCE(current_thread_info()->preempt_count);
4  }
5
6  static __always_inline volatile int *preempt_count_ptr(void)
7  {
8 return &current_thread_info()->preempt_count;
9  }
10
11 static __always_inline void preempt_count_set(int pc)
12 {
13 *preempt_count_ptr() = pc;
14 }

1번째 줄에서 preempt_count() 함수는 current_thread_info() 함수를 써서 thread_info 구조체의 필드인 preempt_count에 접근합니다.

6번째 줄을 봅시다. preempt_count_ptr() 함수도 current_thread_info() 함수를 써서 thread_info 구조체의 필드인 preempt_count의 주소를 반환합니다.

11~13번째 줄에서는 preempt_count_ptr() 함수를 이용해 thread_info 구조체의 preempt_count 필드에 pc라는 인자를 할당합니다.

이번에는 다른 코드를 봅시다. 

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/smp.h
#define raw_smp_processor_id() (current_thread_info()->cpu)

raw_smp_processor_id() 함수는 thread_info 구조체의 cpu 필드에 접근하는 매크로 함수입니다. 현재 이 함수는 실행 중인 CPU 번호를 반환합니다.

앞에서 소개한 바와 같이 current_thread_info() 매크로 함수는 리눅스 커널 전반에 아주 자주 사용되므로 눈에 잘 익혀둡시다. 

current_thread_info() 매크로 함수의 구현부 분석

이번에는 current_thread_info() 매크로 함수를 분석하겠습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/include/asm/thread_info.h
1 register unsigned long current_stack_pointer asm ("sp");
2
3 static inline struct thread_info *current_thread_info(void)
4 {
5 return (struct thread_info *)
6 (current_stack_pointer & ~(THREAD_SIZE - 1));
7 }

참고로 THREAD_SIZE란 0x2000 값으로서 ARM 아키텍처에서 실행되는 프로세스의 스택 크기를 의미합니다.

#define THREAD_SIZE 0x2000

---
ARM64 아키텍처에서는 커널 프로세스의 스택 크기가 0x4000바이트입니다. 이 책에서는 라즈베리 파이에 탑재된 32비트 ARMv7 아키텍처를 기준으로 코드를 분석합니다.
---

current_thread_info() 매크로 함수에서 current_stack_pointer 코드의 의미를 먼저 확인합시다. 

1 register unsigned long current_stack_pointer asm ("sp");

현재 구동 중인 프로세스의 스택 주소를 current_stack_pointer 변수로 가져오는 명령어입니다. 여기서 asm ("sp") 어셈블리 명령어는 현재 실행 중인 프로세스 스택 주소를 알려줍니다. 

우리가 보는 모든 함수는 프로세스 스택 공간에서 실행됩니다. 위 명령어와 조합해서 1번 코드와 같이 선언하면 current_stack_pointer 전역변수에 스택 주소를 저장합니다.

분석한 내용을 종합하면 5~6번째 줄 코드는 다음과 같이 바꿔볼 수 있습니다.

(current_stack_pointer & ~(THREAD_SIZE - 1)); 
(current_stack_pointer & ~((0x2000) - 1));
(current_stack_pointer & ~(0x1fff)); 

THREAD_SIZE가 0x2000이니 0x2000에서 1을 빼면 0x1fff입니다. 위 코드에서 ~라는 비트 연산자는 비트를 모두 역전시키는 역할을 합니다. 따라서 0x1fff를 이진수로 바꿔서 ~ 비트 연산을 수행하면 다음과 같은 결괏값을 얻을 수 있습니다.

0001|1111|1111|1111
~연산
-------------------
1110|0000|0000|0000

1110|0000|0000|0000이라는 이진수를 16진수로 바꾸면 0xE000이 됩니다.

정리하면 현재 스택 주소에서 0xE000라는 값을 AND 연산하는 코드입니다. 

(current_stack_pointer & ~((0x2000) - 1));
(current_stack_pointer & ~(0x1fff));
(current_stack_pointer & (0xe000)); 

그런데 현재 스택 주소와 0xE000을 AND 연산하는 것에는 어떤 의미가 있을까요? 아래에 굵게 표시된 0~12비트는 모두 0이니 다른 주소와 AND 연산을 하면 0이 됩니다.

1110|0000|0000|0000(0xE000)

0xD000C248 & (0000|0000|0000) 연산을 하면 0xD000C000이 됩니다. 16진수 3자리 값을 모두 0으로 변환시킵니다. 

1100|0000|0000|0000|1011|0010|0100|1000 (0xD000C248)
                                           0000|0000|0000
-----------------------------------------------------AND 연산
1100|0000|0000|0000|1011|0000|0000|0000 (0xD000C000)


이번에 0xE000에서 0xE만 빼서 이진수로 어떤 동작을 하는지 확인할 차례입니다. 이제 0xE와 AND 연산을 하는 의미를 알아보려는 것입니다.

1110|0000|0000|0000

1110에 대해 비트 연산을 수행하면 현재 값이 짝수면 그 값을 그대로 유지하고 홀수면 -1만큼 뺍니다. 아래 연산 결과를 참고하면 이해하기가 쉬울 것입니다.
 
이제 정리해 봅시다. 각각 스택 주소가 다음과 같을 때 0xE000와 AND 연산을 하면 결괏값은 다음과 같습니다.

[스택주소]
0xD0002248 & 0xE000 = 0xD0002000
0xD0003248 & 0xE000 = 0xD0002000
0xD0004248 & 0xE000 = 0xD0004000
0xD0005248 & 0xE000 = 0xD0004000


현재 스택 주소에서 위와 같은 규칙으로 비트를 연산하는 이유는 뭘까요? ARM32 아키텍처에서는 0x2000바이트 크기만큼 스택을 지정하는데 그 이유는 커널이 프로세스 스택 주소를 할당할 때 0x2000바이트를 기준으로 정렬을 맞춰서 할당하기 때문입니다.

프로세스 최상단 주소는 짝수 바이트입니다. 그래서 위와 같은 비트 연산으로 스택의 최상단 주소를 계산한 것입니다.

* 유튜브 강의 동영상도 있으니 같이 들으시면 좋습니다. 




#프로세스

프로세스 소개 
프로세스 확인하기  
프로세스는 어떻게 생성할까?  
유저 레벨 프로세스 실행 실습  
커널 스레드  
커널 내부 프로세스의 생성 과정   
프로세스의 종료 과정 분석  
태스크 디스크립터(task_struct 구조체)  
스레드 정보: thread_info 구조체  
프로세스의 태스크 디스크립터에 접근하는 매크로 함수  
프로세스 디버깅  
   * glibc의 fork() 함수를 gdb로 디버깅하기  


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2


 





핑백

덧글

댓글 입력 영역