OS起動編⑮-1 startothers() (Xv6を読む~OSコードリーディング~)

前回
jupiteroak.hatenablog.com
トップページ
jupiteroak.hatenablog.com




main.c
https://github.com/mit-pdos/xv6-public/blob/master/main.c#L34

int
main(void)
{
  ... 
  startothers();   // start other processors
  ...
}

https://github.com/mit-pdos/xv6-public/blob/master/main.c#L63

// Start the non-boot (AP) processors.
static void
startothers(void)
{
  extern uchar _binary_entryother_start[], _binary_entryother_size[];
  uchar *code;
  struct cpu *c;
  char *stack;

  // Write entry code to unused memory at 0x7000.
  // The linker has placed the image of entryother.S in
  // _binary_entryother_start.
  code = P2V(0x7000);
  memmove(code, _binary_entryother_start, (uint)_binary_entryother_size);

  for(c = cpus; c < cpus+ncpu; c++){
    if(c == mycpu())  // We've started already.
      continue;

    // Tell entryother.S what stack to use, where to enter, and what
    // pgdir to use. We cannot use kpgdir yet, because the AP processor
    // is running in low  memory, so we use entrypgdir for the APs too.
    stack = kalloc();
    *(void**)(code-4) = stack + KSTACKSIZE;
    *(void(**)(void))(code-8) = mpenter;
    *(int**)(code-12) = (void *) V2P(entrypgdir);

    lapicstartap(c->apicid, V2P(code));

    // wait for cpu to finish mpmain()
    while(c->started == 0)
      ;
  }
}

startothers関数は、BSP(BootStrap Processor)以外のシステム上にある各AP(Application Processor)に対して起動処理を行います。


ブート時から起動しているプロセッサをBSP(BootStrap Processor)と呼び、それ以外のシステム上にあるプロセッサのことをAP(Application Processor)と呼びます。startothers関数を実行するプロセッサはBSP(BootStrap Processor)です。


処理の内容

entryother.Sのバイナリファイルのコピー先となるメモリ領域の先頭アドレスを取得する

code = P2V(0x7000);

P2Vマクロを使って、物理アドレス0x7000に対応する仮想アドレスを取得します。
取得した仮想アドレスP2V(0x7000)は、entryother.Sのバイナリファイルのコピー先となるメモリ領域の先頭アドレスであり、entryother.Sのバイナリファイルのエントリポイント(実行開始アドレス)になるアドレスです。

entryother.Sのバイナリファイルの内容をコピーする

memmove(code, _binary_entryother_start, (uint)_binary_entryother_size);

memmove関数を使って、entryother.Sのバイナリファイルの内容(entryother.Sのオブジェクトファイルのうちtextセクションのみを含む)をアドレスcode以降のメモリ領域にコピーします。
memmove関数は、先頭アドレスが_binary_entryother_start・サイズが_binary_entryother_sizeとなるメモリ領域から、先頭アドレスがcode・サイズが_binary_entryother_sizeとなるメモリ領域へ、データをコピーします。
_binary_entryother_startは、entryother.Sのバイナリファイル(entryother.Sのオブジェクトファイルのうちtextセクションのみを含む)の先頭アドレスです。_binary_entryother_sizeは、entryother.Sのバイナリファイル(entryother.Sのオブジェクトファイルのうちtextセクションのみを含む)のサイズ(バイト単位)です。

各APに対して起動処理を行う

for(c = cpus; c < cpus+ncpu; c++){
  ....

各AP(Application Processor)に対して起動処理を行うために、cpu構造体の配列からAP(Application Processor)に対応するcpu構造体を取得します。
cpu構造体の配列にアクセスするために、cpu構造体の配列の先頭アドレスcpusを利用しています。

起動処理の対象となるプロセッサがBSPではないことを確認する

if(c == mycpu()) 
continue;

配列から取り出したcpu構造体 と mycpu関数を呼び出して取得したBSP(BootStrap Processor)に対応するcpu構造体 を比較し、配列から取り出したcpu構造体 が BSP(BootStrap Processor)に対応するcpu構造体ではないことを確認します。
c == mycpu()が真となる場合→配列から取り出したcpu構造体 が BSP(BootStrap Processor)に対応するcpu構造体である場合は、continueして以降の処理(起動処理)を行わないようにします。

スタック領域を確保する

stack = kalloc();

kalloc関数を呼び出して、4KBのスタック領域を確保します。変数stackには、スタック領域のトップアドレスの限界値(確保したスタック領域の先頭アドレス)が格納されます。ここで確保されたスタック領域は、AP(Application Processor)によって実行されるカーネルスレッドのスタック領域です。

スタック領域のボトムアドレスをentryoyherプログラム内に保存する

*(void**)(code-4) = stack + KSTACKSIZE;

ポインタ(code-4)と間接参照演算子によって指定されるメモリ領域に、スタック領域のボトムアドレス(stack + 4096)を格納します。
格納されたスタックのボトムアドレスは、entryorherプログラム(先ほどのentryother.Sのバイナリファイル)で使用されます(spレジスタにセットされます)。
ポインタ(code-4)と間接参照演算子が指定するメモリ領域(=ポインタ)にアドレスを格納するので、(code-4)をポインタのポインタとしてキャストし( (void**)(code-4) )、間接参照演算子を使用して( *(void**)(code-4) )、アクセスしています。

mpenterのアドレスをentryoyherプログラム内に保存する

*(void(**)(void))(code-8) = mpenter;

ポインタ(code-8)と間接参照演算子によって指定されるメモリ領域に、mpenter関数のアドレスを格納します。
格納されたmpenter関数のアドレスは、entryorherプログラム(先ほどのentryother.Sのバイナリファイル)で使用されます(entryorherの処理からmpenterへjmpするために使用されます)。
ポインタ(code-8)と間接参照演算子によって指定されるメモリ領域(=ポインタ)に関数のアドレスを格納するので、(code-8)を戻り値・引数なしの関数へのポインタのポインタとしてキャストし( (void(**)(void))(code-8) )、間接参照演算子を使用して( *(void(**)(void))(code-8) )、アクセスしています。

ページディレクトリの先頭物理アドレスをentryoyherプログラム内に保存する

*(int**)(code-12) = (void *) V2P(entrypgdir);

ポインタ(code-12)と間接参照演算子によって指定されるメモリ領域に、AP(Application Processor)が使用するページディレクトリの先頭物理アドレスを格納します。
格納されたページディレクトリの先頭物理アドレスは、entryorherプログラム(先ほどのentryother.Sのバイナリファイル)で使用されます(AP(Application Processor)のCR3レジスタにセットされます)。
ポインタ(code-12)と間接参照演算子によって指定されるメモリ領域(=ポインタ)にアドレスを格納するので、(code-12)をint型へのポインタのポインタとしてキャストし( (int**)(code-12) )、間接参照演算子を使用して( *(void(**)(void))(code-8) )、アクセスしています。

APに対してuniversal startup algorithmを行う

lapicstartap(c->apicid, V2P(code));

lapicstartap関数は、c->apicidで指定されたLocal APIC IDを持つAP(Application Processor)を、V2P(code)で指定されたアドレスから起動させるために、universal startup algorithmと呼ばれる手続きを行います。
lapicstartap関数の処理が完了すると、c->apicidで指定されたLocal APIC IDを持つAP(Application Processor)がentryorherプログラムの実行を開始します。

APがmpmain関数内にあるxchg命令を実行完了するまで待つ

while(c->started == 0)

c->apicidで指定されたAP(Application Processor)は、起動後、entryother.S→mpenter関数→mpmain関数の順に、処理を進めていきます。
c->apicidで指定されたAP(Application Processor)がmpmain関数内にあるxchg関数の処理を完了するまで、BSP(BootStrap Processor)の処理はこのwhileループから脱出できません。
c->apicidで指定されたAP(Application Processor)がxchg関数の処理を完了し、そのAP(Application Processor)に対応するcpu構造体のstartedが1になったら→c->started == 0 が偽になったら、BSP(BootStrap Processor)の処理はこのループを脱出します。
for文を使い、システム上にある全てのAP(Application Processor)について、以上の処理を行います。




次回
jupiteroak.hatenablog.com