システムコール
最も頻繁に発生するカーネルへのトラップは、クロック処理を省き、
システムコールの呼び出しである。
システムの性能を向上させる為には、カーネルがシステムコールを受け付けて
対応するオーバーヘッドを最小限に抑える事が不可欠である。
- ハンドラ
- システムコールのハンドラである syscall() は、次の作業を行う。
- システムコールのパラメータが''有効なユーザアドレスに
置かれている事''を確認し、
それらをユーザのアドレス空間からカーネル空間へコピーする
- そのシステムコールを実装するカーネル関数を呼び出す
- syscall()
- 実際の syscall() 関数を書きに記す。
lr とは、システムコールを呼び出したユーザ空間への
戻りアドレスの事である。
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| | /* sys/boot/arm/uboot/start.S */
/*
* syscall()
*/
ENTRY(syscall)
/* Save caller's lr */
ldr ip, =saved_regs
str lr, [ip, #4]
/* Save loader's r8 */
ldr ip, =saved_regs
str r8, [ip, #8]
/* Restore U-Boot's r8 */
ldr ip, =saved_regs
ldr r8, [ip, #0]
/* Call into U-Boot */
ldr lr, =return_from_syscall
ldr ip, =syscall_ptr
ldr pc, [ip]
return_from_syscall:
/* Restore loader's r8 */
ldr ip, =saved_regs
ldr r8, [ip, #8]
/* Restore caller's lr */
ldr ip, =saved_regs
ldr lr, [ip, #4]
/* Return to caller */
mov pc, lr
|
- Note
例えば、write()システムコールを、ユーザ空間から呼び出したとする。
書き込み先のデバイスは、引数のファイルディスクリプタで指定できる。
物理デバイス、仮想デバイス、外部ストレージなど様々であるが、
実際に書き込みを行う関数は個々に存在している。
ufs に書き込む関数、suj に書き込む関数、
どの関数を使用するかを判断するのもカーネルの役割である。
カーネルは、個々の write() 関数を write() システムコールとして
ユーザへ提供している。
処理結果の受渡し
- ルール
- エラーが発生したとしても、システムコールは呼び出したプロセスに
戻らなければならない
- システムコールが正常終了したか否かは、
ユーザプロセスのプロセッサ状態ロングワードのキャリービットで表される
- これがゼロの場合は成功、そうでなければエラーが発生したことを意味する
- 戻り値
- C言語の関数の戻り値は汎用レジスタを使用して渡される
- システムコールを実装するカーネル内の関数は、大域変数errnoで参照される値を返す
- 処理が終わった後、カーネルのシステムコールハンドラはこの値を
レジスタに残したままにする
- システムコールが失敗した場合、Cのライブラリ関数はその値をerrnoに移し、
戻り値のレジスタを-1に設定する
- 例
- 下記、read() 関数のエラー処理を記す。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
|
|
|
-
|
|
!
|
|
!
|
ssize_t
read(int fd, void *dest, size_t bcount)
{
struct open_file *f = &files[fd];
size_t resid;
if ((unsigned)fd >= SOPEN_MAX || !(f->f_flags & F_READ)) {
errno = EBADF;
return (-1);
}
・
・
}
|
- read() は、引数であるファイルディスクリプタが不正である場合、
errno に EBADF をセットし、-1を返す
- この例では、open() していないファイルディスクリプタを指定した、
又は read 権限が与えられていないファイルディスクリプタを指定した場合にエラーとなる
- read() 関数を呼び出したプロセスは、
まず戻り値レジスタの内容を見て、その後で errno を検査することになる
エラーとなる要因と回避
通常、システムコールがエラーとなる主な要因は、2種類の状況が考えられる。
- 要因
- カーネル関数がエラーを検出した場合
- システムコールが割込まれた場合
プロセスがシグナルハンドラを初期化する際に、下記を選択可能である。
具体的には、sigaction() でシグナルハンドラのフラグ集合を設定する際、
SA_RESTART というフラグをセットし、処理を再開するか否かを指定する。
- 割込まれた時に処理を再開する
- ハンドラはそのプロセスのプログラムカウンタを、
そのシステムコールのトラップが発生した命令に設定し直す
- システムコールの途中で割込まれると、プロセスにはシグナルが送られ、
プロセスがシグナルハンドラから戻ると、
ハンドラが提供するプログラムカウンタの値から実行を再開する為、
同じシステムコールが再度呼び出されることになる
- 割込み発生 EINTR というエラーを返す
- システムコールの途中で割込まれると、プロセスにはシグナルが送られ、
プロセスがシグナルによって、システムコールが強制終了する
注意点
プログラムカウンタを再設定することでシステムコールを再開する際に、
いくつか注意しなければならない点がある。
- カーネルはプロセスのアドレス空間上にある入力パラメータを変更してはならない
- 繰り返すことができない処理を行っていないことを保証できなければならない
上記の注意点を考慮しなければ、再実行された時、
既に読込まれたデータが失われることになる。
システムコールの終了
システムコールの実行中、またはシグナルをブロックして休眠している間に、
- そのプロセスに対してシグナルが送られる
- 別のプロセスがより高いスケジュール順位を得る
場合がある。
システムコールの処理を終えると、システムコールの終了処理コードは、
これらのイベントが発生していなかったかどうかを確認する。
- システムコールの終了処理コードとは
- まずシグナルの到着を確認する
- シグナルの中には、そのシステムコールに割込みを通知するものと、
システムコールの実行中に到着するとその終了まで保留されるものとがある
- デフォルトで、またはプログラムによって明示的に無視するように設定されているシグナルは、
プロセスが再開する前に処理が終わっているはずで、そのプロセスは中断されるか強制終了されていることになる
シグナルが捕捉可能な状態であれば、システムコールの終了処理コードは、
そのシステムコールの処理から戻らずに、適切なシグナルハンドラを起動する。
- シグナルの到着検査が終わると
- 現在のプロセスよりも高い優先度を持っているプロセスがいないかどうかを調べる
- もし、該当するプロセスが存在すれば、システムコール終了処理コードは、
その高い優先度を持つプロセスが実行できるようにコンテキスト切り替え関数を呼び出す。後になって、その時実行されていたプロセスが再び最高の優先度を持つようになると、システムコールからユーザプロセスへ戻るところから実行を再開する
- プロセスのプロファイルをとるようにシステムが設定されている場合
- そのシステムコールに費やされた時間を計算する
- この時間は、そのシステムコールを呼び出したユーザプロセスの関数に算入される
参考