プラットフォームを抽象化したC API

C言語への感謝の正拳突き えっと今日は9日目ですね。。

macroネタ、結構ウケるかな？と思ってたんですけどね、、
本日はAPIネタです。

# C言語で書く目的

組み込み系などの制約の強い環境や、bootloaderなどのCPUやデバイスに非常に近いレイヤを除き、
リソースの豊富なサーバサイドのアプリケーション(or ライブラリ)をCで書く目的というのはどういうものがあるのでしょうか。
※上記組み込み系やCPUやデバドラまわりをC++で書くことを否定するわけではないです。

思いつく限り挙げると、

(1) マルチプラットフォーム対応かつ依存コンポーネントを減らしたい(portability?)
C++を利用する場合、マルチプラットフォーム対応したライブラリは結構あって、そういうライブラリ(例えばBOOST)は非常に便利なのだけど、そのライブラリ(例えばBOOST)への依存が発生する。
JavaやScalaを利用する場合、JVMがマルチプラットフォーム対応してくれるけれども、、以下略。
Erlangを利用する場合、以下略。
Goのマルチプラットフォーム対応も素晴らしいですが、外部アプリケーションから利用しやすいライブラリにし難い。
(Go1.5からshared libraryとして提供できるようですが、何かしら制約があるでしょうし、まだ試せてないです。)

Cの場合、マルチプラットフォーム対応なんて自前で実装して、かつ依存コンポーネントも減らしてやるよ。。という話が多い気がします。

(2) ABI互換とライブラリとして組み込む際の運用性
これを考えたらCかC++しかないのですが、Cのほうが他言語から利用しやすいのではないでしょうか。
C++もC APIにwrapすれば似たようなものだと思いますが、一手間ありますね。

Java/Scala、ErlangはVMに依存するため、ABI互換(Bytecode?)はVMが保証しますが、
ライブラリとして組み込む範囲を狭めてしまいます。
利用範囲が明確であり、いろいろと諦めざるところを理解していれば、非常によい選択肢だと思います。

(3) 0から組んで、限界性能を引き出したい
これによく該当するのはC/C++だと思います。
C++のほうが遅いみたいな話、今ではほとんど聞かないですね。

あと、検索してみると結構でてくるのは、
Why was XXX written in C and not C++?
って質問ですね。

gitとか
http://cpplover.blogspot.jp/2013/05/linus-torvalsc.html

h2oとか
https://github.com/h2o/h2o/issues/179

zeromqとか(これは逆でなぜC++なのか)
http://250bpm.com/blog:4

redisとか
https://www.quora.com/Why-is-Redis-written-in-C-not-C++

grpc coreは、portabilityを理由にあげていたかな。
grpc coreの外側はC++だし。

nginxはこの手の話がみつからないな。。

### おっと、これ以上はいけない！
今回は特にプラットフォーム依存をどのように吸収しているのかに関して焦点を当てたいと思います。

マルチプラットフォーム対応する場合、モバイル環境への対応が問題になりがちですが、
ここでは、主にWindows Linux BSD OSXなどのマルチプラットフォーム対応に絞ります。

最近Cで書かれたプロダクトに、WiredTigerっていうのがあります。
Berkeley DataBaseの後継っぽいNoSQLDatabaseで、2015年頭 MongoDBに買収されたところです。

WiredTigerの概要
https://github.com/wiredtiger/wiredtiger

特徴とかはこのへんが詳しい
http://www.slideshare.net/wiredtiger/wired-tiger-overview
BTreeとLSMTreeベースだなんて、今風じゃないですか。

大体はwikiみれば書いてる
https://github.com/wiredtiger/wiredtiger/wiki

WiredTigerを具体例にして、Cで実装されたプロダクトがどのようにマルチプラットフォーム対応しているのか、
どのようなAPI設計になっているのかを調べてみました。

# 具体例

WiredTigerの内部では、プラットフォームを抽象化したレイヤが存在し、ディレクトリを分けて定義されていました。

```C:
src :: wiredtiger/src
  src/async     async apiの定義
  src/block     block manager(ioを管理してるとこ)
  src/bloom     bloom filter(LSMTree用)
  src/btree     BTreeのlayoutとかcursorの定義
  src/config    config
  src/conn      conn？
  src/cursor    各種cursorの定義。column cursorとかある。
  src/docs      docs
  src/evict     eviction、txnとかreconcileに依存があって辛い
  src/include   include
  src/log       log
  src/lsm       LSMTreeのlayoutとかcursorの定義
  src/meta      metadataの定義
  src/os_posix  posix用のAPI    <-- ここに注目します。
  src/os_win    windowns用のAPI <-- ここに注目します。
  src/packing   packing?
  src/reconcile inmemory pageからdiskへの追い出し、読み込み制御
  src/schema    内部のtableのschema定義
  src/session   session context
  src/support   support api,checksumとかcryptoとはcityhashとかhuffmanとか
  src/txn       transactionまわり
  src/utilities wiredtigerのライブラリを制御するutilityコマンドの定義かな

```

os_posixやos_winの中には、WiredTigerが使用する最小限のAPIが定義されていて、主にfile ioのAPIですね。
Win向けとPosix向けに実装がわかれていて、WiredTigerの他コンポーネントは、プラットフォームを抽象化したAPIを呼び出しています。

１つ例をあげると、src/dir_list.cで定義されているwt_exist()ですね。
filenameで指定したファイルの存在を確認するAPIです。

```C:src/os_win/os_exist.c
/*
 * __wt_exist --
 *      Return if the file exists.
 */
int
__wt_exist(WT_SESSION_IMPL *session, const char *filename, int *existp)
{
        WT_DECL_RET;
        char *path;

        WT_RET(__wt_filename(session, filename, &path));

        ret = GetFileAttributesA(path);

        __wt_free(session, path);

        if (ret != INVALID_FILE_ATTRIBUTES)
                *existp = 1;
        else
                *existp = 0;

        return (0);
}
```

```C:src/os_posix/os_exist.c

/*
 * __wt_exist --
 *      Return if the file exists.
 */
int
__wt_exist(WT_SESSION_IMPL *session, const char *filename, int *existp)
{
        struct stat sb;
        WT_DECL_RET;
        char *path;

        *existp = 0;

        WT_RET(__wt_filename(session, filename, &path));

        WT_SYSCALL_RETRY(stat(path, &sb), ret);

        __wt_free(session, path);

        if (ret == 0) {
                *existp = 1;
                return (0);
        }
        if (ret == ENOENT)
                return (0);

        WT_RET_MSG(session, ret, "%s: fstat", filename);
}

```

APIは__wt_exist()に統一されていて、
Windowsのほうは、GetFileAttributesA()を呼び出していて、
Linuxのほうは、stat()を呼び出しているようです。

それ以外にもいろいろと見どころはあって、
(1) 第一引数はtop level contextであり、第2引数がパラメータ、第3引数が結果をかえす。
(2) return typeがintかつ、0か 0以外のいずれかしか返さない
(3) WT_RETやWT_RET_MSGってなによ。。 返値が0以外だったら、即返値をreturnするマクロ

なかなかクセがあるAPIだと思います。
WiredTigerは、0が成功で、0以外が失敗、かつ様々なコードを割り当ててそうな気がします。
Windowsの場合、return typeはboolでtrue/falseのいずれか、詳細なエラーはGetLastError()やWSAGetLastError()で取得しろってのに多少は似ていますね。
WiredTigerの場合、0かそれ以外か、かつ関数呼び出しの返値は基本的にWT_RETマクロで検査し、0以外だったら即returnするようですね。

ここまで把握できたら、file ioを中心につかうWiredTigerがどのようなAPIを切っていて、WindowsとPosixにおいてどのようなAPIを呼び出しているのかを把握するだけでよいと思います。

ざっとAPIの一覧を示します。

```C:src/os_xxx
file                  os_win                      os_posix

os_alloc.c                                        calloc
                                                  realloc
                                                  posix_memalign
                                                  free
os_dir.c              FindFirstFileA              opendir
                      FindNextFileA               readdir
                      FindClose                   closedir
os_dlopen.c           GetModuleHandleExA          dlopen
                      GetProcAddress              dlsym
                      FreeLibrary                 dlclose
os_errno.c            GetLastError                errno
os_exist.c            FileAttributeA              stat
os_fallocate.c                                    fallocate
os_filesize.c         GetFileSizeEx               fstat
                      GetFileAttributeExA         stat
os_flock.c            LockFile                    fcntl
                      UnlockFile
os_fsync.c            FlushFileBuffers            fcntl fdatasync fsync sync_file_range
os_ftruncate.c        SetFilePointerEx            ftruncate
                      SetEndOfFile
os_getenv.c           GetEnvironmentVariableA     getenv
os_map.c              CreateFileMappingA          mmap
                      MapViewOfFile               posix_madvise
                      CloseHandle                 munmap
                      UnmapViewOfFile
os_mtx_cond.c         InitializeCriticalSection   pthread_mutex_init
                      InitializeConditionVariable pthread_cond_init
                      EnterCriticalSection        pthread_mutex_lock
                      LeaveCriticalSection        pthread_mutex_unlock
                      SleepConditionVariableCS    pthread_cond_timedwait pthread_cond_wait
                      WakeAllConditionVariable    pthread_cond_broadcast
                      DeleteCriticalSection       pthread_cond_destroy
os_once.c             InitiOnceExecuteOnce        pthread_once
os_open.c             CreateFileA                 open
os_remove.c           RemoveFileA                 remove
os_rename.c           GetFileAttributeA           rename
os_rw.c               ReadFile                    pread
                      WriteFile                   pwrite
os_sleep.c            Sleep                       select
os_snprintf.c    w    vsnprintf
os_thread.c           beginthreadex               pthread_create
                      WaitForSingleObject         pthread_join
os_time.c             GetSystemTimeAsFileTime     clock_gettime gettimeofday
os_vsnprintf.c   w    vsnprintf
os_yield.c            SwitchToThread              sched_yield
```

WiredTigerの特徴としては、基本的にマルチスレッドで動作して、pollとかは一切しない。
IOはWindowsの場合はfilehandle+overlapped、Linuxの場合はpread/pwriteのみ実行し、
IOとマルチスレッドの制御は、block managerがいろいろやっているようでした。

# まとめ
C(やC++)でマルチプラットフォーム対応する場合、多方面のAPIを叩く予定がなければ、
自前でプラットフォームを抽象化したAPIを用意するのもありかもしれませんね。。

マルチプラットフォーム対応したVMやライブラリはこういうことを頑張ってたんですねー。
こういうのはBOOSTとかJVMとかBEAM VMとかGolangにお任せしたいです。
(BOOSTとOracleJVMはC++製、BEAM VMはC製、GolangはGo製です)

以上