UP | HOME

システムコールとは - 2023年度 システムプログラミング

システムコールとは

はじめに

これまで,出力装置に 'A' を出すプログラムでは, 0xffff000c 番地に直接 65 を書込むことで実現してきました. ここでは,オペレーティングシステム (OS) を意識した出力方法を学びます.

野蛮な文字出力

以下は,出力装置に 'A' を出すプログラムでした.

   .text
   .align 2
putc:
   lw   $t0, 0xffff0008 # 0xffff0008の中身を$t0に
   li   $t1, 1          # $t1 = 1
   and  $t0, $t0, $t1   # $t0 = $t0 & $t1
   beqz $t0, putc       # if ($t0 == 0) goto putc
   sw   $a0, 0xffff000c # 0xffff000c に$a0を書込
   j    $ra             #

しかし,実は,これは,かなり野蛮な方法です.なぜなら:

  1. 0xffff000c (計算機毎に変わり得るアドレス)を プログラマが意識して使うのは面倒.アドレスを知る術がないこともある.
  2. 他のプログラムと同時に走行した場合,競合が発生する.
  3. 印刷可能になるまで待つ事をしないプログラムを誤って作成した場合,機器が壊れる可能性がある.

そのため,入出力装置に対応するメモリを一般のプログラムでは, 直接読み書きできないのが普通 (UNIX や Windows も)です.

行儀よくプリンタを使うには?

通常のOSでは,これらの問題をカーネルと呼ばれる専用のサブルーチン集を用意して, 全てのプログラムがそのサブルーチンを呼出すことで解決しています. カーネルを用意するメリットは,以下の通りです.

  1. 呼出し方を決めておけば,プリンタのアドレスが変化しても, (プリンタの出力方式が変わっても)プログラムを変更する必要がない.
  2. 他のプログラムとの競合を調整してもらえる.

このサブルーチンの事をシステムコールといいます.

システムコールは, カーネル(OS) 毎に引数の意味, つまり,どのレジスタにどういう意味を持たせるか異なります. 以下は,本演習で使用するSPIMがあらかじめ持っているシステムコールの一覧から, 本演習で使用する部分の抜粋です. (教科書A.9節「SPIM」の「システム・コール」より)

サービス No 引数 戻値
print_int 1 $a0 = integer  
print_string 4 $a0 = string  
read_int 5   $v0
read_string 8 $a0 = buffer, $a1 = length  

以下は,システムコールを使った,プログラムの例です.

   .text
   .align 2
main:
   li   $v0, 1   # No = 1 (print_int)
   li   $a0, 5   # 5 を表示 '5' ではない
   syscall       # 画面に '5' と出る
   j    $ra

ここで, syscall 命令を使っていますが, なぜこれまでのサブルーチンのように jal を使わないのでしょうか. syscall 命令が必要な理由は何でしょうか.主な理由は,以下の通りです.

  1. サブルーチンのアドレスは,カーネルがバージョンアップする毎に 変化するので(例: Windows7 → Windows8),結局, プリンタのアドレスを知るのと同じ問題が発生する.
  2. カーネルのとんでもないアドレスに jal してもらっては困る. つまり,番地を指定して任意のアドレスから実行できるとカーネルを悪用できる.

これらの理由から,カーネルの中にある手続き(関数)へは, 直接ジャンプできないように,メモリが保護されています.

まとめると,以下の2つがシステムの安全性を保証しています.

  1. プリンタを操作するアドレス (0xffff000c 等) は, カーネル中のプログラムからしかアクセスできない.
  2. カーネル中の任意のプログラム部分に外部からはジャンプできない.

これを実現するための仕組みである「走行モード」 と syscall について理解しましょう.

走行モードの切り替え

システムの安全を保証するためには,以下の保護が必要であることが分かりました.

  1. カーネルのプログラムが走行しているときだけ, 特権を持ったモード(全メモリを自由に操作できる)になる必要がある.
  2. 誰にでも簡単に特権モードになられては困る.
  3. カーネルの内のアドレスに外部から jal してもらっても困る. そうでないと,
    • 番地を指定して,任意のアドレスから実行できる.
    • その結果,カーネルを悪用できる.

この問題を解決するのが, syscall 命令です.

syscall 命令
ユーザプログラムの権限から特権モードに移行する代わりに, カーネルが設定した特定のアドレスにしかジャンプできない命令.

syscall 命令を使用したプログラムの動作を以下に示します.

  1. ユーザプログラムでレジスタに引数を入れて syscall 命令を実行
  2. syscall によって,カーネルが設定したアドレスにジャンプ
  3. syscall 実行と同時に,自動的に特権モードになる
  4. カーネル内で引数の厳重なチェック
  5. ユーザプログラムの望む処理をカーネルが代わりに実行
  6. 通常の走行モードに移行
  7. ユーザプログラム(の syscall 呼び出し直後)に戻る

この 1から3 までが, syscall 命令の動作です. これによって,安全にユーザプログラムからカーネルやメモリ資源を保護することができ, カーネルに所望の処理を依頼できるのです.

Author: Yoshinari Nomura

Emacs 27.1 (Org mode 9.3)