1. カーネル空間およびユーザランドのソフトウェアの開発技術

  • オペレーティングシステム(基本ソフトウェア)はカーネルとユーザランドに大別
  • カーネルとユーザランドでは、CPU使用時の特権レベル、メモリアドレス空間が異なる
  • カーネルは高いCPU特権レベルで動作し、システムの各種のリソースの管理やハードウェアの直接制御などの低位の処理をおこなう
  • ユーザランドは低いCPU特権レベルで動作し、必要に応じてカーネルの機能をシステムコール経由で利用する
  • カーネルのメモリアドレス空間は、ユーザランドのプロセスからは保護されており(逆はアクセス可能)、仮想メモリのスワップ領域へスワップアウトされない
  • ユーザランドの個々のプロセスのアドレス空間は、他のプロセスから保護されていおり、必要に応じて仮想メモリのスワップ領域へスワップアウトされる
  • 両者は動作原理が異なるため、その開発に必要な技術も異なってくる

Next


システムコールによるカーネルとユーザランドの連携

  • システムコールは一般的にソフトウェア割り込みで実現され、CPUが特権レベルをカーネルモードへ切り替えてカーネル内部の処理を実行する
  • システムコール呼び出し元のプロセスは、コンテキスト(レジスタやスタックなどの状態)が退避されプロセスの実行は中断される(コンテキストスイッチ)
  • カーネル内部の処理でハードウェアの操作があり処理時間を要する場合、他のプロセスなどの処理へ制御を切り替える
  • ハードウェアの処理完了は割り込みによって通知され、カーネルはハードウェアからデータを読み出すなどの処理を再開する
  • カーネル内部の処理が完了すると、システムコール呼び出し元の中断されたプロセスは実行可能キューへ登録され、カーネルは他の処理へ制御を切り替える
  • カーネルが中断されたプロセスを実行可能キューから選択してそのプロセスに実行権が移ると、ユーザモードのCPU特権レベルで保存していたコンテキストを復元し中断直後の状態から実行が再開される
  • ユーザランド カーネル ハードウェア
    システムコール発行
    プロセス実行中断
    システムコール処理実行
    ハードウェア操作
    他の処理へ切り替え 処理実行
    他の処理の実行 処理完了
    ハードウェア割り込み
    他の処理の実行中断
    割り込みハンドラ実行
    システムコール処理再開
    システムコール処理完了
    プロセスを実行可能キューへ登録
    他の処理へ切り替え
    他の処理の実行完了
    実行可能キューからプロセス選択
    プロセス実行再開

Next


カーネルとユーザプロセスの仮想メモリ空間

  • FreeBSD/i386のカーネルとユーザプロセスの仮想メモリ空間の割り当て
  • アドレス カレントプロセス 待機プロセス
    0x00000000 ユーザプロセス text .... ....
    data .... ....
    bss .... ....
    heap .... ....
    空き .... ....
    stack .... ....
    0xbfbfe000 プロセス管理情報
    カーネル用stack(4+4KB)
    .... ....
    0xbfc00000 カーネル ページテーブル
    0xc0000000 KERNELBASE
    0xc011cc80 text
    data
    bss
    heap
    空き
    0xffffffff
  • 個々のユーザプロセスは約3GBの仮想メモリ空間を使用可能
  • すべての仮想メモリ空間に物理メモリが割り当てられるわけではなく、必要な分だけがページ(4KB)単位で割り当てられる
  • 仮想メモリ空間はいくつかの領域に分けられている
  • 領域名 説明
    text 実行形式のマシン語格納領域
    data 実行形式の初期化済み変数格納領域
    bss 実行形式の未初期化変数格納領域(プロセス生成時に0で初期化される)
    heap 動的に確保可能なメモリ領域で、アドレス増加方向へ成長
    stack サブルーチン呼び出しなどで消費・返却される領域で、アドレス減少方向へ成長

Next


オペレーティングシステムの基本構造

     +--------------------------------------------------+
     |+------------------------------------------------+|
     ||             ユーザランドプログラム             ||
     |+------------------------------------------------+|
     |             ^          +------------------------+| ユーザプロセス空間
     |             |          |       ライブラリ       ||
     |             |          +------------------------+|
     +-------------|-----------------------^------------+
                   |                       |             
     +-------------v-----------------------v------------+
     |+------------------------------------------------+|
     ||           システムコールインタフェース         ||
     |+------------------------------------------------+|
     |+--------------------+  +------------------------+|
     ||    プロセス管理    |  |    ファイルシステム    ||
     |+--------------------+  |  [バッファキャッシュ]  ||
     |                        +------------------------+|
     |+--------------------+  +------------------------+| カーネル空間
     ||     メモリ管理     |  |    ネットワーク層      ||
     |+--------------------+  +------------------------+|
     |                                                  |
     |+--------------------+  +------------------------+|
     ||   アーキテクチャ   |  |    デバイスドライバ    ||
     ||    依存コード    |  |                        ||
     |+--------------------+  +------------------------+|
     +-----------^-------------------------^------------+
                 |                         |             
                 v                         v             
     +--------------------------------------------------+
     |                  ハードウェア                    |
     +--------------------------------------------------+
    

Next


プログラム開発の視点からの比較

  • カーネルおよびユーザランドのプログラム開発の視点からの比較を以下にまとめる
  • カーネルユーザランド
    開発言語C言語、アセンブリ言語など
    比較的低位の伝統的言語に限定
    制限なし
    日進月歩でより高生産性の言語が誕生
    ライブラリ専用の伝統的ライブラリに限定 高生産性の言語ごとのライブラリやフレームワークが豊富
    開発環境テキストエディタ、make 統合開発環境など充実
    テスト自動化特に無し テスト支援ツール、カバレッジ取得ツールなど豊富
    デバッグprintf、カーネル組み込みデバッガ、クラッシュダンプ解析 各言語ごとにソースレベルのリアルタイムのデバッグ環境が用意
    例外発生時システムクラッシュ(システム停止)原因調査、修正後に即再実行可能
  • カーネルは生産性向上のための道具が乏しく、また不具合があった場合のデバッグの困難さから、初期段階からの品質が重要となる
  • ユーザランドはある程度のスキル不足は道具がカバーしてくれるものの、道具を使いこなせるかで生産性・品質に大きな差が生じる

Next


カーネルソースコードの構成

  • FreeBSD/i386のカーネルソースコードの構成
  • 分類ステップ数 (K steps)概要
    アーキテクチャ 非依存コード690 システムコールインタフェース、プロセス管理、ファイルシステム、メモリ管理、ネットワーク層など
    アーキテクチャ 依存コード110 アセンブリ言語関数、ISA/PCIバス、仮想記憶など
    デバイスドライバ850 増えつづける数百におよぶデバイスをサポート
  • Linuxにおいても同様の傾向
  • 最新のデバイスをサポートするため、デバイスドライバのコード量は増加しつづけている

Next


デバイスドライバ

  • デバイスドライバの基本構成は以下の3つの部分からなる
    • 検出、初期化ルーチン
    • デバイスドライバTop Half
    • デバイスドライバBottom Half

  • 検出、初期化ルーチン
  • ハードウェアデバイスの存在を検出(probe)し、デバイス自身およびソフトウェア状態を初期化する。

  • デバイスドライバTop Half
  • システムコールインタフェース経由の入出力要求を処理し、必要に応じてカレントプロセスを中断させる(tsleep()を使用)。

  • デバイスドライバBottom Half
  • システムが割り込みを受け取った時に呼び出される割り込みハンドラであり、中断させたプロセスを再開させる(wakeup()を使用)。

                                                                   
                                                                   
     入力要求                                      出力要求        
          |                                             |          
          |   +-------------------------------------+   |          
          |   |+------------+         +------------+|   |          
          |   ||入力バッファ|         |出力バッファ|<---+          
          |   |+------------+         +------------+|   Upper Half 
          +-->| --+       ^                      |  |              
        ----------|-------|----------------------|----------       
              |   |     +----------------+       |  |              
              |   |     |割り込みハンドラ|       |  |    Bottm Half
              |   |     +----------------+       |  |              
              +---|-------^------------^---------|--+              
                  v       |            |         v                 
                +---------------------------------+                
                |      ハードウェアデバイス       |                
                +---------------------------------+                
    

Next


カーネルのデバッグ

  • カーネルのデバッグの手法
    • printf()またはBEEP音
    • Cコンパイラにアセンブリ言語ソースを生成させる
    • カーネル組み込みデバッガ
    • ソースレベルカーネルデバッガ

  • printf()またはBEEP音
  • どこまで実行が進んだのか、あるいはどこで変数の内容が破壊されたのかを調べる原始的な方法。 突然リブートして出力内容を確認できない場合、調べたい個所に無限ループやhlt命令を埋め込む。 printf()が使えない局面(ブートストラップの初期段階やスタンバイ状態からの復帰など)では、 替わりにBEEP音を鳴らしてどこまで処理が進んでいるかを探索する。

  • Cコンパイラにアセンブリ言語ソースを生成させる
  • 机上デバッグの段階でC言語ソースのレベルでは気づきにくいバグも、アセンブリ言語ソースの レベルで調べると判明するバグも多い。 カーネルのビルドにおいて、"cc -c"となっているところを"cc -S"に変えてアセンブリ言語ソースを生成させる。

  • カーネル組み込みデバッガ
  • カーネルに内蔵されているアセンブリ言語レベルの簡易デバッガとしてFreeBSDにはDDBがある。 カーネル内部処理での例外発生時にリブートする前に起動される。また、コンソールから「Ctrl+Alt+Esc」を 入力することで、手動で起動可能。

  • ソースレベルカーネルデバッガ
  • FreeBSDにはシステムクラッシュ時に実メモリの内容を専用パーティションへ書き込み、リブート後に その内容をクラッシュダンプとしてファイルへ格納し、gdbによるソースレベルの解析をおこなう。 シリアルケーブル(NULLモデムケーブル)で接続されたPC側でgdbを起動してオンラインカーネルデバッグをおこなうことも可能。

Next


カーネル組み込みデバッガ: DDB

  • DDBの主なコマンド
  • コマンド 説明 使用例
    break ブレークポイントを設定 b seq_write
    delete ブレークポイントを解除 d seq_write
    continue 次のブレークポイントまで実行を継続する c
    show registers レジスタセットを表示する
    trace スタックトレースを表示する tr
    step シングルステップ実行(step into) s
    next 次のサブルーチンのreturnまで実行(step over) n
    examine 指定されたアドレスの内容を表示 x sequence_number
    write 指定されたアドレスに値を格納 write sequence_number 10
    call 指定されたサブルーチンをcallする call sync
    call boot
    help コマンド一覧を表示

Next


ソースレベルカーネルデバッガ: KGDB

  • KGDBはgdbのカーネルデバッグ用のフロントエンドであるため、使用方法の詳細はgdbのマニュアルを参照。
  • KGDBの使用例
  • [root@freebsd ~]# cd /sys/i386/compile/VMWBSD
    [root@freebsd /sys/i386/compile/VMWBSD]# kgdb kernel.debug /var/crash/vmcore.0
    [snip]
    panic: free: address 0xce747c00(0xce747000) has not been allocated.
    
    KDB: enter: panic
    Uptime: 2m48s
    Dumping 191 MB (3 chunks)
      chunk 0: 1MB (159 pages) ... ok
      chunk 1: 190MB (48624 pages) 174 158 142 126 110 94 78 62 46 30 14 ... ok
      chunk 2: 1MB (256 pages)
    
    #0  doadump () at pcpu.h:165
    165             __asm __volatile("movl %%fs:0,%0" : "=r" (td));
    (kgdb) bt
    #0  doadump () at pcpu.h:165
    #1  0xc06797d6 in boot (howto=260) at ../../../kern/kern_shutdown.c:409
    #2  0xc0679a9c in panic (
        fmt=0xc0904e1a "free: address %p(%p) has not been allocated.\n")
        at ../../../kern/kern_shutdown.c:565
    #3  0xc066ec46 in free (addr=0xce747c00, mtp=0xc0979ba0)
        at ../../../kern/kern_malloc.c:374
    #4  0xc05c7172 in seq_write (dev=0xc1d82200, uio=0x64, flag=0)
        at ../../../dev/seq/seq.c:97
    #5  0xc062e5ec in devfs_write_f (fp=0xc1ef1af8, uio=0xce747cbc,
        cred=0xc1d25d00, flags=0, td=0xc1e4d780)
        at ../../../fs/devfs/devfs_vnops.c:1290
    #6  0xc069c50f in dofilewrite (td=0xc1e4d780, fd=1, fp=0xc1ef1af8,
        auio=0xce747cbc, offset=Unhandled dwarf expression opcode 0x93
    ) at file.h:252
    #7  0xc069c3b3 in kern_writev (td=0xc1e4d780, fd=1, auio=0xce747cbc)
        at ../../../kern/sys_generic.c:402
    #8  0xc069c2d9 in write (td=0xc1e4d780, uap=0x0)
        at ../../../kern/sys_generic.c:326
    #9  0xc08908f3 in syscall (frame=
          {tf_fs = 59, tf_es = 59, tf_ds = 59, tf_edi = 0, tf_esi = 134651904, tf_eb
    p = -1077941192, tf_isp = -831226524, tf_ebx = 4, tf_edx = 134651904, tf_ecx = 4
    , tf_eax = 4, tf_trapno = 12, tf_err = 2, tf_eip = 672805679, tf_cs = 51, tf_efl
    ags = 66118, tf_esp = -1077941236, tf_ss = 59})
        at ../../../i386/i386/trap.c:983
    ---Type  to continue, or q  to quit---q
    Quit
    (kgdb) up
    #1  0xc06797d6 in boot (howto=260) at ../../../kern/kern_shutdown.c:409
    409                     doadump();
    (kgdb) up
    #2  0xc0679a9c in panic (
        fmt=0xc0904e1a "free: address %p(%p) has not been allocated.\n")
        at ../../../kern/kern_shutdown.c:565
    565             boot(bootopt);
    (kgdb) up
    #3  0xc066ec46 in free (addr=0xce747c00, mtp=0xc0979ba0)
        at ../../../kern/kern_malloc.c:374
    374                     panic("free: address %p(%p) has not been allocated.\n",
    (kgdb) up
    #4  0xc05c7172 in seq_write (dev=0xc1d82200, uio=0x64, flag=0)
        at ../../../dev/seq/seq.c:97
    97              free(&seq_buf, M_TEMP);
    (kgdb) up
    #5  0xc062e5ec in devfs_write_f (fp=0xc1ef1af8, uio=0xce747cbc,
        cred=0xc1d25d00, flags=0, td=0xc1e4d780)
        at ../../../fs/devfs/devfs_vnops.c:1290
    1290            error = dsw->d_write(dev, uio, ioflag);
    (kgdb) down
    #4  0xc05c7172 in seq_write (dev=0xc1d82200, uio=0x64, flag=0)
        at ../../../dev/seq/seq.c:97
    97              free(&seq_buf, M_TEMP);
    (kgdb) list
    92                      sequence_number = val;
    93                      sx_xunlock(&sequence_lock);
    94                      break;
    95              }
    96
    97              free(&seq_buf, M_TEMP);
    98
    99              return (error);
    100     }
    101
    (kgdb) p sequence_lock
    $1 = {sx_object = {lo_class = 0xc097cb0c, lo_name = 0xc08f7d63 "sequence",
        lo_type = 0xc08f7d63 "sequence", lo_flags = 3866624, lo_list = {
          tqe_next = 0x0, tqe_prev = 0x0}, lo_witness = 0x0},
      sx_lock = 0xc09e51e0, sx_cnt = 0, sx_shrd_cv = {
        cv_description = 0xc08f7d63 "sequence", cv_waiters = 0},
      sx_shrd_wcnt = 0, sx_excl_cv = {cv_description = 0xc08f7d63 "sequence",
        cv_waiters = 0}, sx_excl_wcnt = 0, sx_xholder = 0x0}
    (kgdb) quit
    

Next


[演習] 擬似デバイスドライバ seq の開発とデバッグ

  • 符号無し整数のシーケンス番号を生成するドライバ
  • デバイススペシャルファイル /dev/seq を読み出すことで、次のシーケンス番号が文字列で一行取得できる
  • 数値の文字列を /dev/seq に書き込むと、次のシーケンス番号を設定できる
  • シーケンス番号が最大値に達すると初期値の0に戻る
  • 基本的にアーキテクチャ非依存であるが、シーケンス番号の最大値はアーキテクチャ依存(32ビットでは0xffffffff、つまり10進数で4,294,967,295)
  • 使用例
  • $ head -1 /dev/seq
    0
    $ head -1 /dev/seq
    1
    $ cat /dev/seq
    2
    3
    4
    (中略)
    123456
    ^C
    $
    

Next


[演習] 擬似デバイスドライバ seq の開発とデバッグ(続き)

  • ソースコードの主要部分
  •     34  static struct sx        sequence_lock;
        35  static unsigned long    sequence_number = 0;
        36  static char             sequence_buffer[DEV_BSIZE];
    [snip]
        47  /* ARGSUSED */
        48  static int
        49  seq_read(struct cdev *dev __unused, struct uio *uio, int flag)
        50  {
        51          int c = 0, error;
        52          void *seq_buf;
        53
        54          sx_xlock(&sequence_lock);
        55          snprintf(sequence_buffer, sizeof(sequence_buffer),
        56                  "%lu\n", sequence_number);
        57          seq_buf = (void *)sequence_buffer;
        58
        59          c = strlen(sequence_buffer);
        60          error = uiomove(seq_buf, c, uio);
        61          if (error == 0)
        62          {
        63                  sequence_number++;
        64          }
        65
        66          sx_xunlock(&sequence_lock);
        67          return (error);
        68  }
    
  • ソースコードの修正とカーネルのリビルドおよびインストール手順
  • 
    # cd /sys/dev/seq
    # vi seq.c
    
    # cd /sys/i386/compile/VMWBSD
    # make
    # make install
    # shutdown -r now
    
    

Next


[演習] 擬似デバイスドライバ seq の開発とデバッグ(続き)

  • ミッション1
  • 現在、シーケンス番号を生成する関数 seq_read() は問題なく動作しているが、シーケンス番号を設定する関数 seq_write() はシステムクラッシュ(panic)を引き起こす。 カーネル組み込みデバッガDDBおよびカーネルデバッガKGDBを使用して原因を調査し、デバイスドライバのソースコードを適切に修正せよ。

  • ミッション2
  • シーケンス番号の最大値は32ビットとなっているが、これを64ビットまで拡張したい。 この要望に合うようにデバイスドライバのソースコードを適切に修正せよ。

  • ミッション3
  • デバイスドライバ seq のソースコードを参考に、POSIX Epoch (1970年1月1日0:00(GMT))からの経過秒数を示す文字列を 取得するデバイスドライバ time を作成せよ。

    カーネル内部の現在時刻の取得にはシステムコールインタフェース /sys/kern/kern_time.c:gettimeofday()、 経過秒数への変換はライブラリtime(3)のソース /usr/src/lib/libc/gen/time.c を参考にする。

    なお、新規のデバイスドライバのソースコードの組み込みには、/sys/conf/files、/sys/i386/conf/VMWBSDにある seq 用の記述を参考にする。

    また、新規デバイスを追加したので、カーネルの再構築とリビルドを以下の手順でおこなう必要がある。

    # rm -rf /sys/i386/compile/VMWBSD
    # cd /sys/i386/config
    # vi VMWBSD     ←device timeを追加
    # config VMWBSD
    # cd ../compile/VMWBSD
    # make cleandepend; make depend
    # make
    # make install
    # shutdown -r now
    

Next


2. RDBMSとJavaによるWebアプリケーションの開発

  • Webアプリケーションの代表的要素技術の変遷
    • CGI (Common Gateway Interface)
    • Webの機能を動的に拡張するために考案されたサーバサイド技術。プロセス生成・消滅のオーバヘッドや状態保持の問題。

    • ASP (Active Server Pages)
    • Microsoftによって開発された技術で、HTMLにVBScriptを埋め込んだページをWebサーバのモジュールであるスクリプトエンジンによって実行する。 MS IISが前提環境。

    • JavaApplet
    • クライアントサイドの技術で、SunMicrosystemsによって開発されたJava言語で記述されたアプリケーションを、 Webブラウザ上にダウンロードして実行させる技術。 生産性、マルチメディア(特に音声や動画)の扱いが劣ることから、競合技術であるFlashにシェアを奪われる。

    • JavaScript
    • クライアントサイドの技術で、Netscape 社が開発したスクリプト言語で、Webブラウザ側にあるインタプリタで実行される。 Java言語とは互換性がまったく無い。Ajaxの基盤技術の一つになっている。

    • Servelet
    • JavaクラスをWebサーバへロードして、メソッドを呼び出すことで動的な応答をさせる技術。 Javaプログラムのメソッド内でHTMLを生成するため、ページのデザインが難しい。

    • JSP (Java Server Pages)
    • ASPに対抗して開発された技術で、ASPと同様にHTMLにJavaコードを埋め込む。Servletの弱点を克服。 ロジックとデザインの分離を可能とし、さまざまなプラットフォームで稼動できる。

    • ASP.NET
    • ASPやJSPで実現されたロジックとデザインの分離を、より明確にブロック分けしたMicrosoftによって開発された技術。 開発言語としてC#、VBなど複数のものが選択可能となり、どの言語も同一のライブラリである.NET Frameworkが使用できる。

    • Ajax (Asynchronous JavaScript and XML)
    • JavaScriptを主軸とした複合技術によるサーバとの非同期通信というアプローチをとった技術で、ページ遷移を行わずにコンテンツの動的更新が可能となる。

Next


Webアプリケーションの典型的構成

  • Web 3層構成
  •                                                                         
      +---------------+          +--------+   +--------+   +--------+       
      |Webクライアント|<-------->|        |   |        |   |        |       
      +---------------+          |        |   |        |   |        |       
                                 |        |   |        |   |        |       
                                 |        |   |        |   |        |       
      +---------------+          |  Web   |   |   AP   |   |   DB   |      
      |Webクライアント|<-------->| サーバ |<->| サーバ |<->| サーバ |       
      +---------------+          |        |   |        |   |        |       
                                 |        |   |        |   |        |       
                                 |        |   |        |   |        |       
                                 |        |   |        |   |        |       
      +---------------+          |        |   |        |   |        |       
      |Webクライアント|<-------->|        |   |        |   |        |       
      +---------------+          +--------+   +--------+   +--------+       
                                                                            
    
  • Webサーバ
  • WebクライアントからのHTTPによるアクセス要求を分散処理する。

  • APサーバ
  • HTTPトランザクションの一貫性を保持し、システム固有の処理を行い、バックエンドで動作するデータベースなどの検索/加工処理などを司る。 今回の演習ではTomcatを使用する。

  • DBサーバ
  • システムのデータや管理情報を司る。今回の演習ではPostgreSQLを使用する。

Next


[演習]会議室予約システムの仕様

  • ユーザIDとメールアドレスを入力して、アカウント作成またはログイン
  • Enter your user name and mail address

    User Name Mail Address

    Or create a new account

    User Name Mail Address
  • 会議室の一覧を確認し、予約したい期間を入力
  • (User ID = 2)

    List of rooms

    NameCapacity
    room#0110
    room#0210
    room#0320
    room#0420
    room#0530

    From: Date Time
    To : Date Time

Next


[演習]会議室予約システムの仕様(続き)

  • 予約したい期間に空いている会議室の検索結果から、予約したい会議室を選択し予約確定
  • (User ID = 2)

    Available rooms

    NameCapacity
    room#0110
    room#0210
    room#0320
    room#0420
    room#0530

    From: Date Time
    To : Date Time

    Room(s):
  • 予約内容を確認し、ログアウト
  • (User ID = 2)

    Confirmation

    From:2007-10-01 10
    To:2007-10-01 11
    Rooms:1

Next


[演習]会議室予約システムの構築準備: DB作成

  • 以下のSQLを実行し、会議室予約システムで使用するDBを作成する。
  •      1  DROP SEQUENCE user_master_uids_seq;
         2
         3  CREATE SEQUENCE user_master_uids_seq
         4          START WITH 1
         5          INCREMENT BY 1
         6          NO MAXVALUE
         7          NO MINVALUE
         8          CACHE 1;
         9
        10  DROP TABLE user_master;
        11
        12  CREATE TABLE user_master (
        13          uid             INT
        14                          DEFAULT NEXTVAL('user_master_uids_seq'::text) NOT NULL,
        15          user_name       VARCHAR(128)
        16                          NOT NULL,
        17          mail_address    VARCHAR(128)
        18                          NOT NULL,
        19
        20          CONSTRAINT user_master_pk PRIMARY KEY (
        21                  uid
        22          )
        23  );
        24
        25  INSERT INTO user_master(user_name, mail_address)
        26      VALUES('iwasaki', 'iwasaki@freebsd.org');
        27
        28  DROP TABLE room_master;
        29
        30  CREATE TABLE room_master (
        31          rid             INT,
        32          room_name       VARCHAR(128),
        33          capacity        INT,
        34
        35          CONSTRAINT room_master_pk PRIMARY KEY (
        36                  rid
        37          )
        38  );
        39
        40  INSERT INTO room_master(rid, room_name, capacity) VALUES(1, 'room#01', 10);
        41  INSERT INTO room_master(rid, room_name, capacity) VALUES(2, 'room#02', 10);
        42  INSERT INTO room_master(rid, room_name, capacity) VALUES(3, 'room#03', 20);
        43  INSERT INTO room_master(rid, room_name, capacity) VALUES(4, 'room#04', 20);
        44  INSERT INTO room_master(rid, room_name, capacity) VALUES(5, 'room#05', 30);
        45
        46  DROP TABLE room_reservation;
        47
        48  CREATE TABLE room_reservation (
        49          rid             INT,
        50          reserved_from   TIMESTAMP,
        51          reserved_to     TIMESTAMP,
        52          uid             INT,
        53
        54          CONSTRAINT room_reservation_pk PRIMARY KEY (
        55                  rid,
        56                  reserved_from,
        57                  reserved_to
        58          )
        59  );
        60
        61  INSERT INTO room_reservation(rid, reserved_from, reserved_to, uid)
        62          VALUES(1, TO_TIMESTAMP('2007-10-01 08:00', 'yyyy-mm-dd hh24:mi'),
        63                 TO_TIMESTAMP('2007-10-01 12:00', 'yyyy-mm-dd hh24:mi'), 1);
        64  INSERT INTO room_reservation(rid, reserved_from, reserved_to, uid)
        65          VALUES(2, TO_TIMESTAMP('2007-10-01 07:00', 'yyyy-mm-dd hh24:mi'),
        66                 TO_TIMESTAMP('2007-10-03 20:00', 'yyyy-mm-dd hh24:mi'), 1);
    
    

Next


[演習]会議室予約システムの構築準備: DB作成(続き)

  • 作成準備用のSQLスクリプトファイルを準備しているので、それをpsqlへリダイレクト入力してDBを作成する。
  • $ psql < create_db.sql
    DROP SEQUENCE
    CREATE SEQUENCE
    DROP TABLE
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "user_master_pk"
    for table "user_master"
    CREATE TABLE
    INSERT 413825 1
    DROP TABLE
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "room_master_pk"
    for table "room_master"
    CREATE TABLE
    INSERT 413830 1
    INSERT 413831 1
    INSERT 413832 1
    INSERT 413833 1
    INSERT 413834 1
    DROP TABLE
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "room_reservation_pk" 
    for table "room_reservation"
    CREATE TABLE
    INSERT 413839 1
    INSERT 413840 1
    DROP FUNCTION
    CREATE FUNCTION
     is_occupied
    -------------
     f
    (1 row)
    [snip]
     is_occupied
    -------------
     f
    (1 row)
    $
    

Next


[演習]会議室予約システムの構築準備: DB作成(続き2)

  • psqlを起動し、作成したDBの内容を確認する。
  • $ psql
    Welcome to psql 7.4.17, the PostgreSQL interactive terminal.
    
    Type:  \copyright for distribution terms
           \h for help with SQL commands
           \? for help on internal slash commands
           \g or terminate with semicolon to execute query
           \q to quit
    
    iwasaki=> \?
    General
      \c[onnect] [DBNAME|- [USER]]
                     connect to new database (currently "iwasaki")
      \cd [DIR]      change the current working directory
      \copyright     show PostgreSQL usage and distribution terms
      \encoding [ENCODING]
                     show or set client encoding
      \h [NAME]      help on syntax of SQL commands, * for all commands
      \q             quit psql
      \set [NAME [VALUE]]
                     set internal variable, or list all if no parameters
      \timing        toggle timing of commands (currently off)
      \unset NAME    unset (delete) internal variable
      \! [COMMAND]   execute command in shell or start interactive shell
    
    Query Buffer
      \e [FILE]      edit the query buffer (or file) with external editor
      \g [FILE]      send query buffer to server (and results to file or |pipe)
      \p             show the contents of the query buffer
      \r             reset (clear) the query buffer
      \s [FILE]      display history or save it to file
      \w [FILE]      write query buffer to file
    
    Input/Output
      \echo [STRING] write string to standard output
      \i FILE        execute commands from file
      \o [FILE]      send all query results to file or |pipe
      \qecho [STRING]
                     write string to query output stream (see \o)
    
    Informational
      \d [NAME]      describe table, index, sequence, or view
      \d{t|i|s|v|S} [PATTERN] (add "+" for more detail)
                     list tables/indexes/sequences/views/system tables
      \da [PATTERN]  list aggregate functions
      \dc [PATTERN]  list conversions
      \dC            list casts
      \dd [PATTERN]  show comment for object
      \dD [PATTERN]  list domains
      \df [PATTERN]  list functions (add "+" for more detail)
      \dn [PATTERN]  list schemas
      \do [NAME]     list operators
      \dl            list large objects, same as \lo_list
      \dl            list large objects, same as \lo_list
      \dp [PATTERN]  list table access privileges
      \dT [PATTERN]  list data types (add "+" for more detail)
      \du [PATTERN]  list users
      \l             list all databases (add "+" for more detail)
      \z [PATTERN]   list table access privileges (same as \dp)
    
    Formatting
      \a             toggle between unaligned and aligned output mode
      \C [STRING]    set table title, or unset if none
      \f [STRING]    show or set field separator for unaligned query output
      \H             toggle HTML output mode (currently off)
      \pset NAME [VALUE]
                     set table output option
                     (NAME := {format|border|expanded|fieldsep|footer|null|
                     recordsep|tuples_only|title|tableattr|pager})
      \t             show only rows (currently off)
      \T [STRING]    set HTML <table> tag attributes, or unset if none
      \x             toggle expanded output (currently off)
    
    Copy, Large Object
      \copy ...      perform SQL COPY with data stream to the client host
      \lo_export
      \lo_import
      \lo_list
      \lo_unlink     large object operations
    
    iwasaki=> \d
                     List of relations
     Schema |         Name         |   Type   |  Owner
    --------+----------------------+----------+---------
     public | room_master          | table    | iwasaki
     public | room_reservation     | table    | iwasaki
     public | user_master          | table    | iwasaki
     public | user_master_uids_seq | sequence | iwasaki
    (4 rows)
    
    iwasaki=> select * from room_master;
     rid | room_name | capacity
    -----+-----------+----------
       1 | room#01   |       10
       2 | room#02   |       10
       3 | room#03   |       20
       4 | room#04   |       20
       5 | room#05   |       30
    (5 rows)
    
    iwasaki=> select * from room_reservation;
     rid |    reserved_from    |     reserved_to     | uid
    -----+---------------------+---------------------+-----
       1 | 2007-10-01 08:00:00 | 2007-10-01 12:00:00 |   1
       2 | 2007-10-01 07:00:00 | 2007-10-03 20:00:00 |   1
    (2 rows)
    
    iwasaki=> select * from user_master;
     uid | user_name |    mail_address
    -----+-----------+---------------------
       1 | iwasaki   | iwasaki@freebsd.org
    (1 row)
    
    iwasaki=> select r.*, u.user_name from room_reservation r, user_master u
    iwasaki-> where r.uid + = u.uid;
     rid |    reserved_from    |     reserved_to     | uid | user_name
    -----+---------------------+---------------------+-----+-----------
       1 | 2007-10-01 08:00:00 | 2007-10-01 12:00:00 |   1 | iwasaki
       2 | 2007-10-01 07:00:00 | 2007-10-03 20:00:00 |   1 | iwasaki
    (2 rows)
    
    iwasaki=> select m.*, r.uid from room_master m
    iwasaki->   left outer join
    iwasaki->   (select * from room_reservation
    iwasaki->    where (('2007-10-01 07:00' between reserved_from and reserved_to) or
    iwasaki->           ('2007-10-01 09:00' between reserved_from and reserved_to))
    iwasaki->   ) r
    iwasaki->   on m.rid = r.rid;
     rid | room_name | capacity | uid
    -----+-----------+----------+-----
       1 | room#01   |       10 |   1
       2 | room#02   |       10 |   1
       3 | room#03   |       20 |
       4 | room#04   |       20 |
       5 | room#05   |       30 |
    (5 rows)
    
    iwasaki=> begin;
    BEGIN
    iwasaki=> update room_reservation set uid = 2 where rid = 1;
    UPDATE 1
    iwasaki=> select * from room_reservation;
     rid |    reserved_from    |     reserved_to     | uid
    -----+---------------------+---------------------+-----
       1 | 2007-10-01 08:00:00 | 2007-10-01 12:00:00 |   2
       2 | 2007-10-01 07:00:00 | 2007-10-03 20:00:00 |   1
    (2 rows)
    
    iwasaki=> rollback;
    ROLLBACK
    iwasaki=> select * from room_reservation;
     rid |    reserved_from    |     reserved_to     | uid
    -----+---------------------+---------------------+-----
       1 | 2007-10-01 08:00:00 | 2007-10-01 12:00:00 |   1
       2 | 2007-10-01 07:00:00 | 2007-10-03 20:00:00 |   1
    (2 rows)
    
    iwasaki=> \q
    $
    

Next


[演習]会議室予約システムの構築準備: Javaクラスのコンパイル

  • JSPで使用するJavaクラスを、Javaコンパイラjavacを使用して以下の手順でコンパイルする。
  • $ cd course/day-2/roomReservation/WEB-INF/classes/
    $ javac -classpath . simpleApp/*.java
    
  • Javaクラスは2つあり、ひとつはDBアクセスをおこなうクラスDataAccess、もうひとつはユーザ管理をおこなうクラスUserManagerである。
  • この二つのクラスはパッケージsimpleAppとしてまとめられている。
  • 以下はクラスDataAccessのソース。
  •      1  package simpleApp;
         2
         3  import  java.sql.*;
         4  import  javax.sql.*;
         5  import  javax.naming.*;
         6
         7  public class DataAccess
         8  {
         9          private DataSource              ds_;
        10          private Connection              con_;
        11          private Statement               st_;
        12          private ResultSet               rs_;
        13
        14          public DataAccess()
        15          {
        16                  ds_ = null;
        17                  con_ = null;
        18                  st_ = null;
        19                  rs_ = null;
        20          }
        21
        22          public synchronized void open()
        23                  throws Exception
        24          {
        25                  Context ctx = new InitialContext();
        26                  ds_ = (DataSource)ctx.lookup("java:/comp/env/jdbc/postgres");
        27                  con_ = ds_.getConnection();
        28                  st_ = con_.createStatement();
        29          }
        30
        31          public ResultSet getResultSet(String sql)
        32                  throws Exception
        33          {
        34                  if (st_.execute(sql)) {
        35                          rs_ = st_.getResultSet();
        36                          return rs_;
        37                  }
        38                  return null;
        39          }
        40
        41          public void execute(String sql)
        42                  throws Exception
        43          {
        44                  st_.execute(sql);
        45          }
        46
        47          public PreparedStatement prepareStatement(String sql)
        48                  throws Exception
        49          {
        50                  return con_.prepareStatement(sql);
        51          }
        52
        53          public synchronized void close()
        54                  throws Exception
        55          {
        56                  if (rs_ != null ) {
        57                          rs_.close();
        58                  }
        59                  if (st_ != null ) {
        60                          st_.close();
        61                  }
        62                  if (con_ != null ) {
        63                          con_.close();
        64                  }
        65          }
        66  }
        67
    

Next


[演習]会議室予約システムの構築準備: Javaクラスのコンパイル(続き)

  • 以下はクラスUserManagerのソース。クラスDataAccessを使用している。
  •      1  package simpleApp;
         2
         3  import  java.sql.*;
         4  import  simpleApp.*;
         5
         6  public class UserManager
         7  {
         8          private DataAccess      da_;
         9
        10          public UserManager()
        11          {
        12                  da_ = new DataAccess();
        13          }
        14
        15          public int search(String user_name, String mail_address)
        16                  throws Exception
        17          {
        18                  int     uid = -1;
        19
        20                  try {
        21                          da_.open();
        22
        23                          String  sql = "SELECT uid FROM user_master WHERE " +
        24                                  "user_name=? AND mail_address=?";
        25                          PreparedStatement ps = da_.prepareStatement(sql);
        26                          ps.setString(1, user_name);
        27                          ps.setString(2, mail_address);
        28                          ResultSet rs = ps.executeQuery();
        29                          if (rs.next()) {
        30                                  uid = rs.getInt("uid");
        31                          }
        32                          rs.close();
        33                          ps.close();
        34                  } finally {
        35                          da_.close();
        36                  }
        37
        38                  return uid;
        39          }
        40
        41          public int add(String user_name, String mail_address)
        42                  throws Exception
        43          {
        44                  try {
        45                          da_.open();
        46
        47                          String sql = "INSERT INTO user_master(user_name, mail_address) " +
        48                                  " VALUES(?, ?)";
        49
        50                          PreparedStatement ps = da_.prepareStatement(sql)
        51                          ps.setString(1, user_name);
        52                          ps.setString(2, mail_address);
        53                          ps.executeUpdate();
        54
        55                          ps.close();
        56                  } finally {
        57                          da_.close();
        58                  }
        59
        60                  return search(user_name, mail_address);
        61          }
        62  }
        63
    
  • Java APIについては、以下のURLなどを参照。
  • http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/api/index.html

Next


[演習]会議室予約システムの構築

  • ミッション1
  • 会議室の検索結果に、予約希望期間にすでに予約が入っている会議室も含まれているため、 その会議室を予約処理するとダブルブッキングが生じてしまう。 会議室の検索結果の表示の際にすでに予約済みの会議室が選択できないよう、search.jspを修正せよ。
    ヒント: ストアド関数is_occupied()

  • ミッション2
  • 会議室の検索結果の表示の際にすでに予約済みの会議室があった場合、予約した人と連絡が取れるよう e-mailアドレスが表示されると便利である。この要望に合うよう、search.jspを修正せよ。

  • ミッション3
  • ミッション1でおこなった修正は一度会議室一覧の情報を作成してから、予約済み会議室を取得して会議室一覧の 情報を加工するものであった。この一連のDBへのアクセスは、実は一回のSELECTでおこなうことが可能である。 その方法を検討し、処理を効率的におこなうようsearch.jspを修正せよ。

  • ミッション4
  • ログイン後、自分が予約した会議室の予約情報を表示し、取り消し処理がおこなえるようにしたい。 search.jspを修正し、取り消し処理をおこなうcancel.jspを作成せよ。 なお、cancel.jspでは予約取り消し(Cancel Reservations)ボタンとsearch.jspへ戻る(Back)ボタンを用意し、どちらも search.jspへ遷移すること。

  • ミッション5
  • 複数の会議室を一度に予約する場合、他のユーザの処理と競合してデータの矛盾が発生する可能性がある。 simpleApp.DataAccessクラスを拡張し、トランザクションの機能を追加せよ。 またreserve.jspをトランザクションを使用してデータの矛盾が起こらないように修正せよ。 具体的には、INSERT処理の前処理としてroom_masterの該当レコードをSELECT...FOR UPDATEでロック、 対象の会議室が予約希望期間に空いていることを最終確認し(空いていない場合はROLLBACKでトランザクションを終了させる)、 room_reservationへのINSERT処理をおこない、COMMITでトランザクションを確定させる。 処理の途中で例外が発生した場合は、catch()ブロックの中でROLLBACKをおこなうこと。

  • ミッション6
  • このシステムではJSPファイルでSQLを記述しDBアクセスをおこなっているが、これはデザインとロジックの分離の観点から好ましくない。 simleApp.UserManagerクラスを参考に、会議室予約業務ロジックを担当するクラスを設計および実装し、 これを使用するようすべてのJSPファイルを修正せよ。

  • ミッション7
  • このシステムには隠された不具合が多数存在する。それを発見し修正せよ。

Next