<commit_before>+++

title = "Golang의 내부, 6부: 부트스트래핑과 메모리 할당자"
draft = true
date = "2016-09-20T16:20:29-04:00"

tags = ["Golang", "Internals", "runtime", "bootstrap", "memory", "allocator"]
categories = ["번역", "핵킹"]
series = ["Golang  Internals"]
authors = ["<NAME>"]

toc = true

+++

이 포스트는 Golang 내부 시리즈의 연속이다. Go 런타임을 자세히 이해하는데 열쇠와 같은 부트스트랩 과정을 살펴볼 것이다. 이번에는 시작하는 순서의 두번째 부분을 섭렵해서 어떻게 인수들이 초기화되고, 어떤 함수들이 호출되는지 등을 배우겠다.

# 시작하는 순서

지난 번에 얘기하다가 만 *runtime.rt0_go* 함수를 다시 다루어야 겠다. 아직 이 함수에서 살펴보지 않은 부분이 여전히 있다.

>```
01 CLD                         // convention is D is always left cleared
02 CALL    runtime·check(SB)
03
04 MOVL    16(SP), AX          // copy argc
05 MOVL    AX, 0(SP)
06 MOVQ    24(SP), AX          // copy argv
07 MOVQ    AX, 8(SP)
08 CALL    runtime·args(SB)
09 CALL    runtime·osinit(SB)
10 CALL    runtime·schedinit(SB)
```

첫번째 명령 (CLD)는 *FLAGS* 레지스터의 [direction](https://en.wikipedia.org/wiki/Direction_flag) 프래그를 지운다. 이 플래그는 문자열 처리 방향에 영향을 준다.

다음 함수는 *runtime.check* 함수를 호출하는데, 그 또한 이 문서의 목적에 비추어 그리 중요하지는 않다. 런타임은 모든 내장 타입의 인스턴스들을 만들고, 타입의 크기와 파라미터들을 확인하는 정도의 일을 한다. 그리고 만약 작업중 문제가 생기면 *panic* 한다. [function](https://github.com/golang/go/blob/go1.5.1/src/runtime/runtime1.go#L136)를 통해 쉽게 알아볼 수 있다.

# 인수 분석하기

그 다음 함수 [runtime.Args](https://github.com/golang/go/blob/go1.5.1/src/runtime/runtime1.go#L48) 는 좀 더 흥미롭다. The next function, 리눅스 시스템에서 (*argc* 와 *argv*) 인수를 정적 변수속에 저장하는 것말고도 이 함수는 ELF 보조 벡터를 분석하며 *syscall* 주소를 초기화한다.

설명이 좀 더 필요하겠다. 운영체계가 프로그램을 메모리에 올릴때, 그 프로그램의 초기 스택을 미리 정해진 포맷의 어떤 데이타로 초기화한다. 스택의 꼭대기에는 환경 변수들의 포인터인 인수들이 깔린다. 스택의 바닥에는 "ELF 보조 벡터"를 볼 수 인데, 실제로 이 것은 어떤 유용한 정보를 담고 있는 기록의 배열들이다. 예를 들면, 프로그램 헤더의 수와 크기들이다. 여기 이 [문서](http://articles.manugarg.com/aboutelfauxiliaryvectors)를 통해 ELF 보조 벡터 포맷에 대해 좀 자세히 알아 보라.

*runtime.Args* 함수는 벡터를 파싱하는 책임이 있다. 런타임은 벡터에 담고 있는 모든 정보들 중에 단지 *startupRandomData* 만을 사용하는데, 주로 헤싱 함수(hashing functions)들과 어떤 syscall들의 위치를 가리키는 포인터들을 초기화하는데 주로 사용된다. 다음에 나오는 변수들을 초기화한다:

>```
1 __vdso_time_sym
2 __vdso_gettimeofday_sym
3 __vdso_clock_gettime_sym
```

이 것들은 여러 함수들내 현재 시간을 획득하는데 사용된다. 이 모든 변수들은 기본값을 가진다. 이렇게 함으로써 Golang에서 상응하는 함수들을 호출하기 위해 *[vsyscall](http://www.ukuug.org/events/linux2001/papers/html/AArcangeli-vsyscalls.html)* 메카니즘의 사용이 허용된다.

# *runtime.osinit* 함수의 내부

시작 순서에서 그 다음으로 호출되는 함수는 *[runtime.osinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/os1_linux.go#L172)* 이다. 리눅스 시스템에서 단 한가지 하는 일이 있는데 그것은 시스템내 CPU 숫자를 가지고 있는 ncpu 변수를 한 syscall을 통해 초기화하는 것이다.

# *runtime.schedinit* 함수의 내부

시작 순서에서 다음 함수인 *[runtime.schedinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/proc1.go#L40)* 는 더 흥미롭다. 현재의 고루틴을 얻는 것으로 시작하는데, 사실 이 것은 *[g](https://github.com/golang/go/blob/go1.5.1/src/runtime/runtime2.go#L211)* 구조의 포인터이다. 이미 이 포인터가 어떻게 저장되는 지는 TLS 구현을 논할 때 얘기한 바있다. 다음은 *[runtime.raceinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/race1.go#L110)* 를 호출한다. runtime.raceinit에 대한 토론은 넘어가겠다. 왜냐하면 이 함수는 race 조건이 활성화되지 않은 경우는 보통 호출되지 않기 때문이다. 그 다음에는 몇몇 다른 초기화 함수들이 호출된다.

하나씩 살펴보자.

# traceback 초기화


The *[runtime.tracebackinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/traceback.go#L58)* function is responsible for initializing traceback. Traceback is a stack of functions that were called before we got to the current point of execution. For example, we can see it each time a panic occurs. Traceback is generated by a given program counter by calling a function called *[runtime.gentraceback](https://github.com/golang/go/blob/go1.5.1/src/runtime/traceback.go#L120)*. For this function to work, we need to know the addresses of some built-in functions (e.g., because we don’t want them to be included into the traceback). *runtime.tracebackinit* is responsible for initializing these addresses.



# Verifying linker symbols

Linker symbols are data emitted by the linker to the executable and the object file. Most of these symbols’ contents have been discussed in [Golang Internals, Part 3: The Linker, Object Files, and Relocations](http://blog.altoros.com/golang-internals-part-3-the-linker-and-object-files.html). In the runtime package, linker symbols are mapped to the *[moduledata](https://github.com/golang/go/blob/go1.5.1/src/runtime/symtab.go#L37)* struct. The *[runtime.moduledataverify](https://github.com/golang/go/blob/go1.5.1/src/runtime/symtab.go#L95)* function is responsible for performing some checks against this data and verifying that it has the correct structure and is not corrupted.



# Initializing the stack pool

To understand the next initialization step, you need a bit of knowledge about how stack growth is implemented in Go. When a new goroutine is created, a small fixed-size stack is allocated for it. When the stack reaches some threshold, its size is doubled and the stack is copied to another location.

There is still a lot of detail on how reaching this threshold is determined and how Go adjusts pointers in the stack. I have already touched on some of these things in my previous blog posts, when talking about the *stackguard0* field and function metadata. You can also find a lot of useful information on this subject in [this document](https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub).

Go uses a stack pool to cache currently unused stacks. The stack pool is an array initialized in the *[runtime.stackinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/stack1.go#L54)* function. Each item in this array contains a linked list of stacks of the same size.

Another variable initialized at this stage is *runtime.stackFreeQueue*. It also contains a linked list of stacks, but these are added to the list during garbage collection and are cleared after it is finished. Note that only 2 KB, 4 KB, 8 KB, and 16 KB stacks are cached. Larger ones are allocated directly.



# Initializing the memory allocator

The process of memory allocation is described in this [source code comment](https://github.com/golang/go/blob/go1.5.1/src/runtime/malloc.go#L5). I strongly encourage you to read it, if you want to understand how memory allocation works. This topic will be covered in more detail in one of the upcoming posts. Initialization of the memory allocator is located in the *[runtime.mallocinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/malloc.go#L216)* function, so let’s take a closer look at it.



# Initializing size classes

The first thing we can see here is that *runtime.mallocinit* is calling another function—*[initSizes](https://github.com/golang/go/blob/go1.5.1/src/runtime/msize.go#L66)*, which is responsible for calculating size classes. But what size does a class have? When allocating a small object (less than 32 KB), the Go runtime first rounds its size up to a pre-defined class size. So the allocated block of memory can only have one of the predefined sizes that is usually larger than what is required for the object itself. This leads to a small memory wastage, but it enables you to easily re-use allocated memory blocks for different objects.

The *initSizes* function is responsible for calculating these classes. At the top of this function, we can see the following code:

>```
01     align := 8
02     for size := align; size <= _MaxSmallSize; size += align {<commit_msg>Bfr initializing stack pool<commit_after>+++

title = "Golang의 내부, 6부: 부트스트래핑과 메모리 할당자"
draft = true
date = "2016-09-20T16:20:29-04:00"

tags = ["Golang", "Internals", "runtime", "bootstrap", "memory", "allocator"]
categories = ["번역", "핵킹"]
series = ["Golang  Internals"]
authors = ["<NAME>"]

toc = true

+++

이 포스트는 Golang 내부 시리즈의 연속이다. Go 런타임을 자세히 이해하는데 열쇠와 같은 부트스트랩 과정을 살펴볼 것이다. 이번에는 시작하는 순서의 두번째 부분을 섭렵해서 어떻게 인수들이 초기화되고, 어떤 함수들이 호출되는지 등을 배우겠다.

# 시작하는 순서

지난 번에 얘기하다가 만 *runtime.rt0_go* 함수를 다시 다루어야 겠다. 아직 이 함수에서 살펴보지 않은 부분이 여전히 있다.

>```
01 CLD                         // convention is D is always left cleared
02 CALL    runtime·check(SB)
03
04 MOVL    16(SP), AX          // copy argc
05 MOVL    AX, 0(SP)
06 MOVQ    24(SP), AX          // copy argv
07 MOVQ    AX, 8(SP)
08 CALL    runtime·args(SB)
09 CALL    runtime·osinit(SB)
10 CALL    runtime·schedinit(SB)
```

첫번째 명령 (CLD)는 *FLAGS* 레지스터의 [direction](https://en.wikipedia.org/wiki/Direction_flag) 프래그를 지운다. 이 플래그는 문자열 처리 방향에 영향을 준다.

다음 함수는 *runtime.check* 함수를 호출하는데, 그 또한 이 문서의 목적에 비추어 그리 중요하지는 않다. 런타임은 모든 내장 타입의 인스턴스들을 만들고, 타입의 크기와 파라미터들을 확인하는 정도의 일을 한다. 그리고 만약 작업중 문제가 생기면 *panic* 한다. [function](https://github.com/golang/go/blob/go1.5.1/src/runtime/runtime1.go#L136)를 통해 쉽게 알아볼 수 있다.

# 인수 분석하기

그 다음 함수 [runtime.Args](https://github.com/golang/go/blob/go1.5.1/src/runtime/runtime1.go#L48) 는 좀 더 흥미롭다. The next function, 리눅스 시스템에서 (*argc* 와 *argv*) 인수를 정적 변수속에 저장하는 것말고도 이 함수는 ELF 보조 벡터를 분석하며 *syscall* 주소를 초기화한다.

설명이 좀 더 필요하겠다. 운영체계가 프로그램을 메모리에 올릴때, 그 프로그램의 초기 스택을 미리 정해진 포맷의 어떤 데이타로 초기화한다. 스택의 꼭대기에는 환경 변수들의 포인터인 인수들이 깔린다. 스택의 바닥에는 "ELF 보조 벡터"를 볼 수 인데, 실제로 이 것은 어떤 유용한 정보를 담고 있는 기록의 배열들이다. 예를 들면, 프로그램 헤더의 수와 크기들이다. 여기 이 [문서](http://articles.manugarg.com/aboutelfauxiliaryvectors)를 통해 ELF 보조 벡터 포맷에 대해 좀 자세히 알아 보라.

*runtime.Args* 함수는 벡터를 파싱하는 책임이 있다. 런타임은 벡터에 담고 있는 모든 정보들 중에 단지 *startupRandomData* 만을 사용하는데, 주로 헤싱 함수(hashing functions)들과 어떤 syscall들의 위치를 가리키는 포인터들을 초기화하는데 주로 사용된다. 다음에 나오는 변수들을 초기화한다:

>```
1 __vdso_time_sym
2 __vdso_gettimeofday_sym
3 __vdso_clock_gettime_sym
```

이 것들은 여러 함수들내 현재 시간을 획득하는데 사용된다. 이 모든 변수들은 기본값을 가진다. 이렇게 함으로써 Golang에서 상응하는 함수들을 호출하기 위해 *[vsyscall](http://www.ukuug.org/events/linux2001/papers/html/AArcangeli-vsyscalls.html)* 메카니즘의 사용이 허용된다.

# *runtime.osinit* 함수의 내부

시작 순서에서 그 다음으로 호출되는 함수는 *[runtime.osinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/os1_linux.go#L172)* 이다. 리눅스 시스템에서 단 한가지 하는 일이 있는데 그것은 시스템내 CPU 숫자를 가지고 있는 ncpu 변수를 한 syscall을 통해 초기화하는 것이다.

# *runtime.schedinit* 함수의 내부

시작 순서에서 다음 함수인 *[runtime.schedinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/proc1.go#L40)* 는 더 흥미롭다. 현재의 고루틴을 얻는 것으로 시작하는데, 사실 이 것은 *[g](https://github.com/golang/go/blob/go1.5.1/src/runtime/runtime2.go#L211)* 구조의 포인터이다. 이미 이 포인터가 어떻게 저장되는 지는 TLS 구현을 논할 때 얘기한 바있다. 다음은 *[runtime.raceinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/race1.go#L110)* 를 호출한다. runtime.raceinit에 대한 토론은 넘어가겠다. 왜냐하면 이 함수는 race 조건이 활성화되지 않은 경우는 보통 호출되지 않기 때문이다. 그 다음에는 몇몇 다른 초기화 함수들이 호출된다.

하나씩 살펴보자.

# traceback 초기화

*[runtime.tracebackinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/traceback.go#L58)* 함수는 traceback을 초기화하는 일을 한다. Traceback이라는 것은 현재의 실행지점에 오기까지 호출된 함수들의 스택을 말한다. 예를 들어, panic이 일어날 때마다 볼 수 있다. Traceback은 주어진 프로그램 카운터로 *[runtime.gentraceback](https://github.com/golang/go/blob/go1.5.1/src/runtime/traceback.go#L120)* 함수를 호출함으로써 얻어진다. 이 함수가 동작하려면, 몇몇 내장함수들의 주소를 알 필요가 있는데, 그 이유는 traceback에 이 함수들이 나타나는 것을 원하지 않기 때문이다. 이 주소들은 *runtime.tracebackinit* 을 통해 초기화된다.

# 링커 심볼들 검사하기

링커 심볼들은 링커가 실행파일과 오브젝트 파일에 뿜어내는 데이터를 말한다. 대부분의 내용은 [Golang의 내부, 3부: 링커, 오브젝트 파일, 그리고 재배치](/post/golang-internals/part3/)에서 논했다. runtime 패키지에서 링커 심볼들은 *[moduledata](https://github.com/golang/go/blob/go1.5.1/src/runtime/symtab.go#L37)* 구조체에 맵핑되어 있다. *[runtime.moduledataverify](https://github.com/golang/go/blob/go1.5.1/src/runtime/symtab.go#L95)* 함수를 통해 이 데이타에 대한 확인이 이루어지고 오염여부와 구조가 바른지를 검사한다.

# 스택 풀 초기화하기


To understand the next initialization step, you need a bit of knowledge about how stack growth is implemented in Go. When a new goroutine is created, a small fixed-size stack is allocated for it. When the stack reaches some threshold, its size is doubled and the stack is copied to another location.

There is still a lot of detail on how reaching this threshold is determined and how Go adjusts pointers in the stack. I have already touched on some of these things in my previous blog posts, when talking about the *stackguard0* field and function metadata. You can also find a lot of useful information on this subject in [this document](https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub).

Go uses a stack pool to cache currently unused stacks. The stack pool is an array initialized in the *[runtime.stackinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/stack1.go#L54)* function. Each item in this array contains a linked list of stacks of the same size.

Another variable initialized at this stage is *runtime.stackFreeQueue*. It also contains a linked list of stacks, but these are added to the list during garbage collection and are cleared after it is finished. Note that only 2 KB, 4 KB, 8 KB, and 16 KB stacks are cached. Larger ones are allocated directly.



# Initializing the memory allocator

The process of memory allocation is described in this [source code comment](https://github.com/golang/go/blob/go1.5.1/src/runtime/malloc.go#L5). I strongly encourage you to read it, if you want to understand how memory allocation works. This topic will be covered in more detail in one of the upcoming posts. Initialization of the memory allocator is located in the *[runtime.mallocinit](https://github.com/golang/go/blob/go1.5.1/src/runtime/malloc.go#L216)* function, so let’s take a closer look at it.



# Initializing size classes

The first thing we can see here is that *runtime.mallocinit* is calling another function—*[initSizes](https://github.com/golang/go/blob/go1.5.1/src/runtime/msize.go#L66)*, which is responsible for calculating size classes. But what size does a class have? When allocating a small object (less than 32 KB), the Go runtime first rounds its size up to a pre-defined class size. So the allocated block of memory can only have one of the predefined sizes that is usually larger than what is required for the object itself. This leads to a small memory wastage, but it enables you to easily re-use allocated memory blocks for different objects.

The *initSizes* function is responsible for calculating these classes. At the top of this function, we can see the following code:

>```
01     align := 8
02     for size := align; size <= _MaxSmallSize; size += align {
{'fraction_non_alphanumeric': 0.09416331224841863, 'fraction_numerical': 0.022857964347326048, 'mean_word_length': 3.8783309957924264, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 32, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6704282', 'n_tokens_mistral': 6962, 'n_tokens_neox': 7873, 'n_words': 1917}