xv 6にcpuとプロセス情報を格納するテクニック
7764 ワード
xv 6はマルチプロセッサ対応のUnix-likeオペレーティングシステムです.
最近ソースコードを読んだところ、xv 6は現在のCPUとプロセスの状態を記録しているときに非常にtrickyであることが分かった.
まず、上のコード:
ここでstruct cpuは各cpuの運転状態を保存するための構造体であり、
コードの最初の行は構造体配列cpus[NCPU]を定義し、NCPUはcpuの総数(最大8)に対応し、すなわちcpusはすべてのcpuの動作状態を格納するために使用される.
では、上記のカーネルコードは各cpuで実行されていますが、各cpuはどのように自分の現在の実行状態を知っていますか?
この問題に対してlapicでcpu自身の番号を取得し、番号を再利用してcpusをアドレスすればよい.
すなわち、任意のcpuについて、struct cpu*c=&cpus[cpunum()];
しかし、2つ目の問題は、cpu自身の状態を参照するたびにlapicで番号を取得することはできません.グローバル変数を作って状態位置を一度に保存してもらえませんか.
このようにstruct cpu*cpu;//グローバル変数は、cpu自体の状態を格納し、初期化コードにcpu=cを格納します.
各cpuが実行中であるプロセスを記録する場合も、struct proc*procと書くことができますか?//現在のcpuが実行中のプロセスステータスを格納するグローバル変数
では、3つ目の問題は、各cpuは独立して並列であり、各cpuで実行されるカーネルコードは同じであり、ページテーブルも同じです.
これは、グローバル変数cpuとprocのアドレスも同じであり、異なるcpuの状態を区別するために使用できないことを意味する.
したがって,各cpuにおいて同じシンボルで状態を記録する方法が必要であるが,これらのシンボルは異なるアドレスにマッピングされる.
ページテーブルが同じである以上、絶対的な数値でアドレスすることはできません.よく考えてみると、ページテーブルの上に何がありますか.ページ表の上に、段表がありますね.
セグメントテーブルを作成するときにセグメントを異なるメモリ領域にマッピングすれば、segment registerを使用してアドレスする必要があります.そのため、次の宣言があります.
セグメントレジスタとしてgsを用い,cpuは[%gs],procは[%gs+4],
その中でなぜ最初にexternを使うのでしょうか.私はある大神に聞いたことがありますが、gsセグメントは外部で構築されているので、外部定義に相当します.の
OK、最後の問題が来ました.gsセグメントはどこを指して、各cpuのgsセグメントが異なる領域にあることを確保することができますか?
最も直感的な考えはもちろん対応するcpus[num]内部を指しているのでstruct cpuの末尾に2つのドメインを追加します.
次に、セグメントテーブルを作成するときにgsセグメントを追加し、末尾の2つのドメインにマッピングします.
実はここでcpuとproc変数はスレッドのローカルストレージとの性質があまり悪くなく、各プロセッサは同じ変数を参照することができますが、これらの変数は異なるストレージ領域に対応しています.
この実装テクニックはTLS(スレッドローカルストレージ)とあまり差がない可能性があり,TLSの実装を検討する暇がある.
最近ソースコードを読んだところ、xv 6は現在のCPUとプロセスの状態を記録しているときに非常にtrickyであることが分かった.
まず、上のコード:
1 extern struct cpu cpus[NCPU];
2 extern int ncpu;
3
4 // Per-CPU variables, holding pointers to the
5 // current cpu and to the current process.
6 // The asm suffix tells gcc to use "%gs:0" to refer to cpu
7 // and "%gs:4" to refer to proc. seginit sets up the
8 // %gs segment register so that %gs refers to the memory
9 // holding those two variables in the local cpu's struct cpu.
10 // This is similar to how thread-local variables are implemented
11 // in thread libraries such as Linux pthreads.
12 extern struct cpu *cpu asm("%gs:0"); // &cpus[cpunum()]
13 extern struct proc *proc asm("%gs:4"); // cpus[cpunum()].proc
ここでstruct cpuは各cpuの運転状態を保存するための構造体であり、
コードの最初の行は構造体配列cpus[NCPU]を定義し、NCPUはcpuの総数(最大8)に対応し、すなわちcpusはすべてのcpuの動作状態を格納するために使用される.
では、上記のカーネルコードは各cpuで実行されていますが、各cpuはどのように自分の現在の実行状態を知っていますか?
この問題に対してlapicでcpu自身の番号を取得し、番号を再利用してcpusをアドレスすればよい.
すなわち、任意のcpuについて、struct cpu*c=&cpus[cpunum()];
しかし、2つ目の問題は、cpu自身の状態を参照するたびにlapicで番号を取得することはできません.グローバル変数を作って状態位置を一度に保存してもらえませんか.
このようにstruct cpu*cpu;//グローバル変数は、cpu自体の状態を格納し、初期化コードにcpu=cを格納します.
各cpuが実行中であるプロセスを記録する場合も、struct proc*procと書くことができますか?//現在のcpuが実行中のプロセスステータスを格納するグローバル変数
では、3つ目の問題は、各cpuは独立して並列であり、各cpuで実行されるカーネルコードは同じであり、ページテーブルも同じです.
これは、グローバル変数cpuとprocのアドレスも同じであり、異なるcpuの状態を区別するために使用できないことを意味する.
したがって,各cpuにおいて同じシンボルで状態を記録する方法が必要であるが,これらのシンボルは異なるアドレスにマッピングされる.
ページテーブルが同じである以上、絶対的な数値でアドレスすることはできません.よく考えてみると、ページテーブルの上に何がありますか.ページ表の上に、段表がありますね.
セグメントテーブルを作成するときにセグメントを異なるメモリ領域にマッピングすれば、segment registerを使用してアドレスする必要があります.そのため、次の宣言があります.
1 extern struct cpu *cpu asm("%gs:0"); // &cpus[cpunum()]
2 extern struct proc *proc asm("%gs:4"); // cpus[cpunum()].proc
セグメントレジスタとしてgsを用い,cpuは[%gs],procは[%gs+4],
その中でなぜ最初にexternを使うのでしょうか.私はある大神に聞いたことがありますが、gsセグメントは外部で構築されているので、外部定義に相当します.の
OK、最後の問題が来ました.gsセグメントはどこを指して、各cpuのgsセグメントが異なる領域にあることを確保することができますか?
最も直感的な考えはもちろん対応するcpus[num]内部を指しているのでstruct cpuの末尾に2つのドメインを追加します.
1 struct cpu{
2 ........ //cpu
3
4 // Cpu-local storage variables; see below
5 struct cpu *cpu;
6 struct proc *proc; // The currently-running process.
7 }
次に、セグメントテーブルを作成するときにgsセグメントを追加し、末尾の2つのドメインにマッピングします.
1 c = &cpus[cpunum()];
2
3 ......... //
4
5 // gs , ( cpu proc ), &c->cpu
6 c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0);
7
8 // gdt
9 lgdt(c->gdt, sizeof(c->gdt));
10 // gs
11 loadgs(SEG_KCPU << 3);
12
13 // cpu proc cpu proc
14 // cpu %gs:0, proc %gs:4
15 cpu = c;
16 proc = 0;
実はここでcpuとproc変数はスレッドのローカルストレージとの性質があまり悪くなく、各プロセッサは同じ変数を参照することができますが、これらの変数は異なるストレージ領域に対応しています.
この実装テクニックはTLS(スレッドローカルストレージ)とあまり差がない可能性があり,TLSの実装を検討する暇がある.