[Linux Kernel] alloc_thread_stack_node() 함수 분석
'디버깅을 통해 배우는 리눅스 커널의 구조와 원리' 책을 읽으면서 정리를 하다가 한 가지 코드 상의 차이를 발견했다.
커널 버전은 책에 나온 대로 v4.19다.
개요
프로세스가 새로 생성될 때 불리는 _do_fork() 함수 내 copy_process() 함수에서 일어나는 동작 중 하나로 프로세스가 이용할 스택을 할당하는 게 있다.
태스크 디스크립터 생성하고 slab cache에서 스택 공간 할당받는 함수인 alloc_thread_stack_node() 함수가 있는데 책에 있는 코드와 구현 상의 차이가 있길래 초보자 입장에서는 흥미롭게 다가왔다.
Before
static unsigned long *alloc_thread_stack_node(struct task_struct *tsk, int node)
{
...
return page ? page_address(page) : NULL;
}
After
static unsigned long *alloc_thread_stack_node(struct task_struct *tsk, int node)
{
...
if (likely(page)) {
tsk->stack = page_address(page);
return tsk->stack;
}
return NULL;
}
책의 저자이신 AustinKim(김동현)님이 쓸 때만 해도 v4.19.98 이하 버전이라 Before의 소스였는데 v4.19.99로 올라오면서 뭔가 더 다듬어진 코드로 바뀐 걸 확인할 수 있었다.
그냥 '음 바뀌었구나...'라고 생각하고 넘어갈 수도 있었지만 문득 궁금해졌다. 정확히 무슨 이유 때문에 저렇게 패치를 한 걸까?
Search
Kernel Mailing List의 방대한 아카이브를 처음으로 찾아보는 거라 좀 긴장했는데 구글링 해서 도움 되는 사이트들을 이용해서 찾았다.
▶ 검색에 쓰인 사이트들
답변의 여러 링크 중 실질적으로 도움을 받은 사이트 ↓
https://marc.info/?l=linux-kernel
Tip) 커널 버전을 특정해서 검색해도 좋고 author를 기준으로 검색해도 좋고 다 좋은데
원하는 커널 버전 + 궁금한 파일의 이름(이 글 기준으로는 fork) + 함수 이름 혹은 특정 변수
를 알고 검색하면 쉽게 찾을 수 있다.
그래도 못 찾겠으면 특정 버전의 리뷰 안에 리스트 있으니까 검색해서 찾으면 된다.
여차저차 해서 Andrea Arcangeli 님이 리퀘스트한 메일 내용을 볼 수 있었다.
https://marc.info/?l=linux-kernel&m=157986512931731&w=2
수정한 이유는 다음과 같다.
페이지를 할당한 다음에는 반드시 가상 주소로 변환해야 하는데 해당 가상화에 관여하는 config 옵션이 VMAP_STACK=y다. 일반적인 상황에서 Enterprise kernel은 이 설정을 바꾸지 않지만 custom 해서 성능상 이득을 보기 위해 해당 옵션을 n으로 설정해 꺼놓고 사용했다.
여기서 기존 코드는 tsk->stack을 초기화해주지 않음으로 생겨나는 dangling pointer 때문에 free_thread_stack() 함수에서 compaction fail이 일어나 크래시가 발생한다는 것이다.
Before, After 코드는 해당 옵션이 꺼진 상태에서 alloc_thread_stack_node() 함수가 매크로에 의해 정의되는 코드다. 보면 실제로 tsk->stack이 초기화되지 않는다. 이 때문에 free_thread_stack() 함수를 부를 때 초기화되지 않은 tsk->stack을 free 하려고 해서 크래시가 일어나는 것이다.
결국 Andrea Arcangeli 님이 회사에서 BSP 개발하다가 일어난 버그 고치기 위해 컨트리뷰션 한 것이라 판단할 수 있다.
Conclusion
간단한 코드 리뷰였지만 처음이라 설레었다. 덕분에 커널 세계에 발톱 끝을 담가본 느낌이다.
하지만 아직 기본기가 탄탄하지 않으니 앞으로 열심히 해서 오픈 소스 컨트리뷰터가 되고 싶다 ㅎㅎ.
Reference