2014年1月12日日曜日

main() の初期化処理

main() はカーネルが管理するヒープ領域とシステムプロセス proc[0] の設定を 行った後、newproc() により proc[1] を生成する。 newproc() を呼び出したコンテキスト(proc[0]) は 0 を返すので、

if (newproc()) {
    /* ... */
}
sched();

を実行すると sched() に入る。sched() では実行可能状態(SRAN)だが、 ロードされていないプロセスを探索するが、見つからないので sleep() する。 sleep() により、コンテキスト切り替え swtch() が発生して proc[1] が実行される。 swtch() では newproc() で proc[1] を生成する際に savu により保存した pc, r5 を retu により復帰させることでコンテキストを proc[1] に切り替えている。 また、swtch() からの return 1 にて上記の if (newproc()) の呼び出しから 戻り値 1 でリターンする。

いくつかのアセンブラコードの解析

copyseg は第一引数で指定したセグメントから第二引数で指定したセグメントへ 1 block 分のデータをコピーする。

_copyseg:
        mov     PS,-(sp)        ; PS を push (-2)
        mov     UISA0,-(sp)     ; UISA0 を push (-4)
        mov     UISA1,-(sp)     ; UISA1 を push (-6)
        mov     $30340,PS       ; 現モード = カーネル、前モード = ユーザ
        mov     10(sp),UISA0    ; 第一引数を UISA0 に設定
        mov     12(sp),UISA1    ; 第二引数を UISA1 に設定
        mov     UISD0,-(sp)     ; UISD0 を push
        mov     UISD1,-(sp)     ; UISD1 を push
        mov     $6,UISD0        ; UISD0 を read/write に設定
        mov     $6,UISD1        ; UISD1 を read/write に設定
        mov     r2,-(sp)        ; r2 を push
        clr     r0              ; r0 は VA=0(APF=0) 番地を指す
        mov     $8192.,r1       ; r1 は VA=8192(APF=1) 番地を指す
        mov     $32.,r2         ; コピーするカウンタ(1block分)
1:
        mfpi    (r0)+           ; r0(UISD0) からスタックに push
        mtpi    (r1)+           ; スタックから pop して r1(UISD1) へ移動
        sob     r2,1b           ; r2 に設定した回数まで繰り返し
        mov     (sp)+,r2        ; 以下、スタックから pop して元の値を復帰
        mov     (sp)+,UISD1
        mov     (sp)+,UISD0
        mov     (sp)+,UISA1
        mov     (sp)+,UISA0
        mov     (sp)+,PS
        rts     pc

clearseg は 0 番地から 32 word を 0 クリアする。 このとき仮想アドレスは UISA0 を通して物理アドレスに変換される。 物理アドレスへの対応は引数で指定した値を UISA0 の PAF に設定することで決定する。

_clearseg:
        mov     PS,-(sp)
        mov     UISA0,-(sp)
        mov     $30340,PS        ; 現モード = カーネル、前モード = ユーザ
        mov     6(sp),UISA0      ; 引数に指定した PAF を UISA0 に設定する
        mov     UISD0,-(sp)
        mov     $6,UISD0         ; 対応する UISD0 は read/write 設定
        clr     r0
        mov     $32.,r1
1:
        clr     -(sp)            ; 次の mpti がスタックから pop した値を使用するため 0 を先に push しておく
        mtpi    (r0)+
        sob     r1,1b            ; -1 した結果が 0 でなければ 2 word 前に分岐
        mov     (sp)+,UISD0
        mov     (sp)+,UISA0
        mov     (sp)+,PS
        rts     pc

saveu は user 構造体のメンバ ursav[2] にカレントスタックポインタと 環境ポインタ(r5)を保存する。引数は ursav へのポインタを与える。

_savu:
        bis     $340,PS         ; プロセッサ優先度を 7(最高) に設定
        mov     (sp)+,r1        ; リターンアドレスを pop
        mov     (sp),r0         ; 引数で指定した user 構造体のメンバ u_rsav
        mov     sp,(r0)+        ; カレントプロセスのスタックポインタを u_rsav[0]
        mov     r5,(r0)+        ; に保存し、r5 を u_rsav[1] に保存
        bic     $340,PS         ; プロセッサ優先度を 0(最低) に設定
        jmp     (r1)            ; 呼び出し元に戻る
_retu:
        bis     $340,PS         ; プロセッサ優先度を 7(最高) に設定
        mov     (sp)+,r1        ; リターンアドレスを pop
        mov     (sp),KISA6      ; KISA6 を引数で指定した物理アドレスに切り替える
        mov     $_u,r0          ; 構造体 u の先頭アドレスを取得
1:
        mov     (r0)+,sp        ; u_rsav[0] からスタックポインタを復帰
        mov     (r0)+,r5        ; u_rsav[1] から環境ポインタ(r5)を復帰
        bic     $340,ps         ; プロセッサ優先度を 0(最低) に設定
        jmp     (r1)            ; 呼び出し元に戻る

2014年1月7日火曜日

Pre K&R C の無名構造体を用いたバイトアクセス

UNIX V6 の Pre K&R C では無名の構造体のメンバが外部に公開されており、 -> 演算子を使って構造体メンバにアクセスできる。 (この時、必ずしも左辺値はポインタ型でなくてもよい。) 16bit の int 型へのポインタから無名構造体のメンバにバイトアクセスする方法を 実験してみた。

struct { 
    char a; 
    char b;
};

int main()
{
    register *p;
    register pa, pb;
    int x;

    x = (0125 << 8) | 0252;
    p = &x;
    printf("%d\n", *p);
    pa = (*p).a;
    pb = (*p).b;
    printf("%d %d\n", pa, pb);
    pa = p->a;
    pb = p->b;
    printf("%d %d\n", pa, pb);

    return (0);
}

実行結果は以下の通り。

# ed main.c
?
a
struct {        char a;         char b;};int main(){    register *p;    register pa, pb;        int x;          x = (0125 << 8) | 0252; p = &x; printf("%d\n", *p);     pa = (*p).a;    pb = (*p).b;    printf("%d %d\n", pa, pb);      pa = p->a;      pb = p->b;      printf("%d %d\n", pa, pb);      return (0);}
.
w
252
q
# cc main.c
# ./a.out
21930
-86 85
-86 85
#

x に 0x55AA ((0125 << 8) | 0252) を代入して、p に x へのポインタを代入する。 当然ながら、*p の値は 0x55AA (21930) になる。 ANSI C と同様に、(*p).a と p->a の結果は一致するが、p->a と p->b の値は それぞれ -86 と 85 になっている。これは、PDP11 がリトルエンディアンであるため、 16bit の p->b によってアクセスされるのは 0x55 (85) であると解釈できる。 同様に、p->a によってアクセスされるのは 0xAA であるが、構造体メンバ a が char 型なので符号拡張されて、0xFFAA (-86) になると解釈できる。