新しいシステムコールを提案し、実装してみよ。
kernelのconfigの過程で、
使用するシステムコールの番号等を指定するファイルがある。これを使うと、
- アプリケーションが呼び出すライブラリ(libc)
- ライブラリが呼び出すtrap
- trapを受け取り振り分ける部分
- 実際に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」というのが今回のシステムコールからの
出力である。よって、追加したシステムコールは正常に動作している。
考察
システムコールに関してはカーネル中で多数のマクロが用意されており、
また、実装形式自体がわかりやすいものになっている。
このおかげで、カーネルにシステムコールを追加する作業は
非常に容易いものになっている。
この部分は、常に更新されているカーネルソースの拡張性の高さ、
ソースの簡潔さが出ている部分だろう。