FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

xv6を移植するときに書き換えるルーチンについて調査(7)

http://img.deusm.com/eetimes/2014/08/1323406/microprocessor-x-366.jpg

msyksphinz.hatenablog.com

xv6のRISC-Vへのインポートの続き。

そういえばISSRISC-V用ビルドには、IDEモジュールとUARTモジュールを追加していなかった。 x86では、IDEやUARTはinbやoutb命令で操作できるが(つまりポートマップドI/O)、MIPSRISC-Vではそうはいかない。メモリマップに、これらのモジュールを定義しておかなければならない。

メモリマップドI/O - Wikipedia

ポートマップドI/Oでは、入出力用の特別なCPU命令を使用する。例えば、インテルx86には入出力専用の IN 命令と OUT 命令があり、入出力機器の1つのバイトの読み書きを行う。

RISC-Vの実装で、外部アクセス系を具体的にどこにマップすればよいのか資料を見つけることはできなかったが、とりあえず適当にマップを作成する。このマップは後で変更するつもりだけれども。

start end
IDEモジュール 0xffff_0000 0xffff_7fff
UARTモジュール 0xffff_8000 0xffff_ffff

github.com

基本的にメモリマップI/Oなので、ロードストア処理の部分に分岐を突っ込んでやっておけば外部I/Oモジュールにアクセスできるようになる。

この実装はずいぶんと付け焼刃な感じがして、拡張性に乏しいが、後でちゃんとテーブルとか作って書き直そう。

MemResult RiscvEnv::LoadFromBus (Addr_t addr, Size_t size, DWord_t *data)
{
  Byte_t byte[8];
  MemResult result;
  if (addr >= ide_start && addr <= ide_end) {
    Addr_t ide_addr = addr - ide_start;
    Word_t w_data;
    result = m_ide->LoadData (ide_addr, size, &w_data);
    *data = static_cast<Word_t>(w_data);
  } else if (addr >= uart_start && addr <= uart_end) {
    Addr_t uart_addr = addr - uart_start;
    Word_t w_data;
    result = m_uart->LoadData (uart_addr, size, &w_data);
    *data = static_cast<Word_t>(w_data);
  } else {
    result = LoadFromBus (addr, size, byte);
    memcpy (data, byte, 8);
  }
  return result;
}

IDEモジュールについて

今回実装しているIDEモジュールは、MIPS用シミュレータを作ったときに実装したものだけれど、基本的にそれを流用すればよいと思っている。

msyksphinz.hatenablog.com

ATA/ATAPI/I/O Port, Register - OS Project Wiki

UARTモジュールについて

こっちは、Intel 8250というモジュールを利用している。これも、MIPS用シミュレータを作ったときに実装したものだ。

8250 UART - Wikipedia

実装した結果、まずはRISC-VのIPLが動作してOSをローディングするようになった。つまりIPL(Initial Program Loader)が動作し始めた。

       103:M:MBar:[000002a0] ffff06b7 : lui        r13,0xffff0          r13<=ffff0000
       104:M:MBar:[000002a4] 04000713 : addi       r14,r00,0x040        r00=>00000000 r14<=00000040
<IDE: CommandPort:RegularStatus(0x000001f7) Load>
       105:M:MBar:[000002a8] 1f76c783 : lbu        r15,r13,0x1f7        r13=>ffff0000 r15<=00000040
       106:M:MBar:[000002ac] 0c07f793 : andi       r15,r15,0x0c0        r15=>00000040 r15<=00000040
       107:M:MBar:[000002b0] fee79ce3 : bne        r15,r14,0x7f         r15=>00000040 r14=>00000040
       108:M:MBar:[000002b4] 00c12403 : lw         r08,r02,0x00c        r02=>bfffff98 (bfffffa4)=>000000ff r08<=ffffffff
       109:M:MBar:[000002b8] 01010113 : addi       r02,r02,0x010        r02=>bfffff98 r02<=bfffffa8
       110:M:MBar:[000002bc] 00008067 : jalr       r00,r01,0x067        r01=>00000328 pc<=00000328
       111:M:MBar:[00000328] 20048693 : addi       r13,r09,0x200        r09=>00010000 r13<=00010200
       112:M:MBar:[0000032c] ffff0737 : lui        r14,0xffff0          r14<=ffff0000
<IDE: DataPort(0x000001f0) Load>
<ReadImgFile(Index=0)=>000000b3>
       113:M:MBar:[00000330] 1f072783 : lw         r15,r14,0x1f0        r14=>ffff0000 r15<=00000000
       114:M:MBar:[00000334] 00f4a023 : sw         r09,r15,0x00|r00     r09=>00010000 r15=>00000000 (00010000)<=00000000
       115:M:MBar:[00000338] 00448493 : addi       r09,r09,0x004        r09=>00010000 r09<=00010004
       116:M:MBar:[0000033c] fe969ae3 : bne        r13,r09,0x7f         r13=>00010200 r09=>00010004 pc<=00000330
<IDE: DataPort(0x000001f0) Load>
<ReadImgFile(Index=1)=>00000133>
       117:M:MBar:[00000330] 1f072783 : lw         r15,r14,0x1f0        r14=>ffff0000 r15<=00000000
       118:M:MBar:[00000334] 00f4a023 : sw         r09,r15,0x00|r00     r09=>00010004 r15=>00000000 (00010004)<=00000000
       119:M:MBar:[00000338] 00448493 : addi       r09,r09,0x004        r09=>00010004 r09<=00010008
       120:M:MBar:[0000033c] fe969ae3 : bne        r13,r09,0x7f         r13=>00010200 r09=>00010008 pc<=00000330
<IDE: DataPort(0x000001f0) Load>
<ReadImgFile(Index=2)=>000001b3>
       121:M:MBar:[00000330] 1f072783 : lw         r15,r14,0x1f0        r14=>ffff0000 r15<=00000000
       122:M:MBar:[00000334] 00f4a023 : sw         r09,r15,0x00|r00     r09=>00010008 r15=>00000000 (00010008)<=00000000
       123:M:MBar:[00000338] 00448493 : addi       r09,r09,0x004        r09=>00010008 r09<=0001000c
       124:M:MBar:[0000033c] fe969ae3 : bne        r13,r09,0x7f         r13=>00010200 r09=>0001000c pc<=00000330
<IDE: DataPort(0x000001f0) Load>
<ReadImgFile(Index=3)=>00000233>
       125:M:MBar:[00000330] 1f072783 : lw         r15,r14,0x1f0        r14=>ffff0000 r15<=00000000
       126:M:MBar:[00000334] 00f4a023 : sw         r09,r15,0x00|r00     r09=>0001000c r15=>00000000 (0001000c)<=00000000
       127:M:MBar:[00000338] 00448493 : addi       r09,r09,0x004        r09=>0001000c r09<=00010010

Initial Program Loader とは

IPLとは、CPUがブートしたときに最初に動作するプログラムで、ROMなどの不揮発性の媒体に格納されている。

CPUが起動すると、まずこのIPLを実行する。IPLはディスクの特定の場所からOSのイメージをロードし、メインメモリに転送する。 OS起動用のプログラムがメインメモリに格納されると、OSプログラムのブートアドレスにジャンプし、OSの起動が開始するという訳だ。

とりあえず、ここまではうまくいったようだ。RISC-V版で、xv6が起動するまで、頑張ろう。

TensorFlow 0.8で並列分散処理がサポートされたけどマルチコア環境ではすでに並列処理で動いている

TensorFlow 0.8で、並列分散処理がサポートされるようになったとのことだ。元記事を読んでいてもふわっとしていていてよく分からないのだが、どうやら大規模分散環境で並列処理ができるようになったらしい。 あれ?というかもともとTensorFlowって並列処理は出来てたんだよね。そのあたりが、僕の中で非常にあいまいだった。 一応、TensorFlowが最新版でなくても、マルチコアの環境ならば十分に並列処理してくれることを確認したい。

TensorFlow 0.8RC0と0.7.1 をインストールした4コアの環境を用意する

といっても利用するのはVagrantだ。Vagrant上に、4コアを搭載したUbuntuでTensorFlow 0.8RC0とTensorFlow 0.7.1の環境を作成し、動作を確認、および実行速度の計測を行う。

github.com

github.com

TensorFlow 0.8でもTensorFlow 0.7でも、マルチコアによる並列処理は実現可能

別に大規模分散環境を持っていなくても悲観することはなく、TensorFlowはGPGPUでも実行できるし、マルチコア環境でも十分にCPU能力を使ってくれる。 利用したのは以下のチュートリアルなのだが実行するのに非常に時間がかかるので一分短縮して学習処理を行わせた。

Deep MNIST for Experts

一部学習処理を短縮している。あまりにも時間がかかりすぎるためだ。

import time

start = time.time()
for i in range(3000):    # Change 20000 to 3000
  batch = mnist.train.next_batch(50)
  if i%100 == 0:
    train_accuracy = accuracy.eval(feed_dict={
        x:batch[0], y_: batch[1], keep_prob: 1.0})
    print("step %d, training accuracy %g"%(i, train_accuracy))
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

elapsed_time = time.time() - start

実行時間は0.8RC0で約500秒、0.7.1で約419秒だった。これも厳密に同じ環境とは言えないので、この程度ならばバージョンが変わったことによる影響で、分散環境をサポート可否とは無関係だ。

0.8.0RC0
>>> print("Elapsed-time:{0}".format(elapsed_time))
Elapsed-time:502.545868158

0.7.1
>>> print("Elapsed-time:{0}".format(elapsed_time))
Elapsed-time:419.51450491

ちなみに、4コアのCPUもマルチコアで使ってくれる。300%くらいしか到達していないけれども。topコマンドの結果:

top - 02:13:13 up 27 min,  2 users,  load average: 3.32, 1.27, 0.50
Tasks: 103 total,   2 running, 101 sleeping,   0 stopped,   0 zombie
%Cpu(s): 43.8 us, 33.6 sy,  0.0 ni, 22.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   4048000 total,   755432 used,  3292568 free,    20328 buffers
KiB Swap:        0 total,        0 used,        0 free.   134596 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 1929 vagrant   20   0 1113688 456748  27232 S 316.5 11.3   6:54.01 python
  157 root      20   0       0      0      0 S   0.3  0.0   0:00.43 kworker/2:1
    1 root      20   0   33584   2956   1496 S   0.0  0.1   0:00.83 init

趣味で動かす程度ならば0.8のアップデートで劇的に性能が改善することはない

それよりも、GPGPUを買ってGPGPU対応のTensorFlowを動かしたほうが、よっぽど効率的かもね。

Understanding the detailed Architecture of AMD's 64bit Core "The Integer Core"の翻訳を完了

非常に荒削りだが、chip-architect.com の"Understanding the detailed Architecture of AMD's 64bit Core"の第1章にあたる、"The Integer Core"の翻訳を終了した。

http://www.chip-architect.org/news/Opteron_1600x1200.jpg

オリジナルの文章はこちら。

Chip Architect: Detailed Architecture of AMD's Opteron

翻訳の文章はこちら。

github.com

プロセッサの設計者であったり、コンピュータアーキテクチャん興味がある人だったら、楽しく読める解説記事なのではないかと思う。かなり古い文章だけれども、スーパスケーラプロセッサの基礎になる部分が含まれている。

スケジューリングから実行までに必要なサイクル数をなるべく減らしていくということ

Opteronを例に取っているのだが、例えばリザベーションステーションで前の命令の結果を待ち合わせているときに、1サイクルでも次の命令を速く発行できるように、結果を取得する1サイクル前にタグを出力する。 タグを監視しているリザベーションステーションは、一致するタグが流れてきた時点で命令を発行してしまう。データの到着は後でも良いという訳だ。 これにより、リザベーションステーションでデータを待ち合わせるのではなく、ALU内でデータが到着することが分かった時点で命令が発行されるようになり、発行までの時間を早めることができる。

まあこれは現在のプロセッサにとってはかなり常識的なことかもしれないけど、こういう1サイクルを削り取る努力により、アーキテクチャというのは進化していくのだなあと思う。

アーキテクチャだけでない、動作周波数を上げるための回路の単純化

例えば、リオーダバッファを1つのレジスタファイルで作ってしまうと、読み出しポートと書き込みポートが莫大必要になり、巨大な回路を生成&動作周波数が低下してしまう。 これを解決するために、最初から同時命令発行数の最大値が3なのであれば、リオーダバッファを3つに分割し、その中でデータのやりとりをする。 これにより、読み出しと書き込みのポート数を各リオーダバッファで1つに抑えることができ、回路の単純化に繋がる。

アーキテクチャの改善には、動作周波数を上げるための様々な単純化が必要になる。単純さを維持しつつ、アーキテクチャとしても性能を維持しなければならないというのが、プロセッサアーキテクトの腕の見せ所なんだよなあ(とても大変だけれども)。

xv6を移植するときに書き換えるルーチンについて調査(6)

msyksphinz.hatenablog.com

xv6を移植するときに書き換えるルーチンについて調査(5) の続き。

コンパイルはできるようになったのだが、imgファイルを確認すると、まだうまくいっていないところがある。 RISC-Vはリセットベクタが0x0200となっているのだが、imgをダンプするとそこにはよくわからない値が入っていた。

$ hexdump xv6.img | head -n 20
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
0000200 457f 464c 0101 0001 0000 0000 0000 0000
0000210 0002 00f3 0001 0000 0000 8010 0034 0000
0000220 3448 0003 0000 0000 0034 0020 0001 0028
0000230 0014 0011 0001 0000 1000 0000 0000 8010
0000240 0000 0010 d0f8 0000 6bac 0001 0007 0000
0000250 1000 0000 0000 0000 0000 0000 0000 0000
0000260 0000 0000 0000 0000 0000 0000 0000 0000
*
0001200 f073 3000 e117 0000 0113 18c1 5297 0000
0001210 8293 e882 8067 0002 0013 0000 0113 fe01
0001220 2e23 0011 2c23 0081 0413 0201 a7b7 8010

ちなみに、オリジナルのx86版のxv6でも同様の値が挿入されていた。どうやら、オブジェクトファイルをそのままimgに変換するとこれが挿入されるらしい。x86ではシステムがリセットされると0番地からブートするので特に問題ないのだが、RISC-Vの場合はちょうど0x200番地に邪魔者が入っており、ブートできない、というわけだ。

  • x86版xv6のimgファイルダンプ結果
$ hexdump xv6.img | head -n 50
...
0000140 e800 ffa3 ffff c483 810c 003d 0100 7f00
0000150 4c45 7546 a150 001c 0001 988d 0000 0001
0000160 b70f 2c35 0100 c100 05e6 de01 f339 2f73
0000170 7b8b ff0c 0473 73ff 5710 6ae8 ffff 8bff
0000180 144b 438b 8310 0cc4 c139 0c76 c701 c129
0000190 00b8 0000 fc00 aaf3 c383 3920 77de ffd1
00001a0 1815 0100 8d00 f465 5e5b 5d5f 00c3 0000
00001b0 0000 0000 0000 0000 0000 0000 0000 0000
*
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55
0000200 457f 464c 0101 0001 0000 0000 0000 0000
0000210 0002 0003 0001 0000 000c 0010 0034 0000
0000220 5cc0 0002 0000 0000 0034 0020 0002 0028
0000230 0011 000e 0001 0000 1000 0000 0000 8010
0000240 0000 0010 b5b6 0000 525c 0001 0007 0000

よくよく見てみると、そういえば面倒くさがってbootasm.Sを省略していたのだった。xv6ではimgファイルを作成するのに以下のコマンドを利用している。

xv6.img: bootblock kernel fs.img
        dd if=/dev/zero of=xv6.img count=10000
        dd if=bootblock of=xv6.img conv=notrunc
        dd if=kernel of=xv6.img seek=1 conv=notrunc

上から順に、

  1. 10000バイト分0で埋めた、xv6.imgのひな型を作成する。
  2. bootblock オブジェクトファイルを先頭から挿入する
  3. kernelオブジェクトファイルを1ブロック分シークした場所から挿入する

となっている。bootblockは、bootasm.Sとbootmain.cから構成されているので、これを作成しないと最初のブートができないというわけだ。

まずはbootasm.Sだが、RISC-Vは先ほども言った通り0x200からブートするので、先頭の0x200バイトは埋めておく必要がある。これにはgasのrept疑似コードを使ってnopを挿入しておく。実際には割り込みルーチンが入ってくるはずなのだが、とりあえず今回は保留だ。

.rept 0x80
        nop
.endr

.globl start
start:
        add     x1,  x0, x0
        add     x2,  x0, x0
...

あとはとりあえずレジスタフォーマットの記述でも入れておこう。x86ではブートのための複雑な処理が入っているが、これはRISC-Vでは関係ないので無視。

.globl start
start:
        add     x1,  x0, x0
        add     x2,  x0, x0
        add     x3,  x0, x0
        add     x4,  x0, x0
        add     x5,  x0, x0
        add     x6,  x0, x0
        add     x7,  x0, x0
        add     x8,  x0, x0
        add     x9,  x0, x0
        add     x10, x0, x0
        add     x11, x0, x0
        add     x12, x0, x0
        add     x13, x0, x0
        add     x14, x0, x0
        add     x15, x0, x0
        add     x16, x0, x0
        add     x17, x0, x0
        add     x18, x0, x0
        add     x19, x0, x0
        add     x20, x0, x0
        add     x21, x0, x0
        add     x22, x0, x0
        add     x23, x0, x0
        add     x24, x0, x0
        add     x25, x0, x0
        add     x26, x0, x0
        add     x27, x0, x0
        add     x28, x0, x0
        add     x29, x0, x0
        add     x30, x0, x0
        add     x31, x0, x0

        # Set up the stack pointer and call into C.
            la      t0, bootmain
            jr      t0

これで、ISSでブート可能なxv6バイナリが完成した。ちゃんとRISC-V ISSでも最初の命令を実行できた!あとはデバッグあるのみ。。。

$ ../swimmer_iss/build_riscv/swimmer_riscv --imgfile xv6.img --debug --max 59
Swimmer-RISCV
  Version 20160410 Revision b2f1940
  developed by Masayuki Kimura <masayuki.kimura.1986@gmail.com>
<Load Image xv6.img>
<Info: NewMemory Region 00000000 is defined.>
<Info: NewMemory Region 00001000 is defined.>
StartDebug max_step=59 <= curr_step(0) + stepCount(59)
Result Max_step= 59
         0:M:MBar:[00000200] 000000b3 : add        r01,r00,r00          r00=>00000000 r00=>00000000 r01<=00000000
         1:M:MBar:[00000204] 00000133 : add        r02,r00,r00          r00=>00000000 r00=>00000000 r02<=00000000
         2:M:MBar:[00000208] 000001b3 : add        r03,r00,r00          r00=>00000000 r00=>00000000 r03<=00000000
         3:M:MBar:[0000020c] 00000233 : add        r04,r00,r00          r00=>00000000 r00=>00000000 r04<=00000000
         4:M:MBar:[00000210] 000002b3 : add        r05,r00,r00          r00=>00000000 r00=>00000000 r05<=00000000
         5:M:MBar:[00000214] 00000333 : add        r06,r00,r00          r00=>00000000 r00=>00000000 r06<=00000000
         6:M:MBar:[00000218] 000003b3 : add        r07,r00,r00          r00=>00000000 r00=>00000000 r07<=00000000
         7:M:MBar:[0000021c] 00000433 : add        r08,r00,r00          r00=>00000000 r00=>00000000 r08<=00000000
         8:M:MBar:[00000220] 000004b3 : add        r09,r00,r00          r00=>00000000 r00=>00000000 r09<=00000000
         9:M:MBar:[00000224] 00000533 : add        r10,r00,r00          r00=>00000000 r00=>00000000 r10<=00000000
        10:M:MBar:[00000228] 000005b3 : add        r11,r00,r00          r00=>00000000 r00=>00000000 r11<=00000000
        11:M:MBar:[0000022c] 00000633 : add        r12,r00,r00          r00=>00000000 r00=>00000000 r12<=00000000
        12:M:MBar:[00000230] 000006b3 : add        r13,r00,r00          r00=>00000000 r00=>00000000 r13<=00000000
        13:M:MBar:[00000234] 00000733 : add        r14,r00,r00          r00=>00000000 r00=>00000000 r14<=00000000
        14:M:MBar:[00000238] 000007b3 : add        r15,r00,r00          r00=>00000000 r00=>00000000 r15<=00000000
        15:M:MBar:[0000023c] 00000833 : add        r16,r00,r00          r00=>00000000 r00=>00000000 r16<=00000000
        16:M:MBar:[00000240] 000008b3 : add        r17,r00,r00          r00=>00000000 r00=>00000000 r17<=00000000
        17:M:MBar:[00000244] 00000933 : add        r18,r00,r00          r00=>00000000 r00=>00000000 r18<=00000000
        18:M:MBar:[00000248] 000009b3 : add        r19,r00,r00          r00=>00000000 r00=>00000000 r19<=00000000
        19:M:MBar:[0000024c] 00000a33 : add        r20,r00,r00          r00=>00000000 r00=>00000000 r20<=00000000
        20:M:MBar:[00000250] 00000ab3 : add        r21,r00,r00          r00=>00000000 r00=>00000000 r21<=00000000
        21:M:MBar:[00000254] 00000b33 : add        r22,r00,r00          r00=>00000000 r00=>00000000 r22<=00000000
        22:M:MBar:[00000258] 00000bb3 : add        r23,r00,r00          r00=>00000000 r00=>00000000 r23<=00000000
        23:M:MBar:[0000025c] 00000c33 : add        r24,r00,r00          r00=>00000000 r00=>00000000 r24<=00000000
        24:M:MBar:[00000260] 00000cb3 : add        r25,r00,r00          r00=>00000000 r00=>00000000 r25<=00000000
        25:M:MBar:[00000264] 00000d33 : add        r26,r00,r00          r00=>00000000 r00=>00000000 r26<=00000000
        26:M:MBar:[00000268] 00000db3 : add        r27,r00,r00          r00=>00000000 r00=>00000000 r27<=00000000
        27:M:MBar:[0000026c] 00000e33 : add        r28,r00,r00          r00=>00000000 r00=>00000000 r28<=00000000
        28:M:MBar:[00000270] 00000eb3 : add        r29,r00,r00          r00=>00000000 r00=>00000000 r29<=00000000
        29:M:MBar:[00000274] 00000f33 : add        r30,r00,r00          r00=>00000000 r00=>00000000 r30<=00000000
        30:M:MBar:[00000278] 00000fb3 : add        r31,r00,r00          r00=>00000000 r00=>00000000 r31<=00000000
        31:M:MBar:[0000027c] 00000297 : auipc      r05,0x00000          r05<=0000027c
        32:M:MBar:[00000280] 10028293 : addi       r05,r05,0x100        r05=>0000027c r05<=0000037c
        33:M:MBar:[00000284] 00028067 : jalr       r00,r05,0x067        r05=>0000037c pc<=0000037c
        34:M:MBar:[0000037c] ff010113 : addi       r02,r02,0xff0        r02=>00000000 r02<=fffffffffffffff0
        35:M:MBar:[00000380] 00112623 : sw         r02,r01,0x00|r12     r02=>fffffffffffffff0 r01=>00000000 (fffffffffffffffc)<=00000000
        36:M:MBar:[00000384] 00812423 : sw         r02,r08,0x00|r08     r02=>fffffffffffffff0 r08=>00000000 (fffffffffffffff8)<=00000000
        37:M:MBar:[00000388] 00912223 : sw         r02,r09,0x00|r04     r02=>fffffffffffffff0 r09=>00000000 (fffffffffffffff4)<=00000000
        38:M:MBar:[0000038c] 01212023 : sw         r02,r18,0x00|r00     r02=>fffffffffffffff0 r18=>00000000 (fffffffffffffff0)<=00000000
        39:M:MBar:[00000390] 01010413 : addi       r08,r02,0x010        r02=>fffffffffffffff0 r08<=00000000
        40:M:MBar:[00000394] 00000613 : addi       r12,r00,0x000        r00=>00000000 r12<=00000000

x86版xv6のブートコードは何をしているのか

実装の際には一切無視したが、x86番の場合、まずCPUを立ち上げるために何が必要なのかを確認しておこう。

  1. まずは16bitモードから立ち上がる
.code16                       # Assemble for 16-bit mode
.globl start

この状態で、まずは割り込みを禁止し、余計な処理が入るのを防ぐ。さらに各種必要なレジスタの初期化が入る。

次にinbで何かを取得しているのだが、調べてみるとこれはPS/2のステータスを待っているらしい。 キーボードが認識されるのを待っているのか。wait for not busyと書いてあるから、PS/2のステータスが安定するのを待っているのね。

seta20.1:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

次にキーボードに対していくつかコマンドを発行する。なぜキーボードに対して発行するのかがいまいちなぞだが、アドレスのモードをこれで変更するらしい。xv6の教科書にも以下のように書いてある。

If the
second bit of the keyboard controller’s output port is low, the 21st physical address bit
is always cleared; if high, the 21st bit acts normally. The boot loader must enable the
21st address bit using I/O to the keyboard controller on ports 0x64 and 0x60 (8920-
8936).

次にプロテクトモードに入り、仮想アドレスを32ビットに増加させる。この時はまだ、仮想アドレスと物理アドレスが同一のまま?

  # Switch from real to protected mode.  Use a bootstrap GDT that makes
  # virtual addresses map directly to physical addresses so that the
  # effective memory map doesn't change during the transition.
  lgdt    gdtdesc
  movl    %cr0, %eax
  orl     $CR0_PE, %eax
  movl    %eax, %cr0

32bitモードに入ると、アクセスできるようになる各種レジスタを初期化していく。

.code32  # Tell assembler to generate 32-bit code now.
start32:
  # Set up the protected-mode data segment registers
  movw    $(SEG_KDATA<<3), %ax    # Our data segment selector
  movw    %ax, %ds                # -> DS: Data Segment
  movw    %ax, %es                # -> ES: Extra Segment
  movw    %ax, %ss                # -> SS: Stack Segment
  movw    $0, %ax                 # Zero segments not ready for use
  movw    %ax, %fs                # -> FS
  movw    %ax, %gs                # -> GS

最終的に、bootmainのアドレスを設定してそこにジャンプしている。

  # Set up the stack pointer and call into C.
  movl    $start, %esp
  call    bootmain

パタヘネのARM Editionを購入

Computer Organization and Design(パタヘネ)といえは、有名なコンピュータアーキテクチャの入門書籍だが、基本的に命令セットがMIPSをベースにして説明されている。 しかし、今回MIPSではなく、ARMを前提としたパタヘネが出ていたので早速購入してみた。いったいどこが違っているのだろう。

Computer Organization and Design: The Hardware Software Interface: ARM Edition (The Morgan Kaufmann Series in Computer Architecture and Design)

Computer Organization and Design: The Hardware Software Interface: ARM Edition (The Morgan Kaufmann Series in Computer Architecture and Design)

ペーパーバックなのに例によって非常に分厚いのだが、まず第一の感想は、「内容的にはMIPS版とほとんど変わらない」ということだ。 それはそうだ。MIPSだろうがARMだろうが、コンピュータアーキテクチャの基礎は一緒だし、命令を置き換えた程度しか、入門書では説明は必要ないだろう。 というわけで、内容的には特に目新しいところはない。

ARMがベースになっているとのことだが、実際にはLEG(Lessen Extrinsic Garrulity)というARMのサブセットのような命令セットを使っている。 まあ、別にMIPSでも同じようなものだし、特に違いはない。

なかなか皮肉な例として、ARM以外の例を出すためにMIPSアーキテクチャについて述べられていたり、MIPSとARMの立場が逆転していて面白い。

せっかく買ったのだが、あまり内容を読み込むことなく終わってしまいそうで心配だなあ。

https://pbs.twimg.com/media/Cf2RRibUAAAcuYj.jpg

Bash on WindowsでTensorFlowをインストールして動作させる

Bash on Windowsのベータ版がWindows 10のInsider Previewの機能として公開された。 いままでCygwinやmsys2など、WindowsLinuxのコマンドを実現する機能は複数あったとは言え、Microsoft自身がUbuntuと手を組んでBashをサポートしてしまうとは驚きだった。 去年はVisual Studio Codeのオープンソース化、そして今年はBash on Windowsと、最近のMicrosoftは面白いことがたくさんある。 昔はソースコードを公開しない、クローズドな会社で、市場を支配している嫌なイメージ、というものがあったけど、今はそんなこと無くなったなあ。

僕はWindowsBashか、あるいはLinuxの環境をサポートすることをずっと心待ちにしていた。 厳密には、Linuxで利用されている大量の資産やライブラリ、プログラムを、簡単にWindowsでも動作させ、自由にトライできる環境が欲しいと思っていた。 Bash on Windowsは、そのための第一歩だ。 WindowsLinuxのライブラリが動作するようになったらまず何を試したかったって、Google機械学習ライブラリであるTensorFlowをWindowsで動かしたかったのだ。 もちろん、Windowsで動かすだけなら、Cygwinを入れたり、仮想マシンを導入することによって簡単に実現することができる、ってか既にやっている。 それよりも重要なのは、最終的にBash on WindowsGPGPUのライブラリやCUDAが動作し、Windows上で動作するTensorFlowがGPGPUをサポートできることだ。 最終的にはこれが目標なのだが、とりあえず今回はそこまで高望みせずに、BashにTensorFlowのインストールしながら、何が出来るかを見ていこう。

ちなみにTensorFlowがWindowsをサポートすることは、多くのユーザに望まれていた。以下のディスカッションを参照。

github.com

結局Dockerを使ったり、何か詐欺くさい。とはいえ、Bash on Windowsの上でPythonを動かして、TensorFlowを動かすのも詐欺といえば詐欺か。

テスト用PCのWindowsをInsider Preview版にまでアップグレードさせる

このこと自体は別に問題ない。Insider Previewへのアップーデートは誰のPCにでも出来る。 とりあえず僕は怖いので、テスト用の古いPCのWindows 10をアップデートしてみた。

♯ ちなみに、このブログもその古いPCで書いているのでとても遅い。

Bash on Windowsを有効にする

これも特に問題ない。「Windowsの機能の有効化と無効化」で、「Windows Subsystem for Linux(Beta)」のチェックボックスを有効にすれば良い。 これにより、ちょっとした時間をかけてBash on Windowsがインストールされる。

f:id:msyksphinz:20160412020200p:plain

基本的なUbuntuの機能はほとんど動作する

bashを起動すると、まずはrootユーザとしてログインされるため、一般ユーザを作成して、sudoersに登録するという作業をしておいた。 ちなみにaptitudeも動作する。パケージは簡単に導入できるようだ。本当にVMを使っているみたいだな!

TensorFlowをソースコードからコンパイルしてみる

TensorFlowを導入するにあたり、まずはソースコードからコンパイルしてみようと思った。

github.com

そのためには、まずはbazelのインストール、さらにはJava8のインスートルが必要となる。ここでハマった。

Bash on WindowsJava-8がインストールできない

これはBash on Windows自身のの問題なのか、正直まだ良く分からない。ただし、下記のサイトで説明されている方法でインストールしようとしたら失敗してしまった。

Installing Bazel - Bazel

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer

以下のエラーメッセージが出る。

141312K ........ ........ ........ ........ ........ ........ 81% 5.65M 6s
144384K ........ ........ ........ ........ ........ ........ 83% 9.21M 5s
147456K ........ ........ ........ ........ ........ ........ 84% 10.2M 5s
150528K ........ ........ ........ ........ ........ ........ 86% 8.23M 4s
153600K ........ ........ ........ ........ ........ ........ 88% 11.8M 4s
156672K ........ ........ ........ ........ ........ ........ 90% 10.9M 3s
159744K ........ ........ ........ ........ ........ ........ 91% 11.4M 2s
162816K ........ ........ ........ ........ ........ ........ 93% 10.6M 2s
165888K ........ ........ ........ ........ ........ ........ 95% 5.91M 1s
168960K ........ ........ ........ ........ ........ ........ 97% 10.7M 1s
172032K ........ ........ ........ ........ ........ ........ 98% 9.93M 0s
175104K ........ ........ ........ .......                   100% 10.9M=29s

2016-04-11 15:42:15 (5.94 MB/s) - ‘jdk-8u77-linux-x64.tar.gz’ saved [181365687/181365687]

Download done.
Removing outdated cached downloads...
sha256sum mismatch jdk-8u77-linux-x64.tar.gz
Oracle JDK 8 is NOT installed.
dpkg: error processing package oracle-java8-installer (--configure):
 subprocess installed post-installation script returned error exit status 1
Errors were encountered while processing:
 oracle-java8-installer
E: Sub-process /usr/bin/dpkg returned an error code (1)

他のQ&Aサイトも調べていろいろ試行したのだが、結局インストールできなかった。sha256sumのエラーだから、文字コードとか、何かの問題かなあ?

バイナリからTensorFlowをインストール

という訳で、ソースコードからコンパイルしてインストールするのは諦めた。 バイナリパッケージを使ってインストールしてみる。

github.com

$ sudo pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.7.1-cp27-none-linux_x86_64.whl

しばらく待っていると、これは無事にインストールが完了した。

...
    Creating build/scripts.linux-x86_64-2.7/f2py
      adding 'build/scripts.linux-x86_64-2.7/f2py' to scripts
    changing mode of build/scripts.linux-x86_64-2.7/f2py from 644 to 755

    warning: no previously-included files matching '*.pyo' found anywhere in distribution
    warning: no previously-included files matching '*.pyd' found anywhere in distribution
    changing mode of /usr/local/bin/f2py to 755
  Found existing installation: wheel 0.24.0
    Not uninstalling wheel at /usr/lib/python2.7/dist-packages, owned by OS
  Found existing installation: six 1.5.2
    Not uninstalling six at /usr/lib/python2.7/dist-packages, owned by OS
  Found existing installation: setuptools 3.3
    Not uninstalling setuptools at /usr/lib/python2.7/dist-packages, owned by OS
Successfully installed tensorflow numpy protobuf wheel six setuptools
Cleaning up...

TensorFlowの動作確認

とりあえず、実際の学習作業は今日はやめておいて、動作確認だけやっておこう。

$ python
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>>
>>> print(sess.run(hello))
Hello, TensorFlow!
>>>
>>> a = tf.constant(10)
>>> b = tf.constant(32)
>>> print(sess.run(a + b))
42

ちゃんと動作したね!

最終的にはGPGPU上で動作できること

いちおう、Bash on WindowsでTensorFlowが動作することは確認したが、これではあまり芸が無くて、Linuxを使わなくても、Windows上でTensorFlowがちゃんとGPGPUを使えるようにしたい。これは、もうちょっと調査して、ライブラリがどれくらい使えるのかを見てからかな。

Detailed Architecture of AMD's opteronを読み解く(1)

ちょっと勉強のためというか、この手の解説文をあまり読んだことが無かったので、昔先輩にお勧めされた記事を翻訳してみることにした。 chip-architect.comというページに載っている、アウトオブオーダ処理について解説された記事だ。非常に長い。

Chip Architect: Detailed Architecture of AMD's Opteron

AMDOpteronのアウトオブオーダ処理について非常に詳細な解説がされており、とても興味深い。

ちなみに、非常に汚い翻訳ではあるが、日本語化を進めている(原著者に許可はもらっていないけれども)。

github.com

その中でも、アウトオブオーダ処理の基本的な部分であるリオーダバッファ、レジスタファイル、フューチャーファイルについての部分を最近翻訳している。 要約すると、

1.9 レジスタリネーミングとアウトオブオーダ処理

IPCの低下を防ぐためのレジスタリネーミングについて。これはヘネパタでも紹介されている非常に基本的な構造。 Write After Writeによるハザードを防ぐために、命令毎に書き込み先のレジスタをダイナミックに変更するというもの。

1.11 IFFRF: Integer Future File and Register File

1.12 IFFRFの"Future File"セクション

フューチャーファイルとアーキテクチャレジスタの解説。アーキテクチャレジスタは、プロセッサにおいて命令の実行が完全人終了し、例外が発生したりその命令が取り消されることのない状態を管理している。 そのため、例外が発生するとフューチャーファイルにアーキテクチャレジスタの内容が書き戻される(下記の図のUsed Only On Exceptionsの部分が相当する)。

https://github.com/msyksphinz/sicp_exercise/raw/master/chip_archtect/2013_09_21/future-file.JPG

一方で、フューチャーファイルは現在実行中の命令、というか実行が完了したが前の命令の影響でまだリタイアできない(基本的にインオーダ完了なので、ある命令が完了するためには、前の命令がすべて完了している必要がある)状態のものも含めて格納されている。 フューチャーファイルは、これからディスパッチされる命令がソースオペランドを読み込むために利用される。現在の最新の状態で、すべての命令がキャンセルされることなく実行されることを想定すると、フューチャーファイルから最新の値を取得してディスパッチするのが最適、というわけだ。

かなり長い解説記事、現時点ではまだここまで。さらにリオーダバッファについてと、浮動小数点オペレーションについても読み進めていこう。