クラウド向けOS「OSv」とは何か - ソースコードから調べてみる -

なにこれ

KVM開発エンジニアらが立ち上げたベンチャーCloudius、クラウド向けOS「OSv」を発表 | OSDN Magazineで紹介されていたので興味を持って弄ってみた。
KVMXen専用のOSで、アプリケーションとしてJavaアプリケーションが動くという事のようだ。
基本的にはJava製のWebアプリを想定しているのだろう。

ライセンスはBSDライセンス

Ubuntuでのビルド方法

sudo apt-get install openjdk-7-jdk
sudo apt-get install autotools-dev libltdl-dev libtool autoconf autopoint
sudo apt-get install libboost-all-dev genromfs zfs-fuse autoconf ant
git clone https://github.com/cloudius-systems/osv
cd osv
git submodule update --init
make external all

ビルド方法はgithubに書いてあるが、Ubuntuではlibtoolやjdk周りのパッケージも入れる必要があった。

実行方法

sudo ./scripts/run.py -n -v

と実行するとQEMUが起動してこんな感じのコンソールが立ち上がる:

SCRIPT, tap0








acpi 0 apic 0
acpi 1 apic 1
acpi 2 apic 2
acpi 3 apic 3
APIC base fee00000
Loader Copyright 2013 Cloudius Systems
locale works
VFS: mounting ramfs at /
VFS: mounting devfs at /dev
ACPI: RSDP 0xfd8d0 00014 (v00 BOCHS )ACPI: RSDT 0x3fffe380 00034 (v01 BOCHS  BXP
CRSDT 00000001 BXPC 00000001)ACPI: FACP 0x3fffff80 00074 (v01 BOCHS  BXPCFACP 00
000001 BXPC 00000001)ACPI: DSDT 0x3fffe3c0 011A9 (v01   BXPC   BXDSDT 00000001 I
NTL 20100528)ACPI: FACS 0x3fffff40 00040ACPI: SSDT 0x3ffff6e0 00858 (v01 BOCHS 
 BXPCSSDT 00000001 BXPC 00000001)ACPI: APIC 0x3ffff5b0 00090 (v01 BOCHS  BXPCAPIC
 00000001 BXPC 00000001)ACPI: HPET 0x3ffff570 00038 (v01 BOCHS  BXPCHPET 0000000
1 BXPC 00000001)RAM disk at 0x3eb59000 (4096K bytes)
Initializing network stack...
Done!
eth0: ethernet address: 52:54:0:12:34:56
VFS: mounting zfs at /usr
zfs: mounting osv/usr from device /dev/vblk0.1
[I/34 dhcp]: Waiting for IP...
[I/325 dhcp]: Configuring eth0: ip 192.168.122.89 subnet mask 255.255.255.0 gateway 192.168.122.1
[I/325 dhcp]: Server acknowledged IP for interface eth0
stub dlclose()
OpenJDK 64-Bit Server VM warning: Can't detect initial thread stack location - find_vma failed
device random not found
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Warning: default mime table not found: /usr/lib/jvm/jre/lib/content-types.properties
vfork stubbed
  ____   _____
 / __ \ / ____|
| |  | | (_____   __
| |  | |\___ \ \ / /
| |__| |____) \ V /
 \____/|_____/ \_/



[/]% Puma 2.5.1 starting...
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:8080
== Sinatra/1.4.3 has taken the stage on 8080 for development with backup from Puma


[/]% ls
dev/            tmp/            tests/          etc/            
tools/          testrunner.so   java/           java.so         
usr/            
[/]% help
Try one of these commands with the -h or --help switch:                         
                                                                                
NAME      DESCRIPTION                                                           
cat       concatenate files and print on standard output                        
cd        change the current working directory                                  
dashboard                                                                       
dhclient                                                                        
env       display the term env                                                  
filter    A filter for a stream of map                                          
help      provides basic help                                                   
java      Java application launcher                                             
jdbc      JDBC connection                                                       
jmx       Java Management Extensions                                            
jndi      Java Naming and Directory Interface                                   
jpa       Java persistance API                                                  
jvm       JVM informations                                                      
log       logging commands                                                      
ls        list directory contents                                               
man       format and display the on-line manual pages                           
md5sum                                                                          
perf                                                                            
pwd       print name of current working directory                               
shell     shell related command                                                 
sleep     sleep for some time                                                   
sort      Sort a map                                                            
system    vm system properties commands                                         
test                                                                            
thread    JVM thread commands   

使い方

まだよくわからない。
Webインタフェースからjarをデプロイしたり、CLIからjavaコマンドでJavaアプリを起動したり出来そうには見えるが。

内部構造概要

ここに色々と書いてあるが、要するにVMM上でOpenJDKをスタンドアロン起動出来るようにしたという話に読める。

メモリ空間の切り替えによる保護もカーネル・ユーザモード切り替えによる保護もなく、ユーザプログラムからカーネルAPIは関数コールで叩ける、システムの保護はJVMがやればいいというような事が書いてある。
JVMJVMを走らせるのに最低限必要なC/C++のコードが走っているだけなんじゃないだろうか。

ソースコード調査(ドライバ編)

drivers以下にドライバのコードが置いてあった。
C++でクラスも継承も使ってオブジェクティブに書かれたコードだが、定数の名前とかからなんとなくBSDのドライバのにおいがするような気がする…
virtio-netのドライバでは

#include <bsd/sys/net/ethernet.h>

とか

struct mbuf *m

とか出てきて、ネットワークスタックはBSDのものを使っているのねという察しが付く。
ドライバの数から分かるように、実機起動は考えられていない。virtioが前提。

ソースコード調査(BSD編)

ネットワークスタックやZFSなどの機能を提供するためにFreeBSDソースコードを流用しているようだ。
bsd/sys以下にFreeBSDカーネルのコードが置いてある。
不要な部分は容赦なく#if 0されており、必要なルーチンだけC++APIから呼び出す形で実装されている。
ZFSbsd/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/のあたり、ネットワークスタックはbsd/sys/netbsd/sys/netinetのあたり。

ネットワークスタックの初期化はbsd/net.ccで行っている。
OSv内のAPIFreeBSDカーネルAPI双方向のグルーコードはbsd/portingに集められている。
例えば、bsd/porting/networking.cではNICをifupするAPIが実装されている。

ネットワークスタックはbsdディレクトリ以下で完結しているようなのだが、ファイルシステムfs以下にVFSと幾つかのFSが登録されていて、ZFSはその一つとして扱われている(他の二つはramfs、devfs)。
具体的には、bsd/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vfsops.cに定義されているzfs_vfsopsがfs/vfs/vfs_conf.cで参照されている。

ソースコード調査(ブート周り)

詳しく追ってないが概要だけ。
ブートの初期段階はarch/x64のboot*.S、entry*.Sの辺りから起動してきて、C++のコードを実行可能な初期化を行う。
アセンブリコードが終わるとloader.ccのpremain()、main()が実行される。
ここから先述のドライバ、ネットワークスタック、ファイルシステム等々が一通り初期化される。
この先の処理がどう書かれているのか読み切れていないが、最終的にelf::programで/java.soにある引数を渡し(引き数値がどこから来るのかよくわかってない)、スレッドを立ててスレッド内でロード&実行するようだ。

ソースコード調査(java.so)

elf::programの正体はcore/elf.ccで、C++で書かれたELFローダになっている。
ELFローダはあるんだが、ユーザが操作出来るシェルは恐らくELFバイナリを実行出来るようにはなっていなくて、JVMを呼び出す機能しかない気がする。
ともかく、こいつで/java.soをロードして、loader.ccから/java.soのmain関数を実行する(.soとなっているのにmainがあるのは何か変な感じがするが…?)。
他にもcore/ディレクトリにはカーネルの核となる機能が幾つか突っ込まれている模様。
/java.soの正体はjava/java.ccで、これは先ず最初に/usr/lib/jvm/jre/lib/amd64/server/libjvm.soをロードしている。
これは、OSvのビルドステージでバイナリ形式で持ってきたLinux/amd64用のOpenJDKそのものである。
ダイナミックリンクライブラリとしてロード後、JVMが提供するC API経由でVMを起動、ライブラリをロードしている。

何故JVMをOSvにあわせて改変し、直接リンクしてしまわずにこんな回りくどいことをしているのだろうと思ったのだが、恐らくGPL汚染対策ではないだろうか。
OpenJDKのLinux版DLLを無改変で実行する為に、これに合わせてローダやAPIシステムコールハンドラなどをOSv側に用意しているようだ。

ソースコード調査(libc)

OpenJDKを動かすのに使うのか、libcが用意されている。
このlibcはFreeBSDのじゃなくてmusl libcらしい。
何故これを使うのかは良く分からないが。

ソースコード調査(Linuxシステムコール

OpenJDKがlibcを通さずに直接呼ぶのか、linux.ccには__NR_gettidと__NR_clock_gettimeをハンドルするシステムコールハンドラが用意されている。

ソースコード調査(CLI

CLICRaSH the shell for the Java Platformが使われているらしい。
コマンドはmgmt/crash/src/main/resources/crash/commands/cloudius以下にある.groovyファイルで定義されているようだ。
名前の通り、Groovyで書かれている。

感想

JVMスタンドアロンで動かす仕組みと言っているように見えるにも関わらず、ソースコードをいくら追ってもJVMが出てこないので最初とても困惑した。
実際に動かしてみたが、遊んでいるうちに「malloc_large(): out of memory: can't find 1519616 bytes. aborting.」とか出て死んだりしてまだあやしい。
今のままじゃ出来ることがかなり限られるけれども、もうちょっと機能が増えればおもしろく遊べそうだし、何もJavaじゃなくても、JVM部分を捨てて別の言語の処理系を載せても楽しめるのではないだろうか。
取り敢えず、そういう事が出来るフレームワークがBSDLで公開されているのはいいね。