#!/usr/bin/env python # coding: utf-8 # # Go言語で潜る # # 2018/9/12 勉強会 # ## アジェンダ # # 1. 少し前の勉強会の復習 # 1. ファイルディスクリプタについて # 1. CPUの動作モードについて # 1. システムコールについて # 1. Go言語を使って潜ってみる # 1. 参考資料 # # # 少し前の勉強会の復習 # # - 7月末の中西さんの発表と関連 # ## ファイルディスクリプタとは # ### 別名 # - File Descriptor # - ファイル記述子 # - FD # ## ファイルディスクリプタとは # # - ファイルへの参照を抽象化したもの # - 標準入出力、ソケット、OSなどファイルじゃないものにも割り当てられ、ファイルと同じようにアクセスできる # - POSIXでは # - C言語のint型 # - プロセスを起動すると0,1,2の3つのファイルが標準で開かれ、停止時に閉じられる # - 0: 標準入力 # - 1: 標準出力 # - 2: 標準エラー出力 # - 以降は3から連番で割り当てられていく # # ![](https://www.computerhope.com/jargon/f/file-descriptor.jpg) # 【画像引用元】[What is file descriptor?](https://www.computerhope.com/jargon/f/file-descriptor.htm) # ### ファイルディスクリプタには上限がある # # - 上限を超えると「Too many open files」のようなエラーが出力される # - `ulimit -n`というコマンドで確認できる # - 上限を増やす方法もある # - `/etc/security/limits.conf`らへんをいじる # ## ファイルディスクリプタを確認する # # 1. 適当なコードを実行する(`sleep 365d > /dev/null &`とか) # 2. `ps`コマンドでそのプロセスのPIDを確認 # 3. `ls /proc//fd -o`でファイルディスクリプタの一覧とそれぞれがどこを指しているのかが見れる # ### ちょっとやってみる # # - 環境はCentOS # ## CPUの動作モードについて # ### リングプロテクション # # - 複数の特権レベルの階層構造を持ったコンピュータアーキテクチャの一種 # - 0がクラッシュすれば全部死ぬが、2がクラッシュしても3にしか影響がない(要出典) # - リング間には特別なゲートがあり、そこを通らないと外側から内側には入れない # - ハードウェアアクセスはリング1のでバイトドライバで行うが、リング3のプログラムが勝手にwebカメラをONにできない # - UNIX, Windowsともに0と3しか使用していない # # ![](https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Priv_rings.svg/300px-Priv_rings.svg.png) # # - 【画像引用元】[リングプロテクション - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%97%E3%83%AD%E3%83%86%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3) # # - 【参考】[カーネルモード・ユーザモード - マイクロソフト系技術情報 Wiki](https://techinfoofmicrosofttech.osscons.jp/index.php?%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E3%83%A2%E3%83%BC%E3%83%89%E3%83%BB%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%A2%E3%83%BC%E3%83%89) # ## 2つのモード # # - CPUによって異なる # # ### カーネルモード # # - 別名、「スーパーバイザーモード」、「特権モード」など # - 完全に無制限のCPU動作を許す # - 任意の命令を実行できる # - 入出力操作を開始できる # - 全メモリ空間にアクセス可能 # # ### ユーザーモード # # - 別名、「スレーブモード」など # - 一般的なアプリケーションが動作する # - 一部の命令が実行できない # - 入出力操作ができない # - メモリ空間の一部にアクセスできない # # # ## キーワード # - OOM Killer # # ### ユーザー空間 # - アプリケーションが使用するメモリ領域 # # ### カーネル空間 # - カーネルが使用するメモリ領域 # ## システムコールとは # ## システムコールとは # # - 特権モードでOSの機能を呼ぶこと # - 特権モードでのみ許されている機能をユーザーモードのアプリケーションから使える # - OSの備える関数 (API) のことを指すこともある # # > システムコールが発行されると、ユーザーモードからカーネルモードへのコンテキストスイッチが起こり、高い特権レベルで実行される(しすぱふぉ本p89) # # > ユーザーアプリケーションは抽象キー( = ファイル記述子 )をシステムコール経由でカーネルに渡し、カーネルはそのキーに対応するファイルにアクセスする(wiki) # # # - FreeBSDは530種類ほど(?) # - Linuxは317個ほど(?) # # # ### ないとどうなるのか # # - 計算はできるが以下のことができない # - 結果を画面に出力できない # - プロセス間通信が必要 # - 結果をファイルに保存できない # - ファイル入出力の機能が必要 # - 結果を共有メモリに書き出すことができない # - 共有メモリを作成する機能が必要 # - 結果を外部のウェブサービスなどに送信することもできない # - 外部のウェブサービスと通信する機能が必要 # # # ### システムコールの例 # # - open,close # - read,write # - fork,kill など # # # - http://man7.org/linux/man-pages/man2/syscalls.2.html (あとで読む) # - https://godoc.org/golang.org/x/sys/unix (あとで読む) # # 本題: Goで潜る # # - IDEの「Goto Definetion」などの機能で潜っていく # - 下の方はOSによって変わってくるけど、今回はMac # ## 適当な自作Goコード # # # ``` # func main() { # file, err := os.Create("test.txt") // ←ここに入る # if err != nil { # panic(err) # } # defer file.Close() # file.Write([]byte("system call example\n")) # } # ``` # # ### 簡単な説明 # # - `os.Create`で「test.txt」というファイルを作成 # - `file.Write`でバイト文字列を書き込み # - 関数を抜けるタイミングでfileをClose # ## /usr/local/opt/go/libexec/src/os/file.go # # ``` # // Create creates the named file with mode 0666 (before umask), truncating # // it if it already exists. If successful, methods on the returned # // File can be used for I/O; the associated file descriptor has mode # // O_RDWR. # // If there is an error, it will be of type *PathError. # func Create(name string) (*File, error) { # return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) // ←ここに入る # } # ``` # # ### 簡単な説明 # # - ここから先はGo言語の内部 # - `Create()`は`OpenFile()`を使いやすくする便利関数のようなもの # ## /usr/local/opt/go/libexec/src/os/file.go(同じファイル) # # ``` # // OpenFile is the generalized open call; most users will use Open # // or Create instead. It opens the named file with specified flag # // (O_RDONLY etc.) and perm (before umask), if applicable. If successful, # // methods on the returned File can be used for I/O. # // If there is an error, it will be of type *PathError. # func OpenFile(name string, flag int, perm FileMode) (*File, error) { # testlog.Open(name) # return openFileNolog(name, flag, perm) // ←ここに入る # } # ``` # # # ### コメントの翻訳 # # > OpenFile()関数は一般化されたオープンコールです。ほとんどのユーザーはOpen()またはCreate()を代わりに使用します。該当する場合には、指定されたフラグ(O_RDONLYなど)とperm(umaskの前)で名前付きファイルを開きます。成功した場合は、返されたFileのメソッドをI /Oに使用できます。エラーがある場合、* PathError型になります。 # # - よくわからん! # ## /usr/local/opt/go/libexec/src/os/file_unix.go # # # ``` # // openFileNolog is the Unix implementation of OpenFile. # func openFileNolog(name string, flag int, perm FileMode) (*File, error) { # setSticky := false # if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 { # if _, err := Stat(name); IsNotExist(err) { # setSticky = true # } # } # # var r int # for { # var e error # r, e = syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm)) // ←ここに入る # if e == nil { # break # } # .. # .. # .. # ``` # # - この関数はさっきの`OpenFile()`のUnix実装版 # - 色々書いているが`syscall.Open()`という関数を実行している部分がある # - システムコールだ! # ## /usr/local/opt/go/libexec/src/syscall/zsyscall_darwin_amd64.go # # ``` # // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT # # func Open(path string, mode int, perm uint32) (fd int, err error) { # var _p0 *byte # _p0, err = BytePtrFromString(path) # if err != nil { # return # } # r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm)) // ←ここに入る # fd = int(r0) # if e1 != 0 { # err = errnoErr(e1) # } # return # } # ``` # # - ファイル名の「Darwin」はApple製のUnix系のPOSIX準拠OSの名前 # - OSS # - Intel Macではないインテル機でも動作する # - コメントにはこのコードはGo言語の処理系に含まれるツールによって自動生成されているとある # - Go形式の文字列をC形式の文字列に変換してる # - システムコールに渡せるのが数値だけだから # - SYS_OPENは指示するための番号として各OSのヘッダーファイルなどから自動生成された定数 # ## /usr/local/opt/go/libexec/src/syscall/asm_darwin_amd64.s # # ``` # // func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno); # TEXT ·Syscall(SB),NOSPLIT,$0-56 # CALL runtime·entersyscall(SB) # MOVQ a1+8(FP), DI # MOVQ a2+16(FP), SI # MOVQ a3+24(FP), DX # MOVQ $0, R10 # MOVQ $0, R8 # MOVQ $0, R9 # MOVQ trap+0(FP), AX // syscall entry # ADDQ $0x2000000, AX # SYSCALL // ←ここでシステムコール! # JCC ok # MOVQ $-1, r1+32(FP) # MOVQ $0, r2+40(FP) # MOVQ AX, err+48(FP) # CALL runtime·exitsyscall(SB) # RET # ok: # MOVQ AX, r1+32(FP) # MOVQ DX, r2+40(FP) # MOVQ $0, err+48(FP) # CALL runtime·exitsyscall(SB) # RET # ``` # # - ついにアセンブリ言語! # - SYSCALLの中で`entersyscall()`関数と`exitsyscall()`関数が呼ばれる # - これらはスレッドを作成するための処理が必要になるまで行わないために使われる # - スレッド作成は重い処理だから # # 所感 # # - 抽象化の仕方絶妙すぎる # - 激強マンは知識量はもちろんのこと、実装力も強い # - Windowsは内部コードを公開していないので、言語実装者はMSの用意したAPIを使うしかなく、それって大変そう # - OS側を理解するためにはCPUのレジスタなどをざっくり知る必要があるなと思った # # 参考(編集中) # # - Goならわかるシステムプログラミング # - Real World HTTPと同じ著者 # # - 絵で見てわかる # - http://d.hatena.ne.jp/higher_tomorrow/20110426/1303830417 # # - おもろい # - http://www.atmarkit.co.jp/ait/articles/1112/13/news117.html # - システムコールを使いすぎると遅くなることをCで手を動かして確認するう #