$30 off During Our Annual Pro Sale. View Details »

cgroup v2とgVisor

Hiro.Kamezawa
November 30, 2018

cgroup v2とgVisor

ComSys 2018でしゃべったスライドです。音声なしでは非常に不親切なスライドかとは思いますが公開しないのも違うかなと言うことで公開します。

Hiro.Kamezawa

November 30, 2018
Tweet

More Decks by Hiro.Kamezawa

Other Decks in Technology

Transcript

  1. Copyright 2018 FUJITSU LIMITED
    コンテナ技術とLinux
    2018.11.29 ComSys 2018
    富士通株式会社 亀澤 寛之

    0

    View Slide

  2. Copyright 2018 FUJITSU LIMITED
     名前: 亀澤 寛之
     勤務先: 富士通株式会社 (武蔵中原)
     部門: Linux開発統括部
     現在の業務:
    サービスの開発
     来歴
    ~2003年:東京大学(OS研究)
    2003~2012: Linux Kernel(x86)のOSS開発(コミッタ)
    2013~2017: コンテナ関連技術のOSS開発(マネージャ)
    2018~: 新サービス検討・開発(マネージャ)
     Linux開発でコミッタとして主に貢献した機能
     Memory管理⇒ Sparse Memory, Memory Migration, Hotplug.
     Cgroup (v1) ⇒ Memory cgroup の性能チューニング、メンテナ(~2012)
     ps コマンド修正、 /procの表示高速化、oom-killerの改善等
    自己紹介
    1

    View Slide

  3. 今日の話
     Cgroup v1 と v2
     Cgroup v2 とは何か?
     Memory cgroupへの変更を中心にv2への変更を概観
     今年加わった機能
     gVisor
     問題と妄想
     不揮発メモリのある世界
     L7 networkの威力
     組み合わせ爆発の世界
    Copyright 2018 FUJITSU LIMITED
    2

    View Slide

  4. コンテナとカーネル
    Copyright 2018 FUJITSU LIMITED
    コンテナ機能
    (UserLand)
    cgroup
    namespaces
    資源管理
    (CPU,メモリ,etc…)
    名前空間
    (ID,ファイルシステム,etc…)
    SELinux
    AppArmor
    seccomp
    セキュリティ
    3

    View Slide

  5. cgroup v2とは何か?
     cgroup v2 はコンテナ機能を踏まえて、再編されたLinuxのリソースコントローラ
     cgroup v1 のデザインでは機能的に難しかったことを実現できるようにする
     cgroup v1 の“実際にユーザが使ってみて”ダメだったところをちゃんと直す
     cgroup v1 から v2 への主な変更点
    Copyright 2018 FUJITSU LIMITED
    コントローラ毎のツリー構造 全コントローラ共通のツリー構造
    コントローラ毎にバラバラのUI 統一されたUI
    統合しきれなかったコントローラ間連携 統合されたコントローラ間連携
    v1 v2
    v1は汎用のリソースコントローラとして設計されたが
    (汎用を目指しすぎて)バラバラになったものを
    “コンテナ”というメジャーなユースケースを踏まえて再デザインしたもの
    複雑なヒエラルキ処理 Leaf nodeでのみ制御
    4

    View Slide

  6. v1とv2のTree構造の違い
    cgroup v1
    Copyright 2018 FUJITSU LIMITED
    CPU A
    B
    C D
    Memory A
    B
    C
    E
    I/O A
    B
    C
    各資源コントローラ毎に
    バラバラのTree
    cgroup v2
    CPU

    B
    C D
    Memory
    I/O
    Treeは一つ
    各グループ毎にコントローラをセット
    CPU
    Memory
    5

    View Slide

  7. Tree構造変更が解決したバラバラなコントロールの問題
    具体的に、CPU、memory, I/Oコントローラがバラバラだった
    Copyright 2018 FUJITSU LIMITED
    Memory
    CPU I/O
    メモリ回収でCPU消費 Swap out
    Buffered I/O
    Cgroup v1 の限界
    CPUとmemory, I/Oをバラバラにリソースコントロールしようとしてもお互いに影響する。
    ところが、cgroup v1のデザイン上、バラバラに動作するように作り込まないといけない
    例えば、Process A B が同じmemory cgroupで別のI/O cgroupだったらどうするのか?
    その上、v1設計時に想定したような自由度の高い使い方は結果的にほぼなかった。
    v1ではdirect I/Oのみ
    6

    View Slide

  8. ヒエラルキの違い
    cgroup v1
    Copyright 2018 FUJITSU LIMITED

    B
    C D
    process
    process
    process
    cgroup v2

    B
    C D
    process
    process
    Process
    所属不可能
    v2ではプロセスは
    Leaf にのみ所属
    7

    View Slide

  9. 混乱しやすかったヒエラルキ(CPUを例にすると)
    Copyright 2018 FUJITSU LIMITED

    B(100)
    C(100)
    cpu share
    D(100)
    E(100)
    B:C = 1:1
    C=D+E
    D:E=1:1
    Then B:D=2:1
    上記の通りに分配されるのその通りなのだが、
    Cの直下にプロセスが居る場合、
    D+E+Process(C)=Cになり、計算がものすごく面倒
    cgroup v1
    8

    View Slide

  10. 混乱しやすかったヒエラルキ(memory)
    Copyright 2018 FUJITSU LIMITED
    cgroup v1

    B(1G)
    C(1G)
    D(500M)
    E(500M)
    F(1G)
    use_hierarchy=1
    use_hierarchy=0
    G(500M)
    H(300M)
    Memory cgroupの場合、サブツリー毎に
    親子関係を伝搬させるか設定できた
    性能問題が主な理由だったが複雑化
    ツリーの途中にプロセスが居る場合に
    やはり考え方が難しい
    9

    View Slide

  11. cgroup v2のメモリ制御
    cgroup v2では以下の制御を実装
    Copyright 2018 FUJITSU LIMITED
    名前 制御
    memory.max メモリ使用量の上限(ハードリミット)
    ここに達するとフォアグラウンドでメモリ回収実施
    memory.high メモリ使用量の上限(ソフトリミット)
    ここに達するとバックグラウンドでメモリ回収実施
    memory.low メモリの下限(ソフトリミット)
    これ以下の使用量の場合メモリ回収対象になりにくい
    memory.min メモリの下限(ハードリミット)
    これ以下の使用量の場合メモリ回収にならない
    memory.swap.max Swapの上限(ハードリミット)
    これ以上swapを使わない
    ※Documentation/admin-guide/cgroup-v2.rst参照
    10

    View Slide

  12. メモリの回収
     大まかに3種類のメモリ
     ユーザメモリ(ANON) ...... malloc()等で確保され、Page Faultを通して利用
     ページキャッシュ …… ファイルのキャッシュ
     カーネルメモリ …… カーネルが利用する細々とした管理構造やページテーブル・ネットワークバッファ等
     回収法も3通り
     ユーザメモリ …… 参照を全て外して(page tableを書き換えて)スワップアウトし、解放
     ページキャッシュ …… 必要ならストレージに内容を書き戻し、参照を全て外して解放
     カーネルメモリ …… アクティブな参照がないオブジェクトのみのページを解放
     回収の契機
     ページアロケーションの延長、kswapd(カーネルスレッド)、memory limit
    歴史的にはページキャッシュがまず解放候補で、ページキャッシュを管理するためのカーネルメモリなどを
    追い出すのが優先。ただし、“アクセスされない”ユーザメモリのほうが“アクセスのある”ページキャッシュ
    よりも価値は高い(それでもswapは一方的に嫌われるが)。
    Copyright 2018 FUJITSU LIMITED
    11

    View Slide

  13. cgroup v2 のmemory.high
    Copyright 2018 FUJITSU LIMITED
    cgroup v1 cgroup v2
    limit
    usage
    使用量がLimitにヒットしたら
    ヒットしたスレッドで直接メモリ回収
    直接メモリ回収
    max
    usage 間接メモリ回収
    high
    直接メモリ回収
    使用量がhighを超えたら
    バックグラウンド(workqueue)でメモリ回収
    使用量がLimitにヒットしたら
    ヒットしたスレッドで直接メモリ回収
    Limitにヒットしてスレッドが遅延することが減った(cpu課金が少し他所に逃げるが…)
    12

    View Slide

  14. Page Fault
     ページフォルト発生の動作(簡易)
    1. 物理ページの無い仮想アドレスにCPUがアクセスする
    2. カーネルに制御が移る
    3. 仮想アドレス情報からページの属性を求める
    4. ページをアロケーションする
    ⇒メモリが無ければ(直接)メモリ回収処理へ
    5. ページに課金する
    ⇒上限に達していたらメモリ回収処理へ
    6. 実際に獲得したページを仮想アドレスに貼り付ける
    7. ユーザに帰る
    Copyright 2018 FUJITSU LIMITED
    空きメモリが足りないので
    解放可能なページを解放
    “グローバルリクレイム”
    上限に達したので
    解放可能なページを解放
    メモリ管理のv1->v2のポイントの一つは”グローバルメモリ回収“処理
    cgroup v1では保守的な造りになっており、cgroupはメモリ回収を邪魔しない
    cgroup v2ではチューニングで守ることが出来る
    13

    View Slide

  15. Global Reclaimを邪魔しない……の変更
     cgroup v1
    Copyright 2018 FUJITSU LIMITED
     cgroup v2
    メモリを獲得しようとして
    十分にメモリが足らない
    cgroupを
    一つ選択 メモリを追い出し
    例えば、cgroup v1では memory+swapの総量でLimitをかけていたので
    意図的にswapを小さくしてメモリ固定はできない
    メモリを獲得しようとして
    十分にメモリが足らない
    cgroupを
    一つ選択
    追い出し対象に
    なり得るかチェック
    メモリを追い出し
    例えば、cgroup v2では swapの量をLimitできるの
    意図的にswap=0の設定が可能
    保守的にデザインした結果(実装も複雑)
    ユーザは多分賢い
    14

    View Slide

  16. ページキャッシュ
     ページキャッシュの一生(簡易)
    1. processがread()を出す
    2. メモリがアロケートされる。ページキャッシュ(read中)として登録。
    3. ファイルシステム経由でディスクからデータ転送。ページキャッシュ(clean)に。
    4. 誰かがデータを書き込むとページキャッシュ(dirty)になる
    A) 後からカーネルスレッドでwritebackされるとcleanになる
    5. メモリ回収の延長でページキャッシュを解放する
    A) Cleanなら速やかに解放する
    B) DirtyならCleanになるのを待ってから解放する
    Copyright 2018 FUJITSU LIMITED
    ここで、Cleanにする際、I/O連携できないのがcgroup v1で困ったポイント
    cgroup v1の場合、pageを最初に読んだ人が”owner”として紐づけされているが
    blkio cgroupとの関係が不明/流動的でI/Oの課金を誰につけていいのかわからなくなった
    cgroup v2ではTreeが同じことが強制されているので
    メモリのowner=I/Oのownerと捉えて課金する。
    メモリに課金
    I/Oに課金
    I/Oに課金
    15

    View Slide

  17. Writeback
     cgroup管理はMemory cgroupとI/O cgroupで連動
    1. inode を読み込んだ時に、inodeを読み込んだプロセスのmemory cgroupをownerとして登録
    2. memory cgroupのdirty_ratioをトリガにwritebackを起動
    3. memory cgroupがownerのinodeをscan、dirty pageをI/Oに引き渡し
    4. I/Oではmemcg にマッチするblock cgroupを参照し、帯域制御を実施
    ※memcg<->blkcgの関係がcgroup v2では自明になる。
    Copyright 2018 FUJITSU LIMITED
    background_dirty_ratio
    Dirty
    dirty_ratio
    Buffered I/Oではプロセスが利用可能なメモリ
    の一定比率(dirty_ratio)をトリガに
    Writebackを開始
    Writeback起動
    Write()を停止
    16

    View Slide

  18. cgroupv2のメモリ保護と最低保証
    Copyright 2018 FUJITSU LIMITED
    回収すべきNode(Zone)選択
    回収すべきcgroup選択
    回収すべきpage選択
    mem_cgroup_protected()
    MEMCG_PROT_NONE
    - メモリcgroupは保護されていない
    MEMCG_PROT_LOW
    - usage < memory.low
    - メモリcgroupは保護されているが、他から
    メモリ回収できてないなら回収対象となる
    MEMCG_PROT_MIN
    - usage < memory.min
    - メモリcgroupは保護されている
    17

    View Slide

  19. memory.lowのovercommit(minも同じ)
     Tree構造と使用量を計算、実際のmemory.low (effective-low)は以下の計算になる
    (mm/memcontrol.cのコメントとコード参照)
    Copyright 2018 FUJITSU LIMITED
    A
    C
    B
    D
    E
    Low=2G
    Cur=6G
    low_usage
    elow = min( memory.low, parent->elow * -------------------------- )
    siblings_low_usage
    | memory.current, if memory.current < memory.low
    low_usage = |
    | memory.low, otherwise.
    Low=3G,Cur=2G
    Low=1G,Cur=2G
    Low=0G,Cur=2G
    Low=10G,Cur=0G
    今この瞬間だと
    B:elow=約1.3G
    C:elow=約0.7G
    D:elow=約0G
    均等に回収できたとすると、この数値に向かって
    メモリ回収されていく
    ※もちろん動的に再計算される
    18

    View Slide

  20. 最近加わった機能
     I/O Latency Protection
     OOM Group
     PSI
     おまけ①:Synchronous swap-in
     おまけ②:seccomp
    Copyright 2018 FUJITSU LIMITED
    19

    View Slide

  21. I/O Latency Protection
     I/O Latencyについてカーネルにヒントを与える
    (Documentation/admin-guide/cgroup-v2.rst参照)
     コントロール
    #echo Major:Minor us > io.latency file
     “平均” Latencyをターゲットに向けて制御
     平均レイテンシが目標値を超えている場合、他のI/Oより優遇して処理される
    Copyright 2018 FUJITSU LIMITED
    A
    C
    B
    D

    E
    この間でバランス
    この間でバランス
    • ライバルのQueueを浅くする
    • ライバルに遅延を挿入する
    20

    View Slide

  22. OOM Group
    Memory cgroupの機能の一部
    OOM-Killer発生時、cgroup内のプロセスを纏めてKill
    OOM-Killer
    メモリ枯渇時にプロセスを殺す
    まとめて殺す意味
    例えば、あるコンテナのなかに複数のプロセスが居る場合
    •一つが殺されただけで、そのコンテナの担う業務がまともに動作しない
    •監視対象になっていないプロセスが殺された場合、検知が遅れる
    ⇒まとめて殺した方が良い
    Copyright 2018 FUJITSU LIMITED
    21

    View Slide

  23. PSI
     ワークロードのサイジングのヒント情報の表示。cgroup単位にも表示
     Documentation/accounting/psi.txt
     /kernel/sched/psi.c
     PSI: Pressure Stall Information
     CPUやメモリ、I/O がリソース競合状態になったかどうかを報告
    具体的には以下の状況が起きている時間をカウントし、全体の時間に対する比率を報告する
     SOME, FULLの2つの指標について報告される
     SOME : IDLEではないいくつかのタスクがStallしている
     FULL: 全てのIDLE出ないタスクがStallしている
    Copyright 2018 FUJITSU LIMITED
    cpu: some tasks are runnable but not executing on a CPU
    memory: tasks are reclaiming, or waiting for swapin or thrashing cache
    io: tasks are waiting for io completions
    どう見ればいいかは
    自分で探す
    22

    View Slide

  24. おまけ①Synchronous swap-in
    従来のswap-in
    1. Page faultする
    2. メモリ獲得
    3. SwapCacheとして登録
    4. 非同期I/O(readahead付)
    5. メモリにマップ
    Copyright 2018 FUJITSU LIMITED
    Synchronous swap-in
    1. Page faultする
    2. メモリ獲得
    3. 同期I/O
    4. メモリにマップ
    メモリ系デバイスをswap deviceに設定すると自動で有効化
    スワップインのコストを削減
    (パッチのレポートでは45%削減とのこと)
    23

    View Slide

  25. おまけ②seccomp
    https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt
    SECCOMP_FILTER
    BPFを利用してシステムコールにフィルタプログラムを仕込む
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog);
    do_syscall64()->syscall_trace_enter()からキャッチ
    利用にはライブラリの利用が一般的
    https://github.com/seccomp/libseccomp
    https://github.com/seccomp/libseccomp-golang
    Copyright 2018 FUJITSU LIMITED
    ※getpid()にエラーを返させる
    call, err := GetSyscallFromName("getpid")
    err = filter1.AddRule(call, ActErrno.SetReturnCode(0x1))
    err = filter1.Load()
    pid := syscall.Getpid()
    24

    View Slide

  26. セキュアなコンテナ
    コンテナを取り巻くセキュリティの問題
    そもそものイメージの信頼性
    セキュアな情報の渡し方
    “root”権限で動きたいアプリケーション達
    呼んでほしくないシステムコール
    Spectreのような “snoop”リスク
    Copyright 2018 FUJITSU LIMITED
    CNCFでTUF/Notaryという
    プロジェクトを支援
    暗号化
    権限細分化
    seccomp
    完全仮想化
    今日はgVisorの話
    25

    View Slide

  27. gVisor
    システムコールを横取りする
    Copyright 2018 FUJITSU LIMITED
    Application
    gVisor
    Kernel
    Systemcallを横取り
    ※いまのところcgoupv2に対応していない
    26

    View Slide

  28. システムコールを横取りする
    Copyright 2018 FUJITSU LIMITED
    Application
    System Call
    Ptrace フック(PTRACE_EMU)
    Sentry
    Kernel
    Gofer
    ファイルアクセスを代行
    seccomp
    再実装したシステムコール
    gVisorのUserLand
    IP層
    ネットワーク
    • Ptrace()の場合,UML
    みたいなもの
    • システムコールを代行
    • 書き換えて独自発行
    • フィルタリング
    • ネットワークスタック独自実装
    • DHCP/TCP等を実装
    • Checkpoint/restart実装
    GoferとSentryで
    seccomp設定が異なる
    Raw socket
    27

    View Slide

  29. 基本形
    KVM等と構造は同じ
    Copyright 2018 FUJITSU LIMITED
    アプリ実行 Sentry
    トラップ
    継続
    システムコール代行
    CPUID命令のエミュレーション
    シグナル処理
    ……
    Ptrace() or KVM
    28

    View Slide

  30. Sentryでのシステムコールの代行
    単にカーネルに渡すのではなく再実装的
    getrandom(2)
    _GRND_NONBLOCK|_GRND_RANDOMを無視
    syslog(2)
    非破壊的なカーネルバッファの読み出しのみ許可
    mount(2)
    remountやbind mountなどを不許可
    madvise(2)
    ユーザからカーネルに与えるヒントは色々無視
    他にmsync(2)でMS_INVALIDATEを無視するなど
    Copyright 2018 FUJITSU LIMITED
    29

    View Slide

  31. mmapとpage faultの概略
    1. mmap() : Sentry内にセグメントのオブジェクトを作成
    1. Sentry 内でfilemapを獲得、PTRACE_SYSCALLでゲストのコンテキストでmmap()
    2. App 側で同じものをマップする。
    2. page fault 発生
    カーネルがページフォルトを処理
    Copyright 2018 FUJITSU LIMITED
    Sentry VMA: VA:0x400000 -> /tmp/foo:0x0
    Sentry filemap: /tmp/foo:0x0 -> host:memory-file:0x3000
    Host VMA: VA:0x400000 -----------------> host:memory-file:0x3000
    Sentry VMA: VA:0x400000 -> /tmp/foo:0x0
    Sentry filemap: /tmp/foo:0x0 -> host:memory-file:0x3000
    Host VMA: VA:0x400000 -----------------> host:memory-file:0x3000
    Host filemap: host:memory-file:0x3000 -> PA:0x2fb000
    Host PTE: VA:0x400000 --------------------------------------------------> PA:0x2fb000
    30

    View Slide

  32. GoferのI/O(read)
    Copyright 2018 FUJITSU LIMITED
    アプリのread()
    エミュレート
    トラップ
    Sentry
    App
    v9fs server
    Network conn
    tmpfsや/sys, /proc等ほぼエミュレート。
    例えば cpuinfoもモデル名なども見せない
    Gofer
    Mountの際にセットアップ
    ※v9fs protocol
    ホストアクセス
    31

    View Slide

  33. Network横取り
     Sentry起動時にはIP Addressを扱うためのデバイスは準備
     アプリケーションがsocket()でTCPソケットを作る
     Ptrace()がsocket() システムコールを横取り、内部実装へ飛ばす
     Netstackライブラリ がtcpipを初期化する
     Userlandでtcpが処理される
    Copyright 2018 FUJITSU LIMITED
    32

    View Slide

  34. 気になる所
    GAEだとプロダクション
    インプリされてないので使えないシステムコールがある
    システムコールが(まだインプリされてないので)動きが変わる
    fdatasync()がfsync()になる、syncfs()がsync()になるとか
     アプリのコアダンプがない
     TCP/UDPがユーザランド実装なので例えば/procに情報が出ない
     Mlock系がない(swapないけど)、madvise()系もない、splice()系がない
     SYSVIPCのshm/semはあってmqueueがない
     SOCK_RAWは使えないので pingやtcpdumpが困る
    Copyright 2018 FUJITSU LIMITED
    LinuxのABIを持つ別のOS.
    今使うなら用途は選ぼう
    33

    View Slide

  35. Checkpoint/Restart
    gVisorがsystemcallを再実装している大きな恩恵
     必要なステートも全てgVisor内にある
    https://github.com/google/gvisor/tree/master/pkg/sentry/kernel
    Copyright 2018 FUJITSU LIMITED
    34

    View Slide

  36. 今回、この資料を作っていて考えたコト
    問題と妄想
    Copyright 2018 FUJITSU LIMITED
    35

    View Slide

  37. 不揮発メモリのある世界
    不揮発メモリの使われ方は主に3種類
    ストレージの代替品
    メモリの代替品
    ファイルシステム直接アクセス(DAX)
    コンテナ的・メモリ管理的に気になる所
    よくあるケース:“永続”ストレージはリモート。ローカルストレージは不揮発メモリで高速
    ジレンマ
    •データをローカルに置くとコンテナの位置が縛られる
    •Swapの方がファイルキャッシュより安価?
    •I/OコントローラはNetwork 経由のI/Oをどう扱うか?扱えないか?
    •何をDIMMに置いて、何を不揮発メモリに置くべきか?
    •cgroup制御はどうあるべき?
    Copyright 2018 FUJITSU LIMITED
    36

    View Slide

  38. L7 networkの威力
    インターネットはオープン
    特に企業内・企業間ネットワークでは“閉じた”インターネットを使いたい
    インフラ屋の発想⇒ SDN(L2/L3)
    アプリ屋の発想⇒サービスメッシュ/Queue
    “つなぐ”というのは単にデータが通るだけではなく、末端やアプリから見た機能が大事
    L7 networkベースの発想がますます重要に
    コネクション接続のコストがバカにできない
    暗号化された世界でのキャッシュのさばき方
    遠距離のコネクション
    UDPベースのHTTP/3 も登場
    Copyright 2018 FUJITSU LIMITED
    37

    View Slide

  39. アジリティ?
    Copyright 2018 FUJITSU LIMITED
    設計
    開発
    運用 検証
    評価
    コンテナ/クラウド:検証を早くする
    トライアンドエラー

    ビジネス日程
    一日に何度もトライできるとして・・・・
    どう設計する??
    38

    View Slide

  40. 組み合わせ爆発の世界
     NUMA、マルチラック、マルチクラスタ、マルチデータセンタ、マルチクラウド
     揺らぎ
     マシン配置・インターネット・NUMA・キャッシュ ………
     共有リソース
     制約条件
     性能要件
     安全性要件
     コスト要件
     データ・サービス間のアフィニティ
     キャパシティ
    ………..
     GoogleのAuxon
     超大規模のトポロジ―パターン数でモンテカルロシミュレーション
     https://ai.google/research/pubs/pub45385
    Copyright 2018 FUJITSU LIMITED
    39

    View Slide

  41. https://ai.google/research/pubs/pub45385
    Copyright 2018 FUJITSU LIMITED
    40

    View Slide

  42. 今日のまとめ
    cgroup v2 では v1の問題点を積極的な変更で改善
    gVisor はLinuxで動き、Linux ABIを持つ新OS。面白いので読んでみると◎
    今は使いどころを選ぼう
    サポートしろと言われると頭が痛い
    全体的に見て、まだまだやることはありそう。
    OS観点だと新ハードが出るとやることが増える
    アプリ観点だとインフラに依存しないやり方が結局効くのか?
    クローズドな環境で確率的に安定状態を取る世界をどう取り扱うか?
    OS屋の花形がスケジューラだとすると、お題としては挑戦的かもしれない
    Copyright 2018 FUJITSU LIMITED
    41

    View Slide

  43. View Slide