->back to toppage

課題内容 - Level.4 -

新しいシステムコールを提案し、実装してみよ。

kernelのconfigの過程で、 使用するシステムコールの番号等を指定するファイルがある。これを使うと、
  1. アプリケーションが呼び出すライブラリ(libc)
  2. ライブラリが呼び出すtrap
  3. trapを受け取り振り分ける部分
  4. 実際にkernel内部で処理する部分
のうち、3番目は、自動的に生成される。 もちろん、テストするアプリケーションも必要である。 アプリケーションからは、trapをアセンブラで直接起動しても良いし、libcを修正して、 普通のCの関数に見えるようにしても良い。


報告内容

実装の手順

その1「システムコール番号対応表の修正

カーネル内部では、システムコールはその関数名に対応付けされた番号で管理されている。 カーネルソース中ではlinux/include/asm~/unistd.h このファイルにシステムコールと番号群の対応がマクロの形で記述されているので 追加するシステムコールに関して追加する。

追加するにはシステムコールの空き番号に
#define __NR_(システムコール名) 空き番号
という行を追加する。

システムコール対応の最後にあるsyscallsというのはシステムコールの総数 を表すだけで実装されていない関数なので、それを除いたものが空き番号になる。
linux-2.6.16.18/include/asm(-powerpc)/unistd.h の一部
...
#define __NR_restart_syscall	  0
#define __NR_exit		  1
#define __NR_fork		  2
#define __NR_read		  3
#define __NR_write		  4
#define __NR_open		  5
...
#define __NR_spu_run		278
#define __NR_spu_create		279
#define __NR_pselect6		280
#define __NR_ppoll		281
#define __NR_unshare		282
#define __NR_syscalls		283
...
これにtestというシステムコールを追加する場合、以下のようになる。
linux-2.6.16.18/include/asm(-powerpc)/unistd.h の一部
...
#define __NR_spu_create		279
#define __NR_pselect6		280
#define __NR_ppoll		281
#define __NR_unshare		282

/* added systemcall */
#define __NR_test		283

#define __NR_syscalls		284
...


その2「呼び出しテーブルの修正」

メモリ上では全システムコール(へのジャンプ用アドレス)は特定の箇所に順番通りに固まっている。 それの記述はlinux2.4.16.18/arch/powerpc/kernel/systbl.S内にある。 このファイルはアセンブラによるシステムコールのテーブルになっており、 テーブルの記述自体はマクロを使っている。 i386アーキテクチャ側のテーブル(linux2.4.16.18/arch/i386/kernel/syscall_table.S) はそうはなっていないが、これはpowerpcがG5から64ビットプロセッサを採用したためである。 マクロを利用することでG4以前のpowerpc用とG5用を、ある程度共通のソースを用いて記述している。

システムコールを追加する場合に、複数あるマクロのどれかを利用することになるが、 G4以前の(32ビットプロセッサ時代の)powerpcであればどれを利用しても大きな差はない。 それぞれが何に対応するのかは調べきれなかったが、ソースを読んだ感じでは

SYSCALL 以下以外のすべてのシステムコール。
CAMPAT_SYS ファイルシステムや時間のカウントを使用するシステムコールに使用されている。
PPC_SYS 不明。fork系、clone、newuname、rtasが該当する。プロセス作成に関係するものか。
OLDSYS stat系、debug_setcontextが該当する。情報取得システムコール
SYS32ONLY 不明。sigaction、sigsuspend、sigreturn、mmap2、truncate64、ftruncate64、sendfile64が該当する。名前からして32ビットプロセッサのみで使用できるシステムコールだと思われる。
SYSX 32ビットプロセッサと64ビットプロセッサで全く別の関数が用意されているもの。これのみ引数が3つあり、それぞれに対応するシステムコール名を入れる。

となっている。32ビットプロセッサではこの内SYSCALL,COMPAT_SYS,OLDSYS,SYS32ONLYの3つが最終的に同じ文に展開される。

PPC_SYSとSYSXを利用する理由がなければ、
SYSCAL(システムコール名)
とすればよい。
linux-2.4.16.18/arch/powerpc/kernel/systbl.S
... SYSCALL(inotify_add_watch) SYSCALL(inotify_rm_watch) SYSCALL(spu_run) SYSCALL(spu_create) COMPAT_SYS(pselect6) COMPAT_SYS(ppoll) SYSCALL(unshare)
これにtestという名前のシステムコールを追加する場合、以下のようになる。 ファイルの最後には改行を入れておく。なくてもコンパイルに問題はないが、 アセンブラは警告を吐く。
...
SYSCALL(inotify_add_watch)
SYSCALL(inotify_rm_watch)
SYSCALL(spu_run)
SYSCALL(spu_create)
COMPAT_SYS(pselect6)
COMPAT_SYS(ppoll)
SYSCALL(unshare)
SYSCALL(test)


その3「システムコール本体の実装」

linux/kernel/sys.cに実際のシステムコール本体を記述する。形式は
関数型 sys_名前(引数){}
というように、普通に関数を作る形でよい。ただ、実際にはソース中の 他のシステムコールは
asmlinkage 関数型 sys_名前(引数){}
というように実装されている。asmlinkageはマクロであり、 linux-2.6.16.18/include/linux/linkage.hに定義されている。

linux-2.6.16.18/include/linux/linkage.h の一部
...
#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif

#ifndef asmlinkage
#define asmlinkage CPP_ASMLINKAGE
#endif
...
これによれば、asmlinkageはC++用のオプションで、 C言語で書かれている関数を考慮して、C++の場合は 関数にC言語互換を持たせるための[extern "C"]を追加する、 というもののようである。 Cコンパイラを使用するのであれば記述がなくても問題ないが、関数をC言語で記述し、 C++コンパイラを使う可能性があるなら必要なオプションである。

今回追加したtestシステムコールは以下のようになる。
added system calls
int sys_test(char *mess, int len)
{
  char *kmess;

  if ((kmess = vmalloc(len+1))==0)
    return -ENOMEM;

  copy_from_user(kmess, mess, len);
  kmess[len] = '\0';
  console_print(kmess);

  vfree(kmess);
  return 0;

}


その4「システムコールを利用するプログラムの作成」

システムコールを利用する関数を別に自作する。
システムコールを動かすには、syscallマクロを利用できる。
システムコールの対応表を修正した[unistd.h]と[errno.h]をインクルードし、ヘッダ部分で
_syscall(引数の数)(型、システムコール名、引数・・・)
とすれば、プログラム中で
システムコール名(引数)
のように普通の関数のように利用できる。

syscallマクロはlinux-2.6.16.18/include/asm-powerpc/unistd.h 内に定義されており、システムコールを呼び出す関数へと展開してくれる。

今回の例であれば、システムコール名が[test]、引数が2つなので システムコールを呼び出すプログラムは以下のようになる。
test program
#include<stdio.h>
#include</usr/src/linux-2.6.16.18/include/asm-powerpc/unistd.h>
#include<errno.h>
#include<string.h>

_syscall2(int, test, char *, mess, int ,len);

int main(){

  char message[] = "Print straight to console\n";

  if (test(message, strlen(message))==-1)
    printf("Error occurred in printcons() call\n");

}
また、今回は使用していないが、アセンブラを利用する方法もある。 powerpcの場合、システムコールを呼び出す命令としてscという命令が用意されており、 引数を対応するレジスタに格納して実行することでシステムコールを利用することができる。 レジスタ番号と引数の対応は以下の通り
r0 システムコール番号 r3 第1引数 r4 第2引数 r5 第3引数 r6 第4引数 r7 第5引数
呼び出しの例
li r0, 1  //システムコール番号、1はexitシステムコール
li r3, 0  //第1引数。exitシステムコールは引数を1つとる
sc        //システムコール実行

i386であれば、命令は[int 0x80]となる。i386でのレジスタと引数の対応は以下の通り
eax システムコール番号 ebx 第1引数 ecx 第2引数 edx 第3引数 esi 第4引数 edi 第5引数


「実行結果」

プログラムをコンパイルし実行すると、標準出力には出力されないが dmsegコマンドでみることのできるカーネルログに以下のような出力がみられる。
%dmesg | tail -1 Print straight to console
この「Print straight to console」というのが今回のシステムコールからの 出力である。よって、追加したシステムコールは正常に動作している。



考察

システムコールに関してはカーネル中で多数のマクロが用意されており、 また、実装形式自体がわかりやすいものになっている。
このおかげで、カーネルにシステムコールを追加する作業は 非常に容易いものになっている。
この部分は、常に更新されているカーネルソースの拡張性の高さ、 ソースの簡潔さが出ている部分だろう。



参考文献



<-previous problem    go to top    next problem->