SSブログ
前の10件 | -

Xilinx ISE WebPackインストール(Ubuntu on VirtualBox on OS X)

さてさて、PapilioというFPGA版Arduinoなるボードを買った。
で、買った後わかったのだが、Mac OS Xに開発環境が対応してない、、、。

しょうがなく、Mac OS XにVirtualBoxを入れて、Ubuntu上の開発で
やってみることにした。

XilinkのISE WebPackなるものが必要らしいが、以下インストール時に参照したページ。

■Xilinx ISE Design Suite 14.7をUbuntu 14.04にインストールする

ライセンス取得から起動までの流れが分かりやすかった。

■LinuxでISEを使う1(VirtualBox上のUbuntuを使ってISE14.4をインストール)

Linux対応のUSBドライバのインストールはこちらを参照した。


以上。





PDP-11のトラップのテスト動作

PDP-11のトラップの動きの確認したく、簡単なアセンブラプログラミングをして、
simhでブレークをはって以下の情報を確かめてみた。
 - カーネルモードとユーザモードのSPのレジスタが別であること
 - トラップ時のPSWの値の確認
 - mfpi 命令の動き

アセンブラプログラムは以下:


        .global start

PS = 0177776
.text
.even
start:
mov $0010000, sp / set kernel mode's stack pointer
mov $0140000, PS / change current mode to user mode
mov $0020000, sp / set user mode's stack pointer
mov 0, r0

/ copy vector table to zero address
mov $vector, r1
mov $vectorend, r2
1$: mov (r1)+,(r0)+
cmp r1,r2
bne 1$

trap 0377
2$: br 2$

trap:
mfpi sp / push in stack previous mode's stack pointer
rti

.data
.even
vector:
.word 0
.word 0
/ trap vectors
.word 0, 0 / bus error
.word 0, 0 / illegal instruction
.word 0, 0 / bpt-trace trap
.word 0, 0 / iot trap
.word 0, 0 / power fail
.word 0, 0 / emulator trap
.word trap, 0346 / system entry
vectorend = .


.end





上記は、SPの値を
 カーネルモード → 010000
 ユーザモード  → 020000
にしている。
さて、simhに01000番地にプログラムをロードしてから、



sim> e -m 1000:1100
1000: MOV #10000,SP
1004: MOV #140000,177776
1012: MOV #20000,SP
1016: MOV 0,R0
1022: MOV #1050,R1
1026: MOV #1110,R2
1032: MOV (R1)+,(R0)+
1034: CMP R1,R2
1036: BNE 1032
1040: TRAP 377
1042: BR 1042
1044: MFPI SP
1046: RTI
1050: HALT
1052: HALT
1054: HALT
1056: HALT
1060: HALT
1062: HALT
1064: HALT
1066: HALT
1070: HALT
1072: HALT
1074: HALT
1076: HALT
1100: HALT
sim> br 1044
sim> g

Breakpoint, PC: 001044 (MFPI SP)
sim> e psw
PSW: 030346
sim>

さて、トラップが起きた直後のアドレスにブレークポイントをはって止めた状態で、
PSWの値を確かめてみたが、030346は想定通り。
03xxxxは前モードがユーザモードであることを意味している。
xxx346は、トラップベクターに設定した値そのまま。
        .word   trap, 0346      / system entry



続いて、

sim> e sp
SP: 007774
sim> e 7770:10000
7770: 000000
7772: 000000
7774: 001042
7776: 140004
10000: 000000

SPはカーネルモードの010000からになっている。狙い通りの動き。


さらに、


sim> s

Step expired, PC: 001046 (RTI)
sim> e 7770:10000
7770: 000000
7772: 020000
7774: 001042
7776: 140004
10000: 000000



一回ステップ実行して、MFPI SPを実行させてみた。 
スタックには、ユーザモードのSP値の020000が積まれている。 
うむ、仕様通りの動き、確認できた。と。 

 以上。



PDP-11のtrap命令とUNIX V6のシステムコール

UNIX V6のシステムコールは、アセンブラのsys疑似命令を使って実装されている。
sys命令はUNIX V6のアセンブラの実装であって、PDP-11の命令には存在しない。

sys は PDP-11の trap 命令にマッピングされている。
実際にどういう風に展開されるかは、、、調べてないが、
まあ、要は trap 命令を使って、CPUにトラップ処理させる。割り込みと同じ。
(ちなみに、pdp11のgccにもsys命令はあるが、これはtrapの名前を置き換えただけ)

このtrap命令のアセンブラは、

   trap  001

のように数字を指定できる。
実際には、この数字はオペーコードの下位8ビットにマッピンされる。
マシンコード的には、

  trap 0000  --> 104400 (8進)
  trap 0001  --> 104401 (8進)
    ...
  trap 0377  --> 104777 (8進)

になる。
しかし、UNIX V6では、下位6ビット分しか使っていない。

■参照:ken/trap.c の trap()
・・・
        case 6+USER: /* sys call */
                u.u_error = 0;
                ps =& ~EBIT;
                callp = &sysent[fuiword(pc-2)&077];
・・・

つまり、システムコールの上限は64個、ということですな。


以上。

UNIX V6: プロセス切り替えのところを読む

プロセス切り替えの肝、swtch()を読みつつ、 UNIX V6のプロセス管理の仕組みの理解を整理しつつ。

まず一般的に考えてみて、
あるプロセスから別のプロセスに切り替わる作業は、
 ・今のプロセスのコンテクストを保存しておく
 ・切り替え先プロセスのコンテクストに戻す
を行う。
コンテクストはプログラムが走っている途中の状態を指すが、実際は、
 ・PC
 ・SP
 ・作業中のレジスタ情報
の情報になる。(これらが復帰すれば、いったん中断された状態に戻れる)
もちろん、そのプロセスが使っているスタック、変数領域は全部、破壊されずに保存されているのが前提。

で、UNIXの場合、
   関数(サブルーチン)呼び出しの時は、戻り先PCとレジスタ群はスタックに積まれる。

コンテクストを戻す際は、スタックの状態が安全に保存されている前提であれば、
最低限戻すべき情報は
  SP
だけで良いはず。

しかし、実際はUNIXというかCは、関数内ローカル変数のアクセスに
r5 (フレームポインタ)を使っているので、この情報も戻さないと動きがおかしくなる。
UNIX V6のCはr5をフレームポインタに使っているが、pdp-11版gccのも同じ役割でr5を使っていた。

スタックの処理が肝になるのだが、まだよくわからない点:
・システムコール時のSPはどこを指しているの?
 ・PDP-11のSPは、割り込み(カーネルモード)用とユーザ用とがあるが、切り替えはどこ?
 ・プロセスごとにカーネル作業用スタック領域があるがこれは何?
 ・プロセスごとのスタックは、制御がプロセスに戻るときに使われるんだろうな、


とりあえず、以上。



PDP-11のjsrの動き

最近、「はじめてのOSコードリーディング」を読んで、
UNIX V6のソースコードを追いながら、動きを理解する作業をしている。

PDP-11のjsrは適当に感覚で読んでいたが、とあることで
やっと動きを調べて理解した。
命令は、下記のようなものだが、

   jsr  <reg>, <target_address>

動きは、

  (1) <reg>の値をスタックに積む
  (2) <reg> にPCの値を入れる (PCはjsrの次の命令を指している)
  (3) PC に<target_address>を入れる

で、target_addressに飛ぶ訳だ。
実は (2) の処理があるのを分かってなくて、以下のコードが意味不明だった。

jsr r5, csv

csv:
   mov  r5, r0
   mov  sp, r5
   mov  r4, -(sp)
   mov  r3, -(sp)
   mov  r2, -(sp)
   jsr pc, (r0)


最後のjsrはr0の指すアドレスに飛ぶのだが、このr0はcsvの最初でr5が元になっている。
え、このr5の中身はどうなってる???
というところが疑問だった訳だ。
まあ、調べれば一発なんですがね・・・。












  

UNIX V6 のカーネルローダーをCで実装してみる

UNIX V6のinode、ファイルシステムの勉強がてらカーネルローダーを自作してみた。
やってることはfsboot.sとほぼ同じで、PDP-11(simh)のrk05ディスクドライブから、カーネルのバイナリを探してロードして起動する。
とりあえず方針として、
・できるだけC言語で実装する
・高速化は考えない(シンプルに実装する)
・カーネルファイル名は"unix"で探す
とした。以下ソース。




"ld.scr"
ENTRY(start)
SECTIONS
{
  . = 24*2048-512;
  .text : {
    *(.text)
    *(.rodata)
  }
  .data : {
    *(.data)
  }
  .bss :  {
    *(.bss)
  }
  end = .;
}



"start.S" .GLOBAL _cstart STACK = 24*2048-514 .text .even .globl start start: mov $STACK, sp jsr pc, _cstart mov $start,-(sp) clr pc .end
"inod.h" typedef unsigned int uint16_t; typedef int int16_t; struct inode { uint16_t i_mode; char i_nlink; char i_uid; char i_gid; char i_size0; uint16_t i_size1; uint16_t i_addr[8]; int16_t i_atime[2]; int16_t i_mtime[2]; }; typedef struct inode inode_t; /* modes */ #define IALLOC 0100000 #define IFMT 060000 #define IFDIR 040000 #define IFCHR 020000 #define IFBLK 060000 #define ILARG 010000 #define ISUID 04000 #define ISGID 02000 #define ISVTX 01000 #define IREAD 0400 #define IWRITE 0200 #define IEXEC 0100
"main.c" #include "ino.h" #define RK11_CTL_REG_ADDR (0177400) #define CODE_ADDR (((unsigned int)24*2048)-512) #define INODE_BUF_ADDR (CODE_ADDR-1024) #define RKRDY (1<<7) struct dir_info { uint16_t ino; char name[14]; }; struct rk11 { uint16_t rkds; // 0177400 uint16_t rker; // 0177402 uint16_t rkcs; // 0177404 uint16_t rkwc; // 0177406 uint16_t rkba; // 0177410 uint16_t rkda; // 0177412 }; static volatile struct rk11 *rk11_p = (struct rk11*)RK11_CTL_REG_ADDR; static volatile inode_t *inod = (inode_t*)INODE_BUF_ADDR; static volatile int *buf = (int*)INODE_BUF_ADDR+sizeof(inode_t); /*---------------------------------- -----------------------------------*/ void memcpy(volatile void *dst, volatile void *src, int n) { while(n--) *(char*)dst++ = *(char*)src++; } int streq(char *src, char *dst) { while(*src != 0 && *dst != 0) if(*src++ != *dst++) return 0; return 1; } void read_blk(volatile void *buf, int blk_no) { unsigned int cylnd = (blk_no/12)>>1; unsigned int plane = (blk_no/12)&1; unsigned int sect = (blk_no%12); rk11_p->rkda = (cylnd<<5)|(plane<<4)|sect; rk11_p->rkba = (uint16_t)buf; rk11_p->rkwc = -256; rk11_p->rkcs = 5; // READ & GO while(!(rk11_p->rkcs & RKRDY)) ; } void iget(int ino) { int blk_no = (31+ino)/16; int i_offset = 32*((31+ino)%16); read_blk(buf, blk_no); memcpy(inod, buf+i_offset, 32); } int cstart() { static volatile void *zero_adr = 0; struct dir_info *dir_p; int i; // get root's inode info iget(1); read_blk(buf, inod->i_addr[0]); // get kernel inode info dir_p = (struct dir_info*)buf; for(i = 0; i < 512/16 && dir_p->name[0] != 0; i++, dir_p++) { if(streq(dir_p->name, "unix")) { iget(dir_p->ino); break; } } // read kernel binary file to address zero read_blk(buf, inod->i_addr[0]); for(i = 0; i < 256 && buf[i]!=0; i++) { read_blk(zero_adr+i*512, buf[i]); } if( *(int*)zero_adr == 0407) { memcpy(zero_adr, zero_adr+020, inod->i_size1); } return 0; }







バイナリファイルのサイズが512 byteに収まらなかった・・・。(771 byte)
gccの-Oオプションをつけると、実行に失敗するし。(pdp11のgccのバグかしら) 
まじめに収めようとすると、やはりアセンブラで組まないといかんかな。 


Bare Metalプログラミングする時はスタック領域は把握してこ

rk11ディスクから、1ブロック分512バイトの任意のアドレスに読み込むのに
07000〜010000あたりを指すとおかしな挙動をするという謎があったが、、、
解けた、というか単にバグだった。

えー、しょっぱなのstart.Sでスタックポンタの設定を0x1000番地にしていて、これは010000(8進)だった。



t_rdsk $cat start.S
.GLOBAL start
.GLOBAL _cstart

STACK = 0x1000

.text
start:
mov $STACK, sp
jsr pc, _cstart
halt

.end





アホやね。普通に自分でスタック破壊してるだけだった。
まあアセンブラプログラミングの基礎中の基礎だろけど、慣れていこ。

以上。



PDP-11(simh)のrk11 のデータ読み込みのなぞ

PDP-11のRK11 ディスクをたたいて、ディスクの読み書きをC言語からやっている。
とりあえず、以下の関数read_blk()を実装して任意のブロックを1ブロック分読めるようになった。



#define RK11_CTL_REG_ADDR (0177400)

static volatile struct rk11 {
  uint16_t rkds; // 0177400                                                                   
  uint16_t rker; // 0177402                                                                   
  uint16_t rkcs; // 0177404                                                                   
  uint16_t rkwc; // 0177406                                                                   
  uint16_t rkba; // 0177410                                                                   
  uint16_t rkda; // 0177412                                                                 
} *rk11_p = (struct rk11*)RK11_CTL_REG_ADDR;

void read_blk(void *buf, int blk_no)
{
  unsigned int cylnd = (blk_no/12)>>1;
  unsigned int plane = (blk_no/12)&1;
  unsigned int sect = (blk_no%12);

  rk11_p->rkda = (cylnd<<5)|(plane<<4)|sect;
  rk11_p->rkba = (uint16_t)buf;
  rk11_p->rkwc = -256;
  rk11_p->rkcs = 5;    // READ & GO                                                                                                                   

  while(!(rk11_p->rkcs & RKRDY))
    ;
}


が、ディスクのブロックを0番地に順に読み込んでいって、 なぜか転送先07000番地に指定すると、おかしな挙動になる。 simhのeコマンドでメモリダンプすると、07000-07777はデータはあるし、 転送してるっぽいけど、010000の境界が何か悪さしてるんだろか。 謎だ。とりあえず時間切れなので、あとで調べるか。

UNIXのカーネルブートローダーを読む

PDP-11でのブートプロセスは、
  (1) ROMのブートストラップローダ
  (2) ディスクのブートセクターに格納されているカーネルブートローダ
  (3) UNIXカーネル

の順に実行される。(1)が(2)をメモリにロードして、(2)が(3)をメモリにロードする。
で、下記サイト

に置いてあるUNIX V6のバイナリの、unix0_v6_rk.dsk のブートセクターに格納されてる
カーネルブートローダのソースは、mdec/fsboot.s である。

これの中身がアセンブラなので、少々流れがつかみづらい。
結構トリッキーなこともやっている。
(呼び出し先のサブルーチンが、実行結果によってスタックに積まれた戻りアドレスを直接変更しちゃってるとか。)
PDP-11の全盛期は、アセンブラプログラミングが当たり前なので、こういうのって当たり前なテクニックだったのかもしれん。

まあ、それはともかく、fsbootでは、以下のことをやっている。

(1) 自分自身のコードを退避している
 Kernelバイナリを0番地にコピーするので、自分自身を24*2048-512番地に退避している。
これメモリサイズが48KBを想定しているてことかな。
(2) メモリをクリアしている。(0番地〜上記24*2048-512の手前まで)
(3) @文字を出力して、文字入力を受け付ける。(文字はカーネルのバイナリファイル名で、通常はunix)
(4) rootディレクトリが格納されているinode 1番が格納されているディスクブロック(512バイト)
     を内部バッファにコピーし、その中からinodeの情報も抽出して、別のバッファにコピーしている
(5) inode情報からブロックデータをバッファにコピーし、それはフォルダーの構造になっているので
  そこから入力されたバイナリファイル名と一致するinodeを探す
(6) 上記一致したinodeのブロックデータを、0番地にコピーして、0番地に飛ぶ

かな。
コードの最後は読み切れてないが、UNIXのファイル構造にしたがって、ディスクアクセスしつつ
内部バッファにデータをためて、ファイル名検索して、という感じだ。

ファイルシステムの構造が分かれば、アセンブラで実装しようがCで実装しようがあまり関係ないが、
カーネルブートローダー自身は512バイトに収まる必要がある。


以上。


RK11ディスクドライブメモ

さて、UNIX V6のディスクブートローだubootの部分を読み始めた。
RK11 Disk Controllerの制御レジスタ部分のメモ:



 Hex     Oct   name 
------------------------------------------------
0xFF00 0177400 RKDS ドライブの状態を表す
0xFF02 0177402 RKER エラーの状態を表す
0xFF04 0177404 RKCS ディスク制御に使用される
0xFF06 0177406 RKWC 転送データサイズを表す
0xFF08 0177410 RKBA メモリ中の転送データのアドレス
0xFF0A 0177412 RKDA ドライブ中の転送データのアドレス
------------------------------------------------




※参考書は:「はじめてのOSコードリーディング」

前の10件 | -

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。