Slide 1

Slide 1 text

Goならわかる Linuxのメモリ管理 Jumpei Takiyasu @juntaki M3, Inc.

Slide 2

Slide 2 text

= &Me{ Name: "Jumpei Takiyasu", Company: "M3, Inc.", Web: "https://juntaki.com", } About me

Slide 3

Slide 3 text

x86_64のLinuxで実行すると、何が表示される? 実行環境: go 1.10 / Linux version 4.18.0-rc8 / Intel(R) Core(TM) i7-8650U、デバッガはmain.mainでbreak 1. hoge 2. 0x0 - 0x528000 のどこか 3. 0x528000 - 0x547000 [heap] のどこか 4. 0x7ffffffde000 - 0x7ffffffff000 [stack] のどこか 5. 2,3,4以外の領域のどこか 6. それ以外 func main() { a := "hoge" fmt.Println(&a) } (gdb) i proc mappings process 26627 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x483000 0x83000 0x0 /home/juntaki/memory/quiz/main 0x483000 0x514000 0x91000 0x83000 /home/juntaki/memory/quiz/main 0x514000 0x528000 0x14000 0x114000 /home/juntaki/memory/quiz/main 0x528000 0x547000 0x1f000 0x0 [heap] 0xc000000000 0xc000001000 0x1000 0x0 0xc41fff8000 0xc420100000 0x108000 0x0 0x7ffff7f5a000 0x7ffff7ffa000 0xa0000 0x0 0x7ffff7ffa000 0x7ffff7ffd000 0x3000 0x0 [vvar] 0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x0 [vdso] 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack] 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]

Slide 4

Slide 4 text

なあハム太郎!!!!お前もそう思うだろ!!???!!そうなのだ!!!!!まったくもってその通りなのだ!!!!!!! プログラムとは メモリ操作とIO

Slide 5

Slide 5 text

ユーザ空間での メモリ割り当て

Slide 6

Slide 6 text

エスケープ解析でヒープorスタックが決まる ヒープになっちゃうのはfmt.Println()にポインタを渡してるためです $ go build -gcflags '-m -N -l' alloc.go # command-line-arguments ./alloc.go:9:13: a escapes to heap ./alloc.go:8:10: new(int) escapes to heap ./alloc.go:9:13: main ... argument does not escape ./alloc.go:11:10: main new(int) does not escape func main() { a := new(int) // ヒープに取れる fmt.Println(a) b := new(int) // スタックに取れる *b = 0xdeadbeef }

Slide 7

Slide 7 text

ふつうのプロセスにおけるヒープとスタック 64bitの場合、0x00007fffffffffffがユーザスペースの上限。Intelの仕様のためこれ以上とっても意味ない ヒープ アロケーションが遅い GCが必要(free) スタック アロケーションがはやい 関数のreturnと同時に消滅 Program text Stack Heap 0x00007fffffffffff 0x0000000000000000

Slide 8

Slide 8 text

呼び出し規約とスタックの仕組み やきにく たべたい package main func main() { yakiniku() } //go:noinline func yakiniku() { tabetai(0xdeadbeef) } //go:noinline func tabetai(a int) { a++ }

Slide 9

Slide 9 text

関数を呼び出すだけの関数をみる 要するに$spが指していて書き込み可能なら別に決まった場所じゃなくてもよい mov %fs:0xfffffffffffffff8,%rcx cmp 0x10(%rcx),%rsp jbe 44c1b5 sub $0x10,%rsp mov %rbp,0x8(%rsp) lea 0x8(%rsp),%rbp mov $0xdeadbeef,%eax mov %rax,(%rsp) callq 44c1c0 mov 0x8(%rsp),%rbp add $0x10,%rsp retq callq 444570 jmp 44c180 スタックが足りなくなる場合は runtime.morestackで拡張 スタックに%rbpと、引数を積む callで、戻り番地が積まれる スタックを元に戻す caller’s rbp 引数 rbp 戻り番地 rsp

Slide 10

Slide 10 text

goroutineのスタックはヒープから取る main関数もgoroutineです。ユーザ空間でスケジューリングするので、スイッチングのコストが低い 関数呼び出し時に足りない場合は、コピーして拡張する [Five things that make Go fast | Dave Cheney](https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast)

Slide 11

Slide 11 text

ヒープはTCMallocで割り当てる この辺の仕掛けはglibc mallocも似たような感じだが、main arena相当もなく、コードはTCMallocのほうが簡潔 取得/解放コストを下げたいが、メモリは使いたくない ● OSからのメモリ確保はプールして回避 ● ロック競合と探索を避けて再利用する [Post-Mortem Heap Analysis: TCMalloc | Backtrace.io](https://backtrace.io/blog/backtrace/memory-allocator-tcmalloc/)

Slide 12

Slide 12 text

カーネルからの メモリ取得

Slide 13

Slide 13 text

mmap 「Goならわかる」なのでカーネルはさっぱりと vmaを追加

Slide 14

Slide 14 text

メモリアドレッシング メモリは本当に必要になるまで、割り当ててもらえないです ページフォルトのハンドラで割り当てる リニアアドレス 物理アドレス MMU ページ ページ page fault

Slide 15

Slide 15 text

ページフォルト時の動作 Buddyアロケータから、ページ(4KB)単位でメモリをとれる 要求されたアドレスが、vmaの範囲内か(など)をチェック cr3レジスタから連なるマッピングテーブルを作る

Slide 16

Slide 16 text

まとめ 自分が何を作ったのか、 もうちょっと知っておきませんか

Slide 17

Slide 17 text

(社内向け)Goならわかるシステムプログラミング輪読会 やろう 第1回は8/27(月) 第1章 Go言語で覗くシステムプログラミングの世界 第2章 低レベルアクセスへの入口 1:io.Writer 参加者募集!!! https://meetup.sugooi.net/meetup/bdt0rjlhqd8g00ja7v6g

Slide 18

Slide 18 text

参考資料 malloc動画はオススメ malloc動画 [The 67th Yokohama kernel reading party - YouTube] (https://www.youtube.com/watch?v=0-vWT-t0UHg) Goのメモリ管理(特にスタックのはなし) [Five things that make Go fast | Dave Cheney] (https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast) [Why is a Goroutine’s stack infinite ? | Dave Cheney] (https://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite) [Contiguous stacks] (https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub) main.mainより前には何が起きるか [Golang Internals, Part 6: Bootstrapping and Memory Allocator Initialization | Altoros] (https://blog.altoros.com/golang-internals-part-6-bootstrapping-and-memory-allocator-initialization.html)