Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


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

이제껏 알아봤듯 프로세스 속성 정보를 관리하는 struct task_struct 타입인 태스크 디스크립터는 커널에서 가장 중요하게 관리하는 자료구조입니다. 여기에는 그럴만한 이유가 있습니다.

    커널은 태스크 디스크립터에 접근해 프로세스 정보를 수시로 접근하고 저장한다.
   태스크 디스크립터에 속성 정보로 함수 실행 흐름이 바뀐다.
   
그렇다보니 다음 요건을 충족하는 태스크 디스크립터 주소에 접근하는 매크로 코드가 있으면 좋겠다고 생각합니다.
간단한 형태의 코드 
시스템에 부하를 주지 않는 코드

커널에서는 이런 요구 사항을 만족하는 매크로를 제공하는데 이를 current 매크로라고 부릅니다. 

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

current 매크로 함수 사용 예시 코드 알아보기
먼저 current 매크로를 쓰는 예제 코드를 보겠습니다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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);

파일 디스크립터를 할당하는 get_unused_fd_flags() 함수입니다.
3번째 줄 코드를 보면 current 매크로로 current->files 필드에 접근해서 __alloc_fd() 함수 첫 번째 파라미터로 전달합니다. 코드를 조금 눈여겨보면 뭔가 이상합니다. 어느 코드에도 current를 지역변수나 전역 변수로 선언한 코드가 없습니다.

하지만 current 매크로는 struct task_struct 구조체 주소를 포인터 형태로 반환합니다.
그래서 current->files 코드로 struct task_struct 구조체 필드 files(파일 디스크립터)에 접근하는 것입니다.

이번에는 __put_task_struct() 함수 코드를 봅시다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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란 변수가 나타났습니다.

__put_task_struct() 함수 인자인 tsk는 struct task_struct 구조체 포인터 타입인데 current와 같은지 비교하는 코드입니다.

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

current 매크로 함수 구현부 분석하기
current 매크로의 정체가 무엇인지 알아볼까요? 

    current 매크로는 현재 구동 중인 프로세스의 태스크 디스크립터의 주소를 알려주는 
   역할입니다. 

구조체는 struct task_struct이고 포인터형 변수입니다. 그래서 current->comm, current->files 형태로 각 필드에 접근할 수 있습니다.  

current 매크로 구현부 코드는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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() 함수의 정체는 무엇일까요?

다음 소절에서 다룰 struct thread_info 구조체입니다. 그런데 struct thread_info 구조체 필드 중 task는 프로세스의 태스크 디스크립터 주소를 저장합니다.

위에서 본 정보를 모으면 current 매크로는 다음과 같이 풀어서 설명할 수 있습니다.
실행 중인 프로세스 스택 주소를 이용해 최상단 주소에 접근해서 struct thread_info 구조체 task 필드 주소를 반환하는 코드입니다.

정리하는 차원에서 current 매크로가 치환되는 과정을 확인해볼까요?
current
get_current()
current_thread_info()->task
struct thread_info->task

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


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

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

#Reference(프로세스 관리)
프로세스 디버깅
   glibc fork 함수 gdb 디버깅




핑백

덧글

  • 영악한 얼음여왕 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함수 그대로 복원시켜두었습니다..
    도와주실수 있나요.. ? :( ]
댓글 입력 영역