Real World HTTPに引き続き今年2冊目の書籍が出版されます。ASCII.jpの連載をまとめて、加筆修正したものになります。最初は、連載をつなげて、はじめに、と次回予告を書き換えてつなげればOKという感じからスタートしましたが、章構成を書き換えたり、書き始めのときに「そのうち連載で触れる予定です」を「◯◯章で説明します」に書き換えたり、せっかくだから内容を追記しようとか考え始めたり、レビューアのメンバーが「これは並列・並行の結果であって原則とは違う」みたいな細かい定義のところまで指摘してくれて説明を大幅に書き換えたり、蓋を開けたら前から最後までかなり時間をかけて修正することになりました。せっかく転職で有給消化が一ヶ月あったのですが、この原稿の修正で一ヶ月がするっと溶けました。
11/16追記 Amazonでも販売がはじまっています!
大きな修正ポイントは次の通りです:
- 連載の分量の関係で、同じテーマを何回かに分けていたのをまとめた
- 並列処理の記事からチャネル関連を抜き出して、加筆を大幅に加えて独立した章に
- タイマーやクロックについての章を追加
- セキュリティについての章を追加
- Go 1.9で追加されたもろもろを追加
- 締切の一週間前に、連載で紹介したIntelliJ IDEAのCommunity版で、Goプラグインがダウンロードできなくなったというレビューの報告を受けて急遽Visual Studio Codeに差し替え
- 高尾編集長の徹底的な修正で日本語が大幅に改善した
- 雪だるまの絵が、妻の書いたフクロウに変わった
これ以外にも数多くの修正を加えています。紙の書籍にするにあたって、数多くの知人に協力してもらって、レビューしていただいたのですが、1ページにつき1件というペースに近い300件近い意見をいただきました。これは連載をしていたので、一度細かい所まで見て修正したので些細なミスはあまりないはず、というところからの出発でしたが、遠慮なく徹底的にみてくださったレビューアのお陰で、かなり改善されています。ASCII.jpの連載終了から440コミットありました。
元は、LLでウェブアプリケーションフレームワークを普段作っているけど、低レイヤーを学ぶチャンスがなかなかない人に向けて本を書くといいのではないか、とラムダノートの鹿野さんと雑談したところから始まった連載でした。Real World HTTPも通信の低レイヤーではありましたが、こちらはOSの方面の低レイヤーになります。C言語を使った、読む側にもそれなりの知識が必要とされる本はいくつもありましたが(とても良い本です!)、本書はそういう既存の本よりも読みやすい本が提供できたのではないか、と思っています。アプリケーションレイヤーの応用例もイメージでき、古い本では紹介されているけど、現実的にあまり使われていないものは省き、逆に最近のトピックに触れるといったことで違いを出しました。
販売計画
ラムダノートさんは、取次を使っておらず、販売チャンネルも厳選されています。その分でお値段の割に濃い内容をお届けできているわけですが、直販サイトでは、10/19(木)午前中から予約開始予定です。実際にお手元に届くのは25日(水)ないし26日(木)から順次発送予定です。店頭販売は、10/22(日)の技術書典3での初売りを皮切りに、いくつかのラムダノートの書籍を扱う大手の書店で順次並び始めます。最速で入手したい方は、技術書典がチャンスです。近々、Amazonでも販売される予定です。
IT系でいうと、海外に比べて日本が遅れて云々、という話題が上がることもありますが、日本語で手に入る情報の質や量はかなり高まっていると思います。Goの書籍として見ても、そのあたりの海外の書籍には負けないものになったと思います。技術書展で出てくる本もかなり面白そうな本が多く、日本の底力を感じます。日本語という言語の壁はネガティブにしか見られないことが多いのですが、逆に日本の中ですごいことをやって世界をあっと言わせるチャンスでもあるはずです。
本書の他にも、定理証明の面白そうな本も出ますし、近々、僕の知人のグループx2がオライリーからそれぞれ電子書籍が出ます。こちらもよろしくお願いします。
前者はSphinxの入門書の改訂版で、使われることが増えてきたMarkdownと併用する方法なども書かれています。後者は以前の技術書典に出てた本のパワーアップ版で、各種手法の使い分けや、その名の通り既存のサービスに組み込んでいく方法などが説明されています。なお、本書も含めて、これらの本はSphinxで書かれています。これらの本はre:view変換して書かれました。本書は鹿野さんが作った拡張機能などの魔改造Sphinxで書かれており、僕も知らないなぞテクニックが駆使されています。そのうちこれの解説記事とかが公開されるのを期待。
こんなに知人同士の執筆時期がかぶると、お互いにレビューに参加するのも難しかったり、結構大変でした。みなさんお疲れ様でした。
目次
最後に目次をざっと紹介します。
はじめに 1. Go言語で覗くシステムプログラミングの世界 1.1. システムプログラミングとは 1.1.1. OSの機能について 1.2. Go言語 1.3. Go言語のインストールと準備 1.3.1. Visual Studio CodeでGoが使えるようにする 1.3.2. はじめてのGoプロジェクト 1.4. デバッガーを使ってシステムコールを「見る」 1.4.1. Visual Studio Codeでデバッガーを有効にする 1.4.2. デバッガーを使って"Hello World!"の裏側を覗く 1.5. 本章のまとめと次章予告 1.6. 問題 2. 低レベルアクセスへの入口1:io.Writer 2.1. io.WriterはOSが持つファイルのシステムコールの相似形 2.2. Go言語のインタフェース 2.3. io.Writerは「インタフェース」 2.4. io.Writerを使う構造体の例 2.4.1. ファイル出力 2.4.2. 画面出力 2.4.3. 書かれた内容を記憶しておくバッファ 2.4.4. インターネットアクセスの送信 2.4.5. io.Writerのデコレータ 2.4.6. フォーマットしてデータをio.Writerに書き出す 2.5. インタフェースの実装状況・利用状況を調べる 2.6. 低レベルの機能を組み合わせて入出力APIを作る 2.7. 本章のまとめと次章予告 2.8. 問題 3. 低レベルアクセスへの入口2:io.Reader 3.1. io.Reader 3.2. io.Readerの補助関数 3.2.1. 読み込みの補助関数 3.2.2. コピーの補助関数 3.3. 入出力に関するio.Writerとio.Reader以外のインタフェース 3.3.1. 入出力関連の複合インタフェース 3.3.2. 入出力関連インタフェースのキャスト 3.4. io.Readerを満たす構造体で、よく使うもの 3.4.1. 標準入力 3.4.2. ファイル入力 3.4.3. ネットワーク通信の読み込み 3.4.4. メモリに蓄えた内容をio.Readerとして読み出すバッファ 3.5. バイナリ解析用のio.Reader関連機能 3.5.1. 必要な部位を切り出すio.LimitReader/io.SectionReader 3.5.2. エンディアン変換 3.5.3. PNGファイルを分析してみる 3.5.4. PNG画像に秘密のテキストを入れてみる 3.6. テキスト解析用のio.Reader関連機能 3.6.1. 改行/単語で区切る 3.6.2. データ型を指定して解析 3.6.3. その他の形式の決まったフォーマットの文字列の解析 3.7. io.Reader/io.Writerでストリームを自由に操る 3.8. 本章のまとめと次章予告 3.9. 問題 4. 低レベルアクセスへの入口3:チャネル 4.1. goroutine 4.2. チャネル 4.2.1. チャネルの使用方法 4.2.2. チャネルの3つの状態 4.2.3. for文 4.2.4. チャネルとselect文 4.2.5. コンテキスト 4.3. システムからの通知 4.3.1. OSからのシグナルをチャネルで受け取る例 4.4. 本章のまとめと次章予告 4.5. 問題 5. システムコール 5.1. システムコールとは何か? 5.1.1. CPUの動作モード 5.1.2. システムコールでモードの壁を越える 5.1.3. システムコールがないとどうなるか? 5.2. Go言語におけるシステムコールの実装 5.2.1. 各OSにおけるシステムコールの実装を見てみよう 5.2.2. macOSにおけるシステムコールの実装(syscall.Open) 5.2.3. LinuxにおけるGoのシステムコール実装(syscall.Open) 5.2.4. WindowsにおけるGoのシステムコール実装(syscall.Open) 5.3. POSIXとC言語の標準規格 5.4. システムコールより内側の世界 5.4.1. システムコール関数はSYSCALL_DEFINExマクロで定義される 5.4.2. CPUが関数を実行できるようにするまで 5.5. Go言語のシステムコールとPOSIX 5.6. システムコールのモニタリング 5.6.1. Linux 5.6.2. FreeBSD 5.6.3. macOS 5.6.4. Windows 5.7. エラー処理 5.8. 本章のまとめと次章予告 5.9. 問題 6. TCPソケットとHTTPの実装 6.1. プロトコルとレイヤー 6.2. HTTPとその上のプロトコルたち 6.2.1. HTTPの基本 6.2.2. RPC 6.2.3. REST 6.2.4. GraphQL 6.3. ソケットとは 6.4. ソケット通信の基本構造 6.5. Go言語でHTTPサーバーを実装する 6.5.1. TCPソケットを使ったHTTPサーバー 6.5.2. TCPソケットを使ったHTTPクライアント 6.6. 速度改善(1): HTTP/1.1のKeep-Aliveに対応させる 6.6.1. Keep-Alive対応のHTTPサーバー 6.6.2. Keep-Alive対応のHTTPクライアント 6.7. 速度改善(2): 圧縮 6.7.1. gzip圧縮に対応したクライアント 6.7.2. gzip圧縮に対応したサーバー 6.8. 速度改善(3): チャンク形式のボディー送信 6.8.1. チャンク形式のサーバーの実装 6.8.2. チャンク形式のクライアントの実装 6.9. 速度改善(4): パイプライニング 6.9.1. パイプライニングのサーバー実装 6.9.2. パイプライニングのクライアント実装 6.9.3. パイプライニングとHTTP/2 6.10. 本章のまとめと次章予告 7. UDPソケットを使ったマルチキャスト通信 7.1. UDPとTCPの用途の違い 7.1.1. UDPが使われる場面は昔と今で変わってきている 7.2. UDPとTCPの処理の流れの違い 7.2.1. サーバー側の実装例 7.2.2. クライアント側の実装例 7.3. UDPのマルチキャストの実装例 7.3.1. サーバー側の実装例 7.3.2. クライアント側の実装例 7.3.3. 実行例 7.4. UDPとTCPの機能面の違い 7.4.1. TCPには再送処理とフロー処理がある 7.4.2. UDPではフレームサイズも気にしよう 7.4.3. 輻輳制御とフェアネス 7.5. 本章のまとめと次章予告 8. 高速なUnixドメインソケット 8.1. Unixドメインソケットの基本 8.2. Unixドメインソケットの使い方 8.2.1. ストリーム型のUnixドメインソケット 8.2.2. Unixドメインソケット版のHTTPサーバーを作る 8.2.3. Unixドメインソケット版のHTTPクライアントを作る 8.2.4. データグラム型のUnixドメインソケット 8.3. Windowsの名前付きパイプ 8.4. UnixドメインソケットとTCPのベンチマーク 8.5. ソケットのシステムコール小話 8.6. 本章のまとめと次章予告 9. ファイルシステムの基礎とGo言語の標準パッケージ 9.1. ファイルシステムの基礎 9.1.1. 複雑なファイルシステムとVFS 9.2. ファイル/ディレクトリを扱うGo言語の関数たち 9.2.1. ファイル作成/読み込み 9.2.2. ディレクトリの作成 9.2.3. ファイルの削除/移動/リネーム 9.2.4. ファイルの属性の取得 9.2.5. ファイルの存在チェック 9.2.6. OS固有のファイル属性を取得する 9.2.7. ファイルの同一性チェック 9.2.8. ファイルの属性の設定 9.2.9. リンク 9.2.10. ディレクトリ情報の取得 9.3. OS内部におけるファイル操作の高速化 9.4. ファイルパスとマルチプラットフォーム 9.4.1. Go言語でパス表記を扱うパッケージ 9.5. path/filepathパッケージの関数たち 9.5.1. ディレクトリのパスとファイル名とを連結する 9.5.2. パスを分割する 9.5.3. 複数のパスからなる文字列を分解する 9.5.4. パスのクリーン化 9.5.5. 環境変数などの展開 9.5.6. ファイル名のパターンにマッチするファイルの抽出 9.5.7. ディレクトリのトラバース 9.6. 本章のまとめと次章予告 10. ファイルシステムの最深部を扱うGo言語の関数 10.1. ファイルの変更監視(syscall.Inotify*) 10.2. ファイルのロック(syscall.Flock()) 10.2.1. syscall.Flock()によるPOSIX系OSでのファイルロック 10.2.2. LockFileEx()によるWindowsでのファイルロック 10.2.3. FileLock構造体の使い方 10.3. ファイルのメモリへのマッピング(syscall.Mmap()) 10.3.1. mmapの実行速度 10.4. 同期・非同期/ブロッキング・ノンブロッキング 10.4.1. 同期・ブロッキング処理 10.4.2. 同期・ノンブロッキング処理 10.4.3. 非同期・ブロッキング処理 10.4.4. 非同期・ノンブロッキング処理 10.4.5. Go言語でさまざまなI/Oモデルを扱う手法 10.5. select属のシステムコールによるI/O多重化 10.6. 本章のまとめと次章予告 11. プロセスの役割とGo言語による操作 11.1. プロセスに含まれるもの(Go言語視点) 11.1.1. プロセスID 11.1.2. プロセスグループ・セッショングループ 11.1.3. ユーザーIDとグループID 11.1.4. 実効ユーザーIDと実効グループID 11.1.5. 作業フォルダ 11.1.6. ファイルディスクリプタ 11.2. プロセスの入出力 11.2.1. コマンドライン引数 11.2.2. 環境変数 11.2.3. 終了コード 11.3. プロセスの名前や資源情報の取得 11.4. OSから見たプロセス 11.5. exec.Cmdによるプロセスの起動 11.5.1. リアルタイムな入出力 11.6. os.Processによるプロセスの起動・操作 11.7. プロセスに関する便利なGo言語のライブラリ 11.7.1. プロセスの出力に色づけをする 11.7.2. 外部プロセスに対して自分が擬似端末だと詐称する 11.8. Go言語では触れることのない世界 11.8.1. fork()/exec() 11.8.2. フォークと並行処理 11.8.3. デーモン化 11.9. 子プロセスの内部実装 11.10. 本章のまとめと次章予告 12. シグナルによるプロセス間の通信 12.1. シグナルのライフサイクル 12.2. シグナルの種類 12.2.1. ハンドルできないシグナル 12.2.2. サーバーアプリケーションでハンドルするシグナル 12.2.3. コンソールアプリケーションでハンドルするシグナル 12.2.4. たまに使うかもしれない、その他のシグナル 12.3. Go言語におけるシグナルの種類 12.4. シグナルのハンドラを書く 12.4.1. シグナルを無視する 12.4.2. シグナルのハンドラをデフォルトに戻す 12.4.3. シグナルの送付を停止させる 12.4.4. シグナルを他のプロセスに送る 12.5. シグナルの応用例(Server::Starter) 12.5.1. Server::Starterの使い方 12.5.2. Server::Starterが子プロセスを再起動する仕組み 12.5.3. Server::Starter対応のサーバーの実装例 12.6. Go言語ランタイムにおけるシグナルの内部実装 12.7. Windowsとシグナル 12.8. 本章のまとめと次章予告 13. Go言語と並列処理 13.1. 複数の仕事を同時に行うとは? 13.2. Go言語の並列処理のための道具 13.2.1. goroutineと情報共有 13.3. スレッドとgoroutineの違い 13.4. GoのランタイムはミニOS 13.5. runtimeパッケージのgoroutine関連の機能 13.5.1. runtime.LockOSThread()/runtime.UnlockOSThread() 13.5.2. runtime.Gosched() 13.5.3. runtime.GOMAXPROCS(n)/runtime.NumCPU() 13.6. Race Detector 13.7. syncパッケージ 13.7.1. sync.Mutex/sync.RWMutex 13.7.2. sync.WaitGroup 13.7.3. sync.Once 13.7.4. sync.Cond 13.7.5. sync.Pool 13.7.6. sync.Map 13.8. sync/atomicパッケージ 13.9. 本章のまとめと次章予告 14. 並行・並列処理の手法と設計のパターン 14.1. 並列・並行処理の手法のパターン 14.1.1. マルチプロセス 14.1.2. イベント駆動 14.1.3. マルチスレッド 14.1.4. ストリーミング・プロセッシング 14.2. Goにおける並行・並列処理のパターン集 14.2.1. 同期→非同期化 14.2.2. 非同期→同期化 14.2.3. タスク生成と処理を分ける:Producer-Consumer 14.2.4. 開始した順で処理する:チャネルのチャネル 14.2.5. タスク処理が詰まったら待機:バックプレッシャー 14.2.6. 並列forループ 14.2.7. 決まった数のgoroutineでタスクを消化:ワーカープール 14.2.8. 依存関係のあるタスクを表現する:Future/Promise 14.2.9. イベントの流れを定義する:ReactiveX 14.2.10. 自立した複数のシステムで協調動作:アクターモデル 14.3. 本章のまとめと次章予告 15. Go言語のメモリ管理 15.1. メモリ確保の旅 15.1.1. 物理メモリと仮想メモリ 15.1.2. OSカーネルがプロセスのメモリを確保するまで 15.1.3. 実行時の動的なメモリ確保:ヒープ 15.1.4. 実行時の動的なメモリ確保:スタック 15.1.5. ユーザーコードでメモリを使う 15.2. Go言語の配列 15.3. Go言語のスライス 15.3.1. スライスの作成方法 15.3.2. スライスのメモリ確保 15.4. ガベージコレクタ 15.5. 本章のまとめと次章予告 16. 時間と時刻 16.1. OSのタイマー/カウンターの仕組み 16.2. さまざまな時間 16.3. 時間に関するシステムコール 16.3.1. runtime.now() 16.3.2. runtime.semasleep() 16.4. Go言語で時間を扱う 16.4.1. 時間と時刻 16.4.2. スリープ 16.4.3. チャネルを使ったタイマー 16.5. 時刻のフォーマット 16.6. 本章のまとめと次章予告 17. Go言語とコンテナ 17.1. 仮想化 17.1.1. 仮想化は低レイヤーの技術の組み合わせ 17.2. コンテナ 17.3. libcontainerでコンテナを自作する 17.3.1. コンテナのブートに必要な下準備 17.4. 本章のまとめ Appendix 1. セキュリティ関連のOSの機能とssh 1.1. 乱数 1.1.1. 2種類の乱数生成アルゴリズム 1.1.2. 乱数のシステムコール 1.1.3. 乱数の使い方 1 .2. TLS(Transport Layer Security) 1.2.1. ルート証明書 1.2.2. ルート証明書の取得 1.3. ssh(Secure Shell) 1.3.1. sshの基本的な流れ 1.3.2. Goによるssh接続 1.3.3. scp(Secure Copy) 1.4. キーチェーン 1.4.1. キーチェーンとは Appendix 2. 参考文献 あとがき 謝辞