Closed5
Goならわかるシステムプログラミングを読む
1章 Go言語で覗くシステムプログラミングの世界
本書と関係ないけどVSCodeでGoのデバッグができなくてめちゃはまった。
結果、シンボリックリンクに続くパスでGoを動かしていたのが原因。
以下のようにlunch.jsonでパスの置き換えを指定する必要がある。
lunch.json
{
// IntelliSense を使用して利用可能な属性を学べます。
// 既存の属性の説明をホバーして表示します。
// 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}",
// "stopOnEntry": true,
// "logOutput": "dap",
// "showLog": true,
"substitutePath": [{
"from": "/Users/yamanakajunichi/work/myapp/study/go-sys/",
"to": "/Users/yamanakajunichi/study/go-sys/"
}]
}
]
}
2章 低レベルアクセスへの入り口1
ファイルディスクリプタ
- オペレーティングシステムがファイルや他の入出力リソース(パイプなど)にアクセスするために使用する抽象的なインデックス。
- 主にUnix系のオペレーティングシステムで使われる
- ファイルディスクリプタは整数で表される
- 標準入力は0、標準出力は1、エラー出力は2
- ファイルをオープンすると利用可能なファイルディスクリプタ番号を割り当てる
- ファイルがクローズされると番号が解放される
- ファイル処理が適切にされないとファイルディスクリプタの枯渇(リソースリーク)
- ファイルディスクリプタはファイルじゃなくても乱数生成などにアクセスする際にも指定する
- このように擬似ファイルを作成し、ファイルディスクリプタ番号を割り当て、カーネルに対してシステムコールを実行する
- Go言語はそのようなファイルディスクリプタのような仕組みを模倣して実装しており、OSの差異が出ないようにしている
参考
3章 低レベルアクセスへの入り口2
- 読み込みはio.Readerインターフェース
- io.ReaderのRead()を使うことで読み込みはできるがバッファの確保しつつ何度もRead()を読み込んだりio.Readerだけでは実装が不便なのでヘルパー関数が用意されている
- io.ReadAll() 全部読み込む
- io.ReadFull() 指定のバイト数だけ読み込む。読み込めなかったらエラー
- io.ReadAtLeast() 最低バイト数指定しつつ、それを込めても読み込む
- io.Copy() ReaderからそのままWriterに渡すなど
- io.CopyBuffer() バッファを指定してコピー。io.Copy()は内部で32KBのバッファを持つ。
16章 Go言語のメモリ管理
- 登場人物
- 物理メモリ、仮想メモリ、メモリ管理ユニット(MMU)、ページテーブル、TLU
- プロセスは直接物理メモリを使用しているわけではなく仮想メモリを通して物理メモリを使用する
- 仮想メモリから物理メモリへのアクセスにはページテーブルを使用して実施される
- プロセスがメモリを確保するとページテーブルを用意し、細切れに配置されている物理メモリもフラットな仮想メモリとしてみることができる
- ページテーブルによる変換が挟まるので遅くなるように思える
- しかし、実際はTLUというキャッシュがあり高速に動作する
- CPUの仕事は仮想メモリとMMU、TLU
- ユーザーのメモリ空間には3つの領域がある
- 小さいアドレスからプログラム+静的データ、ヒープ、スタック
- メモリ確保のシステムコールはmmap
- アプリケーションが使うメモリ領域は大きくヒープとスタック
- 一般的にスタックの方がコストは低い。スタックへの割り当ては不要になれば上書きされる
- CやC++ではローカル変数の宣言などはスタックに割り当てられ、ヒープのメモリ領域に割り当てるにはmalloc()という関数を使う
- CやC++がヒープのメモリ管理を開発者自身が行うのに対して、Goの場合はスタックかヒープかはコンパイラが判断する
- このようなコンパイラが判断する仕組みにはGCが不可欠
- 関数内で宣言したローカル変数はスタックに割り当てられるが、その変数のポインタを戻り値にする場合、スタックフレームが閉じられるため戻り値だった変数にアクセスできなくなりエラーになる。これはCやC++の話
- Goの場合、エラーにはならずスタックに割り当てられていた変数のメモリ領域はヒープに移される。
- 一般的にヒープの方がGCを発生させるなどの理由からコストが高い
- そのため、パフォーマンス的なことを考えるとローカル変数のポインタを返すような関数は注意しなければならない
- これはGoの100Tipsにもあった
- ガベージコレクタにはいくつか種類があり言語によって違うがGoはマーク・アンド・スイープという方式を採用している
- マーク・アンド・スイープでは不要なメモリを削除する間にプログラム全体をストップさせる必要があり、これをストップ・ザ・ワールドという
- Goはバージョンが上がるごとにGCの改良がされ続けており、Go1.8ではほとんど影響が出ないほどに早くなっている
第17章 実行ファイルが起動するまで
- プログラムが実行されるには基本的には言語ランタイムが必要
- ランタイムはプログラムをもとにOSにシステムコールを実行し、メモリの確保やらなんやらを実行する
- ランタイムがなければそういったOSへの依頼の処理も自分で書かないといけない
- Cはそういったことができる言語なのでOSやカーネルに近い部分はCで書かれることが多い
- 実際はlibcという標準のランタイムライブラリをデフォルトでリンクしている
- この標準のランタイムを排除し独自のランタイム実装を使用することもでき、その場合には-nostdlibのようなオプションを使う
このスクラップは2024/03/05にクローズされました