UNIX 6thに足し算システムコールを追加してみた

はじめに

先日「ぐだ生」(@magoroku15さんのUstream. この間までunix v7を取り上げていた)を見ていたところ「新人には足し算システムコールをとりあえず作らせる」という旨の発言がありました。

それを聞いて「よっしゃ、やってみるか」と思ったのが今回の始まりです。

参考リンク

システムコールを追加

/usr/sys/ken/sysent.cの編集
# chdir /usr/sys/ken
# ed sysent.c
2131
62
        0, &nosys,                      /* 49 = x */
s/nosys/myadd/
s/x/myadd/
s/0/2/
.
        2, &myadd,                      /* 49 = myadd */
w
2135
q

system callのテーブルにmyadd(今回追加する足し算システムコール)が追加できました(2910行目参照)。sys 61(10進数で49)が実行されるとmyadd( )という名前の関数がシステムコールハンドラとして呼び出されます。

引数は2個取るようにしました。myadd( )でu.u_arg[ 0 ], u.u_arg[ 1 ]で引数を扱えます。

/usr/sys/ken/myadd.cの追加

システムコールハンドラmyadd( )を追加します。sys 61を実行するとこのmyadd( )が呼び出されます。

# chdir /usr/sys/ken
# ed myadd.c
?
a
\#include "../param.h"
\#include "../user.h"
\#include "../reg.h"

myadd( ) {
  int val1, val2 ;
  int result ;

  val1 = u.u_arg[ 0 ] ;
  val2 = u.u_arg[ 1 ] ;
  result = val1 + val2 ;
  u.u_ar0[ R0 ] = result ;
}
.
w
212
q

引数を受け取り、足し算の結果をユーザレジスタのR0に格納しているだけです。

/usr/sys/runの編集&コンパイル

先ほど作成したmyadd( )がライブラリに追加されるようにrun(コンパイルを行うスクリプト)を編集します。新たにmyadd.cというファイルを作成するのではなく、既存の*.cファイルにmyadd( )を追加する場合はrunの編集は必要ないと思います。

# chdir /usr/sys
# ed run
900
3
ar r ../lib1
s/1/1 *.o/p
ar r ../lib1 *.o
8
ar r ../lib2
s/2/2 *.o/p
ar r ../lib2 *.o
w
908
q

編集が終わったらコンパイルを行います。

# chdir /usr/sys
# sh run

コンパイルが無事終わったらCtrl-E -> quitで抜けて再ログインします。

これでカーネルに新しいシステムコールが追加されました。

コンパイルまわりは参考リンクで挙げた某語録を多分に参考にしています。ありがとうございます。

libcにシステムコールを呼び出す手続きを追加する

システムコールを追加しただけでは、cのソースから呼べない(?)のでlibcにmyaddシステムコールを呼ぶ手続きを追加します。

/usr/source/s4, s5にlibcのアセンブラコードが置かれています。既存のものを参考にmyaddシステムコールを呼ぶアセンブラコードを追加します。

# chdir /usr/source/s5
# ed myadd.s
?
a
.globl _myadd


_myadd:
  mov 2(sp), 0f
  mov 4(sp), 0f+2
  mov r5, -(sp)
  mov sp, r5
  sys 61; 0:..; ..
  mov (sp)+, r5
  rts pc
.
w
131
q

引数をsys命令の後ろに置いてからsys 61(=myadd)を実行しています。本当はsys myaddと書きたかったのですが……(詳細は後述)

myadd.sを作成したらrunファイルを編集して、myadd.sをアセンブルし、/lib/libc.aにmyadd.oを追加するようにします。

# chdir /usr/source/s5
# ed run
1081
36
as write.s; mv a.out write.o
a
as myadd.s; mv a.out myadd.o
.
40
ar r /lib/libc.a
s/.a/.a *.o/p
ar r /lib/libc.a *.o
w
1114
q

runの編集が完了したらコンパイルアセンブルし直します。

# chdir /usr/source/s5
# sh run

無事runの実行が完了したら、libcにmyadd.oが追加されたことを確認しましょう。

# ar t /lib/libc.a
(省略)
cerror.o
myadd.o

以上でlibcへの追加は完了です。

実行確認

# chdir /
# mkdir work
# chdir work
# ed hoge.c
?
a
int main( ) {

  int result ;

  result = myadd( 1, 2 ) ;
  printf( "1 + 2 = %d\n", result ) ;
  result = myadd( 3, 4 ) ;
  printf( "3 + 4 = %d\n", result ) ;

  return 0 ;

}
.
w
176
q

テストコードhoge.cを作成し終わったらコンパイルして実行します。

# cc hoge.c
# a.out
1 + 2 = 3
3 + 4 = 7

成功です! 無事足し算が行われていることが確認できました。

せっかくなのでアセンブラコードを吐いて眺めてみます。

# cc -S hoge.c
# cat hoge.s
(省略)
mov     $2,(sp)
mov     $1,-(sp)
jsr     pc,*$_myadd
tst     (sp)+
mov     r0,-10(r5)
mov     -10(r5),(sp)
mov     $L2,-(sp)
jsr     pc,*$_printf
(省略)

引数(1, 2)をスタックに積んでからjsrで_myadd(/lib/libc.a)に飛んでいるのがわかります。関数を呼ぶ前に引数をスタックに積むのはcの(?)仕様です。_myaddから帰ってくると、結果が格納されているr0とL2(printfで出力する文字列)をスタックに積んでからjsrで_printfに飛んでいます。やはり正しく足し算システムコールが呼び出されているようです。

(余談)

当初はシステムコールを呼び出すcの関数、例えばwaitシステムコールを呼ぶwait( )、からsys waitというアセンブラコードが吐かれるのかと思っていたのですが、jsrでlibcの_waitに飛んでいるだけでした。これは今でも変わらないそうです。@oracchaさんに教えていただきました。

まとめ

今回変更した箇所を絵で描いてみました。あくまでイメージですが。丸が今回追加・編集をした箇所です。

アセンブラにsys myaddを解釈させようとして失敗した

sys myaddと書いてアセンブルできるように、myaddシンボルをアセンブラに解釈させたかったのですが失敗しました。

以前アセンブラを追ったときに、as19.s, as29.sがシステムコールのテーブルを持っているというのを見つけていたので、見よう見まねで以下のように変更してみました。

# chdir /usr/source/as
# ed as19.s
6705
109
<signal\0\0>;           01;0000060
a
<myadd\0\0\0>;          01;0000061
.
w
6740
q
# ed as29.s
3903
76
01;0000060 /signal
a
01;0000061 /myadd
.
w
3921
q

これでrunを実行するとエラー。原因よくわかっていません。今度asを真面目に追ってみるかも。(.dataセグメントが溢れた? as19.s, as29.sには全てのsystem call symbolが入っていないし。溢れないように厳選している?)

終わりに

アセンブラにシンボルの解釈をさせることは失敗しましたが、無事に足し算システムコールが追加できました。これでOSの新人レベルにはなれたのでしょうか。

UNIX v6のコードを読んできたためか、思っていたより簡単に実装できました。

カーネルコンパイルの仕方もわかったので、もっと実用性のあるシステムコールを追加したり、アルゴリズムを変えてみたりしてみたいです。