ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

193239
1625
172590


[리눅스커널] 프로세스: current 매크로란 4. 프로세스(Process) 관리

지금까지 살펴봤듯이 프로세스 속성 정보를 관리하는 struct task_struct 타입의 태스크 디스크립터는 커널에서 가장 중요하게 관리하는 자료구조입니다. 여기에는 그럴 만한 이유가 있는데, 커널은 태스크 디스크립터에 접근해 프로세스 정보를 수시로 접근하고 저장하며, 태스크 디스크립터에 들어 있는 속성 정보로 함수의 실행 흐름이 바뀌기 때문입니다.
   
그렇다 보니 간단하면서도 시스템에 부하를 주지 않는 형태로 태스크 디스크립터의 주소에 접근하는 매크로 코드가 필요하다는 요구가 불거졌고, 커널에서는 이런 요구사항을 만족하는 매크로를 제공하기에 이르렀습니다. 이를 current 매크로라고 합니다. 

그럼 current 매크로는 무엇일까요? current 매크로에 접근하는 이유와 관련 자료 구조에 대해 다음과 같이 설명할 수 있습니다.

 current 매크로는 현재 구동 중인 프로세스의 태스크 디스크립터 주소를 알려줍니다.
 current 매크로를 통해 직접 태스크 디스크립터 필드에 접근할 수 있습니다.

current 매크로 함수의 사용 예

먼저 current 매크로를 사용하는 예제 코드를 보겠습니다. 다음은 파일 디스크립터를 할당하는 get_unused_fd_flags() 함수입니다.

https://elixir.bootlin.com/linux/v4.19.30/source/fs/file.c
1 int get_unused_fd_flags(unsigned flags)
2 {
3 return __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
4 }
5 EXPORT_SYMBOL(get_unused_fd_flags);

여기서 3번째 줄을 보면 current 매크로로 current->files 필드에 접근해서 __alloc_fd() 함수의 첫 번째 파라미터로 전달합니다. 그런데 코드를 조금 눈여겨보면 뭔가 이상합니다. 어느 코드에도 current를 지역변수나 전역변수로 선언한 코드가 없다는 것입니다. 하지만 current 매크로는 task_struct 구조체 주소를 포인터 형태로 반환합니다. 그래서 current->files로 task_struct 구조체의 files(파일 디스크립터) 필드에 접근할 수 있는 것입니다.

이번에는 __put_task_struct() 함수를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/fork.c
1 void __put_task_struct(struct task_struct *tsk)
2 {
3 WARN_ON(!tsk->exit_state);
4 WARN_ON(atomic_read(&tsk->usage));
5 WARN_ON(tsk == current);

5번째 줄을 보면 __put_task_struct() 함수로 전달된 struct task_struct 타입의 tsk 파라미터와 current가 같은지 검사합니다. 이번에도 마찬가지로 함수 내에서 current라는 변수를 선언한 흔적이 없습니다. 그런데 갑자기 current라는 변수가 나타나서 task_struct 구조체 포인터 타입의 tsk를 current와 같은지 비교합니다.

이 밖에도 current 매크로는 리눅스 커널 코드에서 굉장히 자주 볼 수 있습니다. 이 코드의 의미를 정확히 이해해야 커널 코드를 읽는 데 문제가 없습니다.

current 매크로 함수의 구현부 분석

그럼 본격적으로 current 매크로의 정체가 무엇인지 알아볼까요? current 매크로는 현재 구동 중인 프로세스의 태스크 디스크립터의 주소를 알려주는 역할을 합니다. 여기서 구조체는 struct task_struct 이고 포인터 타입입니다. 그래서 current->comm, current->files의 형태로 각 필드에 접근할 수 있습니다.

current 매크로의 구현부는 다음과 같습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/asm-generic/current.h
1 #define get_current() (current_thread_info()->task)
2 #define current get_current()

2번째 줄을 보면 current 매크로는 get_current() 매크로 함수로 치환됩니다. 이어서 1번째 줄을 보면 get_current() 함수는 (current_thread_info()->task)로 치환됩니다. 여기서 current_thread_info() 함수의 정체는 무엇일까요?

current_thread_info() 함수는 thread_info 구조체의 시작 주소입니다. thread_info 구조체의 필드 중 task에는 프로세스의 태스크 디스크립터 주소가 저장됩니다.

앞에서 설명한 정보를 정리하면 current 매크로는 다음과 같이 풀어서 설명할 수 있습니다.

실행 중인 프로세스 스택의 주소를 이용해 최상단 주소에 접근해서 thread_info 구조체의 task 필드의 주소를 반환하는 코드입니다.

좀 더 정리하는 차원에서 current 매크로가 치환되는 과정을 확인해 봅시다.

current
get_current()
current_thread_info()->task
struct thread_info->task

다음 절에서는 current 매크로 코드에서 본 current_thread_info() 매크로 함수를 살펴보겠습니다. 이 매크로도 커널 코드에서 자주 사용되므로 잘 알아둘 필요가 있습니다.

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




#프로세스

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


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

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

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

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

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


Thanks,
Austin Kim

핑백

덧글

  • 영악한 얼음여왕 2019/08/21 00:26 # 답글

    안녕하세요 질문 하나만 해도될까요? 제가 현재 커널에서 realpath를 구현중입니다...ㅠㅠ
    char *realpath(char *buffer, char resolv[256]) 함수입니다.

    매개변수값은 이렇게 되어있습니다.

    buffer는 __user *filename을 복사한 상대경로값입니다.
    resolv[256]는 buffer를 절대경로로 변환한 값입니다.

    함수가 진행이 되면서 buffer의 왼쪽부터 폴더 하나하나를 따라가면서 resolv에 차곡차곡 쌓아가게됩니다.

    그렇게해서 완성된 절대경로를 return resolve; 하여 함수를 종료하게 됩니다.

    그러나 symbolic link, hard link같은 경우에는 링크가 가리키는 절대 경로명을 찾아주고싶습니다.

    그래서
    __old_kernel_stat sb; 을 선언해 준 후

    lstat(resolv, &sb) 해준 다음,
    if S_ISLNK(sb.st_mode) 링크폴더라면
    readlink() 함수로 링크가 가리키는 폴더의 절대경로를 받아 다시 함수를 작동하게 하고싶습니다.

    예를들면 /etc를 카리키는 심볼릭 링크를 test로 생성후 vim /test/tmp 명령어를 실행합니다. 그러면 openat 시스템 콜을 통해 do_sys_open함수의 char __user* filename 변수로 파일값을 전달받고 이 변수를 buffer배열에 copy 합니다. char *path; 선언 후
    path = realpath(buffer, resolv); 함수를 실행합니다.

    그러면 realpath에서 '/'단위로 토큰을 만들어 따라가서 resolv에 쌓아가기 시작합니다. 그러다 resolv 가 "/test" 상태가 되었을 때 lstat를 통해 "/test"가 심볼릭 링크라는 것을 알아낸 후 readlink()함수를 통해 "/test"를 "/etc"로 변환해준 후 다시 토큰을 쌓아 최종적으로 resolv는 "/etc/tmp"를 최종적으로 가졌으면 합니다.

    그러나 커널 내에서는 lstat와 readlink를 사용하지를 못하네요...

    혹시 좋은 방법 있을까요..?

    [ 현재 fs/stat.c 에서 lstat함수를 k_lstat로, readlink함수를 k_readlink로 복사하여 적용을 시도했으나 커널패닉이 계속 나네요..
    __user 를 지워도 보고 copy_to_user를 memcpy로 카피하여 값을 가져오려고도 시도해봤지만 성공을 하지는 못했습니다..
    현재는 lstat와 readlink함수 그대로 복원시켜두었습니다..
    도와주실수 있나요.. ? :( ]
댓글 입력 영역