渋川さんの書かれている Go ならわかるシステムプログラミングが楽しかったので、自分でも試して見た。
自分へのメモ。
Go 言語はシステムコールとの間に、C, C++ が挟まっていないので、低レベルまで Go で理解できるとのこと。これは楽しい。そして、デバッガを使ってインサイドのコードまで見てシステムコールまで追いかけて解説するというなんとも素晴らしい内容だった。是非本を買いたいところだが、物理本のようなので帰国後買って見る。
デバッガをコンフィグする
この連載はデバッガがポイントなので、用意して見る。私は IntelliJ を使っていないので、Visual Studio Code で同様の設定をする。これはとっても簡単。VS Code から下記の場所からデバッガの設定を作る。


テンプレートができたらそのままでOK.私のは名前だけ変えた。
launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Go Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${file}"
},
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${fileDirname}",
"env": {},
"args": [],
"showLog": true
}
]
}
あとは連載を楽しむのみ
実際にデバッガを動かして見ると、インターフェイスが変わっているが、重要なことに絞って理解すると、読み替えることができるだろう。基本は変わらない。
この第一回では、ファイルディスクリプタの解説をしている。Go の場合は、(f *File)io.Write(b []byte) (n int, err error)
のインターフェイスが同じなら、ポリモルフィズムみたいなことができる。
OS レベルでは、ファイルディスクリプタは、ファイルだけではなく、標準出力、ソケット、乱数、などファイルでないものもファイルと同じように扱えるようになっていますが、これは、Unix 系のOSのお話なので、Windows は異なるとのこと。だから、OSの違いを隠蔽できるように、ファイルディスクリプタを表す構造体を作って、それ経由で操作することで、OSの違いを吸収できるようになっている様子。
Write メソッドは例えば、構造体が、File ではなく、FD (ファイルディスクリプタ) に変わっている。
fd_unix.go
func (fd *FD) Write(p []byte) (int, error) {
if err := fd.writeLock(); err != nil {
return 0, err
}
defer fd.writeUnlock()
if err := fd.pd.prepareWrite(fd.isFile); err != nil {
return 0, err
}
var nn int
for {
max := len(p)
if fd.IsStream && max-nn > maxRW {
max = nn + maxRW
}
n, err := syscall.Write(fd.Sysfd, p[nn:max])
if n > 0 {
nn += n
}
if nn == len(p) {
return nn, err
}
if err == syscall.EAGAIN && fd.pd.pollable() {
if err = fd.pd.waitWrite(fd.isFile); err == nil {
continue
}
}
if err != nil {
return nn, err
}
if n == 0 {
return nn, io.ErrUnexpectedEOF
}
}
}
同ファイルの、FD の定義。ファイルディスクリプタは数字のようだが、それ以外にも情報が守るようになっている。
type FD struct {
// Lock sysfd and serialize access to Read and Write methods.
fdmu fdMutex
// System file descriptor. Immutable until Close.
Sysfd int
// I/O poller.
pd pollDesc
// Writev cache.
iovecs *[]syscall.Iovec
// Whether this is a streaming descriptor, as opposed to a
// packet-based descriptor like a UDP socket. Immutable.
IsStream bool
// Whether a zero byte read indicates EOF. This is false for a
// message based socket connection.
ZeroReadIsEOF bool
// Whether this is a file rather than a network socket.
isFile bool
}
integer value | file stream |
---|---|
0 | stdin |
1 | stdout |
2 | stderr |
このあと、今回の記事では、ざまざまな種類の io.Write
メソッドを実装していきます。ファイル、http バッファリングするもの、サーバー、gzip 圧縮など、デコレーターパターンを思い出します。バッファを持つものは、Flush()
関数があり、Go は、バッファリングなしでいきなりシステムコールという方式だが、パフォーマンス的に問題ないという感じ。
サンプルプログラム
さて、写経しながら面白く記事を読んだのですが、師匠の教えにしたがって、自分でも簡単な実装を作って見ました。io.Write
の実装をして見ました。細かくは説明しませんが、アルファベットの暗号化を行うライターを作って見ましたw
package main
import (
"fmt"
"io"
"os"
)
type asciiEncryptWriter struct {
w io.Writer
}
func (ew *asciiEncryptWriter) Write(p []byte) (int, error) {
var b [1]byte
r := 0
for n, _ := range p {
b[0] = p[n]
if ('a' <= b[0] && b[0] <= 'z') || ('A' <= b[0] && b[0] <= 'Z') {
if b[0] == 'z' {
b[0] = 'a'
}
if b[0] == 'Z' {
b[0] = 'A'
}
b[0]++
}
nw, err := ew.w.Write(b[:])
if err != nil {
return nw, err
}
}
return r, nil
}
// NewASCIIEncryptWriter provide super strong word encryption.
func NewASCIIEncryptWriter(w io.Writer) *asciiEncryptWriter {
return &asciiEncryptWriter{w}
}
func main() {
w := NewASCIIEncryptWriter(os.Stdout)
fmt.Fprintln(w, "Very Strong Encryption")
}
実行
Wfsz Tuspoh Fodszqujpo
こら楽しい連載やわ。プログラミングの楽しさを思い出した。今後の内容もとても楽しみ。