More Related Content Similar to Solaris (Branded) Zone Internals (20) Solaris (Branded) Zone Internals2. 自己紹介
• 藤原 克則
(FUJIWARA Katsunori)
• ホームページ
http://www.lares.dti.ne.jp/~foozy/index.ja.ht
ml
• ブログ
http://d.hatena.ne.jp/flying-foozy/
• Twitter
@flyingfoozy
5. 情報 – その1
• “Windows 10のAnniversary Updateによって
Windows 10システム上でLinuxのバイナリー
をシームレスに実行することができるようにな
ると、Microsoftは本日のBuildで発表”
(https://www.infoq.com/jp/news/2016/04/lin
ux-windows-together)
• それ、なんて lx brand zone ????
6. 情報 – その2
• “The dream is alive! Running Linux containers
on an illumos kernel” by Bryan Cantrill 曰く:
– “system calls bounced back to a user-level
emulation library”
(http://www.slideshare.net/bcantrill/illumos-lx)
• “user-level emulation” とは何ぞや????
9. door! door! door!
• 実は zoneadm は Client/Server モデル!
• フロントエンドの zoneadm と、
バックエンドの zoneadmd が連携して処理
• zoneadmd は各ゾーン毎に1プロセス
• zoneadm と zoneadmd は door IPC で通信
• door IPC について話し出すと長くなるので、
この資料では door IPC の話は封印(笑)
12. ゾーンの初期化
1. “zoneadm ready” コマンド実行
2. zoneadmd への Z_READY 要求
3. D:zone_create() 実行
4. zone(ZONE_CREATE) システムコールを発行
5. K:zone(ZONE_CREATE) 実行
6. K:zone_create() 実行
14. ゾーンの起動
1. “zoneadm boot” コマンド実行
2. zoneadmd への Z_BOOT 要求
3. D:zone_boot() 実行
4. zone(ZONE_BOOT) システムコールを発行
5. K:zone(ZONE_BOOT) 実行
6. K:zone_boot() 実行
16. ゾーンの停止
1. “zoneadm halt” コマンド実行
2. zoneadmd への Z_HALT 要求
3. D:zone_halt() 実行
4. D:zone_shutdown() 実行
5. zone(ZONE_SHUTDOWN) システムコールを
発行
6. K:zone(ZONE_SHUTDOWN) 実行
7. K:zone_shutdown() 実行
18. ゾーンの破棄
1. “zoneadm halt” コマンド実行
2. zoneadmd への Z_HALT 要求
3. D:zone_halt() 実行
4. D:zone_destroy () 実行
5. zone(ZONE_DESTROY) システムコールを発行
6. K:zone(ZONE_DESTROY) 実行
7. K:zone_destroy() 実行
21. zone_t の再利用
• zone_t は「再起動中」等の状態を持たない
• DEAD 状態になった zone_t は、一旦「未使
用」扱いになる
• 同一のゾーンを再度起動させた場合でも、
UNINITIALIZED 状態からの遷移のやり直しに
過ぎない
22. グローバルゾーンからの再起動
• zoneadm reboot は以下の逐次実行と等価
– zoneadm halt
– zoneadm ready
– zoneadm bootup
• zoneadm reboot での Z_REBOOT 要求契機で
zoneadm”d” 側で halt/ready/bootup を実施
23. ゾーン内部からの再起動(1)
1. ゾーン内部での "/sbin/uadmin 1" 又は
"halt" コマンド実行
2. uadmin(A_SHUTDOWN) システムコール発行
3. K:uadmin(A_SHUTDOWN) 実行
4. K:zone_kadmin(A_SHUTDOWN) 実行
5. K:zone_ki_call_zoneadmd() でスレッド作成
– Z_REBOOT 引数指定
30. ゾーン毎ブランド情報の初期化
1. D:zone_setattr() 実行
2. zone(ZONE_SETATTR, ZONE_ATTR_BRAND) シス
テムコールを発行
3. K:zone(ZONE_SETATTR, ZONE_ATTR_BRAND) 実
行
4. K:zone_set_brand() 実行
5. 対応する brand_t の引き当て
6. zone_t.zone_brand に設定
7. zone_t.zone_brand->b_ops.b_init_brand_data()
を実行
34. exec() でのブランド処理(1)
1. ブランド設定要否の判定
– 現ゾーンのブランド設定の有無
– 現プロセスのブランド設定の有無
– 明示的なブランド設定指示の有無
2. 必要に応じてプロセス毎ブランド設定を実施
(K:brand_setbrand())
3. ブランド固有の exec 前処理の実施
(BROP(proc)->b_exec())
– Solaris10 ブランドの場合:
• proc_t->p_brand_data->spd_handler のクリア
– 旧 lx ブランドの場合:
• コンテキスト関数(後述)の登録
• Linux/Solaris 間での PID 変換テーブルの更新等々
35. exec() でのブランド処理(2)
1. K:execelf() での実行ファイル読み込みにおいて、ブランド
固有処理呼び出し
(BROP(proc)->b_execelf())
2. ブランドライブラリの vnode 引き当て
(s10_brand.so.1)
3. ブランドライブラリをメモリ空間にロード
4. オリジナル実行ファイルをメモリ空間にロード
5. プロセスエントリポイントをブランドライブラリの U:_start()
に変更
6. ユーザ空間での実行を開始
7. ブランドライブラリの U:_start() を実施
8. U:_start() から U:brand_init() の呼び出し
36. exec() でのブランド処理(3)
1. U:brand_init() 実施
2. U:brand_post_init() 実施
3. brandsys(B_REGISTER) システムコール発行
4. K:brandsys(B_REGISTER) 実施
5. ブランド固有処理として実施
(BROP(proc)->b_brandsys(B_REGISTER))
6. 「ブランドライブラリ中の関数テーブル」のアドレスをカー
ネル空間に取り込む
(proc_t.p_brand_data->spd_handler で参照)
7. オリジナル実行ファイルのELF情報の読み出し
(brandsys(B_ELFDATA))
8. 実行ファイルの本来のエントリポイントに遷移
38. ちなみに……
• 「Solaris 10 オペレーティングシステムでサポートを中止した製品」
曰く:
– この告知は、32 ビット版の静的システムライブラリおよび静的にリン
クしたユーティリティーだけに該当します。64 ビット版の静的システム
ライブラリやユーティリティーは、これまで提供されたことがありませ
ん。
– 32 ビット版の Solaris 静的システムライブラリおよび静的にリンクした
ユーティリティーは、Solaris でサポートされなくなりました。特に、静的
C ライブラリ (/usr/lib/libc.a) は、Solaris でサポートされなくなりました。
– http://docs.oracle.com/cd/E19253-01/819-0305/eywvd/index.html
• 静的リンクバイナリへの配慮は無用な模様
• Solaris11 のゾーン運用マニュアルでの「静的にリンクされたバイナ
リはサポートされません」との明記は何だったのか?(笑)
41. Solaris10 ブランド固有処理
1. 関数テーブル登録有無の確認
2. エミュレーション処理(= ユーザ空間)に「復帰」
3. ユーザ空間でシステムコールをエミュレーション
– システムコール呼び出し時引数そのものを、レジス
タ/スタック経由で受理可能
– エミュレーションの必要性に応じて、適宜、別のシス
テムコールの呼び出しも実施
4. エミュレーション処理終了契機で、システムコー
ル発行元アドレスに直接復帰
– カーネルからの復帰時に、戻りアドレスを調整済み
5. システムコール発行元は、そのまま処理を続行
43. lx ブランド固有処理
• 基本の処理フローは Solaris10 ブランドと同じ
• エミュレーションコード側では、「システムコー
ル番号+1024」無しでのシステムコール呼び
出しを実施
• 何故、エミュレーション処理の無限呼び出し
が発生しないのか?
45. エミュレーション対象形式の隙間
• lx ブランドゾーンのアプリ/ライブラリ
– Linux 由来
– システムコールの発行に ”int 80h” を使用
– エミュレーション対象
• lx ブランド固有ライブラリ
– Solaris 由来
– システムコールの発行に “int 80h” 以外を使用
– エミュレーション対象外
• lx ブランドゾーンでの実行であっても、Solaris 由
来の「lx ブランド固有ライブラリ」が発行するシス
テムコールは、エミュレーション対象から除外
46. 不遇な sysenter 形式
• Linux における “sysenter” 形式の一般向けサポート開
始は 2.6 (released at 2003-12-17)
• OpenSolaris での lx ブランドサポート開始は 2006-09-
11 (コミット日時ベース)
• sysenter 形式を lx ブランドでサポートしなかったのは:
– lx ブランドのサポート対象は 32bit バイナリのみ
– AMD64 は 32bit モードで sysenter 命令を未サポート
– 「ポータブルな 32bit バイナリ」であれば、sysenter を使わ
ない筈、と判断したのかな?
– 安全策として、ENOTSUP 返却とかの対応ぐらいは入れて
おいても良かった気が……
48. SmartOS での “int 80h” 対応の要否
• SmartOS の lx ブランド対応開始に伴い、ブランドゾーンにおける
「エミュレーション対象呼び出し形式」として “int 80h” が復活
• Solaris 系ブランド:
– OpenSolaris 由来の「エミュレーション要否判定」によるエミュレーショ
ンを採用
– “int 80h” 形式はエミュレーションの必要なし
• lx ブランド:
– SmartOS 独自の要否判定ルートからエミュレーションルートに分岐
– 全ての呼び出し形式がエミュレーション対応 (32bit/64bit 両対応)
– OpenSolaris 由来の要否判定テーブルの “int 80h” 欄は NULL
• エミュレーション対象としての “int 80h” 復活は不要なのでは?
– 旧 lx 実装を再利用するため、「lx 実装の破棄」リビジョンを backout
– backout の副作用として “int 80h” 対応が復活
– 「動いているコードを変えるな」原則で既存実装を維持、の流れか?
49. SmartOS でのエミュレーション回避
• SmartOS の lx ブランドは、sysenter や syscall 形式のシステムコー
ルもエミュレーションの対象
• OpenSolaris の lx ブランドのような回避手法が使えない
• 以下の手法でエミュレーションを回避
1. ユーザランドのエミュレーション処理への遷移前に、スレッドを
LX_STACK_MODE_NATIVE でマーク
2. エミュレーション処理で発行したシステムコールは、スレッドの
LX_STACK_MODE_NATIVE 判定により、エミュレーション処理を回避
3. エミュレーション処理終了時は、常にカーネルに戻る
(syscall(SYS_brand, B_EMULATION_DONE) を使用)
4. スレッドを LX_STACK_MODE_BRAND でマーク
5. システムコール呼び出し元に復帰
6. LX_STACK_MODE_BRAND スレッドなので、以後のシステムコールは
エミュレーション処理の対象に
51. SPARC (v9) の場合
• 以下の契機で、システムコールテーブルを、
システムワイドに書き換える
– 最初のブランドモジュールの読み込み
– 最後のブランドモジュールの破棄
54. ユーザ空間エミュレーションは遅い!
• そもそも Solaris はシステムコールのオーバヘッ
ドが大きい
– 「OS毎のシステムコール実行性能 」 by 藤原
http://d.hatena.ne.jp/flying-foozy/20140514/1400090130
• 技術的な理由も色々推測はできる
– エミュレーション処理にバグがあっても、被害の影響
範囲が、ユーザ空間で閉じる
– なんなら、デバッガでアレコレできる
• しかし、どう考えても、ユーザ空間エミュレーショ
ンは性能的に不利
55. むしろライセンス的な理由?
• lx ブランド有効時に Linux 準拠のロジックがカー
ネル空間に置かれるのがまずいのかも?
• Ubuntu がカーネルモジュールとして ZFS を同梱
する際にも、反対意見が諸々あった模様
– “Interpreting, enforcing and changing the GNU GPL, as
applied to combining Linux and ZFS”
by Richard Stallman
https://www.fsf.org/licensing/zfs-and-linux
– “GPL Violations Related to Combining ZFS and Linux”
by Bradley M. Kuhn and Karen M. Sandler
http://sfconservancy.org/blog/2016/feb/25/zfs-and-
linux/
56. SmartOS における lx ブランド
• 冒頭で述べたように、Joyent の SmartOS では lx
ブランドのサポートが再開された
• しかも、一部のシステムコールはカーネル内でエ
ミュレーションを実施!
• 「ユーザランドエミュレーションはライセンス問題
回避のため」との考察は間違っていた?
• カーネル内エミュレーションは以下の様なもの限
定っぽいので、もしかして「“ロジック”は入ってな
いでしょ?」と強弁するつもりか?(笑)
– フラグの値読み替え: e.g. open(2)
– 構造体メモリレイアウトの辻褄あわせ: e.g. stat(2)
59. プロセス起動
1. 起動用ラッパースクリプト経由でコマンド実行
2. 以下の引数で exec() システムコールを実施
– s10_native
– ld.so.1
– ld.so.1 向け -e オプション指定 (s10_npreload.so.1 含む)
– 本来のコマンド+引数
3. ラッパースクリプトはブランド付きプロセスなので
s10_brand.so.1 が事前ロード済み
4. s10_brand.so.1 の exec() のエミュレーション処理に遷移
5. 実行コマンドが s10_native の場合:
– argv[0] を破棄 ⇒ ld.so.1 が実行コマンドに
– brand(B_EXEC_NATIVE) システムコール経由で ld.so.1 をネイ
ティブブランドで実行
60. 起動後処理
1. ld.so.1 による s10_npreload.so.1 読み込み
2. s10_npreload.so.1 の init() 実行
3. brand(B_S10_NATIVE) システムコール実行
4. proc_t のコマンド行/引数情報から、ld.so.1
周りの情報を破棄
5. 以後、以下のコマンドでも、本来のコマンド
行情報を取得可能
– ps
– pgrep
61. 何故こんなに複雑なのか?
• ネイティブ実行の強制にはワンクッション必要
– ブランドの設定は exec(2) 契機でのみ実施
– LD_PRELOAD 機能での init() 処理は、実行ファイルの
exec(2) 後の実施
• ワンクッション置く=ラッパースクリプト経由だと、
ps/pgrep 等が上手く機能しない
– 辻褄あわせが必要 ⇒ brand(B_S10_NATIVE)
• 通常の exec(2) ルートだと、必要のない、エミュ
レーション用のブランドライブラリのロードが発生
– 回避ルートが必要 ⇒ brand(B_EXEC_NATIVE)
63. 実現方式
• zone_enter() システムコールを利用
– 現行プロセスの所属ゾーンを変更
– グローバルゾーンからの変更のみ許可
– ブランド設定は無し
• zone_enter() 利用の実例
– zlogin コマンド (-C 無し時)
– wall コマンド
– ゾーンへのパッケージインストール時の排他
(“pkgadm lock -a” をゾーン内で実行)
64. zlogin の処理フロー
1. 擬似 tty の確保
2. fork() システムコール実施(以下、子プロセス側処理)
3. 擬似 tty のセットアップ
4. zone_enter() システムコールの実施
– 所属ゾーンの変更 ⇒ ファイル参照はゾーンルート相対
– ブランド設定は維持 ⇒ システムコールは Solaris ネイティブ処理
5. 「ログイン」コマンドを exec() システムコールで実行
(コマンド列は当該ゾーンの config.xml から入手)
– lx ゾーンなら login –h zone: ZONENAME USERNAME
– solaris 系ゾーンなら login –z ZONENAME USERNAME
6. 「ログイン」由来プロセスはゾーン側に所属(ブランド付けアリ)
7. 親プロセス側は、グローバル側入出力と、擬似 tty の間で I/O 仲
介をループ
65. zone_enter() プロセスの PID
• zone_enter() 時に proc_t.p_flag |= SZONETOP
• “process has no valid PPID in its zone”
• proc_t 構造上の「親プロセス」参照は維持
• ユーザ空間向けに PPID を返す際には
zsched の PID で差し替える
– /proc/<PID>/psinfo 実装
– getpid(2) 実装
• zone_enter() 以外の SZONETOP 設定契機
– exit(2) 実装: 子プロセスの親プロセス切り替え
67. zlogin –C の処理フロー
1. zoneadmd 起動の保証(※ 理由は後述)
2. UNIX ドメインソケットを以下に conect()
/var/run/zones/ZONENAME.console_sock
3. コマンドラインの入出力と connect() 済みソ
ケットとの間での I/O 仲介をループ
4. ※ 基本的にゾーン側処理には介入しない!
68. zoneadmd による擬似コンソール
1. ゾーン向け仮想コンソールデバイスの作成
– /dev/zcons/ZONENAME/masterconsole (master)
– /dev/zcons/ZONENAME/zoneconsole (slave)
2. UNIX ドメインソケットを以下に bind()
/var/run/zones/ZONENAME.console_sock
3. 仮想コンソールデバイスと bind() 済みソケッ
トとの間での I/O 仲介をループ
4. ※ 基本的にゾーン側処理には介入しない!
69. コンソール連携のシステム構成
• zlogin プロセス ⇔ zoneadmd
– UNIX ドメインソケットによる擬似コンソール
– /var/run/zones/ZONENAME.console_sock
• zoneadmd ⇔ ゾーン内コンソール処理
– zcons デバイス
– /dev/zcons/ZONENAME/masterconsole (master)
– /dev/zcons/ZONENAME/zoneconsole (slave)
• zoneadmd を介した間接アクセスなのは、zlogin
プロセス終了によるコンソール切断等の影響を
排除するためか?
71. zoneadmd によるコンソールへの通知
1. zoneadmd 経由で状態を変更
2. イベント通知用内部 pipe にメッセージ書き込み
3. コンソール I/O 仲介ループで pipe への書き込
みを検出
4. “zlogin –C” 連携用 UNIX ドメインソケットへの
メッセージ書き出し
5. “zlogin –C” 側でメッセージの書き込みを検出
6. 読み込んだメッセージのユーザ側 tty への書き
出し
73. 懸念事項
• 実行可能ファイルの読み込み
– Windows の実行可能ファイル(.EXE)の形式は Solaris
標準の ELF 形式ではない
• システムコールのエミュレーション
– 呼び出し ABI の互換性
– パス区切りや、ドライブ文字、予約ファイル名等の、
ファイルアクセス周りは、システムコールエミュレー
ションで吸収可能な筈
• ウィンドウ/コンソールの取り扱い
• その他
74. Solaris における「実行可能ファイル」
• Solaris は「実行可能ファイル」種別毎にカーネルモジュールを提供
• Solaris では以下の種別が実装&予約済み
– a.out 形式ファイル (SPARC 後方互換向け)
– ELF 形式ファイル
– JAR 形式ファイル
– shebang (“#!”) 付きスクリプトファイル
– シェルバイナリ形式ファイル (“ksh¥0” で始まるファイル)
• ファイル冒頭のマジックナンバー一致で種別判定
• カーネルモジュール追加により、新規種別を追加可能
• 新規に追加可能な種別は、カーネルビルド時固定
– OpenSolaris ソースベースでは4つ
– マジックコード一致の線形探索だから、数を増やしたくないのかな?
75. EXE ファイル対応する場合
• EXE ファイル向けの実行処理を、カーネルモ
ジュールとして実装
• Windows ブランド設定時のみ実行を許可
• ブランド固有処理の一環で以下を実施
– ブランド固有ライブラリの事前読み込み
– Windows *.DLL と UNIX *.so 周りの差異の辻褄併せ
• EXE 形式でも、ブランド固有処理で呼び出す関数
エントリが b_elfexec() なのはご愛嬌(笑)
• EXE 形式解釈ロジックがカーネルモジュールに
含まれる点は、ライセンス的にちょっと不安
76. システムコールABIの互換性
• Win XP 以降のシステムコール呼び出し
– sysenter(@32bit)
– syscall(@64bit)
• x86/AMD64 Solaris も同じ方式
– 常にブランド固有処理にルーティングされる
– 仮に引数/戻り値周りの扱いが違っても、ブラン
ド固有処理で吸収可能な筈
77. ウィンドウ/コンソールの扱い
• zlogin –C や対話的 zlogin は駄目だろうなぁ…
• 「非対話的 zlogin 実行での CUI コマンド実行」
程度なら何とかなるか?
• GUI を使うなら、Windows ブランドゾーン内で
以下のようなプロセスの稼動が必要な筈
– ウィンドウマネージャ
– リモートデスクトップサーバ
78. その他
• bare metal Windows と同等に使うためには、
他にも多数の構成要素が必要
– 認証処理 (ログイン系)
– 承認処理 (権限管理)
– ファイルブラウザ for GUI
– ユーザランド DLL (user32.dll, kernel32.dll etc...)
• Free Software で構成可能な lx コンテナと違
い、必要なバイナリ一式を持ち込むのは、か
なりグレーゾーンぎりぎりな気が……
79. 結論
• ネタとして考える分には「Windows ブランドゾー
ン」は興味深い
• CUI コマンドを zlogin 経由で非対話的に実行す
るあたりまでは、比較的実現性がある
• それ以上の領域は、技術的にも面倒だが、ライ
センス的にヤバそう
• 単にアプリを動かすだけなら、Wine を使う方が
妥当な感じが……
(https://wiki.winehq.org/Wine_Developer%27s_
Guide)
– chroot 併用で、コンテナっぽい雰囲気も出せる筈