Plan9日記

2014-12-01

OSv雑感

次のスライドは@syuu1228さんの「OSvの概要と実装」。

  • ライブラリOSが流行っている感じもするので、そのうちまとめておきたい。USENIX ATCのOSvの発表を聞いて、タネンバウム教授がexokernelと何が違うみたいな質問をしてたそうな。
  • ユーザ空間がなくカーネル空間のみという割り切りはすごい。ハイパーバイザが最後の砦になるので、よいという判断。カーネルじゃなくてライブラリと考えればそんなものかとも思うが、カーネルアプリが同じメモリプールからmallocするとなると、ちょっとしたオーバランでカーネルをぶっ壊してしまいそう。
  • アプリからPF filterやnet channelにフックを仕込んでパケットを奪って処理することが可能。実装の雰囲気としてはカーネルモジュール
  • ttyないのでエディタを開発するのは難しい。REST APIを利用して外部からファイルを流し込むのがOSv流。
  • Dockerと狙いがかぶってそう。完全なisolationが一つの鍵か?
  • Virtual Appliance的な利用を想定しているようだ。
  • 元々ゲストOS上で動くことしか考えてなかったが、実はベアメタルで動きそうなぐらいレガシーデバイスがサポートされつつある。もう一息でベアメタルで動いてしまったり?
  • Huaweiがaarch64サポートを実装。

2014-10-14

CentOS 7でOSvを動かすまで

OSvを動かしてみるぞ〜。

git clone https://github.com/cloudius-systems/osv.git

して、README.mdを眺める。scripts/setup.pyで必要なパッケージをインストールするみたいだけど、CentOS用の記述がないので、Fedoraをベースにコードを追加。versionがexact matchなので、マイナーバージョンアップしたらダメだな、これ。まぁ、いいや。

diff --git a/scripts/setup.py b/scripts/setup.py
index 8487c2a..c73891b 100755
--- a/scripts/setup.py
+++ b/scripts/setup.py
@@ -51,6 +51,30 @@ class Fedora(object):
 
     versions = [Fedora_20, Fedora_21]
 
+class CentOS(object):
+    name = 'CentOS Linux'
+    install = 'yum -y install'
+    packages = ['gcc-c++', 'gcc-c++-aarch64-linux-gnu', 'git', 'gdb', 'qemu-img',
+                'qemu-system-x86', 'libvirt', 'maven', 'java-1.7.0-openjdk',
+                'ant', 'autoconf', 'automake', 'boost-static', 'genromfs', 'libtool',
+                'flex', 'bison', 'maven-shade-plugin', 'python-dpkt', 'tcpdump', 'gdb',
+                'gnutls-utils', 'openssl', 'python-requests', 'p11-kit', 'patch', 'wget',
+                'unzip', 'ncurses', 'ncurses-devel', 'libstdc++-static', 'openssl-libs',
+                'openssl-devel'
+                ]
+    ec2_packages = standard_ec2_packages
+    test_packages = ['openssl-devel']
+    ec2_post_install = standard_ec2_post_install
+
+    class CentOS_7(object):
+        packages = []
+        ec2_packages = []
+        test_packages = []
+        ec2_post_install = None
+        version = '7.0.1406'
+
+    versions = [CentOS_7]
+
 class Debian(object):
     name = 'debian'
     install = 'apt-get -y install'
@@ -105,6 +129,7 @@ class Ubuntu(object):
 distros = [
            Debian(),
            Fedora(),
+           CentOS(),
            Ubuntu()
            ]

あとは、README.mdに従うだけだけど、makeで止まった。なにやら途中でqemuを起動しているのだが、CentOSにはqemu-system-x86_64コマンドがない(代わりに/usr/libexec/qemu-kvmがある)のが問題のようだ。次のようにworkaround。

$ sudo ln -s /usr/libexec/qemu-kvm /usr/bin/qemu-system-x86_64

scripts/run.pyを実行すると、驚くほどあっさりOSvが起動する。ちなみに-nオプションを付けると、ネットワークが有効になる。デフォルトでは192.168.122.0/24からDHCPIPアドレスが払い出される。-vオプションでvhostを使った高速化が有効になる。

$ sudo ./scripts/run.py -nv
OSv v0.13-53-ge31c3dd
DMI: error
eth0: 192.168.122.89
/# help
COMMAND DESCRIPTION
api     execute arbitrary OSv API operations as defined by the schema
cat     concatenate files and print on the standard output
cd      change the shell working directory
date    print the system date and time
df      report file system disk space usage
dmesg   print operating system boot log
echo    display a line of text
exit    close shell and exit
free    display amount of free and used memory in system
hello   a command example
help    list console available commands and show help
ls      list directory contents
mkdir   make directories
rm      remove files or directories
top     display OSv threads
/# 
/# hello
Hello, OSv!

/proc/self/mapsを見て、luaの共有ライブラリがリンクされているので気付いたのだけど、このシェルluaで実装されているのね。

/# cat /cli/commands/hello.lua
--[[
Commands are expected to be Lua modules.
This file is an example of such a module.
For more on Lua modules, see: http://lua-users.org/wiki/ModulesTutorial
]]--

local cmd = {}

cmd.desc = [[a command example]]
cmd.hello = [[Usage: hello

A command example.]]

--- Called when the command is executed
-- @param args List of arguments from the command line
cmd.main = function(args)
	print("Hello, OSv!")
end

return cmd

dmesgを見えるとどんなデバイスが登録されているかわかる。

/# dmesg
dmesg
4 CPUs detected
Firmware vendor: Seabios
bsd: initializing - done
VFS: mounting ramfs at /
VFS: mounting devfs at /dev
RAM disk at 0x0xffff800002796040 (4096K bytes)
net: initializing - done
vga: Add VGA device instance
eth0: ethernet address: 52:54:00:12:34:56
virtio-blk: Add blk device instances 0 as vblk0, devsize=10842275840
random: virtio-rng registered as a source.
random: intel drng, rdrand registered as a source.
random: <Software, Yarrow> initialized
VFS: unmounting /dev
VFS: mounting zfs at /zfs
zfs: mounting osv/zfs from device /dev/vblk0.1
VFS: mounting devfs at /dev
VFS: mounting procfs at /proc
random: device unblocked.
program zpool.so returned 1
BSD shrinker: event handler list found: 0xffffa0000199e500
	BSD shrinker found: 1
BSD shrinker: unlocked, running
[I/43 dhcp]: Waiting for IP...
[I/246 dhcp]: Server acknowledged IP for interface eth0
[I/246 dhcp]: Configuring eth0: ip 192.168.122.89 subnet mask 255.255.255.0 gateway 192.168.122.1 MTU 1500
Running from /init/30-auto-00: /libhttpserver.so &!

シェルだとできることは限られているのだけど、REST API経由でいろいろ弄れるのが今っぽい。ブラウザhttp://192.168.122.89:8000/を開く。あ、ブラウザ経由でリブートやシャットダウンもできちゃった。面白い。ちなみにコンソールからシャットダウンするときは、exit (or Ctrl-a x)でOK。

2013-01-28

[] OpenFlow実践入門

そろそろ「OpenFlow実践入門」についてステマしないといけないのではと思ったので書いてみる。OpenFlowに関して日本語で書かれた書籍はまだ2冊しか出ていないと思うけど、OpenFlowに興味あるならまずはこれを買っておけと。

OpenFlow本と言いつつ、実質はTrema本である。TremaはRubyで書かれたOpenFlowコントローラフレームワークなので、Rubyに愛のない人は厳しいだけど、Rubyを知らなくてもその都度Rubyの文法に関する補足がなされているので、最後まで読み通すことができる。Trema Dayでも、「Python版Tremaはどうなった?」という話が出てきたが、Pythonで再実装するぐらいなら、Rubyを覚えた方が早い。Python使いならRyuという選択肢もある。

高宮さんの巻頭言に感動しながら読み進めていくと、最初に出てくるhello-trema.rbにtypoが!これではまる人はいないと思うが。。。バカ売れして改訂版で直るといいなぁ。

class HelloTrema  Controller # あれ「<」がない
  def start
    puts "Hello, Trema!"
  end
end

つまらない粗探しはこれぐらいにして、へぇと思った部分をいくつか。

  • Flow ModとPacket Outは一緒にやらない方がよい。
  • 統計情報を取得するには、Flow Removedメッセージを使う。(え、それでいいの?という驚き)
  • OpenFlowに関係ないけど、ルーティングテーブルのロンゲストマッチの実装方法。

また、Trema Dayで鈴木さんが、シンプルルータ(simple-router.rb)について、C言語でパケットを書き換えるのは簡単だけど、Rubyは抽象度が高いので苦労した。パケットの生成や書換えをうまくやる方法は確立できてないと裏話を語っておられた。会場からはRacketというライブラリが使えるのではとの指摘があった。

あとは実際に手を動かしながら、読み深めようと思う。

(追記)高宮さんに正誤表のページを教えてもらった。

クラウド時代のネットワーク技術 OpenFlow実践入門 (Software Design plus)

クラウド時代のネットワーク技術 OpenFlow実践入門 (Software Design plus)

2012-01-28

[]「割込み」の名付け親

前回、割込みの起源について触れたけど、もう少し深掘りしてみたいと思ったら、たまたま読んでいた高橋秀俊氏の伝記にPC-2の割込みの話が出てきたので、紹介しようと思う。

同氏は計算機屋からは計算機のパイオニアの一人だが、「物理の散歩道」シリーズで知られるロゲルギストの一人として記憶している人も多いだろう。計算機関係の業績としては、やはりパラメトロン計算機である。後藤英一氏の発明したパラメトロン素子を利用し、計算機PC-1、PC-2を開発した。当時の計算機屋ならば全員が読み、感銘を受けたというウィルクスの「The Preparation of Programs for an Electronic Digital Computer」を参考に、これらの計算機の命令セットはEDSACをベースに設計された。PC-1上で高橋氏が一番最初に書いたプログラムというのが「世界一周」で、先日、和田英一氏がブログにも書いておられた。これは自己書き換えを利用して、自分自身を後ろのメモリにコピーして、そこに制御を移し、動き続けるというプログラムで、メモリテストにもなったという。当然、メモリ保護機能がある、近代的な計算機、OS上でこのような行儀の悪いプログラムは動かない。しかし、自己書き換えはイニシャルオーダにも使われ、当時の計算機では欠かせないテクニックだった。

さて、このイニシャルオーダとは何だろうか。これは紙テープからプログラムを読み込み、機械語に変換し、メモリにロード、実行するプログラムで、今で言うアセンブラとローダを兼ねたものだ。OSなどに繋がる原始的なシステムソフトウェアと言える。PC-1の最初のイニシャルオーダは高橋氏が書いたが、すぐに和田氏が書いたR0に置き換えられた。R0は公開されている。私は学生時代にEDSACのイニシャルオーダを読んで感動したものだが、R0の方も読んでみたい。高橋氏曰く「芸術品」だそうだ。

EDSACやPC-1の頃の入出力と言えば紙テープやプリンタである。当時のCPUは今より圧倒的に遅かったとは言え、相対的に入出力機器が遅い状況は変わらない。入出力機器が動いている間、CPUが止まってしまうのは効率が悪い。割込み機能が欲しいというのは、このころからニーズとしてあった。EDSACにもそのような機能があったようだし、PC-2(予備テストとしてPC-1にも)にも実装された。ただし、ハードウェアとしての機能はあっても、その機能を利用したソフトウェアは結局作られなかったようだ。そもそもメモリが少なすぎて、割込みを活かすことは難しかったようだ。結局、割込みが活きてくるのはOS(モニタ)の登場を待つことになる。というか、割込みがないとOSは作れない。今の計算機の割込みに近い形になったのはIBM Stretchあたりだろうか。「人月の神話」で知られるブルックスJrが1957年に「A Program-Controlled Program Interruption System」という論文を書いている。

さて、この割込みはもちろんInterruptの訳語として使われているが、もとはと言えば、高橋氏がInterruptとは独立して考え、名付けたのだそうな。いや〜、知らなかった。

割込みの歴史に関しては、Mark Smothermanの文書History of Interruptsが参考になる。

(追記:2012-02-13)和田氏が1996年のプロシン用に書かれたPC-1の原稿に割込みに関する記述が書かれていたので、引用する。そうそう、例外を「割出し」と呼ぶこともあったんだよな。これも東大高橋研の命名だろうか。結局、定着しなかったが。

PC-1が完成してほぼ 1 年使い, ライブラリも増えてきたころ, 高橋先生が走行中のプログラムに外から制御が出来ないのは面白くない, といい出された. 先述のフリップフロップは手動でもセットできたが, 入出力機器その他で制御できれば便利だということであった. そこで早速割り込み回路を設計した. 当面テレタイプが受信OKになると, 割り込みが発生し, さらなる割り込みをフリップフロップで禁止し, 次に実行するはずの命令の番地を510 番地へ格納し, 511 番地へ実行を移す. 511 番地には割り込み処理ルーチンへのジャンプ命令がおいてある, というものであった. さっそくサイクリックバッファを共用するかたちで, 主ルーチンと割り込み処理ルーチンの強調プログラムを書き, マルチプログラミングの世界初とも思われる実験を行なった.

割り込みが出来ればトラップ (割だしとも呼んだ) へ考えは及び, メモリープロテクションのアイディアもでたが, これはアイディアだけで, PC-1 への組み込みは行なわなかった.

コンピューターへの道 (1979年)

コンピューターへの道 (1979年)

物理の散歩道 新装版

物理の散歩道 新装版

2011-04-16

[] sleepとwakeup関数

古典的なUNIXでは、カーネル内で走行中のプロセスがバッファやIOなどの共有資源にアクセスするときの同期機構としてsleepとwakeup関数を提供している。ちょっと注意が必要なのは、これらの関数はsleep(1)と直接動作に関係ないこと。実はV6にはsleep(1)を実装するためにsleep(2)があるのだが、V7以降はpause(2)、alarm(2)が導入されたためライブラリ関数になった。

ちょっと教科書風に書くと、UNIXカーネルはリエントラント(再入可能)であり、カーネル内では複数のプロセスが実行可能状態にある。プロセスはカーネル内のデータ構造を共有しているので、それを排他的に利用するために同期機構が必要である。ユニプロセッサでは、同時に一つのプロセスだけが実行でき、他のプロセスはCPUや他の資源待ちでブロッキングされている。このようにプロセスがあるイベントやIO処理を待つためにCPUを放棄することを「ブロック」と呼ぶ。古典的なUNIXはこのようなカーネル内の同期を実現するブロッキング手段として、sleep/wakeup関数を提供している。V6の実装は非常にシンプルであり、スリープキューのような仕組みやマルチプロセッサ対応が入ってくるのはもっと後である*1

閑話休題、イベントを待つプロセスはsleepを呼んで、running状態からsleep状態に遷移してCPUを放棄する。これとは逆に、イベントが発生したので、ブロッキングしているすべてのプロセスを起こすのがwakeupである*2。sleepとwakeupは待ち合わせる資源のアドレスを引数に取る。このアドレスをWCHAN(waiting channel)と呼ぶ。WCHANとして、よく使われるのはtout変数やproc配列のアドレスである。sleepでブロッキングしているプロセスのproc構造体のp_wchanフィールドにはそのアドレスが記録される。

プロセスがどんな条件でsleepしているか調べるには、psにlオプションを付けて、WCHANの項目を見ればよい。今どきのUNIXはWCHANとしてカーネル関数名が見えて優しいけど(System.mapとか/proc/pid/wchanあたりを参照してカーネルシンボルを探しているんだと思う)、V6はアドレスが表示されるだけだ。

# ps alx
TTY F S UID   PID PRI ADDR  SZ  WCHAN COMMAND
?:  3 S   0     0-100 1176   2  63142 ,??j??
?:  1 W   0     1  40 1273   6  63204 /etc/init
8:  1 W   0     8  40 1441  19  63232 -
8:  1 R   0    15 100 1770  17        ps alx
?:  1 W   0     5  90 1703   5  62704 /etc/update

最後の行に/etc/updateってのが見える。これを追ってみよう。man update(8)を読むと、30秒周期でsyncを実行しているデーモンプログラムだとわかる。こいつのWCHANは062704。カーネルイメージ(rkunix)をnmしてみると、ビンゴ! _toutがヒットした。

# nm rkunix|grep 062
:
062704B _tout

updateのプログラムを以下に示すけど、アセンブリだとこんなに短くデーモンプログラムが書けるんだな。Cよりわかりやすくない?

        sys     fork
                br 1f
        sys     exit
1:
        clr     r0
        sys     close
        mov     $1,r0
        sys     close
        mov     $2,r0
        sys     close
1:
        sys     sync
        mov     $30.,r0
        sys     sleep
        br      1b
sleep = 35.
sync = 36.

デーモンプログラムのお約束で、子プロセスを作って、親は死ぬことで端末から切り離している(psの結果でもTTYは「?」になっている)。ちょっと補足すると、fork後の親プロセスの戻りアドレスは「br 1f」じゃなくて「sys exit」になる。これはforkシステムコールから戻るときにPCに細工をしているから。Cで書くとforkの戻り値で場合分けするけど、アセンブリで書くと戻りアドレスで場合分けするという使い方になる。そして標準入出力をcloseして、sync、30秒sleep、syncを繰り返す。つまり、先のps実行時点ではupdateはこのsleepシステムコール処理でsleep状態になっていることがわかる。

また、/etc/initのWCHANは063204。nmで調べると&proc[0]が063156なので、sizeof(proc)=22バイトを加えた063204が&proc[1]になる。これはinitがwait(2)を呼び出してカーネルに入り、wait内のsleepでブロッキングしていることを意味する。exitとwaitの詳細は@superhogeさんが素晴らしいまとめを書いてくださっているので、ここでは割愛する。

(追記:2011-04-17)ついでなので、simhのダンプ機能を使って、updateプロセスのプロセス構造体(struct proc)を見てみよう。「//」以下に対応するメンバと値をコメントしてみた。

sim> e 63306-63332
63306:     000402  // p_flag (SLOAD); p_stat (SWAIT);
63310:     000132  // p_sig (0); p_pri (0132);
63312:     077400  // p_time (0377); p_uid (0);
63314:     000000  // p_nice (0); p_cpu (0);
63316:     000000  // p_ttyp (0);
63320:     000005  // p_pid (5);
63322:     000001  // p_ppid (1);
63324:     001703  // p_addr (01703);
63326:     000045  // p_size (045);
63330:     062704  // p_wchan (062704);
63332:     000000  // p_textp (0);

SWAIT状態、つまり低優先度(シグナル受信可能)でsleepしていて*3、プロセスはメモリ上に存在する(SLOAD)。TTYからは切り離されていて(p_ttyp 0)、親PIDもinit(PID 1)になっていることがわかる。また、p_wchanの値はpsで見た結果と同じだ。

*1:Plan 9のsleep/wakeup関数について以前書いたことをすっかり忘れていた。

*2:wakeupはプロセスの状態を変えるけど、コンテキストスイッチまでは行わないことに注意。スイッチ自体はswtch関数で行われる。

*3:V7以降はSWAITとSSLEEPで状態をわけることはせずに、SSLEEPで一本化されたようだ。