Try   HackMD

コマンドプロセスを作成する際のファイルディスクリプタの操作 - シェルもどきをgoで自作する #8

おさらい

これまで

シェルもどき「oreshell」を自作している。
現状のoreshellはリダイレクションを行うとエラーになるのでリダイレクションできるように修正する。
前回はリダイレクションの「構文」を決めた。

今回から実装を進めていく。
まずはファイルディスクリプタ操作部分の実装から。

子プロセスを作成する際のファイルディスクリプタの操作

通常Linux/Unixの場合、子プロセスを作成する際にそのファイルディスクリプタテーブルは親プロセスのファイルディスクリプタテーブルの状態をそのまま引き継ぐ。

■子プロセス作成前

通常ファイルディスクリプタ#0は標準入力と、#1は標準出力と、#2は標準エラー出力と結びついている。
この状態で何かファイルを開こうとすると、未使用の一番若い番号(現状だと#3)を使う。

■子プロセス作成後

子プロセスを生成すると、子プロセスのファイルディスクリプタ#1,#2,#3が指し示す先は、親プロセスのファイルディスクリプタ#1,#2,#3が指し示す先と全く同じになる。

シェルがリダイレクションする場合のファイルディスクリプタの操作

シェルがリダイレクションする場合はどのようにファイルディスクリプタを操作すればよいか。
例として以下のコマンドを実行する場合の様子を示す。

$ cat hoge.txt > hage.txt

■ 1. 初期状態

おそらく、以降の処理を開始する前にこのファイルディスクリプタテーブルの状態をどこかに退避/保存していると思われる。(あとで元に戻す必要があるため)

■ 2. 子プロセス(catコマンド)を作成する前にリダイレクト先であるhage.txtを開く

ファイルディスクリプタテーブルで未使用の一番若い番号は#3であるためそれを使う。

■ 3. hage.txtファイルに結びついているファイルディスクリプタ#3を複製し、複製したものを#1に割り振る

ファイルディスクリプタの複製のシステムコール : dup2()

#1に割り振る理由は、いま対象としているコマンド

$ cat hoge.txt > hage.txt

のリダイレクション元が#1であるため。(「>」は「1>」と同義)

■ 4. hage.txtを開くときに使ったファイルディスクリプタ#3が不要になったので閉じる

■ 5.catコマンドプロセスを作成する(が、まだcatコマンドは実行しない)

先にも説明した通り、子プロセスを作成する際にそのファイルディスクリプタテーブルは親プロセスのファイルディスクリプタテーブルの状態をそのまま引き継ぐ。

■ 6. catコマンドを実行すると同時にシェルのファイルディスクリプタテーブルの状態を元に戻す

catコマンドプロセスは、自身のファイルディスクリプタテーブルで未使用の一番若い番号である#3を使ってファイルhoge.txtを読み込み、ファイルの内容をファイルディスクリプタ#1に向けて出力する。
リダイレクション指定がなければ#1は標準出力に結びついているが、現在は#1がhage.txtに結びついているため、hage.txtにファイルの内容を出力する。

シェルは子供を産み終わると(catコマンドプロセスを作成/実行開始すると)、自身のファイルディスクリプタテーブルの内容を退避/保存先から元(1.初期状態)に戻す。

goで実装する場合

Unix/Linuxでc/c++で実装する場合は、dup2()などのシステムコールを使って上記の通りファイルディスクリプタの複製等の操作が必要になる。
参考 :
https://www.coins.tsukuba.ac.jp/~syspro/2019/2019-05-15/index.html#sec:redirection-pipe
https://github.com/aamine/stdlinux2-source/blob/master/sh2.c

goで実装する場合は、os.StartProcess()の内部でファイルディスクリプタの複製等の操作(上記の3,4,5と6の親プロセスのファイルディスクリプタ状態復帰)を代わりにやってくれるらしい。(たぶん)

goで以下のコマンドを子プロセスとして実行するサンプルプログラムを示す。

cat hoge.txt > hage.txt

サンプルプログラム

package main import ( "os" ) func main() { // cat hoge.txt > hage.txt f, err := os.Create("hage.txt") if err != nil { panic(err) } defer f.Close() var procAttr os.ProcAttr procAttr.Files = []*os.File{os.Stdin, f, os.Stderr} // 該当するプログラムを探して起動する process, err := os.StartProcess("/usr/bin/cat", []string{"cat", "hoge.txt"}, &procAttr) if err != nil { panic(err) } // 起動したプログラムが終了するまで待つ _, err = process.Wait() if err != nil { panic(err) } }