이전 글에서 설명한 것처럼 프로세스를 처음 생성할 때에는 copy_process()
함수를 호출한다. 이 과정에서 dup_task_struct()
함수를 호출하게 되는데, 이 함수가 태스크 디스크립터와 프로세스가 실행될 스택 공간을 새로 만들게 된다.
dup_task_struct()
dup_task_struct()
함수가 수행하는 핵심적인 기능은 다음과 같다:
alloc_task_struct_node()
함수: 슬랩 할당자로 태스크 디스크립터인 task_struct
구조체를 할당 받음alloc_thread_stack_node()
함수: 프로세스 스택 공간을 할당받음책에서는 슬럽(slub)을 사용한다고 했으나 실제 구현은 슬랩(slab)으로 되어있다. 단순히 커널 config 의 차이일 수도 있다. 필자는 할당자 별 차이를 잘 모르기 때문에 자세한 설명은 생략하려 한다. 그냥 그런다보다 한다.
alloc_thread_stack_node()
함수에서는 stack 을 할당하는데 코드 자체만 보면 따로 스택을 동적할당하는 코드는 보이지 않고 대신 cached_stacks
이라는 곳에서 vm_struct
를 받아오고 이를 가져오는 것 같다. 책에서 설명하는 것처럼 stack
의 크기는 THREAD_SIZE
정해져 있는 것 같고 이는 0x4000
이다.
책에서는 setup_thread_stack()
으로 태스크 디스크립터의 주소를 thread_info
구조체의 task
필드에 저장한다고 나와 있는데 지금은 setup_thread_stack()
함수가 비어있다. 그런데 당연히 그럴 수 밖에 없다. 왜냐하면 CONFIG_THREAD_INFO_IN_TASK
가 켜져 있으면 thread_info
가 task_struct
에 built-in 되기 때문이다.
alloc_task_struct_node()
함수 여기에서는 다시 슬럽이 아니라 슬랩이라고 설명하는 걸 보면 오탈자 같다. kmem_cache_alloc_node()
함수를 호출해서 task_struct
구조체를 할당 받는다. 여기에서 task_strut_cachep
라는 슬랩 캐시를 사용하는데 이는 다음과 같은 역할을 수행한다:
task_struct
구조체 크기만큼 메모리를 미리 확보해 놓고 대기task_struct
구조체 할당 요청이 오면 이미 할당해 놓은 task_struct
구조체의 시작 주소를 반환이래서 리눅스 커널의 한계(limit)이 존재하는 것 같다. 사용자 입장에선 불편할 수 있지만 그 불편함을 감수할 만큼의 독보적인 장점이 있는 구현인 것 같다. 탁월한 설계다. 다만 이걸 이미 빌드된 커널에서 config 한 줄 바꿔서 변경하는 것은 불가능하겠다.
alloc_thread_stack_node()
함수 책에서는 alloc_thread_stack_node()
함수가 단 두 구문(statement)로 구현되어 있는데 필자의 코드에서는 완전히 다른 구현으로 동작한다. 이건 CONFIG_VMAP_STACK
여부에 따라 그 구현이 달라지게 되는데 책의 구현은 CONFIG_VMAP_STACK
이 꺼진 상태이다.
https://www.kernel.org/doc/Documentation/vm/vmalloced-kernel-stacks.rst
관련된 내용은 여기에 있는 것 같은데... 너무 길어서 읽고 싶지 않다.. 흑흑... 그래도 Introduction
만 보자면 Kernel Stack Overflow 문제가 디버깅을 어렵게 만들기 때문에 이 문제를 해결하기 위해, 가상으로 매핑한 커널 스택에 페이지 가드(?)를 붙여서 오버 플로우를 캐칭하겠다는 것이 메인 아이디어인 것 같다.