※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

 Perlは組込みの関数だけでもほとんどのことができますが、どうしてもシステムコールを直接呼ばないとできないことがあります。
 Perlには Function syscall という組込み関数があり、システムコールを呼び出すことができます。(未実装のものもあるで注意)
 perldoc -f syscall を見ると、書式は以下の通りです。
syscall NUMBER, LIST

 説明を読むと、”LISTの最初で指定されたシステムコールを、LISTの以降を引数として呼び出す”とありますが、NUMBERの説明がありません。すこしググると”syscall LIST”と書式がなっているものもあり、マニュアルのNUBERが間違っているように思います。
 以下のサンプルが出ていたので、そこから使いかたを推測します。(以下だらだらと続くので、使いかただけ知りたいひとはまとめを見てください。)

require 'syscall.ph'; # may need to run h2ph
$s = "hi there\n";
syscall(&SYS_write, fileno(STDOUT), $s, length $s);

システムコールの指定方法

 サンプルの3行目で &SYS_write と指定しています。これはwrite(2)システムコールのサンプルですが、 &SYS_システムコール名 と指定すれば良さそうです。Perlでは' & 'を付けて関数を呼び出すので、 SYS_XXXX という関数が定義されているはずです。なお、未実装のシステムコールを呼び出すと致命的なエラー(fatal error)が発生とあるので、 SYS_XXXX に何があるのか知る必要があります。
1行目で syscall.ph をrequireしているので、これを見てみます。 h2ph が必要かもとコメントされていますが、手元の環境では以下のファイルがありました。
$ locate syscall.ph
/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/syscall.ph    ・・・(1)
/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/bits/syscall.ph  ・・・(2)
/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/sys/syscall.ph  ・・・(3)

(1)を見ると、システムコール名などはなく、"sys/syscall.ph"をrequireしているので、(3)を見てます。さらに"bits/syscall.ph"をrequireしているので(2)をみると、以下のように SYS_XXXX 関数の名前が出てきました。
unless(defined(&SYS_write)) {
  sub SYS_write () { &__NR_write;}
}

他のシステムコールもそうですが、 SYS_XXXX 関数が未定義ないなら、 &__NR_XXXX を呼び出すようになっています。
次は &__NR_XXXX を探してみます。先ほどの"bits/syscall.ph"では、"asm/unistd.ph"をrequireしています。これもsyscall.phと同様locateで探して見ていきます。
$ locate unistd.ph
/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/asm/unistd.ph    <--ここでアーキテクチャにより以下の2つをrequire
/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/asm-i386/unistd.ph ・・・(A)
/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/asm-x86_64/unistd.ph

手元の環境はx86_64なので(A)のファイルを見てみると、以下のように定義されていました。
eval 'sub __NR_write () {1;}' unless defined(&__NR_write);

ということで、 &__NR_XXXX は最終的にはここで各システムコールに割り当てられた番号に変換され、システムコールが呼び出されます。syscall関数の引数NUMBERは、このシステムコールの番号を指定するためのものであることがわかります。
syscall.phもこの番号への変換を行うためにrequireされていると思われるので、ためしに、requireをコメントアウトし、SYS_writeを以下のように1で置き換えて実行しても、きちんと実行できました。
# require 'syscall.ph'; # may need to run h2ph
$s = "hi there\n";
syscall( 1 , fileno(STDOUT), $s, length $s);

ちなみに、このシステムコールの番号はLinux(POSIX?)でも定義されており、以下のヘッダファイルをみると書いてあります。
head -n 20 /usr/include/asm-x86_64/unistd.h
#ifndef
_ASM_X86_64_UNISTD_H_
 ・・・
/* at least 8 syscall per cacheline */
#define
__NR_read                0
__SYSCALL(__NR_read, sys_read)
#define
__NR_write                1
__SYSCALL(__NR_write, sys_write)
#define
__NR_open                2
__SYSCALL(__NR_open, sys_open)
#define
__NR_close                3
 ・・・

引数について

perldocを見ると、以下のようにあります。
  1. 引数が数値の場合 int型 と見なされ、それ以外は文字列へのポインタとみなしてシステムコールに渡されます。
  2. 文字列を受け取る場合、受け取る結果が入るだけの十分な長さが必要です。
  3. Perlはどのような文字列の値へのポインタであっても書き込みがあると仮定しないといけないため、文字列リテラル(他の読み取り専用文字列)を引数として syscall を呼び出すことはできません。
  4. 整数値(integer)の引数がリテラルでない、または数値コンテキストで解釈さていれない場合、0を加算することでPerlに数値と認識させることができます。
  5. Perlは14個までの引数しか渡せない。

先のwriteのサンプルでいったん文字列を変数にいれているのは、3つ目の規則のためです。
数値でint型以外を引数にとるシステムコールはどうしたらよいのか不明ですが、これはまた使うときに調べてみます。

返却値、エラー番号(errno)について

perldocを見ると、以下のようにあり、C言語でのシステムコールのハンドリングと同じになっています。
システムコールの呼び出しに失敗した場合、-1を返し、 $! にエラーNo(errno)をセットします。多くのシステムコールではきちんと-1を返します。適切に扱うためには、呼び出しの前に"$!=0;"をチェックし、 syscall が-1を返した場合には $! をチェックする方法が良いでしょう。

まとめ

自分の興味のため、だらだらと書いてきましたが、Perlからシステムコールを呼び出すには
  1. syscall.phをrequireする。
  2. 以下の書式のsyscall関数を使う。
syscall NUMBER, LIST
   NUMBER : 呼出たいシステムコールの番号("&SYS_システムコール名"で指定)
   LIST : システムコールへの引数をカンマで区切って渡す。

■Sample
require 'syscall.ph'; # may need to run h2ph
$s = "hi there\n";
syscall(&SYS_write, fileno(STDOUT), $s, length $s);

なお、 syscall.ph がない場合は、 syscall.h からh2phコマンドで作成します。
また、システムコールの引数で定数をdefine名で指定するには、定数が定義されたヘッダファイルから.phファイルを作成して、requireする必要があります。
perldoc h2phをみてもらえれば、h2phコマンドの使いかたはすぐわかると思いますが、ヘッダファイル内から別のヘッダファイルをincludeしていて、それを個々に探して作っていくのは面倒なので、以下のようにすると/usr/include配下全てにたいして作成できます。
h2ph -d <出力先ディレクトリパス> -r -l /usr/include

なお、出力先ディレクトリパスはrequireできるところでないとだめです。

以下にサンプルを書いておきます。
#!/usr/bin/perl -w

# 対象関数を"SYS_システムコール名"で指定するのに必要。h2phコマンドで作成必要かも
# unistd.hに定義されたシステムコールの番号で指定するなら不要
require("syscall.ph");

# reboot(2)に定数名を使うならreboot.phが必要。
# h2ph -d /home/test/phfiles -r -l /usr/include で他のも含めて一括作成
# reboot.phの作成先をrequireの対象パス(@INC)に追加する。
print join( "\n",@INC );
BEGIN {
        push(@INC, '/home/test/phfiles');
}
print join( "\n",@INC );
require("linux/reboot.ph");

# getpid(2)
$pid = syscall(&SYS_getpid);
print "PID of this process is $pid\n";

# To create directory use the following
$string = "newdir";    # 文字列リテラルは変数に格納してから
syscall( &SYS_mkdir, $string );

# reboot(2)の前にsync(2)
syscall( &SYS_sync ); 

# reboot(2)
# LINUX_REBOOT_CMD_RESTART
# 定数名は &定数名 で指定
syscall( &SYS_reboot, &LINUX_REBOOT_MAGIC1, &LINUX_REBOOT_MAGIC2, &LINUX_REBOOT_CMD_RESTART, 0 );
die "Error reboot errno= $!" if  $ret == -1    # エラーハンドリング

# 定数名使わなければreboot.phは不要
#syscall( &SYS_reboot, 0xfee1dead, 0x28121969, 0x1234567, 0 );

# LINUX_REBOOT_CMD_HALT
#syscall( &SYS_reboot, &LINUX_REBOOT_MAGIC1, &LINUX_REBOOT_MAGIC2, &LINUX_REBOOT_CMD_HALT,, 0 );
#syscall( &SYS_reboot, 0xfee1dead, 0x28121969, 0xcdef0123, 0 );

# LINUX_REBOOT_CMD_POWER_OFF
#syscall( &SYS_reboot, &LINUX_REBOOT_MAGIC1, &LINUX_REBOOT_MAGIC2, &LINUX_REBOOT_CMD_POWER_OFF, 0 );
#syscall( &SYS_reboot, 0xfee1dead, 0x28121969, 0x4321fedc, 0 );

# LINUX_REBOOT_CMD_RESTART2
#syscall( &SYS_reboot, &LINUX_REBOOT_MAGIC1, &LINUX_REBOOT_MAGIC2, &LINUX_REBOOT_CMD_RESTART2, 0 );
#syscall( &SYS_reboot, 0xfee1dead, 0x28121969, 0xa1b2c3d4, 0 );

以下のページに複雑なパターンのサンプルなどもあります。
http://michael.toren.net/slides/perl-syscall/slide001.html


名前:
コメント: