Tech Racho エンジニアの「?」を「!」に。
  • インフラ

4.4BSD-Lite2のUNIXコマンドをC++に書き換える開発で学んだ事 Part1

皆様、明けましておめでとうございます。昨年はTechRachoの執筆もちょくちょくやっていましたが、改めて読み直してみると、昨年はBSDの記事を書いていないことに気づきました。
ちょっとマンネリ気味な所もあったのでここは思い切ってTechRachoに書くことによる願掛けの意味も込めて最近始めている趣味の開発で学んだ事を共有していこうと思います。

4.4BSD-LiteのソースコードをC++に移植

元々、前々からやろうやろうと計画していたのですが中々時間が取れずに日々過ごしていたので、ここでもう一度願掛けの意味で4.4BSD-Lite2のソースコードをC++に移植開発を始めています。
と言っても、久々のOSの深い所に入っていくため、それまで誰かに聞いて知った話が混じってしまったり本で読んだ知識を載せるのもよくありません。
ということで、TechRachoの記事で書くのは実際に調べたものだけを共有したいと思います(中には個人的な僕の間違った解釈も含まれているかもしれませんので、その際はレビューしていただけると幸いです)。また、OSのソースコード移植と言っていますが、まずは手始めにUNIXコマンドから開発を進めていく事で、深い所へネストしていきます。

なぜC++を選んだのか

さて、開発に入る前になぜ僕が移植の開発言語にC++を選んだのか、その理由は以下の3つです。

  1. 単純にC++の勉強をしたかった
  2. 純粋な歯車の再開発はしたくない
  3. 元ソースコードをちゃんと読み、理解し、次にそれを違う言語に移植する際の容易さ

特に重要なのが2と3でしょうか。単純に今あるソースコードを丸々0からC言語でそのまま書いても面白くない、なおかつそれでは知識の深入りができなさそうというのがあり、ソースコードを正しく読んで理解し、そして移植の際の負担が比較的小さくなるようにという意味でC++を選択しました。

移植に対するこだわり

久々に低水準なところを開発することもあるため、忘れている所があるので基本は色々と調べながら開発します。
その上で、僕自身の開発方針としてただ一つだけこだわりたいものがあります。
それが

  1. 抽象度の高いコードにする

もちろん、既存のUNIXシステムのソースコードも抽象度が高いです。元々、カーネルへの呼び出しはシステムコールを挟まなければいけませんし、そのシステムコールだけでも抽象度の高いコードは書けそうです。
ここでの拘りはシステムコールやライブラリ関数どちらでもいいが、単純に関数一つで機能が実現できるのであれば、優先的に関数の機能を使うということだけです。
この拘りの理由としては、エンジニアというか人間の本能(?)、もしかしたら私事情も含まれているかもしれませんが、
既存の知識、技術で容易に解決できるものが目の前にあるのに、それを使えないギャップやストレスは苦痛でしかないし思考もストップしてしまうというのが一番大きな理由です(あえてここは、言葉を選んでます)。
逆に言えば、これだけ守れば楽しく移植開発ができるのではないでしょうか。
では、UNIXコマンドの移植を行なっていきましょう。

echoコマンドの移植

最初に手を出したのはechoコマンドです。引数として与えられた標準入力を単純に標準出力に返すコマンドですね。
4.4BSD-Lite2(以下、元コードと呼びます)のechoのソースコードは以下の通りです。

/*
 * Copyright (c) 1989, 1993
 *  The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *  This product includes software developed by the University of
 *  California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1989, 1993\n\
    The Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char sccsid[] = "@(#)echo.c  8.1 (Berkeley) 5/31/93";
#endif /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int
main(argc, argv)
    int argc;
    char *argv[];
{
    int nflag;

    /* This utility may NOT do getopt(3) option parsing. */
    if (*++argv && !strcmp(*argv, "-n")) {
        ++argv;
        nflag = 1;
    }
    else
        nflag = 0;

    while (*argv) {
        (void)printf("%s", *argv);
        if (*++argv)
            putchar(' ');
    }
    if (!nflag)
        putchar('\n');
    exit(0);
}

これをC++に移植すると以下のような書き方が一番いいでしょうか(コードの部分だけ抜粋)。


#include <cstdio> #include <cstdlib> #include <cstring> int main(int argc, char *argv[]) { int nflag; /* This utility may NOT do getopt(3) option parsing. */ if (*++argv && !strcmp(*argv, "-n")) { ++argv; nflag = 1; } else nflag = 0; while (*argv) { fputs(*argv, stdout); if (*++argv) putchar(' '); } if (!nflag) putchar('\n'); exit(0); }

変わった所はfputsでストリーム出力しているだけですね。C言語の方は **voidに型変換することで一部のコンパイラの警告文を抑えているようです
さて、ここで拘って使った二つの関数 fputs, putchar について見てみましょう。

fputs

書式
int fputs(const char *s, FILE *stream);

streamで指定したストリームに文字列sを出力します。ここでは標準入力をそのまま標準出力に流し込み形で出力したので、それをワンライナーで行えるfputsを使うことにしました。

putchar

書式
int putchar(int c)

putchar(c)で呼び出せます。引数は一つ。
putc(c, stdout)と同じ挙動をするため、一文字を標準出力するならこっちを使います。こういう関数一つでできる機能は見出しにも書いた通りでどんどん使っていきます。

変更箇所は上記のみなので非常に簡単な移植で済みましたね。では、これをBSD的なやりかたでコンパイルしていきましょう。

bsd.prog.mk

元コードにあったMakefileを見てみましょう。

#       @(#)Makefile    8.1 (Berkeley) 5/31/93

PROG=echo
.include <bsd.prog.mk>

bad.prog.mkというものをincludeしているだけで特にコンパイルに必要なものが分からないのでbsd.prog.mkを見てもイマイチピンと来ない、、、
FreeBSDのbsd.READMEにはこう書かれていました

bsd.prog.mk - building programs from source files

bsd.prog.mkはソースコードをコンパイルするためのフレームワークみたいなものだということはわかります。
しかし、どうも元コードのbsd.prog.mkの方はC++に対応していないことが分かります

.SUFFIXES: .out .o .c .y .l .s .8 .7 .6 .5 .4 .3 .2 .1 .0

このフレームワークも書き換えてもいいのですが、他のフレームワークを書き換える必要もあり、ここで時間をかけてしまうのはハイリスクな所があります。
他のBSD各種はこの部分はどうやっているのでしょうか。
4.4BSD-Liteから派生されたFreeBSD 3.0, NetBSD 1.3, OpenBSD(OpenBSDだけGitHubのソースコードでタグづけされていませんでした...)のbsd.prog.mkを見て見ましょう。

bsd.prog.mk

三種三様、コンパイル用のフレームワークと侮っていましたが、かなりの違いがありますね。
OpenBSDはNetBSDのbsd.prog.mkをその前から引き継いでいる様ですね(この辺りの歴史どなたか詳しい人コメントお願いします)。

流し読み程度での感想ですが、ここは一番読みやすかったFreeBSD 11.1-STABLEのbsd.prog.mk(とその関連ファイル全て)を流用したいと思います(と言うか、クロスコンパイル環境を用意できない場合は普通にBSDのOSと合わせた方がいいです)。また、C++の拡張子ですが、ここはUNIXの歴史に基づいて.ccを選びました

#       @(#)Makefile    8.1 (Berkeley) 5/31/93

PROG=   echo
SRCS=   echo.cc
.include <bsd.prog.mk>

上記の SRCS の部分でC++のソースファイルを明示的に指定してあげます。これで make を実行することで echo コマンドが作成できます。

% make
Warning: Object directory not changed from original /usr/home/himrock922/4.4BSD-Lite2/usr/src/bin/echo
c++ -O2 -pipe -g -MD -MF.depend.echo.o -MTecho.o -fstack-protector-strong -Qunused-arguments  -Wno-c++11-extensions  -c echo.cpp -o echo.o
cc -O2 -pipe -g -std=gnu99 -fstack-protector-strong -Qunused-arguments  -o echo.full echo.o  
objcopy --only-keep-debug echo.full echo.debug
objcopy --strip-debug --add-gnu-debuglink=echo.debug  echo.full echo
gzip -cn echo.1 > echo.1.gz
./echo hogehoge
hogehoge

まとめ

というわけで、UNIXコマンドの移植開発から学んだことまとめでした。タイトルにPart1と書きましたが、あまりにもマニアックなネタな気もするのでシリーズ化するかは微妙なところです。
が、結局今回やったことってechoコマンドの移植開発とコンパイルに必要なbsd.prog.mkについてしか書けていないので、もう少し深い所にいきたい所ですね。
そのため、僕のモチベーションが維持されるまではやっていきたいので、応援よろしくお願いします。

追記(2018/01/11)

思ったより各所よりフィードバックいただきました。ありがとうございます。
echoコマンドという題材だったこともあり、C++の特徴、機能を生かしたコードに
できていなかったかなとも思います。
むしろやる気がでてきたので、今度はもう少し複雑なものを題材に選んでPart2に
してみたいと思います。
ぜひ、追加のフィードバックください!

プロジェクト

https://github.com/himrock922/4.4BSD-Lite2/tree/develop

関数に関しての参考文献

ソースコード

UNIXシステムの派生図

https://en.wikipedia.org/wiki/Berkeley_Software_Distribution#/media/File:Unix_history-simple.svg

関連記事

限界までFreeBSDの環境を構築したい


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。